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()
Use the null-conditional operator:
parameter.Default = description.RouteInfo?.DefaultValue;