Search code examples
c#polymorphismasp.net-core-webapi.net-5nswag

C# Polymorphic models with NSwag


Within my Asp.Net Core v5 application we have the following models

public class StorageRecordTypeMetadataBase
{
    public string PropertyName { get; set; }
    public bool Required { get; set; }
}

public class StringRecordTypeMetadata: StorageRecordTypeMetadataBase
{
    public string? ValidationRegex { get; set; }
}

public class NumericRecordTypeMetadata : StorageRecordTypeMetadataBase
{
    public int MinValue { get; set; }
    public int MaxValue { get; set; }
}

In my application Startup.cs I have registered Swagger and NSwag setup as following:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSwaggerDocument();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseOpenApi();
        app.UseSwaggerUi3();
     }

}

In order to support polymorphism, I have followed the guideline as written by NSwag creator: Guide on inheritance in NSwag, and here is how my model looks like updated:

[JsonConverter(typeof(JsonInheritanceConverter), "discriminator")]
[KnownType(typeof(StringRecordTypeMetadata))]
[KnownType(typeof(NumericRecordTypeMetadata))]
public class StorageRecordTypeMetadataBase
{
    public string PropertyName { get; set; }
    public bool Required { get; set; }
}

As soon as I run the application, swagger fails as swagger.json could not be generated. Upon investigating the issue I can see the following error message once I try to manually navigate to /swagger/v1/swagger.json

System.MissingMethodException: Method not found: 'System.String Namotion.Reflection.XmlDocsExtensions.GetXmlDocsSummary(System.Reflection.MemberInfo)'. at NSwag.Generation.Processors.OperationSummaryAndDescriptionProcessor.ProcessSummary(OperationProcessorContext context, List1 attributes) at NSwag.Generation.Processors.OperationSummaryAndDescriptionProcessor.Process(OperationProcessorContext context) at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.RunOperationProcessors(OpenApiDocument document, ApiDescription apiDescription, Type controllerType, MethodInfo methodInfo, OpenApiOperationDescription operationDescription, List1 allOperations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerType, List1 operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver) at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateForControllers(OpenApiDocument document, IGrouping2[] apiGroups, OpenApiSchemaResolver schemaResolver) at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(ApiDescriptionGroupCollection apiDescriptionGroups) at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(Object serviceProvider) at NSwag.AspNetCore.OpenApiDocumentProvider.GenerateAsync(String documentName) at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GenerateDocumentAsync(HttpContext context) at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GetDocumentAsync(HttpContext context) at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GetDocumentAsync(HttpContext context) at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I have tried to include the referenced package Namotion.Reflection even myself but that did not help either. Is there anything that I have missed during my configuration?

This was supposed to add discriminator field within the base model so that it would be automatically recognized when I generate my models on front end (React) side. I can achieve this behavior by moving away from NSwag, to Swashbuckle like following:

public void ConfigureServices(IServiceCollection services)
{
    ...
        services.AddSwaggerGen(c =>
       {
           c.UseAllOfForInheritance();
           c.SelectSubTypesUsing(baseType =>
           {
               return typeof(StorageRecordType).Assembly.GetTypes().Where(type => type.IsSubclassOf(baseType));
           });

           c.SelectDiscriminatorNameUsing((baseType) => "itemType");
           c.SelectDiscriminatorValueUsing((subType) => subType.Name);
       });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger((SwaggerOptions c) => { });
        app.UseSwaggerUI();
     }

}

However this completely breaks the NSwag generation process on my React side. Methods from all controllers are put together into a single Client (instead of being separated per controller name), plus some of the classes required in the parameters seem to be gone as well.

How can I fix the NSwag in order to get the discriminator value in my swagger.json response?


Solution

  • Ok so the issue was, that I was using older version of Nswag.AspNetCore. Instead of version 13.10.8 I upgraded to 13.15.5, which works great with package NJsonSchema v 10.6.6