How to implement api versioning and swagger document dynamically - api

I am working in dotnet core api. I have to implement versioning on api. and swagger document should be categorized by api version.

In .NetCore api versioning can be implement by adding below reference from nuget
Microsoft.AspNetCore.Mvc.Versioning
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
After adding reference do following in startup file of your project. Add below line before AddMvc line. I will use Header-api versioning. It means client will mention the version in header. Header name is customizable.
services.AddApiVersioning(this.Configuration);
Definition of AddApiVersioning would be like as (In different extension class):
public static void AddApiVersioning(this IServiceCollection services, IConfiguration configuration)
{
services.AddApiVersioning(apiVersioningOptions =>
{
apiVersioningOptions.ApiVersionReader = new HeaderApiVersionReader(new string[] { "api-version" }); // It means version will be define in header.and header name would be "api-version".
apiVersioningOptions.AssumeDefaultVersionWhenUnspecified = true;
var apiVersion = new Version(Convert.ToString(configuration["DefaultApiVersion"]));
apiVersioningOptions.DefaultApiVersion = new ApiVersion(apiVersion.Major, apiVersion.Minor);
apiVersioningOptions.ReportApiVersions = true;
apiVersioningOptions.UseApiBehavior = true; // It means include only api controller not mvc controller.
apiVersioningOptions.Conventions.Controller<AppController>().HasApiVersion(apiVersioningOptions.DefaultApiVersion);
apiVersioningOptions.Conventions.Controller<UserController>().HasApiVersion(apiVersioningOptions.DefaultApiVersion);
apiVersioningOptions.ApiVersionSelector = new CurrentImplementationApiVersionSelector(apiVersioningOptions);
});
services.AddVersionedApiExplorer(); // It will be used to explorer api versioning and add custom text box in swagger to take version number.
}
Here configuration["DefaultApiVersion"] is a key in appsetting having value 1.0
As in above code we have used Convention to define api version for each controller. It is useful when there is one api version and you don't want to label each controller with [ApiVersion] attribute.
If you don't want to use the Convention menthod to define version of controller. use attribute label to define version. like as below:
[Route("[controller]")]
[ApiController]
[ApiVersion("1.0")]
public class TenantController : ConfigController
Once this done go to StartUp file and add below code.
app.UseApiVersioning(); //Here app is IApplicationBuilder
That is complete solution for api versioning.
For swagger We have to add nuget package as defined below:
Swashbuckle.AspNetCore
Swashbuckle.AspNetCore.SwaggerGen
Swashbuckle.AspNetCore.SwaggerUI
After adding reference do below: Add below line after Services.UseApiVersioning()
services.AddSwaggerGenerationUI();
The definition of AddSwaggerGenerationUI is below in extens :
public static void AddSwaggerGenerationUI(this IServiceCollection services)
{
var provider = services.BuildServiceProvider()
.GetRequiredService<IApiVersionDescriptionProvider>();
services.AddSwaggerGen(action =>
{
action.OrderActionsBy(orderBy => orderBy.HttpMethod);
action.UseReferencedDefinitionsForEnums();
foreach (var item in provider.ApiVersionDescriptions)
{
action.SwaggerDoc(item.GroupName, new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "Version-" + item.GroupName,
Version = item.ApiVersion.MajorVersion.ToString() + "." + item.ApiVersion.MinorVersion
});
}
});
}
This code will add swagger in pipeline. Now we have to use swagger. do below code in startup file.:
app.UseSwaggerGenerationUI(this.Configuration)
Definition of UseSwaggerGenerationUI would be like as :
public static void UseSwaggerGenerationUI(this IApplicationBuilder applicationBuilder, IApiVersionDescriptionProvider apiVersionDescriptionProvider, IConfiguration configuration)
{
applicationBuilder.UseSwagger(c =>
{
c.RouteTemplate = "/api/help/versions/{documentname}/document.json";
c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.BasePath = "/api");
});
applicationBuilder.UseSwaggerUI(c =>
{
c.RoutePrefix = "api/help";
c.DocumentTitle = "Api Help";
foreach (var item in apiVersionDescriptionProvider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"/api/help/versions/{item.GroupName}/document.json", item.GroupName);
}
});
}

Related

Swagger is not generating api documentation correctly

I have an asp.net core web api project and I am using version 3.1. I installed the swagger package and configured it, everything is normal, but the API of the swagger page is not seen, why is this, this is the configuration information of my reference document.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = new Uri("https://twitter.com/spboyer"),
},
License = new OpenApiLicense
{
Name = "Use under LICX",
Url = new Uri("https://example.com/license"),
}
});
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app)
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger(c =>
{
c.SerializeAsV2 = true;
});
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I reproduced the problem according to your code. I created a Controller myself, but since the routing address was not added, the first time I opened it was like this:
There is only one default route, but in my project I have added a HomeController which also doesn't show up in that API.
public class HomeController : Controller
{
[HttpGet]
public string Index()
{
return "test";
}
}
This is because I did not add the routing address and it did not display.
[Route("api/[controller]")]
public class HomeController : Controller
{
[HttpGet]
[Route("test")]
public string Index()
{
return "test";
}
}
After adding:
I don't know if you are for this reason, if not, can you explain your steps in detail? Or provide an interface where your swagger does not display the API.
Reference documentation:
Get started with Swashbuckle and ASP.NET Core

Why could not my Blazor project consume MyProj.HttpApi.Client correctly?

I used ABP CLI generated a MVC template, with which I would like to try a Blazor Server project. I do add a MyProjBlazorModule which was as same as every common Module, just like the ConsoleTestApp project did:
namespace MyProj.Blazor
{
[DependsOn(
typeof(MyProjHttpApiClientModule),
typeof(AbpHttpClientIdentityModelModule)
)]
public class MyProjBlazorModule : AbpModule
{
}
}
Then I added the module as service to ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddSyncfusionBlazor();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddApplication<TaurusBlazorModule>();
}
for a rapid test, I also copied ClientDemoService class from template project MyProj.HttpApi.Client.ConsoleTestApp , and I consume it in my index.razor like this:
#inject ClientDemoService _clientService
...
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
profile = await _clientService.RunAsync();
}
But it couldn't work, with a error message in browser:
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can
be set using either AddAuthentication(string defaultScheme) or
AddAuthentication(Action configureOptions).
while If I copy code identical to the console test project like this:
using (var application = AbpApplicationFactory.Create<MyProjConsoleApiClientModule>())
{
application.Initialize();
var demo = application.ServiceProvider.GetRequiredService<ClientDemoService>();
profile = AsyncHelper.RunSync(() => demo.RunAsync());
}
and it worked. I would like to know the difference between using ABP module and explicitly calling an ugly ServiceProvider method here, and how can I fix this issue in some correct and beautiful way?
Thanks for everyone's help!
Finally, I have got what's wrong with that. In the template source code from abp CLI, the MyProjHttpApiHostModule's ConfigureAuthentication method register authenticate service like this:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "MyProj";
options.JwtBackChannelHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
});
}
where AddAuthentication() method used empty parameter overload, that caused the No authenticationScheme was specified error. I referenced IdentityServer4 official document and found the right way to do:
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
...
});
That's easy, I should set the default scheme JwtBearerDefaults.AuthenticationScheme
using a different overload of AddAuthentication method just as the error had reported.
I hope this post could help someone facing the same or similar issue.

Unable to create swagger.json file when using aspnet-api-versioning

I have .NET Core 2.2 application. I am trying to set up API with different versions using Microsoft.AspnetCore.Mvc.Versioning nugetpackage. I followed the samples provided in the repository.
I want to use an API version based on the name of the defining controller's namespace.
Project Structure
Controllers
namespace NetCoreApiVersioning.V1.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
namespace NetCoreApiVersioning.V2.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("v{version:apiVersion}/[controller]")]
public class HelloWorldController : ControllerBase
{
public IActionResult Get()
{
return Ok();
}
}
}
Note the controllers does not have [ApiVersion] attribute becuase i want the versioning to be defined by the namespace
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddApiVersioning(
options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
// automatically applies an api version based on the name of the defining controller's namespace
options.Conventions.Add(new VersionByNamespaceConvention());
});
services.AddVersionedApiExplorer(
options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "API v1 ", Version = "v1" });
c.SwaggerDoc("v2", new Info { Title = "API v2", Version = "v2" });
});
// commented code below is from
// https://github.com/microsoft/aspnet-api-versioning/tree/master/samples/aspnetcore/SwaggerSample
//services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
//services.AddSwaggerGen(
// options =>
// {
// // add a custom operation filter which sets default values
// //options.OperationFilter<SwaggerDefaultValues>();
// // integrate xml comments
// //options.IncludeXmlComments(XmlCommentsFilePath);
// });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
{
// remaining configuration omitted for brevity
// Enable middleware to serve generated Swagger as a JSON endpoint.
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());
}
});
app.UseMvc();
}
}
Issue
It is not able to generate swagger.json file. When i browse url /swaggger i see error undefined /swagger/v1/swagger.json
found..
i am missing [HttpGet] attribute in ActionMethods

Nservicebus 5 and later Web Api Depenedency Injection settings

How can I configure Web api dependency settings for NserviceBus 5 and later version.
Version 3 or 4 is like this:
public static class ConfigureWebApi
{
public static Configure ForWebApi(this Configure configure)
{
// Register our http controller activator with NSB
configure.Configurer.RegisterSingleton(typeof(IHttpControllerActivator),
new NSBHttpControllerActivator());
// Find every http controller class so that we can register it
var controllers = Configure.TypesToScan
.Where(t => typeof(IHttpController).IsAssignableFrom(t));
// Register each http controller class with the NServiceBus container
foreach (Type type in controllers)
configure.Configurer.ConfigureComponent(type, ComponentCallModelEnum.Singlecall);
// Set the WebApi dependency resolver to use our resolver
GlobalConfiguration.Configuration.ServiceResolver.SetResolver(new NServiceBusResolverAdapter(configure.Builder));
// Required by the fluent configuration semantics
return configure;
}
}
But Version 5 does not use Configure class, that use BusConfiguration
I try this but can not scan assemblies:
public static class ConfigureWebApi
{
public static BusConfiguration ForWebApi(this BusConfiguration configuration)
{
configuration.RegisterComponents(c => c.RegisterSingleton(typeof(IHttpControllerActivator),
new NServiceBusHttpControllerActivator()));
????
}
}
I'm not sure which way you're thinking. I'm asking, because I might be wrong with my answer. If so, let me know and I'll try to update it.
The way I go about this issue is setting up the container first and then have NServiceBus use that container. I'm using AutoFac and create a special class to set it up.
Disclaimer : I'm copying this from an existing app and didn't try nor compile it. I'm 100% sure this is working though, although I might've forgotten a line or added one too much! :)
public class DependenciesConfig
{
public static IContainer RegisterDependencies()
{
ContainerBuilder builder = new ContainerBuilder();
// MVC Controllers
builder.RegisterModule(new AutofacWebTypesModule());
builder.RegisterControllers(Assembly.GetExecutingAssembly())
// WebAPI controllers
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
// Way more registrations
// Next line is AutoFac specific for WebAPI
builder.RegisterFilterProvider();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
}
}
Then I have an additional class for registering NServiceBus. I don't have handlers in my web app, nor do I publish messages, so I use a SendOnly endpoint.
public class ServiceBus
{
public static ISendOnlyBus Bus { get; private set; }
private static readonly object padlock = new object();
public static void Init(ILifetimeScope container)
{
if (Bus != null) return;
NServiceBus.Logging.LogManager.Use<CommonLoggingFactory>();
lock (padlock)
{
if (Bus != null) return;
var configuration = new BusConfiguration();
configuration.UseSerialization<JsonSerializer>();
configuration.UseContainer<AutofacBuilder>(x => x.ExistingLifetimeScope(container));
configuration.UseTransport<AzureStorageQueueTransport>();
ConventionsBuilder conventions = configuration.Conventions();
conventions.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.StartsWith("Messages") && t.Namespace.EndsWith("Commands"));
Bus = NServiceBus.Bus.CreateSendOnly(configuration);
}
}
}
Is this what you're looking for?

Asp.Net Web Api and Autofac with Custom Authorisation attribute issue (property injection)

I am using Autofac to inject all my project dependencies which is working great. Now I have added a Custom Authorization attribute (I don't need very complex functionality like OWIN and Identity stuff). The custom authorization attribute has dependency to data layer and therefore I am trying to inject it as a property injection. However the property is always Null. The code is below:
public class CustomAuthorizationFilterAttribute : AuthorizeAttribute, IAutofacAuthorizationFilter
{
public IAuthorisationHelper AuthorisationHelper { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
**... removed for brevity**
**// TODO: this should be injected by autofac and is always null??**
if (AuthorisationHelper.IsValidUser(username, password, out roleOfUser))
{
var principal =
new GenericPrincipal((new GenericIdentity(username)),
(new[] { roleOfUser }));
Thread.CurrentPrincipal = principal;
return;
}
... removed for brevity
}
}
Code that injects the AuthorizationHelper:
public static IContainer Container()
{
var builder = new ContainerBuilder();
var assemblies = new List<Assembly>();
assemblies.Add(Assembly.Load("Kids.Math.Interfaces"));
assemblies.Add(Assembly.Load("Kids.Math.Data"));
assemblies.Add(Assembly.Load("Kids.Math.Business"));
assemblies.Add(Assembly.Load("Kids.Math.ImportExport"));
assemblies.Add(Assembly.Load("Kids.Math.Common"));
assemblies.Add(Assembly.Load("Kids.Math.Api"));
builder.RegisterAssemblyTypes(assemblies.ToArray()).
AsImplementedInterfaces();
builder.RegisterType(typeof(MathContext)).As(typeof (DbContext)).InstancePerRequest();
// Register web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// TODO: this is not working, also this should be generic to register it for all controllers
// inject the authorisation filter
builder.RegisterType<AuthorisationHelper>().As<IAuthorisationHelper>();
builder.Register(c => new CustomAuthorizationFilterAttribute()).PropertiesAutowired()
.AsWebApiAuthorizationFilterFor<QuestionsImportController>()
.InstancePerRequest();
// Set the dependency resolver to be Autofac.
var container = builder.Build();
return container;
}
Attribute is registered in FilterConfig as
filters.Add(new CustomAuthorizationFilterAttribute());
All the wiring up works but AuthorisationHelper is always null.
Any comments will be appreciated.
Aren't you missing some key registration steps here? Refer to the Autofac doco
// OPTIONAL: Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
// Set the dependency resolver to be Autofac.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
EDIT: After being told that the configuration has been setup correctly, have you tried registering your filter like this?
builder.RegisterType<CustomAuthorizationFilterAttribute>().PropertiesAutowired()
.AsWebApiAuthorizationFilterFor<QuestionsImportController>()
.InstancePerRequest();
seems like this is a known bug in autofac:
https://code.google.com/p/autofac/issues/detail?id=289