Swagger with vendor extension at API/controller level - c#

I got an Asp.Net web api project with some controllers, enabled swagger doc with swagger-Net. I have been searching for how to enable vendor extensions or custom swagger doc fields which I can set for each API level. I'm successful at settting vendor extension at swagger doc level/swaggerDoc.Info level. But I can't go further to set them at API level. Any help would be extremely grateful!
I tried adding vendor extensions to swaggerDoc.Info and it works great. But I don't know how to add vendor extension to API level (PathItem) which can be set or ediited for individual APIs. Basically I want to enable setting of a vendor extension x-dev at API level.
[HttpGet]
[x-dev("Myself")]
public async Task<User> GetAllUsers(){
.....................
}

Okay, I found a way.
Suppose I want to add a vendor extension "Squad" to each operation,
first create an Attribute which will be used whenever we want to attribute a particular squad to an operation.
public class SquadAttribute : Attribute
{
public string squadName;
public SquadAttribute(string squadName)
{
this.squadName = squadName;
}
}
then in SwaggerConfig.cs add operation filter which will add our attribute as a vendor extension to operations.
public class SquadOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var sq = apiDescription.GetControllerAndActionAttributes<SquadAttribute>().FirstOrDefault();
if (sq != null)
{
operation.vendorExtensions.Add("x-SquadName", sq.squadName);
}
}
}
finally in swagger.config, Apply operationfilter.
c.OperationFilter<SquadOperationFilter>();
Now you can add squad attribute for any openApi operation like this :
[HttpGet]
["Settings"]
[Squad("DevSquad")]
public async Task GetAllSettings()
{
...............................
...............................
}

Related

Add header to response if requested version is deprecated

I would like to add a custom header to my service's response if a deprecated version is requested.
I already have URL-based versioning set up using Microsoft.AspNetCore.Mvc.Versioning, and I additionally have an existing custom ActionFilter class that can write custom headers into the response. I can also get the version requested by the client using context.HttpContext.GetRequestedApiVersion() inside my ActionFilter's definition for OnActionExecuted(ActionExecutedContext context).
However, I'm not sure how I can check whether the requested version is a deprecated version or not from my custom ActionFilter. The documentation on deprecating a service version does not answer this question and I can't find the answer among any of the rest of the documentation on github.
My controller class is annotated as follows:
[ApiVersion("2", Deprecated=true)]
[Route("api/v{v:apiVersion}/[action]")]
[ApiController]
public class CustomControllerV2 : ControllerBase { ... }
[ApiVersion("3")]
[Route("api/v{v:apiVersion}/[action]")]
[ApiController]
public class CustomControllerV3 : ControllerBase { ... }
And this is my custom ActionFilter:
public class CustomActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) {
// not implemented
}
public void OnActionExecuted(ActionExecutedContext context) {
var requestedApiVersion = context.HttpContext.GetRequestedApiVersion();
if (
// Check whether version is deprecated here
) {
context.HttpContext.Response.Headers.Add("warning", "Requested version "+requestedApiVersion.ToString()+" is deprecated.");
}
}
}
From the incoming request you will see what the URL is, and therefore you can determine which controller it will be mapped to. Controllers are classes, and the deprecated ones are marked with the [ApiVersion(Deprecated = true)] attribute. So you can grab the controller, and with reflection you can check its attributes and see if it's deprecated. Documentation here

Generating Sample Request using Swagger depending on model

Helloo everybody. Tell me please. Can I generate a Sample Request in Swagger depending on the model, so as not to write it manually. To see which fields the model has.
Is it even possible?
Because now I have to manually write a description of the request for each api.
I would like to automate this process.
I am using Swashbuckle and ASP.NET Core
There is a place for code examples, you should add /// <example>123</example> on the model instead of the remarks.
Is this close to what you need:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=Company#/Company/Company_Post
The code for that is here:
https://github.com/heldersepu/Swagger-Net-Test/blob/c9b554fde26367418c84fcd3682d308ae1b40d11/Swagger_Test/Models/Company.cs
You can also make dynamic changes with an IDocumentFilter like this:
private class AddExampleDocumentFilter : IDocumentFilter
{
private List<Guid> Guids
{
get
{
return new List<Guid>
{
Guid.Empty, Guid.Empty
};
}
}
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
if (swaggerDoc.paths.ContainsKey("/api/Dictionary"))
{
var del = swaggerDoc.paths["/api/Dictionary"].delete;
if (del != null)
{
del.parameters[0].schema.example = Guids;
}
}
}
}
Here is the result of that:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=Dictionary#/Dictionary/Dictionary_DeleteEcho
And once the user executes it with the sample data the UI shows the curl request:

How to personalize Auto API Controllers endpoints in APB Framework

I'm learning APB framework. ABP can automagically configure the application services as API Controllers by convention. The documentation says it is possible to fully customize it.
In the example they provide, I would like to change an action name of one of the following endpoints:
/api/app/book to /api/app/books.
But unfortunately I cannot find how to do it.
I tried to change the ActionName of the corresponding service method:
public class BookAppService :
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
[ActionName("books"), HttpGet]
public override Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
return base.GetListAsync(input);
}
}
But the resulting endpoint is not what I want:
Any idea how to do it ?
in abp.io (ABP VNext) , you can diable or configure ConventionalControllers in HttpModules config file of your Project .
Edit this Method :
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(SystemApplicationModule).Assembly);
});
}

How can versioning be done in ASP.NET Core Web Api

In previous asp.net web api, I implement DefaultHttpControllerSelector to specify how I want the request to locate my controller. I often have different controllers with different names but intended for same processes. The only difference is that one is of higher version than the other.
For example, I could have a controller named BookingV1Controller, which would be meant to handle the version one of the service. I would also have BookingV2Controller, which was designed to handle the version two of the service. A client application would then make a request to the service with this url http://myservice.com/api/v2/booking/someaction?id=12. To handle the request, I would provide a custom implementation of DefaultHttpControllerSelector to select the appropriate version of the controller required based on the requested version.
However, I seems not to have a way to do this in ASP.NET Core. I have searched everywhere to no avail. No documentation that could help either.
I would appreciate if anyone can be of help to me here. Thanks.
UPDATE
I would also like to know what to do if the version is specified in a custom header. E.g X-Version:v1
UPDATE 2
The requirement was that the version of the service should not be exposed in the URL. If no version is present, the service returns with instruction on how to add the version. If a requested controller is not present in the version requested, the system searches through the lower versions. If it finds it in any lower versions, it uses that. The reason for this is to prevent repetition of controllers on all versions. But with ASP.NET Core, this might not be possible.
This is a very old question that I stumbled upon, but there are much better solutions now. There is this package
Microsoft.AspNetCore.Mvc.Versioning
Which has a much more feature rich way of implementing versioning controls. These include being able to use URL query strings, url paths, headers, or custom version readers. Being able to read the version from HTTPContext etc.
In short, you add the following into your ConfigureServices method in startup.cs
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
});
Then you have to decorate your controllers with an ApiVersion.
[ApiVersion("1.0")]
[Route("api/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
You can also implement it in the path by putting it in the route.
[ApiVersion("1.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV1Controller : Controller
{
[HttpGet]
public string Get() => "Version 1";
}
[ApiVersion("2.0")]
[Route("api/{version:apiVersion}/home")]
public class HomeV2Controller : Controller
{
[HttpGet]
public string Get() => "Version 2";
}
When you go down this method of actually having it implemented via the Microsoft package, it also means that you are able to deprecate versions, have version discovery, access the version number from the HttpContext easily etc. None of which you could really do if it's just hardcoded in your route.
For more info (Including using it in a header) :
http://dotnetcoretutorials.com/2017/01/17/api-versioning-asp-net-core/
http://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
https://github.com/Microsoft/aspnet-api-versioning/wiki
I created a package for this purpose exactly after banging my head on this problem for a few days. It doesn't require attributes.
https://github.com/GoAheadTours/NamespaceVersioning
In summary, you can register an IApplicationModelConvention in your startup file that can iterate through controllers and register routes based on the namespaces. I created a v1 folder, and put my controller inside
The class that implements IApplicationModelConvention implements an Apply method with an ApplicationModel parameter that will have access to the Controllers in your app and their existing routes. If I see a controller does not have a route set up in my class I get the version from the namespace and use a pre-defined URL prefix to generate a route for that version.
public void Apply(ApplicationModel application) {
foreach (var controller in application.Controllers) {
var hasRouteAttribute = controller.Selectors.Any(x => x.AttributeRouteModel != null);
if (hasRouteAttribute) {
continue;
}
var nameSpace = controller.ControllerType.Namespace.Split('.');
var version = nameSpace.FirstOrDefault(x => Regex.IsMatch(x, #"[v][\d*]"));
if (string.IsNullOrEmpty(version)) {
continue;
}
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel() {
Template = string.Format(urlTemplate, apiPrefix, version, controller.ControllerName)
};
}
}
I have all the code up on github and a link to the package on nuget as well
Use the routing attributes to control versions.
i.e.
[Route("api/v1/[controller]")]
public class BookingV1Controller : Controller
{
....
}
[Route("api/v2/[controller]")]
public class BookingV2Controller : Controller
{
....
}
For more information relating to migrating from standard Web Api and .NET Core ASP.NET have a look at: MSDN: Migrating from ASP.NET Web Api
For that Add service API versioning to your ASP.NET Core applications
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning();
// remaining other stuff omitted for brevity
}
QUERYSTRING PARAMETER VERSIONING
[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world!";
}
So this means to get 2.0 over 1.0 in another Controller with the same route, you'd go here:
/api/helloworld?api-version=2.0
we can have the same controller name with different namespaces
URL PATH SEGMENT VERSIONING
[ApiVersion( "1.0" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
public string Get() => "Hello world!";
}
[ApiVersion( "2.0" )]
[ApiVersion( "3.0" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world v2!";
[HttpGet, MapToApiVersion( "3.0" )]
public string GetV3() => "Hello world v3!";
}
Header Versioning
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning(o => o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}
When you do HeaderApiVersioning you won't be able to just do a GET in your browser, so I'll use Postman to add the header (or I could use Curl, or WGet, or PowerShell, or a Unit Test):
Image
please refer https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx

Servicestack - Order of Operations, Validation and Request Filters

I detected a problem in the RequestFilter execution order.
The ValidationFeature in ServiceStack is a Plugin that just registers a Global Request Filter. The Order of Operations points out that Global Request Filters are executed after Filter Attributes with a Priority <0 and before Filter Attributes with a Priority >=0
My BasicAuth filter has -100 priority, and in fact everything goes well if the Service is annotated at class level, but it fails when the annotation is at method level, with the authentication filter being executed after.
I am using 3.9.70
Is there any quick fix for this? Thanks
When you add the annotation at method level then you are creating an Action Request Filter (because you are adding the annotation to an action method) which in the Order of Operations is operation 8, after the other filters have run.
5: Request Filter Attributes with Priority < 0 gets executed
6: Then any Global Request Filters get executed
7: Followed by Request Filter Attributes with Priority >= 0
8: Action Request Filters (New API only)
The best workaround I can suggest is to reconsider your service structure. I imagine you are having these difficulties because you are adding unauthenticated api methods alongside your secure api methods, and thus are using method level attributes to control authentication. So you are presumably doing something like this Your classes and attributes will be different, this is just exemplar:
public class MyService : Service
{
// Unauthenticated API method
public object Get(GetPublicData request)
{
return {};
}
// Secure API method
[MyBasicAuth] // <- Checks user has permission to run this method
public object Get(GetSecureData request)
{
return {};
}
}
I would do this differently, and separate your insecure and secure methods into 2 services. So I use this:
// Wrap in an outer class, then you can still register AppHost with `typeof(MyService).Assembly`
public partial class MyService
{
public class MyPublicService : Service
{
public object Get(GetPublicData request)
{
return {};
}
}
[MyBasicAuth] // <- Check is now class level, can run as expected before Validation
public class MySecureService : Service
{
public object Get(GetSecureData request)
{
return {};
}
}
}
Solution - Deferred Validation:
You can solve your execution order problem by creating your own custom validation feature, which will allow you to defer the validation process. I have created a fully functional self hosted ServiceStack v3 application that demonstrates this.
Full source code here.
Essentially instead of adding the standard ValidationFeature plugin we implement a slightly modified version:
public class MyValidationFeature : IPlugin
{
static readonly ILog Log = LogManager.GetLogger(typeof(MyValidationFeature));
public void Register(IAppHost appHost)
{
// Registers to use your custom validation filter instead of the standard one.
if(!appHost.RequestFilters.Contains(MyValidationFilters.RequestFilter))
appHost.RequestFilters.Add(MyValidationFilters.RequestFilter);
}
}
public static class MyValidationFilters
{
public static void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
// Determine if the Request DTO type has a MyRoleAttribute.
// If it does not, run the validation normally. Otherwise defer doing that, it will happen after MyRoleAttribute.
if(!requestDto.GetType().HasAttribute<MyRoleAttribute>()){
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
return;
}
Console.WriteLine("Deferring Validation until Roles are checked");
}
}
Configure to use our plugin:
// Configure to use our custom Validation Feature (MyValidationFeature)
Plugins.Add(new MyValidationFeature());
Then we need to create our custom attribute. Your attribute will be different of course. The key thing you need to do is call ValidationFilters.RequestFilter(req, res, requestDto); if you are satisfied the user has the required role and meets your conditions.
public class MyRoleAttribute : RequestFilterAttribute
{
readonly string[] _roles;
public MyRoleAttribute(params string[] roles)
{
_roles = roles;
}
#region implemented abstract members of RequestFilterAttribute
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{
Console.WriteLine("Checking for required role");
// Replace with your actual role checking code
var role = req.GetParam("role");
if(role == null || !_roles.Contains(role))
throw HttpError.Unauthorized("You don't have the correct role");
Console.WriteLine("Has required role");
// Perform the deferred validation
Console.WriteLine("Running Validation");
ValidationFilters.RequestFilter(req, res, requestDto);
}
#endregion
}
For this to work we need to apply our custom attribute on the DTO route not the action method. So this will be slightly different to how you are doing it now, but should still be flexible.
[Route("/HaveChristmas", "GET")]
[MyRole("Santa","Rudolph","MrsClaus")] // Notice our custom MyRole attribute.
public class HaveChristmasRequest {}
[Route("/EasterEgg", "GET")]
[MyRole("Easterbunny")]
public class GetEasterEggRequest {}
[Route("/EinsteinsBirthday", "GET")]
public class EinsteinsBirthdayRequest {}
Then your service would look something like this:
public class TestController : Service
{
// Roles: Santa, Rudolph, MrsClaus
public object Get(HaveChristmasRequest request)
{
return new { Presents = "Toy Car, Teddy Bear, Xbox" };
}
// Roles: Easterbunny
public object Get(GetEasterEggRequest request)
{
return new { EasterEgg = "Chocolate" };
}
// No roles required
public object Get(EinsteinsBirthdayRequest request)
{
return new { Birthdate = new DateTime(1879, 3, 14) };
}
}
So when we call the route /EinsteinsBirthday which does not have a MyRole attribute the validation will be called normally, as if using the standard ValidationFeature.
If we call the route /HaveChristmas?role=Santa then our validation plugin will determine that the DTO has our attribute and not run. Then our attribute filter triggers and it will trigger the validation to run. Thus the order is correct.

Categories