Search code examples
c#asp.netasp.net-corenswagasp.net-mvc-apiexplorer

Customize parameter in NSwag/ApiExplorer


I'm using NSwag to generate a Swagger document for my ASP.NET 6 API. My application uses strongly typed ids which are simple records with a value property.

public record Id(Guid Value);

Such ids are used as parameters in controller methods. Example:

public class MyController
{
    [HttpPost]
    public void Test(Id id)  {}
}

Execution wise this works fine. I have a custom model binder alongside with a factory that automatically handles binding from GUID strings. Same goes for serialization where a custom json converter handles this. To ensure that NSwag properly generates a string property I have a type mapper that maps Id properties as strings in the model.

The only remaining issue is that NSwag generates strange input parameters for my controller actions. It ends up looking like this:

enter image description here

I was able to dig into the code of NSwag and find the corresponding code for generating parameters. The OperationParameterProcessor uses the data generated by the api explorer provided by ASP.NET. The api explorer seems to interpret this property wrong and therefore generates two parameters instead of just a single one.

I was unable to find any resources on how to customize the behavior of the api explorer so you can customize models. How do I convince ASP.NET that my Id object is just a plain string instead of a complex object?


Solution

  • It seems that adding a pseudo type converter did the trick. I decorated my Id class with a custom type converter

    internal class IdentityObjectTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
            sourceType == typeof(string);
    }
    

    While this converter itself does not handle the value conversion it does provide ASP.NET with enough metadata so that the Api Explorer generated the proper definition. I would prefer a type converter over a model binder, however type converters are not flexible enough when it comes to polymorphism.

    In the end the type converter fakes the type while the model binder does the actual binding. It's not ideal but probably the best solution as long as type converters are missing proper metadata when converting a value.

    enter image description here