简体   繁体   中英

Optional parameter causes null exception in Swashbuckle.AspNetCore

I have an asp.net core 2.0 api controller action with an optional (and nullable) parameter. Swashbuckle.AspNetCore 1.0.0 generation of Swagger documentation fails when this parameter is present in the method signature, but succeeds if I remove it from the signature. It appears the optional parameter is the cause of the error but I can't tell why...

I am using Swashbuckle integration with aspnet-api-versioning: https://github.com/Microsoft/aspnet-api-versioning/wiki/Swashbuckle-Integration

Controller action :

[HttpGet("{id}")]
public IActionResult GetPerson(int id, [FromQuery] bool? includeContactInfo = null)
{
    var includeInfo = (includeContactInfo.HasValue && includeContactInfo.Value == true);
    var person = _repo.GetPerson(id, includeInfo);
    if (person == null)
    {
        return NotFound();
    }

    if (includeInfo)
    {
        // using AutoMapper to map between entity and dto
        var personFullDto = Mapper.Map<PersonFullDto>(person);
        return Ok(personFullDto);
    }

    var personBasicDto = Mapper.Map<PersonBasicDto>(person);
    return Ok(personBasicDto);
}

Here are the ConfigureServices and Configure methods from my startup.cs :

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
    services.AddMvc();

    services.AddApiVersioning(o =>
    {
        o.ReportApiVersions = true;
        o.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
    });

    services.AddSwaggerGen(options =>
    {
        var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
        foreach (var description in provider.ApiVersionDescriptions)
        {
            options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
        }
        options.OperationFilter<SwaggerDefaultValues>();
        options.IncludeXmlComments(XmlCommentsFilePath);
    });

    var connectString = Startup.Configuration["connectionStrings:ContactsAppDb"];
    services.AddDbContext<ContactsDbContext>(o => o.UseSqlServer(connectString));
    services.AddScoped<IContactsRepository, ContactsRepository>();
}


public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
    if (env.IsEnvironment("PROD"))
    {
        app.UseExceptionHandler();
    }
    else
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStatusCodePages();

    AutoMapper.Mapper.Initialize(cfg =>
    {
        cfg.CreateMap<Entities.Person, Models.PersonBasicDto>();
        cfg.CreateMap<Entities.Person, Models.PersonFullDto>();
        cfg.CreateMap<Entities.ContactInfo, Models.ContactInfoDto>();
    });

    app.UseMvc();

    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        // build a swagger endpoint for each discovered API version
        foreach (var description in provider.ApiVersionDescriptions)
        {
            options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
        }
    });
}

I am also using the SwaggerDefaultValues.cs class, from https://github.com/Microsoft/aspnet-api-versioning/wiki/Swashbuckle-Integration#aspnet-core

public class SwaggerDefaultValues : IOperationFilter
{
    public void Apply( Operation operation, OperationFilterContext context )
    {
        foreach ( var parameter in operation.Parameters.OfType<NonBodyParameter>() )
        {
            var description = context.ApiDescription
                                     .ParameterDescriptions
                                     .First( p => p.Name == parameter.Name );

            if ( parameter.Description == null )
            {
                parameter.Description = description.ModelMetadata.Description;
            }

            if ( parameter.Default == null )
            {
                parameter.Default = description.RouteInfo.DefaultValue;
            }

            parameter.Required |= !description.RouteInfo.IsOptional;
        }
    }
}

When I navigate to the Swagger URL, the following line of code fails (in the SwaggerDefaultValues.cs class):

parameter.Default = description.RouteInfo.DefaultValue;

When I inspect the description object for the includeContactInfo optional/query parameter, the description.RouteInfo is null.

Error message:

Object reference not set to an instance of an object.

Stack trace:

at ContactsApp.Services.SwaggerDefaultValues.Apply(Operation operation, OperationFilterContext context) in C:\Users\me\Documents\Visual Studio 2017\Projects\ContactsApp\ContactsApp\Services\SwaggerDefaultValues.cs:line 37
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreateOperation(ApiDescription apiDescription, ISchemaRegistry schemaRegistry)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreatePathItem(IEnumerable`1 apiDescriptions, ISchemaRegistry schemaRegistry)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath, String[] schemes)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.<Invoke>d__6.MoveNext()

使用空条件运算符:

parameter.Default = description.RouteInfo?.DefaultValue;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM