Skip to content

Settings Explained

fonlow edited this page Mar 6, 2024 · 17 revisions

Settings

Open API definitions could be in YAML format and JSON format. This chapter uses YAML to stand for Open API definitions.

ClientNamespace

/// <summary>
/// The generated codes should be contained in a namespace. The default is My.Namespace.
/// </summary>
public string ClientNamespace { get; set; } = "My.Namespace";

It is recommended to create a unique namespace for each Open API definition file to avoid generated classes sharing the same name, thus conflicting.

You may encounter a service publisher publishing multiple YAML/JSON files each with an API function (path) with some components identical among these YAML files. Technically you may have 2 ways to generate codes without naming conflicts:

  1. Generate codes for each YAML file one by one, each with a unique namespace.
  2. Merge these YAML files into one, and the merging tool should merge all identical components into one component.
  3. Ask the publisher to publish 1 YAML file for all these APIs in cohesion.

The 2nd one generates cleaner codes, as the publisher should have provided just 1 YAML file.

ContainerNameStrategy

/// <summary>
/// How to create container class to group API functions.
/// </summary>
public ContainerNameStrategy ContainerNameStrategy { get; set; }

public enum ContainerNameStrategy
{
	/// <summary>
	/// All client functions will be constructed in a god class named after ContainerClassName
	/// </summary>
	None,

	/// <summary>
	/// Use tags
	/// </summary>
	Tags,

	/// <summary>
	/// Use path as resource for grouping, as a container class name.
	/// </summary>
	Path,
}

Open API definition does not care about namespace and class which are key features of many programming languages. You may try to use Tags or Path to group functions into classes, and use None to get a god class as last resort.

ContainerClassName

/// <summary>
/// Container class name when ContainerNameStrategy is None. The default is Misc.
/// </summary>
public string ContainerClassName { get; set; } = "Misc";

Having a god class is not too bad for small amount of API functions.

ContainerNameSuffix

/// <summary>
/// Suffix of container class name if ContainerNameStrategy is not None. The default is "Client".
/// </summary>
public string ContainerNameSuffix { get; set; } = "Client";

"Proxy" could be a good choice too. This improves the readability of generated client API codes in a service broker.

ActionNameStrategy

/// <summary>
/// How to compose action name
/// </summary>
public ActionNameStrategy ActionNameStrategy { get; set; }

public enum ActionNameStrategy
{
	/// <summary>
	/// Either OperationId or MethodQueryParameters
	/// </summary>
	Default = 0,

	OperationId = 1,

	/// <summary>
	/// Compose something like GetSomeWhereById1AndId2. Generally used with ContainerNameStrategy.Path
	/// </summary>
	MethodQueryParameters = 2,

	PathMethodQueryParameters = 3,

	/// <summary>
	/// According to Open API specification, it is RECOMMENDED that the naming of operationId follows common programming naming conventions. 
	/// However, some YAML may name operationId after a valid function name. For example, "list-data-sets", "Search by name" or "SearchByName@WkUld". 
	/// Regular expression (regex) may be needed to pick up alphanumeric words from such operationId and create a valid function name.
	/// The default RegexForNormalizedOperationId is /w*.
	/// </summary>
	NormalizedOperationId = 4,
}

If the service publisher would use unique Operation IDs which may result in valid action/function names of source codes, this will be perfect. Otherwise, you may evaluate other ways of generating action names.

PathPrefixToRemove

/// <summary>
/// To compose client function name through removing path prefix. Typically / or /api. The default is /.
/// The lenght of the prefix is used to remove path prefix. Applied when ActionNameStrategy is PathMethodQueryParameters.
/// </summary>
public string PathPrefixToRemove { get; set; } = "/";

This is used when ActionNameStrategy is PathMethodQueryParameters, and when ContainerNameStrategy is Path.

RegexForNormalizedOperationId

/// <summary>
/// The default is \w* for picking up alphanumeric words from some crikey operationIds through matching a list of words 
/// which will be merged into a function name in Pascal or camel casing. 
/// Applied when ActionNameStrategy is NorrmalizedOperationId.
/// </summary>
public string RegexForNormalizedOperationId { get; set; } = @"\w*";

This should be used together with ActionNameStrategy.NormalizedOperationId.

The YAML file provides unique operation IDs for each action, however, the IDs may contain some combinations of characters thus the IDs could not become directly valid function names in C# and TypeScript. You may define a regular expression to transform the IDs to function names, removing invalid combinations of characters, and hopefully then you get unique and valid function names.

DecorateDataModelWithDataContract

/// <summary>
/// Generated data types will be decorated with DataContractAttribute and DataMemberAttribute in C#.
/// </summary>
public bool DecorateDataModelWithDataContract { get; set; }

If generated client API C# codes is going to be used in a service broker built on WCF or Web API, it is handy to decorate data models with DataContractAttribute, which is a preferred way of cherry picking in WebApiClientGen.

And if the property name defined in YAML is not a valid property name in C#, the adjusted name will become the property name in C#, and the original property name will be declared in DataContractAttribute so the serialization will be using the original property name.

DataContractNamespace

/// <summary>
/// When DecorateDataModelWithDataContract is true, this is the namespace of DataContractAttribute. For example, "http://mybusiness.com/09/2019
/// </summary>
public string DataContractNamespace { get; set; }

The namespace should follow the recommendation of WCF or SOAP: using URI as namespace.

DecorateDataModelWithSerializable

/// <summary>
/// Decorate the Data Model with the System.SerializableAttribute attribute
/// </summary>
public bool DecorateDataModelWithSerializable { get; set; }

If you the generated C# codes in a service broker, and use WebApiClientGen to generate client API codes of the broker, and you want SerializableAttribute to be the cherry picking method, then set this to true.

EnumToString

/// <summary>
/// Serialize enum to string. For C#, effective if DecorateDataModelWithDataContract is true, and the enum type is decorated by
/// [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] or [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))].
/// For TypeScript, the output is string enums.
/// </summary>
public bool EnumToString { get; set; }

You really need to test the respective backend if it expects normal enum which could be integer, or wants string. If the backend wants string, make this setting true.

GenerateBothAsyncAndSync

/// <summary>
/// For .NET client, generate both async and sync functions for each Web API function. When false, only async.
/// </summary>
public bool GenerateBothAsyncAndSync { get; set; }

There may be situations where you want some client API functions to become block calls. Then set it to true.

UseEnsureSuccessStatusCodeEx

/// <summary>
/// Replace EnsureSuccessStatusCode with EnsureSuccessStatusCodeEx for specific unsuccessful HTTP status handling, which throws YourClientWebApiRequestException.
/// </summary>
public bool UseEnsureSuccessStatusCodeEx { get; set; }

The standard HttpResponseMessage.EnsureSuccessStatusCode method does expose limited error details and sometimes you may want more details. The this setting will enable generating a block of extension codes:

using System;

namespace Fonlow.Net.Http
{
	using System.Net.Http;

	public class WebApiRequestException : HttpRequestException
	{
		public new System.Net.HttpStatusCode StatusCode { get; private set; }

		public string Response { get; private set; }

		public System.Net.Http.Headers.HttpResponseHeaders Headers { get; private set; }

		public System.Net.Http.Headers.MediaTypeHeaderValue ContentType { get; private set; }

		public WebApiRequestException(string message, System.Net.HttpStatusCode statusCode, string response, System.Net.Http.Headers.HttpResponseHeaders headers, System.Net.Http.Headers.MediaTypeHeaderValue contentType) : base(message)
		{
			StatusCode = statusCode;
			Response = response;
			Headers = headers;
			ContentType = contentType;
		}
	}

	public static class ResponseMessageExtensions
	{
		public static void EnsureSuccessStatusCodeEx(this HttpResponseMessage responseMessage)
		{
			if (!responseMessage.IsSuccessStatusCode)
			{
				var responseText = responseMessage.Content.ReadAsStringAsync().Result;
				var contentType = responseMessage.Content.Headers.ContentType;
				throw new WebApiRequestException(responseMessage.ReasonPhrase, responseMessage.StatusCode, responseText, responseMessage.Headers, contentType);
			}
		}
	}
}

And respective client API function wil run the extended method:

		var responseMessage = await client.SendAsync(httpRequestMessage);
		try
		{
			responseMessage.EnsureSuccessStatusCodeEx();

WebApiRequestException caught will reveal more details which you may want to display or log. And overall this feature is in line with what you get in typical AJAX calls of JavaScript.

IncludeEnsureSuccessStatusCodeExBlock

/// <summary>
/// Default  is true so the code block is included in the generated codes.
/// Defined if UseEnsureSuccessStatusCodeEx is true. Respective code block will be included the code gen output. However, if you have a few client APIs generated to be used in the same application,
/// and you may want these client APIs share the same code block, then put the WebApiRequestException code block to an assembly or a standalone CS file.
/// </summary>
public bool IncludeEnsureSuccessStatusCodeExBlock { get; set; } = true;

Alternatively you may have only one set of generated codes containing the WebApiRequestException code block, through setting this to false for generating other client API sets, if all these client API sets are all contained in one assembly, though generally it is recommended to have one client API set per assembly.

DataAnnotationsEnabled

/// <summary>
/// System.ComponentModel.DataAnnotations attributes are to be copied over, including Required, Range, MaxLength, MinLength and StringLength. Applied to C# only.
/// What defined in "default" of YAML will become the default value of respective property.
/// </summary>
public bool DataAnnotationsEnabled { get; set; }

The generated codes may look like:

		/// <summary>
		/// Max length: 6
		/// Min length: 6
		/// </summary>
		[System.ComponentModel.DataAnnotations.Required()]
		[System.Runtime.Serialization.DataMember()]
		[System.ComponentModel.DataAnnotations.StringLength(6, MinimumLength=6)]
		public string BsbCode { get; set; }

		/// <summary>
		/// Minimum items: 0
		/// Maximum items: 5
		/// </summary>
		[System.Runtime.Serialization.DataMember(Name="errors")]
		[System.ComponentModel.DataAnnotations.MinLength(0)]
		[System.ComponentModel.DataAnnotations.MaxLength(5)]
		public ErrorMessageType[] Errors { get; set; }

When used in service codes, .NET runtime will validate the constraints in the data bindings of Web APIs, and throw exceptions when payloads do not conform to the constraints. And the error messages generally contain fairly detailed information, by default are contained in the HTTP response managed by .NET runtime. This is handy in service broker, so less invalid client calls will reach the backend that the YAML represents, since the attributes and .NET runtime in the broker form a defense line for the backend.

DataAnnotationsToComments

/// <summary>
/// System.ComponentModel.DataAnnotations attributes are translated into Doc Comments, 
/// including Required, Range, MaxLength, MinLength, StringLength, DataType and RegularExpression. Applied to C# and TypeScript.
/// </summary>
public bool DataAnnotationsToComments { get; set; }

This setting does not need to be used together with DataAnnotationsEnabled.

HandleHttpRequestHeaders

/// <summary>
/// Function parameters contain a callback to handle HTTP request headers
/// </summary>
public bool HandleHttpRequestHeaders { get; set; }

Generated codes look like:

public async Task<BulkBillStoreForwardResponseType> BulkBillStoreForwardSpecialistAsync(BulkBillStoreForwardRequestType requestBody, Action<System.Net.Http.Headers.HttpRequestHeaders> handleHeaders = null)
{
	var requestUri = "mcp/bulkbillstoreforward/specialist/v1";
	using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
	{
	using (var requestWriter = new System.IO.StringWriter())
	{
	var requestSerializer = JsonSerializer.Create(jsonSerializerSettings);
	requestSerializer.Serialize(requestWriter, requestBody);
	var content = new StringContent(requestWriter.ToString(), System.Text.Encoding.UTF8, "application/json");
	httpRequestMessage.Content = content;
	if (handleHeaders != null)
	{
		handleHeaders(httpRequestMessage.Headers);
	}

	var responseMessage = await client.SendAsync(httpRequestMessage);

CancellationTokenEnabled

/// <summary>
/// Allow cancellation in Send
/// </summary>
public bool CancellationTokenEnabled { get; set; }

Generated codes look like:

public async Task AddPetAsync(Pet requestBody, System.Threading.CancellationToken cancellationToken)
{
	var requestUri = "pet";
	using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
	{
	using (var requestWriter = new System.IO.StringWriter())
	{
	var requestSerializer = JsonSerializer.Create(jsonSerializerSettings);
	requestSerializer.Serialize(requestWriter, requestBody);
	var content = new StringContent(requestWriter.ToString(), System.Text.Encoding.UTF8, "application/json");
	httpRequestMessage.Content = content;
	var responseMessage = await client.SendAsync(httpRequestMessage, cancellationToken);
	try
	{
		responseMessage.EnsureSuccessStatusCodeEx();
	}
	finally
	{
		responseMessage.Dispose();
	}
	}
	}
}

UsePascalCase

/// <summary>
/// Use pascal case for properties and model class names
/// </summary>
public bool UsePascalCase { get; set; }

Generally the codegen respects the original names of models and properties. When this is true, such Pascal Casing is applied.

PrefixWithTypeName

/// <summary>
/// Prefix class names with enclosing Type name. Default True.
/// </summary>
public bool PrefixWithTypeName { get; set; } = true;

By default, if a type (e.g. SubT is defined in another type (e.g. MyT), the class name for the type will be MyTSubT, while the class name of the enclosing type name is MyT. If you are sure that the Swagger/OpenApi definition file does not define multiple instances of SubT but with different detailed definitions, you may set this to false in order to have shorter class names.

UseSystemTextJson

/// <summary>
/// Use System.Text.Json instead of Newtonsoft.Json
/// </summary>
public bool UseSystemTextJson { get; set; }

Use this if you are confident that System.Text.Json can handle all scenarios of data serializations, and you don't want your eco system to have Newtonsoft.Json.

DateToDateOnly

	/// <summary>
	/// For date type, generate DateOnly property. Default true. If false, generate DateTimeOffset decorated by System.ComponentModel.DataAnnotations.DataTypeAttribute(System.ComponentModel.DataAnnotationsDataType.Date)
	/// </summary>
	public bool DateToDateOnly { get; set; } = true;

If you want the generate C# client API codes to be used by .NET Framework clients only, you may set this setting to false. If you want the generated codes to be used by both .NET Framework clients and .NET clients, you may keep DateToDateOnly to true. And make a copy of the generated codes and replace all "DateOnly" identifiers with "DateTimeOffset".

Hints:

When DateToDateOnly is false and the OpenAPI definition defined a date, the C# codes generated will be:

[System.ComponentModel.DataAnnotations.DataTypeAttribute(System.ComponentModel.DataAnnotations.DataType.Date)]
public System.Nullable<System.DateTimeOffset> BirthDate { get; set; }

UseGuid

/// <summary>
/// Use Guid for format:uuid specifier, defaults to string.
/// </summary>
public bool UseGuid { get; set; }

Set to true only if the service is using GUID for UUID.

DecorateDataModelWithPropertyName

/// <summary>
/// Generated data types will be decorated with System.Text.Json.Serialization.JsonPropertyNameAttribute or Newtonsoft.Json.JsonPropertyAttribute with the original property name defined in YAML.
/// </summary>
public bool DecorateDataModelWithPropertyName { get; set; }

This is similar to setting DecorateDataModelWithDataContract. Use this if you don't want to use DataContractAttribute.

And the difference is, with DataContractAttribute, the original property name in YAML is declared only if C# property name is different from the original property name; however, with DecorateDataModelWithPropertyName, the original property name is always declared. Nevertheless, the effect of serialization is the same.

DisableSystemNullableByDefault

/// <summary>
/// OpenApClinetGent declares all value type properties including enum properties as nullable by default in generated C#, and all properties as nullable by default in generated TypeScript codes, unless the property is required. 
/// This is to prevent serializer from creating payload for properties not assigned.  
/// There might be situations in which you don't want such default features and want the codegen to respect OpenApi v3 option nullable. Then turn this setting to true, which affects generated C# codes only.
/// Please note, Some Open API definition files do not define nullable for some premitive types and enum, however, the respective backends do not expect some properties presented in the payload of the request.
/// therefore you need to build some integration test suites to find out what the backend would like.
/// If the YAML file defines a reference type property as nullable, the codegen ignores this setting since in C# a nullable reference type property is invalid, unless you set UseNullableReferenceType to true.
/// </summary>
public bool DisableSystemNullableByDefault { get; set; }

You should use this setting with great caution, carefully examining the behaviors of the backend. In contrast, WebApiClientGen generates client C# codes following the nullish of respective value properties in the service codes, while value type properties of generated client TypeScript codes are always nullable unless required.

With WebApiClientGen, generally, if a data model class is used in CRUD operations, it is better that all properties are nullable on the client sides through decorating the server side properties with [DataMember(IsRequired = true)] or [RequiredAttribute()]. OpenApiClientGen follows such convention.

UseNullableQuestionMark

/// <summary>
/// Use T? instead of System.Nullable<T> for value types, while by default System.Nullable<T> is used. C# 2.0 feature
/// </summary>
public bool UseNullableQuestionMark { get; set; }

Though mostly you won't look at the generated codes, it may be sweeter that generated C# codes also use question marks just as generated TypeScript codes do.

UseNullableReferenceType

/// <summary>
/// Use T? for reference types. C# 8.0 feature: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types
/// https://docs.microsoft.com/en-us/dotnet/csharp/nullable-migration-strategies: The global nullable context does not apply for generated code files.
/// Therefore it is up to you application programmers to make the compiler recognize the generated file is not of generated codes.
/// Check test case: Test_vimeo()
/// </summary>
public bool UseNullableReferenceType { get; set; }

You should use this setting with great caution.

GenerateModelsOnly

/// <summary>
/// Create the Model classes only
/// </summary>
public bool GenerateModelsOnly { get; set; }

This is fairly similar to Poco2TS.exe in WebApiClientGen, which reads an assembly file and generate data models.

ArrayAs

/// <summary>
/// By default, array type will be array in generated C#. You may generated IEnumerable and some of its derived types.
/// </summary>
public ArrayAsIEnumerableDerived ArrayAs { get; set; }
	public enum ArrayAsIEnumerableDerived
	{
		Array,

		IEnumerable,
		IList,
		ICollection,
		IReadOnlyList,
		IReadOnlyCollection,

		List,
		Collection,
		ReadOnlyCollection,
	}

Microsoft best practices often recommend using IEnumerable. The preference of using Array in OpenApiClientGen and WebApiClientGen is more in line with what could be possibly generated for TypeScript. Depending on your own contexts, you may choose other forms of data collection.

ClientLibraryProjectFolderName

/// <summary>
/// Assuming the client API project is the sibling of Web API project. Relative path to the WebApi project should be fine. C# only.
/// </summary>
public string ClientLibraryProjectFolderName { get; set; }

CreateFolder

/// <summary>
/// Create destination folder if not exists. Applied to both CS and TS.
/// </summary>
public bool CreateFolder { get; set; }

You may want this option if you would generate codes for the other developers, and the codes won't become part of your VS SLN.

ClientLibraryFileName

/// <summary>
/// The name of the CS file to be generated under client library project folder. The default is OpenApiClientAuto.cs.
/// </summary>
public string ClientLibraryFileName { get; set; } = "OpenApiClientAuto.cs";

Recommended naming pattern is "BackendNameClientAuto.cs".

SortTypesMembersAndMethods

/// <summary>
/// When generating codes, sort types, member properties and methods by alphabetic order.
/// </summary>
public bool SortTypesMembersAndMethods { get; set; }

By default, The order of types, properties and functions are following the initial orders declared in the OpenAPI definition, as well as the order of recursive searching of casual types. However, sometimes you may want these are sorted by alphabetic order because of any of the reasons:

  1. You just prefer things are sorted always.
  2. You find such consistently sorted entities make diff upon previous version easier.
  3. The author of the OpenAPI definition file has always been changing the visual layout and order of entities significantly however without significant changes in logics.
  4. ...