How to configure MultipleApiVersions in Swashbuckle using aspnet ApiVersioning - c#

How do I configure swashbuckle to work with Aspnet API verisoning?
https://github.com/Microsoft/aspnet-api-versioning
In my Startup.cs I have the following code to initialize attribute based routing, api versioning, and swagger.
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
config.AddApiVersioning();
config.EnableSwagger(c =>
{
c.MultipleApiVersions(
(apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
(vc) =>
{
vc.Version("v1", "Swashbuckle Dummy API V1");
vc.Version("v2", "Swashbuckle Dummy API V2");
});
}
public static bool ResolveVersionSupportByRouteConstraint(ApiDescription apiDesc, string targetApiVersion)
{
var versionConstraint = (apiDesc.Route.Constraints.ContainsKey("apiVersion"))
? apiDesc.Route.Constraints["apiVersion"] as RegexRouteConstraint
: null;
return (versionConstraint == null)
? false
: versionConstraint.Pattern.Split('|').Contains(targetApiVersion);
}
When the ResolveVersionSupportByRouteConstraintmethod fires the route template includes the literal api string "api/v{version}/users" My users controller is decorated with [ApiVersion("1.0")] and I have the following route defined [Route("api/v{version:apiVersion}/users")]. When I hit api/v1/users with postman the call works, but i cannot figure out how to get this working with Swashbuckle/Swagger.
I want my swagger documentation to look like the example for the asp.net core api boilerplate, except I am using Owin with the owin startup class instead of .net core: https://github.com/ASP-NET-Core-Boilerplate/Templates/blob/master/MVC%206%20API.md

You can find examples here
This is how I done this in startup of self hosted owin app:
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
//configure your app
config.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) }
};
config.MapHttpAttributeRoutes(constraintResolver);
SwaggerConfiguration.Configure(config);
appBuilder.UseWebApi(config);
}
Configuration of swagger is very simple, main part here VersionedApiExplorer(ensure, that you passed right groupnameformat of your api, my format was v1, v2, etc):
public static class SwaggerConfiguration
{
public static void Configure(HttpConfiguration config)
{
var apiExplorer = config.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'V");
config.EnableSwagger(
swagger =>
{
swagger.MultipleApiVersions(
(apiDesc, targetApiVersion) => apiDesc.GetGroupName() == targetApiVersion,
versionBuilder =>
{
foreach (var group in apiExplorer.ApiDescriptions)
{
var description = "";
if (group.IsDeprecated) description += "This API deprecated";
versionBuilder.Version(group.Name, $"Service API {group.ApiVersion}")
.Description(description);
}
});
swagger.DocumentFilter<VersionFilter>();
swagger.OperationFilter<VersionOperationFilter>();
})
.EnableSwaggerUi(cfg => cfg.EnableDiscoveryUrlSelector());
}
In controller add attributes ApiVersion and RoutePrefix
[ApiVersion("1")]
[RoutePrefix("api/v{version:apiVersion}/history")]
public class HistoryController: ApiController
If you confused about VersionFilter and VersionOperationFilter there is code for that. This filters modifies resulting routes and parameters in swagger(without that your route will look like /v{version}/{actionName} and contain required parameter version)
public class VersionFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
swaggerDoc.paths = swaggerDoc.paths
.ToDictionary(
path => path.Key.Replace("v{version}", swaggerDoc.info.version),
path => path.Value
);
}
}
public class VersionOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var version = operation.parameters?.FirstOrDefault(p => p.name == "version");
if (version != null)
{
operation.parameters.Remove(version);
}
}
}

I think the ResolveVersionSupportByRouteConstraint method might be wrong, see: https://github.com/domaindrivendev/Swashbuckle/issues/197#issuecomment-75288894

Be aware you can achieve version substition by appropriate configuration of ApiExplorer.
See code fragment:
var apiExplorer = config.AddVersionedApiExplorer(config =>
{
config.SubstituteApiVersionInUrl = true;
This will replace version in paths and remove version parameter from operation parameters.

Related

How to get in Asp.Net Core a request POST from Angular

In Asp.NET MVC I was using Filters according below: (It works fine)
public class TracerAttribute : ActionFilterAttribute
{
public override Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() =>
{
var path = ConfigurationManager.AppSettings["QueueAddress"];
var queue = new MessageQueue(path);
queue.DefaultPropertiesToSend.Recoverable = true;
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var body = TraceMessageHelper.BuildBody(actionContext, assembly);
try
{
var label = ConfigurationManager.AppSettings["MessageLabel"] + body.Timestamp.ToString("yyyyMMddHHmmss");
var message = new Message
{
Formatter = new XmlMessageFormatter(new Type[] { typeof(TraceMessage) }),
Label = label,
Body = body
};
queue.Send(message);
}
catch (Exception e)
{
var logger = LogManager.GetLogger("LogInFile");
logger.Warn(e, LogMessageHelper.FormatRequest("TRACE SEND FAILED", actionContext.Request));
if (body != null)
{
var tracerlogger = LogManager.GetLogger("TracerInFile");
tracerlogger.Info(JsonConvert.SerializeObject(body));
}
}
queue.Close();
});
}
}
}
In Asp.NET CORE I'm using Filters according below: (It doesn't Work)
public class TracerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
{
var path = "FormatName:Direct=TCP:013cdnt2305\\private$\\TracerQueue";
var queue = new MessageQueue(path);
queue.DefaultPropertiesToSend.Recoverable = true;
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var body = TraceMessageHelper.BuildBody(actionContext, assembly);
try
{
var label = "APR_USER_20191018132324";
var message = new Message
{
Formatter = new XmlMessageFormatter(new Type[] { typeof(TraceMessage) }),
Label = label,
Body = body
};
queue.Send(message);
}
catch (Exception e)
{
HttpRequestMessageFeature hreqmf = new HttpRequestMessageFeature(actionContext.HttpContext);
var logger = LogManager.GetLogger("LogInFile");
logger.Warn(e, LogMessageHelper.FormatRequest("TRACE SEND FAILED", hreqmf.HttpRequestMessage));
if (body != null)
{
var tracerlogger = LogManager.GetLogger("TracerInFile");
tracerlogger.Info(JsonConvert.SerializeObject(body));
}
}
queue.Close();
}
}
}
}
In ASP.net Core I need to do change using only one parameter
In Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc(config =>
{
config.Filters.Add(new TracerAttribute());
});
//use our filter as a service type on the Action or Controller level
//services.AddScoped<TracerAttribute>();
}
In ASP.NET MVC (Works Fine)
In ASP.NET CORE(Doesn't work)
Angular return for both:
Anyone help me how to fix it?
If the angular app post json data(Content-Type: application/json) to .net core server side , you can get the values via [FromBody] :
[TracerAttribute]
[HttpPost]
public IActionResult MyAction([FromBody]Mymodel mymodel)
{
}
When action filter is calling , the body stream already has been read and [FromBody] model has been populated. Then you can get the data via :
public class TracerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var modelValues = actionContext.ActionArguments["mymodel"] as Mymodel ;
}
}

Custom Authorize attribute - ASP .NET Core 2.2

I want to create a custom Authorize attribute to be able to send a personalized response when it fails. There are many examples, but I could not find what I'm looking for.
When registering a policy, I add a "claim". Is it possible to access that registered claim within the custom attribute without having to pass the claim by parameter? or is it possible to know if the check of the claim happened and if not, return a personalized response? Thx!
public static void AddCustomAuthorization(this IServiceCollection serviceCollection)
{
serviceCollection.AddAuthorization(x =>
{
x.AddPolicy(UserPolicy.Read,
currentPolicy => currentPolicy.RequireClaim(UserClaims.Read));
});
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext authorizationFilterContext)
{
if (authorizationFilterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!authorizationFilterContext.HttpContext.User.HasClaim(x => x.Value == "CLAIM_NAME")) // ACCESS TO REGISTER CLAIM => currentPolicy => currentPolicy.RequireClaim(UserClaims.Read)
{
authorizationFilterContext.Result = new ObjectResult(new ApiResponse(HttpStatusCode.Unauthorized));
}
}
}
}
[HttpGet]
[CustomAuthorizeAttribute(Policy = UserPolicy.Read)]
public async Task<IEnumerable<UserDTO>> Get()
{
return ...
}
You can use IAuthorizationPolicyProvider to get the policy and then use ClaimsAuthorizationRequirement.ClaimType to get a claim name. And since it has async API, it is better to use IAsyncAuthorizationFilter instead of IAuthorizationFilter. Try this:
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext authorizationFilterContext)
{
var policyProvider = authorizationFilterContext.HttpContext
.RequestServices.GetService<IAuthorizationPolicyProvider>();
var policy = await policyProvider.GetPolicyAsync(UserPolicy.Read);
var requirement = (ClaimsAuthorizationRequirement)policy.Requirements
.First(r => r.GetType() == typeof(ClaimsAuthorizationRequirement));
if (authorizationFilterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (!authorizationFilterContext.HttpContext
.User.HasClaim(x => x.Value == requirement.ClaimType))
{
authorizationFilterContext.Result =
new ObjectResult(new ApiResponse(HttpStatusCode.Unauthorized));
}
}
}
}
This attribute takes an array of strings, which was needed in my case. I needed to pass different users roles to this attribute and return result based on some custom logic.
public class CustomAuthFilter : AuthorizeAttribute, IAuthorizationFilter
{
public CustomAuthFilter(params string[] args)
{
Args = args;
}
public string[] Args { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
//Custom code ...
//Resolving a custom Services from the container
var service = context.HttpContext.RequestServices.GetRequiredService<ISample>();
string name = service.GetName(); // returns "anish"
//Return based on logic
context.Result = new UnauthorizedResult();
}
}
You can decorate your controller with this attribute as shown below
[CustomAuthFilter("Anish","jiya","sample")]
public async Task<IActionResult> Index()
Sample is a class that returns a hard coded string
public class Sample : ISample
{
public string GetName() => "anish";
}
services.AddScoped(); //Register ISample, Sample as scoped.
FOR ASYNCHRONOUS SUPPORT use IAsyncAuthorizationFilter
public class CustomAuthFilter : AuthorizeAttribute, IAsyncAuthorizationFilter
{
public CustomAuthFilter(params string[] args)
{
Args = args;
}
public string[] Args { get; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
//DO Whatever...
//Resolve Services from the container
var service = context.HttpContext.RequestServices.GetRequiredService<ISample>();
var httpClientFactory = context.HttpContext.RequestServices.GetRequiredService<IHttpClientFactory>();
string name = service.GetName();
using var httpClient = httpClientFactory.CreateClient();
var resp = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/todos/1");
var data = await resp.Content.ReadAsStringAsync();
//Return based on logic
context.Result = new UnauthorizedResult();
}
}
Hope that helps..

.Net Core Override Controller Route for Generic Controller

I'm writing a RestFramework and I'm trying to figure out how I can allow the users to create a custom name for a generic controller. I'm registering my generic controllers like so:
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var entityConfig in _entityConfigurations)
{
var entityType = entityConfig.Type;
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType.AsType())
.GetTypeInfo();
//Normally I would expect there to be an overload to configure the controller name
//feature.Controllers.Add(controllerType, entityConfig.ControllerName);
}
}
}
}
How ever I need to figure out a way that I can override the route for the controllers. The only information about this in the documentation shows how to create a controller convention like so:
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>))
{
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name;
}
}
This will not work since it is done at compile time. I need user to be able to override the controller name on Startup, How can I Achieve this?
Based on your comment and code you were pretty much on par with how you would achieve this. Note I have cut down the example quite a bit so I could setup a test.
Say I have a basic generic controller as:
public class GenericController<T> : Controller
where T: class
{
public IActionResult Get()
{
return Content(typeof(T).FullName);
}
}
I now have a typed controller with Get action. Now most of your code was right on the money. So my Feature Provider as (note i have a static array of types):
public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var entityConfig in ControllerEntity.EntityTypes)
{
var entityType = entityConfig;
var typeName = entityType.Name + "Controller";
if (!feature.Controllers.Any(t => t.Name == typeName))
{
var controllerType = typeof(GenericController<>)
.MakeGenericType(entityType)
.GetTypeInfo();
feature.Controllers.Add(controllerType);
}
}
}
}
Next the IControllerModelConvention implementation.
public class GenericControllerModelConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (!controller.ControllerType.IsGenericType || controller.ControllerType.GetGenericTypeDefinition() != typeof(GenericController<>))
{
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = entityType.Name + "Controller";
controller.RouteValues["Controller"] = entityType.Name;
}
}
And finally the startup is where all the magic happens. Basically we register the IControllerModelConvention into the MVC convention options, and then register the FeatureProvider.
public void ConfigureServices(IServiceCollection services)
{
var mvcBuilder = services.AddMvc();
mvcBuilder.AddMvcOptions(o => o.Conventions.Add(new GenericControllerModelConvention()));
mvcBuilder.ConfigureApplicationPartManager(c =>
{
c.FeatureProviders.Add(new GenericControllerFeatureProvider());
});
}
From my review two things struck me.
I am not sure why you have your GenericControllerNameConvention as an attribute?
You should implicitly set the Controller Route Value to your entity type (not the type + name).
Given two entities (EntityA and EntityB) the result of the controllers is
/Entitya/get/ prints WebApplication11.Infrastructure.EntityA
/Entityb/get/ prints WebApplication11.Infrastructure.EntityB

RESTful api versioning and grouping in doc from Swagger with multiple endpoints

I am trying to implement the version options on a MVC dotnet Core app that has API endpoint on it.
The set up i am after is like this
--AiM api
|_v1
|_v2
--RMS api
|_v1
I have it mostly working but the items on v1 are not showing up on v2. The output is like so
But when we get to the version 2 on the AiM v2 endpoint I only the one item
Which is not what i was expecting
I have made a test to get each one showing on its different pages in swagger like this
In controller
[ApiVersion("2.0")]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "aim_v1")]
[Route("aim/v{version:apiVersion}/write/")]
public class aimWriter_v1Controller : Controller
{
[SwaggerOperation(Tags = new[] { "AiM Departments" })]
[HttpPost("departments/delete/{id}")]
public IActionResult departments(string foo)
{
return Json(new
{
results = "edited"
});
}
[SwaggerOperation(Tags = new[] { "AiM Contacts" })]
[HttpPost("contacts/delete/{id}")]
public IActionResult contact_delete(string foo)
{
return Json(new
{
results = "edited"
});
}
[SwaggerOperation(Tags = new[] { "AiM Contacts" })]
[HttpPost("contacts/activate/{id}")]
public IActionResult contact_activate(string foo)
{
return Json(new
{
results = "edited"
});
}
}
[ApiVersion("2.0")]
[ApiExplorerSettings(GroupName = "aim_v2")]
[Route("aim/v{version:apiVersion}/write/")]
public class aimWriter_v2Controller : Controller
{
[SwaggerOperation(Tags = new[] { "AiM Contacts" })]
[HttpPost("contacts/delete/{id}")]
public IActionResult contact_delete(string foo)
{
return Json(new
{
results = "edited"
});
}
}
[ApiVersion("2.0")]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "aim_v1")]
[Route("aim/v{version:apiVersion}/")]
public class aim_v1Controller : Controller
{
[SwaggerOperation(Tags = new[] { "AiM Rooms" })]
[HttpPost("rooms")]
public IActionResult rooms(string foo)
{
return Json(new
{
results = "foo"
});
}
[SwaggerOperation(Tags = new[] { "AiM Buildings" })]
[HttpPost("buildings/rooms/{id}")]
public IActionResult building_rooms(string foo)
{
return Json(new
{
results = "foo"
});
}
[SwaggerOperation(Tags = new[] { "AiM Rooms" })]
[HttpPost("rooms/{id}")]
public IActionResult room(string foo)
{
return Json(new
{
results = "foo"
});
}
}
// set up as just a new endpoint (NOTE: in different controller)
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "rms_v1")]
[Route("rms/v{version:apiVersion}/")]
public class rms_v1Controller : Controller
{
[SwaggerOperation(Tags = new[] { "RMS Orders" })]
[HttpPost("set_order/{id}")]
public IActionResult set_order(string foo)
{
return Json(new
{
results = "foo"
});
}
}
And in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting(options => options.LowercaseUrls = true);
services.AddMvc();
services.AddApiVersioning(options => {
options.AssumeDefaultVersionWhenUnspecified = true ;
options.DefaultApiVersion = new ApiVersion(new DateTime(2016, 7, 1));
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("aim_v1", new Info
{
Version = "aim/v1",
Title = "WSU HTTP API"
});
c.SwaggerDoc("aim_v2", new Info
{
Version = "aim/v2",
Title = "WSU HTTP API v2"
});
c.SwaggerDoc("rms_v1", new Info
{
Version = "rms/v1",
Title = "WSU HTTP API"
});
//Set the comments path for the swagger json and ui.
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var xmlPath = Path.Combine(basePath, "project.in.bin.def.xml");
c.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger(o =>
{
o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Host = httpReq.Host.Value);
o.RouteTemplate = "doc/{documentName}/scheme.json";
});
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "docs";
c.SwaggerEndpoint("/doc/aim_v1/scheme.json", "AiM v1.0.0");
c.SwaggerEndpoint("/doc/rms_v1/scheme.json", "Rms v1.0.0");
c.SwaggerEndpoint("/doc/aim_v2/scheme.json", "AiM v2.0.0");
});
}
And in the index.html for the swagger ui doc template file has
<script type="text/javascript">
window.JSConfig = JSON.parse('{"SwaggerEndpoints":[{"Url":"/doc/aim_v1/scheme.json","Description":"AiM v1.0.0"},{"Url":"/doc/aim_v2/scheme.json","Description":"AiM v2.0.0"},{"Url":"/doc/rms_v1/scheme.json","Description":"RMS v1.0.0"}],"BooleanValues":["false","true"],"DocExpansion":"list","SupportedSubmitMethods":["get","post","put","delete","patch"],"OnCompleteScripts":[],"OnFailureScripts":[],"ShowRequestHeaders":false,"JsonEditor":false,"OAuth2ClientId":"your-client-id","OAuth2ClientSecret":"your-client-secret-if-required","OAuth2Realm":"your-realms","OAuth2AppName":"your-app-name","OAuth2ScopeSeparator":" ","OAuth2AdditionalQueryStringParams":{}}');
$(function () {
hljs.configure({
highlightSizeThreshold: 5000
});
// Pre load translate...
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
window.swaggerUi = new SwaggerUi({
url: "/doc/aim_v1/scheme.json",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post'],
onComplete: function(swaggerApi, swaggerUi){
if(typeof initOAuth == "function") {
initOAuth({
clientId: "ffff==",
clientSecret: "bbbb",
realm: "wsu-api",
appName: "wsu-api-broker",
scopeSeparator: " ",
additionalQueryStringParams: {}
});
}
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
_.each(JSConfig.OnCompleteScripts, function (script) {
$.getScript(script);
});
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: false,
jsonEditor: false,
defaultModelRendering: 'schema',
showRequestHeaders: false
});
window.swaggerUi.load();
function log() {
if ('console' in window) {
console.log.apply(console, arguments);
}
}
});
In order to get the items on the different endpoints I used the [ApiExplorerSettings(GroupName = "aim_v1")] on the classes and matched them up in the Startup.cs and index.html files. At this point I am unsure where to make my edit to get all of the [ApiVersion("1.0")] items show on the [ApiVersion("2.0")] as I think the ApiExplorerSettings GroupName is what it locking this up.
To integrate everything smoothly, you also need to add the official API Explorer package for API Versioning. This will collate all of the API version information for you in a way that Swagger will understand. The official Swagger/Swashbuckle integration wiki topic has additional details and examples.
The setup will look like:
public void ConfigureServices( IServiceCollection services )
{
// note: this option is only necessary when versioning by url segment.
// the SubstitutionFormat property can be used to control the format of the API version
services.AddMvcCore().AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
} );
services.AddMvc();
services.AddApiVersioning();
services.AddSwaggerGen(
options =>
{
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
foreach ( var description in provider.ApiVersionDescriptions )
{
options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) );
}
options.IncludeXmlComments( XmlCommentsFilePath );
} );
}
public void Configure( IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider )
{
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
foreach ( var description in provider.ApiVersionDescriptions )
{
options.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant() );
}
} );
}
static string XmlCommentsFilePath
{
get
{
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var fileName = typeof( Startup ).GetTypeInfo().Assembly.GetName().Name + ".xml";
return Path.Combine( basePath, fileName );
}
}
static Info CreateInfoForApiVersion( ApiVersionDescription description )
{
var info = new Info()
{
Title = $"Sample API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Description = "A sample application with Swagger, Swashbuckle, and API versioning.",
Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei#somewhere.com" },
TermsOfService = "Shareware",
License = new License() { Name = "MIT", Url = "https://opensource.org/licenses/MIT" }
};
if ( description.IsDeprecated )
{
info.Description += " This API version has been deprecated.";
}
return info;
}
A full working answer is in that question:
Grouping and Versioning not working well together in swagger in asp.net core 3.1 web api
As the author said, the DocInclusionPredicate in AddSwaggerGen in the ConfigureServices is doing the trick to map the proper controller to the wanted swagger file.

Web Api How to add a Header parameter for all API in Swagger

I searched for possible ways to add a request header parameter that would be added automatically to every method in my web-api but i couldn't find a clear one.
While searching i found that the method OperationFilter() has to do something about it.
What the user "G T" wrote is correct but it is not working with Swagger 5. We have some new changes:
From: Operation to: OpenApiOperation
From: IParameter to: OpenApiParameter
From: NonBodyParameter to: OpenApiParameter, and the most important is...
From: Type = "string" to: Schema = new OpenApiSchema { Type = "String" }
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace MyAPI
{
public class AuthorizationHeaderParameterOperationFilter: IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "Authorization",
In = ParameterLocation.Header,
Description = "access token",
Required = true,
Schema = new OpenApiSchema
{
Type = "string",
Default = new OpenApiString("Bearer ")
}
});
}
}
}
}
And in Startup => ConfigureServices => services.AddSwaggerGen()
c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
Yes you can do it via inheriting from IOperationFilter
You can find the answer on GitHub here: AddRequiredHeaderParameter
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "X-User-Token",
In = "header",
Type = "string",
Required = false
});
}
}
Then you go to your SwaggerConfig.cs file and add the following in the AddSwaggerGen section:
c.OperationFilter<AddRequiredHeaderParameter>();
Rebuild, and enjoy.
Another way to add custom headers is by adding parameters into controller action.
The following example will add x-test parameter to the UI:
[HttpPost]
public IActionResult Test([FromHeader(Name="x-test")][Required] string requiredHeader)
{
return Ok();
}
I have improved the respectful Wille Esteche's answer a bit.
If you want to apply headers not to all methods, but only to your selected controller methods, you can use attributes.
[HttpPost]
[Route(nameof(Auth))]
[SwaggerHeader(Constants.HeaderDomainSid, "Encrypted User.Sid got from client", "abc123", true)]
public ActionResult<string> Auth([FromHeader(Name = Constants.HeaderDomainSid)] string headerDomainSid = null)
{ .....
Attribute class:
public class SwaggerHeaderAttribute : Attribute
{
public string HeaderName { get; }
public string Description { get; }
public string DefaultValue { get; }
public bool IsRequired { get; }
public SwaggerHeaderAttribute(string headerName, string description = null, string defaultValue = null, bool isRequired = false)
{
HeaderName = headerName;
Description = description;
DefaultValue = defaultValue;
IsRequired = isRequired;
}
}
Filter:
public class SwaggerHeaderFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Parameters ??= new List<OpenApiParameter>();
if (context.MethodInfo.GetCustomAttribute(typeof(SwaggerHeaderAttribute)) is SwaggerHeaderAttribute attribute)
{
var existingParam = operation.Parameters.FirstOrDefault(p =>
p.In == ParameterLocation.Header && p.Name == attribute.HeaderName);
if (existingParam != null) // remove description from [FromHeader] argument attribute
{
operation.Parameters.Remove(existingParam);
}
operation.Parameters.Add(new OpenApiParameter
{
Name = attribute.HeaderName,
In = ParameterLocation.Header,
Description = attribute.Description,
Required = attribute.IsRequired,
Schema = string.IsNullOrEmpty(attribute.DefaultValue)
? null
: new OpenApiSchema
{
Type = "String",
Default = new OpenApiString(attribute.DefaultValue)
}
});
}
}
}
For Asp .Net MVC 5 you can use.
Following the need to be done in Swagger Config file.
private class AddAuthorizationHeaderParameter: IOperationFilter // as a nested class in script config file.
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
operation.parameters.Add(new Parameter
{
name = "Authorization",
#in = "header",
type = "string",
required = true
});
}
}
c.OperationFilter<AddAuthorizationHeaderParameter>(); // finally add this line in .EnableSwagger
You can also add any no of headers for header implementation in Swagger.
In my case (.NET 5) I have to change some :
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter()
{
Name = "userNr",
In = ParameterLocation.Header,
Required = true
});
operation.Parameters.Add(new OpenApiParameter()
{
Name = "periodNo",
In = ParameterLocation.Header,
Required = true
});
}
}
and in Startup.cs --> ConfigureServices --> AddSwaggerGen
add
c.OperationFilter<AddRequiredHeaderParameter>();
If swagger is used in ASP.Net MVC5, and required to add headers to get input from swagger UI.
Create a class inherited from IOperationFilter:
using Swashbuckle.Swagger;
using System.Collections.Generic;
using System.Web.Http.Description;
public class AddHeaderParameters : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
operation.parameters.Add(new Parameter
{
name = "AccountUserName",
#in = "header",
type = "string",
required = true,
//description = "Account username"
});
}
}
Give reference of this class in SwaggerConfig.cs inside Configuration.EnableSwagger as:
c.OperationFilter<AddHeaderParameters>();
Important thing to note that the header name supposed to match with the actual header you have created for API.
This works for Swashbucke.AspNetCore 5.6.3
Create a new file and add the code below to the file
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace YourNameSpace
{
public class AuthorizationHeaderParameterOperationFilter:IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Security == null)
operation.Security = new List<OpenApiSecurityRequirement>();
var scheme = new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "bearer" } };
operation.Security.Add(new OpenApiSecurityRequirement
{
[scheme] = new List<string>()
});
}
}
}
In your Startup.cs add the code below to the ConfigureServices under services.AddSwaggerGen()
c.AddSecurityDefinition("bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
In = ParameterLocation.Header,
Scheme = "bearer"
});
c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
All should work fine now
for more information check here
Also you may have a base model class and use attribute [FromHeader] for properties which should be sent in custom headers. Something like this:
public class AuthenticatedRequest
{
[FromHeader(Name = "User-Identity")]
public string UserIdentity { get; set; }
}
At least it works fine for ASP.NET Core 2.1 and Swashbuckle.AspNetCore 2.5.0.
Another customized implementation for:
Web API (.Net 5)
Swashbuckle.AspNetCore.Swagger (6.2.3)
Swashbuckle.AspNetCore.SwaggerGen (6.2.3)
Swashbuckle.AspNetCore.SwaggerUI (6.2.3)
Following some of the answers from this thread did get me a required field for Authorization. However I had run into a different problems. I need to have the followings fixed:
Display a lock sign beside API actions where authentication is required. For anonymous actions there is no point having a required Authorization field.
For every API endpoi I did not want to input the Authorization key. It becomes a redundant work if we are to test couple of APIs at one go. So I needed a single point where I would put the Auth key and execute the APIs from the Swagger UI. The Swagger UI would handle the Auth part where required.
I did not want to add custom filter or codes in the controller actions where I might have to edit many actions.
Last but not the least, I had a problem where I did get the required Authorization field on the Swagger UI but that was not being post back in request header along the other API fields.
To overcome the above issues I have done the followings:
Create a IOperationFilter type filter to indicate which API endpoints requires authentication and which ones are anonymous type
A button on the Swagger UI to bring a popup to input my Auth token that would be used automatically with the API calls from the Swagger UI
Here are the codes:
#Step-1: The custom IOperationFilter type filrer:
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor)
{
// If [AllowAnonymous] is not applied or [Authorize] or Custom Authorization filter is applied on either the endpoint or the controller
if (!context.ApiDescription.CustomAttributes().Any((a) => a is AllowAnonymousAttribute)
&& (context.ApiDescription.CustomAttributes().Any((a) => a is AuthorizeAttribute)
|| descriptor.ControllerTypeInfo.GetCustomAttribute<AuthorizeAttribute>() != null))
{
if (operation.Security == null)
operation.Security = new List<OpenApiSecurityRequirement>();
operation.Security.Add(
new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme
{
Name = "Authorization",
In = ParameterLocation.Header,
BearerFormat = "Bearer token",
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[]{ }
}
});
}
}
}
Then in the startup.cs file, within the ConfigureServices method add the filter like this:
services.AddSwaggerGen(options =>
{
...
options.OperationFilter<AddRequiredHeaderParameter>();
...
...
}
Doing the above will add an icon to the API endpoints where Authentication is required. Here is the result:
#Step-2: Then we need the Auth token input UI. Add the following code right after the line in the startup.cs where you have added the IOperationFilter filter:
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header. \r\n\r\n Enter the token in the text input below.,
});
This will get you a Authorize button at the top of the API descriptor page. Clicking the button will bring a popup window where you can input the Auth token and have it passed down with each API call.
Not sure if it was answered with the 2021 Sep release for .net 5.0 but I took Rami'es answer and adapted it to the following:
namespace PartnerLicense
{
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Walter.Web.FireWall.Reseller;
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Parameters.Add(new()
{
Description = "Add your reseller ID here",
Name = HeaderKeys.ResellerId,
In = ParameterLocation.Header,
Required = true
});
}
}
}

Categories