You can use the value objects generated by Vogen as parameters in ASP.NET Core Web API endpoints. When an endpoint is run, the .NET runtime will notice the TypeConverter that is generated by default and use that to try to convert the daa from the request (e.g. string or int) to the value object.
Front end tools, such as Swagger, use an OpenAPI specification to describe the endpoints and the types used in those endpoints. By default, the value objects generated for the OpenAPI document are specified as object. For example, for this endpoint:
[HttpGet("/WeatherForecast/{cityName}")]
public IEnumerable<WeatherForecast> Get(CityName cityName)
{
...
}
You can call this from the Swagger UI (e.g., at http://localhost:5053/WeatherForecast/London) without having to do anything else, but Swagger will show the field as a JSON 'object' instead of the underlying type:
If you're using Swashbuckle to generate your OpenAPI document, then there are two ways to fix this. Both ways include setting a parameter in Vogen's global config:
The choices are: GenerateSwashbuckleMappingExtensionMethod or GenerateSwashbuckleSchemaFilter
The extension method mechanism is preferable to the schema filter mechanism, as the schema filter uses Reflection at runtime, which is unavailable when using AOT (Ahead-Of-Time).
This will create a class named VogenSwashbuckleExtensions without a namespace (so it'll be in the global namespaces). The class will contain an extension method named MapVogenTypesInMyWebApp (MyWebApp is the name of your app with illegal characters removed, e.g. MyApp.Core will become MyAppCore)
The extension method that is generated looks like this:
SwaggerGenOptions MapVogenTypesInMyWebApp(this SwaggerGenOptions o)
{
o.MapType<Celcius>(() => new OpenApiSchema { Type = "number" });
o.MapType<City>(() => new OpenApiSchema { Type = "string" });
...
If you decide to use the schema filter, it looks like this:
public class VogenSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext ctx)
{
see if it is ValueObjectAttribute
* get the underlying type
* create a schema object
* copy properties from the primitive to the new schema object
}
}
Many thanks to Vitalii Mikhailov for contributing the schema filter code.
Use with Azure Functions (Microsoft.Azure.WebJobs.Extensions.OpenApi)
Since MapType is not available, you will need to add custom DocumentFilters to your IOpenApiConfigurationOptions instance, for example:
[ValueObject<Guid>]
public readonly partial struct CustomerId;
public class OpenApiConfigurationOptions
: DefaultOpenApiConfigurationOptions
{
public OpenApiConfigurationOptions()
{
DocumentFilters.Add(new CustomTypeDocumentFilter<CustomerId>(
new() { Type = "string", Format = "uuid" }));
}
}
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Serialization;
public class CustomTypeDocumentFilter<T> : IDocumentFilter
{
private readonly static string _typeName =
typeof(T).GetOpenApiTypeName(new CamelCaseNamingStrategy());
private readonly OpenApiSchema _schema;
public CustomTypeDocumentFilter(OpenApiSchema schema)
{
_schema = schema;
}
public void Apply(IHttpRequestDataObject req, OpenApiDocument doc)
{
doc.Components.Schemas[_typeName] = _schema;
}
}
Credits to Jacob Marks for contributing the CustomTypeDocumentFilter code.