I am trying to do versioning in ASP.NET Boilerplate framework.
I have created two versions in Swagger Gen ("v1.0" and "v2.0") and set API version for Web API, but every time I get all API in both versions from Swagger.
Startup.cs:
AddSwaggerGen
in ConfigureServices()
:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1.0", new Info { Title = "My API", Version = "v1.0" });
options.SwaggerDoc("v2.0", new Info { Title = "My API", Version = "v2.0" });
options.DocInclusionPredicate((docName, description) => true);
// Define the BearerAuth scheme that's in use
options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
// Assign scope requirements to operations based on AuthorizeAttribute
options.OperationFilter<SecurityRequirementsOperationFilter>();
});
UseSwaggerUI
in Configure():
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAbp(options => { options.UseAbpRequestLocalization = false; });
app.UseCors(_defaultCorsPolicyName);
app.UseStaticFiles();
app.UseAuthentication();
app.UseAbpRequestLocalization();
app.UseSignalR(routes =>
{
routes.MapHub<AbpCommonHub>("/signalr");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "defaultWithArea",
template: "{area}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.InjectOnCompleteJavaScript("/swagger/ui/abp.js");
options.InjectOnCompleteJavaScript("/swagger/ui/on-complete.js");
options.SwaggerEndpoint("/swagger/v1.0/swagger.json", "My API V1.0");
options.SwaggerEndpoint("/swagger/v2.0/swagger.json", "My API V2.0");
});
}
API Controller - v1.0:
[ApiVersion("v1.0")]
[Route("/api/invoicemodule/1.0/[controller]")]
public class InvoiceController : MyControllerBase
{
[HttpGet, MapToApiVersion("v1.0")]
public IActionResult GetInvoiceById(string invoiceid)
{
//BusinessService.SparePartHistoryService sas = new BusinessService.SparePartHistoryService(_logger, _localizer, _configuration);
if (string.IsNullOrEmpty(invoiceid)) return BadRequest("'Id' cannot be null or empty.");
try
{
BusinessModels.Invoice sp = new BusinessModels.Invoice
{
Id = ""
};
if (sp != null)
{
return Ok(sp);
}
else
{
return NotFound();
}
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
}
API Controller - v2.0:
[ApiVersion("v2.0")]
[Route("/api/invoicemodule/2.0/[controller]")]
public class InvoiceController : MyControllerBase
{
[HttpGet, MapToApiVersion("v2.0")]
public IActionResult GetInvoiceById(string invoiceid)
{
//BusinessService.SparePartHistoryService sas = new BusinessService.SparePartHistoryService(_logger, _localizer, _configuration);
if (string.IsNullOrEmpty(invoiceid)) return BadRequest("'Id' cannot be null or empty.");
try
{
BusinessModels.Invoice sp = new BusinessModels.Invoice
{
Id = ""
};
if (sp != null)
{
return Ok(sp);
}
else
{
return NotFound();
}
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
}
Output
Yeah, I got the solution
Reference : https://github.com/domaindrivendev/Swashbuckle.AspNetCore
I have changed little code of Service.AddSwaggerGen() in ConfigurationService() method of Startup.cs file
Comments added in code where i have changed
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Version = "1" }); // Version must require in integer. can't set Version = "v1"
options.SwaggerDoc("v2", new Info { Version = "2" });
options.DocInclusionPredicate((docName, description) =>
{ // insert this {} code instead of "true"
var versions = description.ControllerAttributes()
.OfType<ApiVersionAttribute>()
.SelectMany(attr => attr.Versions);
return versions.Any(v => $"v{v.ToString()}" == docName);
});
});
Step 1. Create ApiVersion1RoutePrefixAttribute.cs Class
public class ApiVersion1RoutePrefixAttribute: RoutePrefixAttribute {
private const string RouteBase = "api/{apiVersion:apiVersionConstraint(v1)}";
private const string PrefixRouteBase = RouteBase + "/";
public ApiVersion1RoutePrefixAttribute(string routePrefix):base (string.IsNullOrWhiteSpace(routePrefix)?RouteBase: PrefixRouteBase+routePrefix) {
}
}
Step 2. Create ApiVersion2RoutePrefixAttribute.cs Class
public class ApiVersion1RoutePrefixAttribute: RoutePrefixAttribute {
private const string RouteBase = "api/{apiVersion:apiVersionConstraint(v1)}";
private const string PrefixRouteBase = RouteBase + "/";
public ApiVersion1RoutePrefixAttribute(string routePrefix):base (string.IsNullOrWhiteSpace(routePrefix)?RouteBase: PrefixRouteBase+routePrefix) {
}
}
Step 3. create class ApiVersionConstraint
public class ApiVersionConstraint : IHttpRouteConstraint {
public string AllowedVersion { get; private set; }
public ApiVersionConstraint(string allowedVersion) {
this.AllowedVersion = allowedVersion.ToLowerInvariant();
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection) {
object value;
if(values.TryGetValue(parameterName,out value)&& value != null) {
return AllowedVersion.Equals(value.ToString().ToLowerInvariant());
}
return false;
}
}
Step 4 Create a class NamespaceHttpControllerSelector.cs
public class NamespaceHttpControllerSelector : IHttpControllerSelector {
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
public NamespaceHttpControllerSelector(HttpConfiguration config) {
_configuration = config;
_controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
}
public HttpControllerDescriptor SelectController(HttpRequestMessage request) {
var routeData = request.GetRouteData();
if (routeData == null) {
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var controllerName = GetControllerName(routeData);
if (controllerName == null) {
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var namespaceName = GetVersion(routeData);
if (namespaceName == null) {
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var controllerKey = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(controllerKey, out controllerDescriptor)) {
return controllerDescriptor;
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() {
return _controllers.Value;
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary() {
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
var assembliesResolver = _configuration.Services.GetAssembliesResolver();
var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (var controllerType in controllerTypes) {
var segments = controllerType.Namespace.Split(Type.Delimiter);
var controllerName = controllerType.Name.Remove(controllerType.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var controllerKey = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName);
if (!dictionary.Keys.Contains(controllerKey)) {
dictionary[controllerKey] = new HttpControllerDescriptor(_configuration,
controllerType.Name,
controllerType);
}
}
return dictionary;
}
private T GetRouteVariable<T>(IHttpRouteData routeData, string name) {
object result;
if (routeData.Values.TryGetValue(name, out result)) {
return (T)result;
}
return default(T);
}
private string GetControllerName(IHttpRouteData routeData) {
var subroute = routeData.GetSubRoutes().FirstOrDefault();
if (subroute == null) return null;
//((HttpActionDescriptor[])subroute.Route.DataTokens["actions"]).First()
var dataTokenValue = subroute.Route.DataTokens["actions"];
if (dataTokenValue == null)
return null;
var controllerName = ((HttpActionDescriptor[])dataTokenValue).First().ControllerDescriptor.ControllerName.Replace("Controller", string.Empty);
return controllerName;
}
private string GetVersion(IHttpRouteData routeData) {
var subRouteData = routeData.GetSubRoutes().FirstOrDefault();
if (subRouteData == null) return null;
return GetRouteVariable<string>(subRouteData, "apiVersion");
}
}
step 5. your WebApiConfig.cs file should be look like this
public static void Register(HttpConfiguration config) {
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("apiVersionConstraint", typeof(ApiVersionConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(config));
}
step 6. your controller shold be look like this
your Version 1 controller
namespace AgentExperienceAPI.Controllers.v1 {
[Route("GetStatus")]
[AcceptVerbs("GET")]
public HttpResponseMessage GetStatus() {
return Request.CreateResponse(HttpStatusCode.OK, new Dictionary<string, object> {
{ "Status", "OK" }
});
}
}
your Version 2 controller
namespace AgentExperienceAPI.Controllers.v1 {
[Route("GetStatus")]
[AcceptVerbs("GET")]
public HttpResponseMessage GetStatus() {
return Request.CreateResponse(HttpStatusCode.OK, new Dictionary<string, object> {
{ "Status", "OK" }
});
}
}
your controller should be in two separate folder see image
Swagger will handle the rest