Skip to content

Settings Explained

fonlow edited this page Mar 2, 2024 · 18 revisions

Settings

ApiSelection

ExcludedControllerNames

/// <summary>
/// To exclude some controllers. For example, [My.Namespace.Home, My.Namespace.FileUpload] for My.Namespace.HomeController and My.Namespace.FileUploadController.
/// </summary>
public string[] ExcludedControllerNames { get; set; }

WebApiClientGen is focused on Strong Typed Web API, while some Web APIs not strongly typed. Thus this setting excludes them, otherwise, the generated codes are not usable. For such APIs, it is better to hand craft client codes according to their dynamic behaviors.

Hints

It is recommended to decorate respective controllers with [ApiExplorerSettings(IgnoreApi = true)] instead.

DataModelAssemblyNames and CherryPickingMethods

/// <summary>
/// To include assemblies containing data models. Assembly names should be without file extension.
/// </summary>
public string[] DataModelAssemblyNames { get; set; }

/// <summary>
/// Cherry picking methods of POCO classes
/// </summary>
public int? CherryPickingMethods { get; set; }

It is a good practice to put data models into a stand-alone assembly, so multiple domain sepcific assemblies could access the same data models without acknowledging the other domains. And such practice is good to generating client side data models too, since you may want to expose only a subset of data models used on the server side. Such stand-alone assemblies make it easier to cherry-pick what you would expose through grouping by assembly. WebApiClientGen will use the same CherryPickingMethods for all assemblies defined in DataModelAssemblyNames.

Remarks

POCO2TS.exe shares the same CherryPickingMethods setting with WebApiClientGen. POCO2TS.exe uses command line argument to define the setting, while WebApiClientGen uses CodeGen.json.

DataModels

/// <summary>
/// Similar to DataModelAssemblyNames however, each assembly could have a CherryPickingMethods. An assembly should appear in either DataModelAssemblyNames or DataModels, not both.
/// </summary>
public DataModel[] DataModels { get; set; }

public class DataModel
{
	public string AssemblyName { get; set; }

	public int? CherryPickingMethods { get; set; }

	/// <summary>
	/// System.ComponentModel.DataAnnotations attributes are translated into Doc Comments, 
	/// including Required, Range, MaxLength, MinLength, StringLength, DataType and RegularExpression.
	/// If defined, overwrite the global setting in ModelGenOutputs; if not defined, follow the global setting.
	/// </summary>
	public bool? DataAnnotationsToComments { get; set; }
}

The server side data models may be used for various purposes and different serialization methods. With DataModels you may define a set cherry picking methods for each data model assembly.

If the data model assembly has doc comment describing data annotation attributes, you may want to set DataAnnotationsToComments to false. This is particularly useful when you use OpenApiClientGen to generate a client API for service C, and the client API contains data annotation attributes and respective doc comments. Then you use the client API for service C to build a broker service. When generating client API for the broker service, you want to copy data annotation attributes of service models over through DataAnnotationsEnabled=true and DataAnnotationsToComments=true. Having DataAnnotationsToComments =false avoids duplicating doc comments describing data annotations.

ClientApiOutputs (Applied to C# only)

CSClientNamespaceSuffix

/// <summary>
/// The naming of namespace is after the controller's namespace. To distinguish from the server side namespace, it is better to add a suffix like ".Client". The default is ".Client".
/// </summary>
public string CSClientNamespaceSuffix { get; set; } = ".Client";

"Proxy " and "Agent" could be good candidates too.

DataAnnotationsEnabled

/// <summary>
/// System.ComponentModel.DataAnnotations attributes are to be copied over, including Required, Range, MaxLength, MinLength and StringLength.
/// </summary>
public bool DataAnnotationsEnabled { get; set; }

When System.ComponentModel.DataAnnotations attributes are used in the data binding of Web API parameters, .NET runtime may validate the incoming data and throw exceptions with sepecific messages, and the client programs which catch resepctive HTTP errors may display the errors. If the client API generated is used in a service broker, having these attributes copied over will make the broker have the same abilities of error handling, so the broker handles more of user input errors.

DataAnnotationsToComments

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

Having these in the doc comments is handy for client UI programming. In C#, some UI components may provide data input constraints through reading these attributes.

DecorateDataModelWithDataContract and DataContractNamespace

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

/// <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 generated client API codes may be used in a service broker based on .NET Web API, WCF or Biztalk. DataContractAttribute is preferred way of cherry picking and data binding. Another reason why DataContractAttribute is preferred is that DataContractAttbibute supports namespace. This is important in complex business applications, service brokers and Biztalks involving tons of data models for different business domains from different vendors.

DecorateDataModelWithSerializable

Occasionally you may find decorating data models with SerializableAttribute is useful when you client codes really need this attribute.

IEnumerableToArray

By default, IEnumerable and its derived types will be mapped to Array or T[], and these types include: IList, ICollection, IQuerable, IReadOnlyList, List, Collection, ObservableCollection etc. except IDictionary.

Nevertheless, for TypeScript client codes, the mapping is always Array<T>.

Remarks:

IDictionary and its derived types will always be mapped to respective types. Nevertheless, for TypeScript client codes, the mapping is always {[index:T]:Y}.

HelpStrictMode

Give TypeScript strict mode more signal for null value in both data models and client API functions. The returned types and parameters may be null. And some primitive types in data model / interface may be null.

Except for legacy TypeScript apps that you don't plan to conform to the strict mode, it is recommended to turn this option on. For more details, please check Required, Optional and Nullable in TypeScript.

MaybeNullAttributeOnMethod

When this setting is on along with HelpStrictMode=true, the return type of API functions associated with MaybeNullAttribute has an alternative type null, so to signal client codes that the return may be null, and the return type of all other functions will be without an alternative type null.

Generally your Web API functions should avoid returning null value/object. However, there may be legitimate reasons why you would return a null string, or a null object. In such cases, it would be good to give some signal to the client programmers in addition to your doc comments. While System.Diagnostics.CodeAnalysis.MaybeNullAttribute is designed for Code Analysis upon call stacks in the same call stack, WebApiClientGen utilizes this attribute for signaling null value/object in generated TypeScript client API codes. If your TypeScript codes is with StrictMode=true, you will get a TS2531 warning if not checking null first before accessing the property of the returned object.

Web API function:

[HttpGet("{id}")]
[ActionName("GetHero")]
[return: System.Diagnostics.CodeAnalysis.MaybeNull]
public Hero Get(long id)
{
	_ = HeroesData.Instance.Dic.TryGetValue(id, out Hero r);
	return r;
}

Generated TypeScript codes:

getHero(id?: number, headersHandler?: () => HttpHeaders): Observable<DemoWebApi_Controllers_Client.Hero | null> {
	return this.http.get<DemoWebApi_Controllers_Client.Hero | null>(this.baseUri + 'api/Heroes/' + id, { headers: headersHandler ? headersHandler() : undefined });
}

At the mean time, all other functions without

TS2532 warning

So you should then do:

	if (data) {
		console.debug(data.name);
	}

or Visual Studio IDE or alike with do some fix for you, like:

    console.debug(data?.name);

In the generated C# codes, you will see:

[return: System.Diagnostics.CodeAnalysis.MaybeNullAttribute()]
public DemoWebApi.Controllers.Client.Hero GetHero(long id, Action<System.Net.Http.Headers.HttpRequestHeaders> handleHeaders = null)
{
    ......

While the attribute decorated on the API function will probably never trigger any CA warning since no other function shall call this API function, the attribute will be replicated in the C# client API codes. The global nullable context does not apply for generated code files, and the usual call stacks in the clients codes won't trigger a compiler warning anyway. However, the attribute is still good for generating TypeScript codes from the C# client API codes if you are developing a broker service.

MaybeNullAttributeOnMethod should be exclusive to MaybeNullAttributeOnMethod. If NotNullAttributeOnMethod and MaybeNullAttributeOnMethod are both declared, MaybeNullAttributeOnMethod wins. This is because a decent API design should minimize returning null.

NotNullAttributeOnMethod

If for some reasons your API functions mostly return null, you may use this to decorated some functions that never return null. When this setting is on, the return type of TypeScript client API function is always with alternative type null, except those associated with NotNullAttribute.

NotNullAttributeOnMethod should be exclusive to MaybeAttributeOnMethod.

ClientLibraryProjectFolderName and FileName

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

/// <summary>
/// File to be generated under ClientLibraryProjectFolder. The default is WebApiClientAuto.cs.
/// </summary>
public string FileName { get; set; } = "WebApiClientAuto.cs";

Surely you need to create respective CS Project first and use the csproj folder name or relative path. WebApiClientGen supports only 1 CS client project, and if you need to support multiple CS client projects such as .NET Framework, .NET Core, .NET, .NET Standard etc., you may nominate one of the folder in ClientLibraryProjectFolderName, and all the other projects use symbolic links to include the CS file.

GenerateBothAsyncAndSync

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

Occasionally you may find using block calls may be more convenient, then you may set this to true.

StringAsString

/// <summary>
/// Whether the Web API return string as string, rather than JSON object which is a double quoted string.
/// </summary>
public bool StringAsString { get; set; }

ASP.NET Core Web API by default returns objects as JSON object, and string as text/plain. Your Web API should have consistent behavior of serializing strings, text or JSON object.

When StringAsString is true, the generated codes:

C#

var stream = await responseMessage.Content.ReadAsStreamAsync();
using (System.IO.StreamReader streamReader = new System.IO.StreamReader(stream))
{
	return streamReader.ReadToEnd();;
}

TypeScript

getABCDE(headersHandler?: () => HttpHeaders): Observable<string> {
	return this.http.get(this.baseUri + 'api/SuperDemo/String', { headers: headersHandler ? headersHandler() : undefined, responseType: 'text' });
}

When StringAsString is false, the generated codes:

C#

var stream = await responseMessage.Content.ReadAsStreamAsync();
using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
{
	return jsonReader.ReadAsString();
}

TypeScript

getABCDE(headersHandler?: () => HttpHeaders): Observable<string> {
	return this.http.get<string>(this.baseUri + 'api/SuperDemo/String', { headers: headersHandler ? headersHandler() : undefined });
}

Remarks:

How ASP.NET Core responds with string data may depend on multiple factors. According to MS Docs:

By default, when the framework detects that the request is coming from a browser:

The Accept header is ignored. The content is returned in JSON, unless otherwise configured. This approach provides a more consistent experience across browsers when consuming APIs.

Content negotiation is implemented by ObjectResult. Your Web API function with string as return type will by default return a string as JSON object, that is, with double quotes.

CamelCase

/// <summary>
/// Whether to conform to the camel casing convention of javascript and JSON.
/// If not defined, WebApiClientGen will check if GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver is Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver;
/// If CamelCasePropertyNamesContractResolver is presented, camelCasing will be used. If not, no camelCasing transformation will be used.
/// </summary>
public bool? CamelCase { get; set; }

Generally camel casing is preferred.

UseSystemTextJson

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

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.

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.

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();
	}
	}
	}
}

Plugins

Plugins are for plugin assemblies that generate TypeScript codes for JavaScript libraries and frameworks.

AssemblyName

A plugin generally provide 2 derived classes from what in Fonlow.WebApiClientGenCore.Abstract, for example:

public class ClientApiTsNG2FunctionGen : ClientApiTsFunctionGenBase
...
...
public class ControllersTsNG2ClientApiGen : ControllersTsClientApiGenBase
...

AssemblyName should be the filename of the assembly file without extension, for example, "Fonlow.WebApiClientGenCore.NG2". And the NuGet package generally has the same ID.

AsModule

For CLR namespace like "DemoWebApi.DemoData", the code gen will translate to "DemoWebApi_DemoData_Client". And JS/TS libraries and frameworks like Angular require explicit import from the other lib and export, the TypeScript codes generated should be like:

export namespace DemoWebApi_DemoData_Client {
	export interface Address {

and the caller codes should be like:

import { DemoWebApi_DemoData_Client, DemoWebApi_Controllers_Client } from './WebApiCoreNG2FormGroupClientAuto';

AsModule should be true for such cases of opt-in references.

Libraries like jQuery on the other hand prefer opt-out references, and AsModule should be false. The generated codes is like:

///<reference path="../typings/jquery/jquery.d.ts" />
///<reference path="HttpClient.ts" />
namespace DemoWebApi_DemoData_Client {

and the caller codes should be like:

/// <reference path="../ClientApi/WebApiCoreJQClientAuto.ts"/>

Remarks: No matter AsModule is true or false, the TypeScript codes are compiled by TypeScript compiler to:

var DemoWebApi_DemoData_Client;
(function (DemoWebApi_DemoData_Client) {
    let AddressType;

And the debug build of "NG Build" gives the same. However, the release build will remove these human-readable identifiers / containers.

Clone this wiki locally