SwaggerResponseExample override example xml comments irrelevant to status code
soroshsabz opened this issue · 8 comments
ITNOA
I have below code
/// <summary>
/// Add a new test data
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
/// <response code ="200">
/// Returns the newly created item
/// </response>
[HttpPost]
[Route("")]
[ProducesResponseType(typeof(Response<TestDataViewModel>), 200)]
[ProducesResponseType(typeof(Response<TestDataViewModel>), 400)]
[SwaggerResponseExample(400, typeof(TestDataViewModelResponseErrorExample))]
[AllowAnonymous]
public async Task<ActionResult<Response<TestDataViewModel>>> Add([FromBody] TestRequest request)
{
return await Task.FromResult(new Response<TestDataViewModel>()
{
Data = new TestDataViewModel()
{
Id = 1,
Name = "Test"
},
StatusCode = BSN.Commons.PresentationInfrastructure.ResponseStatusCode.OK,
Message = "OK"
});
}
and my TestDataViewModelResponseErrorExample
like below
public class TestDataViewModelResponseErrorExample : IExamplesProvider<Response<TestDataViewModel>>
{
/// <summary>
/// Get examples
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Response<TestDataViewModel> GetExamples() => new Response<TestDataViewModel>()
{
Data = null,
InvalidItems = new List<InvalidItem>()
{
new InvalidItem()
{
Name = "Id",
Reason = "Id is required"
}
},
StatusCode = BSN.Commons.PresentationInfrastructure.ResponseStatusCode.BadRequest,
Message = "Bad Request"
};
}
and my TestDataViewModel
like below
public class TestDataViewModel
{
/// <summary>
/// Id
/// </summary>
/// <example>10</example>
public int Id { get; set; }
/// <summary>
/// Name
/// </summary>
/// <example>Hooshang</example>
public string Name { get; set; }
}
if I remove [SwaggerResponseExample(400, typeof(TestDataViewModelResponseErrorExample))]
and TestDataViewModelResponseErrorExample
, response example show example based on xml in 200 and 400 like below
But when TestDataViewModelResponseErrorExample
exist, all of my examples changes! I say only for 400 we want to see TestDataViewModelResponseErrorExample
but I see this for 200 too. like below
How to say Swashbuckle.AspNetCore.Filters use example xml tags default?
@mattfrear you can see my code in https://github.com/soroshsabz/TestSolution/tree/main/Source/ASPTest/AutofacHandyMVCTest/Controllers/V1
thanks a lot
ITNOA
What does that mean?
Your example solution doesn't compile for me because of your using BSN.Commons.Responses
. I don't know what Response<> is.
I can't reproduce your problem, it works fine for me.
Are you calling c.ExampleFilters();
? I couldn't see that in your sample solution.
Maybe try call c.ExampleFilters_PrioritizingExplicitlyDefinedExamples()
instead?
@mattfrear very thanks to fast response to me
I have complete code (I test it, and compile correctly) in https://github.com/soroshsabz/TestSolution/tree/main/Source/ASPTest/AutofacHandyMVCTest
BSN.Commons.Responses
in https://www.nuget.org/packages/BSN.Commons.PresentationInfrastructure nuget and in https://github.com/BSVN/Commons github- As you can see ConfigureSwaggerOptions.cs I add
ExampleFilters();
like below
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;
namespace AutofacHandyMVCTest
{
/// <summary>
/// Configure Swagger Options
/// </summary>
public class ConfigureSwaggerOptions : IConfigureNamedOptions<SwaggerGenOptions>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="provider"></param>
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider)
{
_provider = provider;
}
/// <summary>
/// Configure each API discovered for Swagger Documentation
/// </summary>
/// <param name="options"></param>
public void Configure(SwaggerGenOptions options)
{
// add swagger document for every API version discovered
foreach (var description in _provider.ApiVersionDescriptions)
{
options.SwaggerDoc(
description.GroupName,
CreateVersionInfo(description));
}
options.ExampleFilters();
// For adding xml commenting (see also Documentation file in project properties)
// https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#generatedocumentationfile
var xmlCommentsPath = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
options.IncludeXmlComments(xmlCommentsPath);
options.MapType(typeof(TimeSpan?), () => new OpenApiSchema
{
Type = "string",
Example = new OpenApiString("00:00:00")
});
// Because capable to use AutoREST we must to disable UseAllOfToExtendReferenceSchemas
// for more information please see https://stackoverflow.com/q/59788412/1539100
// and https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/issues/13
// options.UseAllOfToExtendReferenceSchemas();
// operationId is an optional unique string used to identify an operation.
// If provided, these IDs must be unique among all operations described in your API.
//
// However, AutoRest seems to use that to identify each method.
// I found a GitHub question / issue: <see href:https://github.com/Azure/autorest/issues/2647/>
// where people addressed this by configuring AutoRest to use tags instead of operation ID to identify method.
//
// <see href:https://stackoverflow.com/a/60875558/1539100/>
options.CustomOperationIds(description => (description.ActionDescriptor as ControllerActionDescriptor)?.ActionName);
}
/// <summary>
/// Configure Swagger Options. Inherited from the Interface
/// </summary>
/// <param name="name"></param>
/// <param name="options"></param>
public void Configure(string name, SwaggerGenOptions options)
{
Configure(options);
}
/// <summary>
/// Create information about the version of the API
/// </summary>
/// <param name="desc"></param>
/// <returns>Information about the API</returns>
private OpenApiInfo CreateVersionInfo(ApiVersionDescription desc)
{
var info = new OpenApiInfo()
{
Title = "Web MVC Test Project",
Version = desc.ApiVersion.ToString(),
Description = "Web MVC Test Project with Autofac and Swagger",
TermsOfService = new Uri("https://resaa.net"),
License = new OpenApiLicense
{
Name = "BSN Corporation",
Url = new Uri("https://resaa.net/LICENSE"),
}
};
if (desc.IsDeprecated)
{
info.Description += " This API version has been deprecated. Please use one of the new APIs available from the explorer.";
}
return info;
}
private readonly IApiVersionDescriptionProvider _provider;
}
}
and I call services.AddSwaggerExamplesFromAssemblyOf<Startup>();
in my Startup.cs like below
using Autofac;
using Autofac.Configuration;
using Autofac.Features.AttributeFilters;
using AutofacHandyMVCTest.Controllers;
using AutofacHandyMVCTest.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Swashbuckle.AspNetCore.Filters;
namespace AutofacHandyMVCTest
{
/// <summary>
/// Based on <see href="https://learn.microsoft.com/en-us/aspnet/core/migration/50-to-60?#use-startup-with-the-new-minimal-hosting-model"/>
/// </summary>
public class Startup
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="configuration"></param>
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
/// <summary>
/// The application configuration property (appsettings.json)
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Configure the application services.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
services.AddApiVersioning((opt) =>
{
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.ReportApiVersions = true;
opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version"));
});
services.AddVersionedApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.ConfigureOptions<ConfigureSwaggerOptions>();
// based on https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters?tab=readme-ov-file#installation
services.AddSwaggerExamplesFromAssemblyOf<Startup>();
}
/// <summary>
/// Configure the application container.
/// </summary>
/// <param name="builder"></param>
public void ConfigureContainer(ContainerBuilder builder)
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("autofac.json");
var autoFacConfigurationModule = new ConfigurationModule(configurationBuilder.Build());
builder.RegisterModule(autoFacConfigurationModule);
var controllers = typeof(Startup).Assembly.GetTypes().Where(t => t.BaseType == typeof(Controller)).ToArray(); // for mvc controller
builder.RegisterTypes(controllers).WithAttributeFiltering();
controllers = typeof(Startup).Assembly.GetTypes().Where(t => t.BaseType == typeof(ControllerBase)).ToArray(); // for api controller
builder.RegisterTypes(controllers).WithAttributeFiltering();
}
/// <summary>
/// Configure the application and the HTTP request pipeline.
/// </summary>
/// <typeparam name="App"></typeparam>
/// <param name="app"></param>
/// <param name="env"></param>
public void ConfigureApp<App>(App app, IWebHostEnvironment env) where App : IApplicationBuilder, IEndpointRouteBuilder, IHost
{
// Configure the HTTP request pipeline.
if (env.IsDevelopment())
{
var apiVersionDescriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
}
}
}
My exact problem is [SwaggerResponseExample(400, typeof(TestDataViewModelResponseErrorExample))]
change 400 and 200 examples both of them, but I except and I want to [SwaggerResponseExample(400, typeof(TestDataViewModelResponseErrorExample))]
only change 400 example, and 200 show example base on xml comments
@mattfrear Did you have any idea about my problem?
thanks for that
You still didn't answer my first question about ITNOA.