How can I set swagger to shown all possible responses?
Now only shows the HTTP 200, but there are more possible responses
I have a global exception handling class and I would like to a global solution.
Nothing, but now i will try some possible solutions.
If i use it:
[ProducesResponseType(200)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
at controller method it is work and swagger shows more possible responses. But i would like to a global solution
Is there a swagger settings or something else?
If you add the following attributes to the methods you can specify the output model and the error
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(YOUROBJECT))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
So https://stackoverflow.com/users/113116/helen find the correct and beauty solution.
Swashbuckle general response codes
Is a way to declare common response types to different actions?
What i used for general response handling:
/in AddSwaggerGen
options.OperationFilter<GeneralExceptionOperationFilter>();
.
internal class GeneralExceptionOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Responses.Add("401", new OpenApiResponse() { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse() { Description = "Forbidden" });
//Example where we filter on specific HttpMethod and define the return model
var method = context.MethodInfo.GetCustomAttributes(true)
.OfType<HttpMethodAttribute>()
.Single();
if (method is HttpDeleteAttribute || method is HttpPostAttribute || method is HttpPatchAttribute || method is HttpPutAttribute)
{
operation.Responses.Add("409", new OpenApiResponse()
{
Description = "Conflict",
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = context.SchemaGenerator.GenerateSchema(typeof(string), context.SchemaRepository)
}
}
});
}
}
}
Related
I have a method called GetMessage, it is called like so:
localhost/GetMessage?id={{NewMessage}}
i have now decided to start following standard practises and ommit the "get" keyword from the route and make it like so:
localhost/message?id={{NewMessage}}
however, i do not want to break any existing clients I already have looking at the first route and force them to update. the underlying function will not change, it will just be the route.
my first solution to this problem was to do this:
Although THIS WORKS i realised, i have like 35 functions just like this one, i do not wanna duplicate all those functions.
Is it not possible to add 2 [httpget] attributes to a function each with a different apiversion that would only be accessible if the header specifically has that version.
so something like this:
this way client will be able to upgrade in their own time when they need.
is this possible or would i have to go with my first solution?
Extra info:
my configure services method:
Generally , the method with different version has the same route, So we configure like this:
services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
And user add request header(x-api-version=xxxx) to access method with the specified version
But in your code, Different versions of methods have different routes, So We can't use the method mentioned above.
I use Action filters to achieve a similar effect, I write a simple demo here.
Action filters
public class Action1filter : Attribute,IActionFilter
{
private readonly string _header;
private readonly string _value;
public void OnActionExecuted(ActionExecutedContext context){}
public void OnActionExecuting(ActionExecutingContext context )
{
if ((context.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == _header).Value.FirstOrDefault() != _value) || context.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == _header).Value.FirstOrDefault() == null)
{
context.Result= new BadRequestObjectResult("");
}
}
}
your method
HttpGet("message"), ApiVersion("2.0"), Action1filter("version", "2.0")]
public IActionResult GetMessage2([FromQuery] string Id)
{
return GetMessage(Id);
}
[HttpGet("GetMessage"),ApiVersion("1.0",Deprecated =true), Action1filter("version", "1.0")]
public IActionResult GetMessage([FromQuery]string Id)
{
return Ok(Id);
}
I add the ActionFilter in your action, So only the request with header version=2.0 can access the method by localhost/message?id={{NewMessage}}
But you can't use this method in your second solution, Because one [Action1filter()] will work on both paths.
In my opinion, I prefer to add [Route("xxx/{version:apiVersion}")] to access method with different versions in this situation
I am new the API in general, let me give you the background of the API and what I want it to do.
I have a API have that are external facing and so every incoming request are required to check the signature from header. literality my code in every controller call are checking the signature and created many duplicated code.
my question is how can reduces those duplicated code ? do I use Custom Attributes, or AuthorizeAttribute
here are some of the example code:
[Route("[controller]")]
[ApiController]
public class ExampleController : ControllerBase
{
public async Task<Result> Call_1(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
public async Task<Result> Call_2(Rquest request)
{
string signaturel;
signature = Util.getHeaderSignature(request);
if(unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
(My logic)
}
else{ return "InvalidSinaturemessage" }
}
}
above code is just for showing, the actual Sinature checking logic is around 20 lines of code on every single controller method.
Yes, you can do that using action filters. It's described in documentation
Put your code for checking into OnActionExecuting method. So, you can write Result in the action filter if the signature isn't valid.
In case you need specific result structure you can create your own ObjectResult:
public class ForbiddenObjectResult : ObjectResult
{
public string Message { get; private set; }
public ForbiddenObjectResult(object value, string message)
: base(value)
{
StatusCode = StatusCodes.Status403Forbidden;
Message = message;
}
}
...
string signaturel;
signature = Util.getHeaderSignature(context.HttpContext.Request);
if(!unit.IsSinatureValid(signaturel, someVar1, someVar2))
{
context.Result = new ForbiddenObjectResult(filterContext.ModelState, "InvalidSinaturemessage");
}
And to register it for all your endpoints(if needed):
services.AddControllersWithViews(options =>
{
options.Filters.Add<YourActionFilter>();
});
You can use token based authentication or filter method. For reference
Token based authentication
Custom Filter
I have a logic to apply in case the request received is a BadRequest, to do this I have created a filter:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// Apply logic
}
}
}
In Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => { options.Filters.Add<ValidateModelAttribute>(); });
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class VerifyController : ControllerBase
{
[Route("test")]
[HttpPost]
[ValidateModel]
public ActionResult<Guid> validationTest(PersonalInfo personalInfo)
{
return null;
}
}
Model:
public class PersonalInfo
{
public string FirstName { get; set; }
[RegularExpression("\\d{4}-?\\d{2}-?\\d{2}", ErrorMessage = "Date must be properly formatted according to ISO 8601")]
public string BirthDate { get; set; }
}
The thing is when I put a break point on the line:
if (!context.ModelState.IsValid)
execution reaches this line only if the request I send is valid. Why it is not passing the filter if I send a bad request?
The [ApiController] attribute that you've applied to your controller adds Automatic HTTP 400 Responses to the MVC pipeline, which means that your custom filter and action aren't executed if ModelState is invalid.
I see a few options for affecting how this works:
Remove the [ApiController] attribute
Although you can just remove the [ApiController] attribute, this would also cause the loss of some of the other features it provides, such as Binding source parameter inference.
Disable only the Automatic HTTP 400 Responses
Here's an example from the docs that shows how to disable just this feature:
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// ...
options.SuppressModelStateInvalidFilter = true;
// ...
}
This code goes inside of your Startup's ConfigureServices method.
Customise the automatic response that gets generated
If you just want to provide a custom response to the caller, you can customise what gets returned. I've already described how this works in another answer, here.
An example of intersection for logging is describe in Log automatic 400 responses
Add configuration in Startup.ConfigureServices.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Startup>>();
// Perform logging here.
//E.g. logger.LogError($”{context.ModelState}”);
logger.LogWarning(context.ModelState.ModelStateErrorsToString());
// Invoke the default behavior, which produces a ValidationProblemDetails response.
// To produce a custom response, return a different implementation of IActionResult instead.
return builtInFactory(context);
};
});
public static String ModelStateErrorsToString(this ModelStateDictionary modelState)
{
IEnumerable<ModelError> allErrors = modelState.Values.SelectMany(v => v.Errors);
StringBuilder sb = new StringBuilder();
foreach (ModelError error in allErrors)
{
sb.AppendLine($"error {error.ErrorMessage} {error.Exception}");
}
return sb.ToString();
}
As the attribute filter in the life cycle of the .Net Core you can’t handle it. The filter layer with ModelState will run after the model binding.
You can handle it with .Net Core middleware as the following https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs=aspnetcore2x
If you want to SuppressModelStateInvalidFilter on individual action, consider to use custom attribute suggested on https://learn.microsoft.com/en-us/answers/questions/297568/how-to-suppress-suppressmodelstateinvalidfilter-at.html. (And similar answer https://github.com/aspnet/Mvc/issues/8575)
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
private const string FilterTypeName = "ModelStateInvalidFilterFactory";
public void Apply(ActionModel action)
{
for (var i = 0; i < action.Filters.Count; i++)
{
//if (action.Filters[i] is ModelStateInvalidFilter)
if (action.Filters[i].GetType().Name == FilterTypeName)
{
action.Filters.RemoveAt(i);
break;
}
}
}
}
Example of use
[ApiController]
public class PersonController
{
[SuppressModelStateInvalidFilter]
public ActionResult<Person> Get() => new Person();
}
I have been trying to recreate an Ajax version of the ValidateAntiForgeryToken - there are many blog posts on how to do this for previous versions of MVC, but with the latest MVC 6, none of the code is relevant. The core principle that I am going after, though, is to have the validation look at the Cookie and the Header for the __RequestVerificationToken, instead of comparing the Cookie to a form value. I am using MVC 6.0.0-rc1-final, dnx451 framework, and all of the Microsoft.Extensions libraries are 1.0.0-rc1-final.
My initial thought was to just inherit ValidateAntiForgeryTokenAttribute, but looking at the source code, I would need to return my own implementation of an an Authorization Filter to get it to look at the header.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAjaxAntiForgeryTokenAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
public int Order { get; set; }
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
}
As such, I then made my own version of ValidateAntiforgeryTokenAuthorizationFilter
public class ValidateAjaxAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
private readonly IAntiforgery _antiforgery;
private readonly ILogger _logger;
public ValidateAjaxAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery, ILoggerFactory loggerFactory)
{
if (antiforgery == null)
{
throw new ArgumentNullException(nameof(antiforgery));
}
_antiforgery = antiforgery;
_logger = loggerFactory.CreateLogger<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
}
public async Task OnAuthorizationAsync(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context))
{
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AjaxAntiforgeryValidationException exception)
{
_logger.LogInformation(1, string.Concat("Ajax Antiforgery token validation failed. ", exception.Message));
context.Result = new BadRequestResult();
}
}
}
protected virtual bool ShouldValidate(AuthorizationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return true;
}
private bool IsClosestAntiforgeryPolicy(IList<IFilterMetadata> filters)
{
// Determine if this instance is the 'effective' antiforgery policy.
for (var i = filters.Count - 1; i >= 0; i--)
{
var filter = filters[i];
if (filter is IAntiforgeryPolicy)
{
return object.ReferenceEquals(this, filter);
}
}
Debug.Fail("The current instance should be in the list of filters.");
return false;
}
}
However, I cannot find the proper Nuget package and namespace that contains IAntiforgeryPolicy. While I found the interface on GitHub - what package do I find it in?
My next attempt was to instead go after the IAntiforgery injection, and replace the DefaultAntiforgery with my own AjaxAntiforgery.
public class AjaxAntiforgery : DefaultAntiforgery
{
private readonly AntiforgeryOptions _options;
private readonly IAntiforgeryTokenGenerator _tokenGenerator;
private readonly IAntiforgeryTokenSerializer _tokenSerializer;
private readonly IAntiforgeryTokenStore _tokenStore;
private readonly ILogger<AjaxAntiforgery> _logger;
public AjaxAntiforgery(
IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
IAntiforgeryTokenGenerator tokenGenerator,
IAntiforgeryTokenSerializer tokenSerializer,
IAntiforgeryTokenStore tokenStore,
ILoggerFactory loggerFactory)
{
_options = antiforgeryOptionsAccessor.Value;
_tokenGenerator = tokenGenerator;
_tokenSerializer = tokenSerializer;
_tokenStore = tokenStore;
_logger = loggerFactory.CreateLogger<AjaxAntiforgery>();
}
}
I got this far before I stalled out because there is no generic method on ILoggerFactory for CreateLogger<T>(). The source code for DefaultAntiforgery has Microsoft.Extensions.Options, but I cannot find that namespace in any Nuget package. Microsoft.Extensions.OptionsModel exists, but that just brings in the IOptions<out TOptions> interface.
To follow all of this up, once I do get the Authorization Filter to work, or I get a new implementation of IAntiforgery, where or how do I register it with the dependency injection to use it - and only for the actions that I will be accepting Ajax requests?
I had similar issue. I don't know if any changes are coming regarding this in .NET but, at the time, I added the following lines to ConfigureServices method in Startup.cs, before the line services.AddMvc(), in order to validate the AntiForgeryToken sent via Ajax:
services.AddAntiforgery(options =>
{
options.CookieName = "yourChosenCookieName";
options.HeaderName = "RequestVerificationToken";
});
The AJAX call would be something like the following:
var token = $('input[type=hidden][name=__RequestVerificationToken]', document).val();
var request = $.ajax({
data: { 'yourField': 'yourValue' },
...
headers: { 'RequestVerificationToken': token }
});
Then, just use the native attribute [ValidadeAntiForgeryToken] in your Actions.
I've been wrestling with a similar situation, interfacing angular POSTs with MVC6, and came up with the following.
There are two problems that need to be addressed: getting the security token into MVC's antiforgery validation subsystem, and translating angular's JSON-formatted postback data into an MVC model.
I handle the first step via some custom middleware inserted in Startup.Configure(). The middleware class is pretty simple:
public static class UseAngularXSRFExtension
{
public const string XSRFFieldName = "X-XSRF-TOKEN";
public static IApplicationBuilder UseAngularXSRF( this IApplicationBuilder builder )
{
return builder.Use( next => context =>
{
switch( context.Request.Method.ToLower() )
{
case "post":
case "put":
case "delete":
if( context.Request.Headers.ContainsKey( XSRFFieldName ) )
{
var formFields = new Dictionary<string, StringValues>()
{
{ XSRFFieldName, context.Request.Headers[XSRFFieldName] }
};
// this assumes that any POST, PUT or DELETE having a header
// which includes XSRFFieldName is coming from angular, so
// overwriting context.Request.Form is okay (since it's not
// being parsed by MVC's internals anyway)
context.Request.Form = new FormCollection( formFields );
}
break;
}
return next( context );
} );
}
}
You insert this into the pipeline with the following line inside the Startup.Configure() method:
app.UseAngularXSRF();
I did this right before the call to app.UseMVC().
Note that this extension transfers the XSRF header on any POST, PUT or DELETE where it exists, and it does so by overwriting the existing form field collection. That fits my design pattern -- the only time the XSRF header will be in a request is if it's coming from some angular code I've written -- but it may not fit yours.
I also think you need to configure the antiforgery subsystem to use the correct name for the XSRF field name (I'm not sure what the default is). You can do this by inserting the following line into Startup.ConfigureServices():
services.ConfigureAntiforgery( options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName );
I inserted this right before the line services.AddAntiforgery().
There are several ways of getting the XSRF token into the request stream. What I do is add the following to the view:
...top of view...
#inject Microsoft.AspNet.Antiforgery.IAntiforgery af
...rest of view...
...inside the angular function...
var postHeaders = {
'X-XSRF-TOKEN': '#(af.GetTokens(this.Context).FormToken)',
'Content-Type': 'application/json; charset=utf-8',
};
$http.post( '/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }),
{
headers: postHeaders,
})
...rest of view...
The second part -- translating the JSON data -- is handled by decorating the model class on your action method with [FromBody]:
// the [FromBody] attribute on the model -- and a class model, rather than a
// single integer model -- are necessary so that MVC can parse the JSON-formatted
// text POSTed by angular
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteDataset( [FromBody] DeleteSiteViewModel model )
{
}
[FromBody] only works on class instances. Even though in my case all I'm interested in is a single integer, I still had to dummy up a class, which only contains a single integer property.
Hope this helps.
Using a anti forgery token in a Ajax call is possible but if you are trying to secure a Api I really would suggest using a Access Token instead.
If you are relying on a identity token stored in a cookie as authentication for your Api, you will need to write code to compensate for when your cookie authentication times out, and your Ajax post is getting redirected to a login screen. This is especially important for SPAs and Angular apps.
Using a Access Token implementation instead, will allow you to refresh you access token (using a refresh token), to have long running sessions and also stop cookie thiefs from accessing your Apis.. and it will also stop XSRF :)
A access token purpose is to secure resources, like Web Apis.
I'm using Swashbuckle to generate docs for an API. My controller methods looks like this:
[ResponseType(typeof(CategoryCollectionModel))]
public HttpResponseMessage Get(HttpRequestMessage request, [FromUri]Paging paging)
{
var input = new CategoriesListQuery.Input { Page = paging.Page, Size = paging.Size };
var result = this.queryInvoker.Execute<CategoriesListQuery.Input, CategoriesListQuery.Result>(input);
var items = Mapper.Map<CategoryCollectionModel>(result);
return request.CreateResponse(HttpStatusCode.OK, items);
}
Swashbuckle treats HttpRequestMessage as a parameter in the generated docs. Is there a way to configure Swashbuckle to ignore HttpRequestMessage since it is only included in the signature for testing purposes?
Please refer to the discussion here. In short do not pass in HttpRequestMessage as in input parameter, rather mock the {controller}.Request property.
I found a solution from "http://www.morganskinner.com/2016/02/ignoring-parameters-in-swashbuckle.html"
Summary :
In Swashbuckle you can plug-in operation “filters” that can be used to
alter the emitted data – the filter is passed the context of the
operation being emitted, and you can monkey around with the data that
pops out. All I had to do then was create a filter that would look for
this datatype, and remove the corresponding data from the results. I
ended up with this…
public class IgnoreHttpRequestMessageOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry,
ApiDescription apiDescription)
{
apiDescription.ParameterDescriptions
.Where(desc => desc.ParameterDescriptor.ParameterType
== typeof(HttpRequestMessage))
.ToList()
.ForEach(param =>
{
var toRemove = operation.parameters
.SingleOrDefault(p => p.name == param.Name);
if (null != toRemove)
operation.parameters.Remove(toRemove);
});
}
}
With that class in place, I just needed to plug this in to the swagger
config file as follows...
c.OperationFilter<IgnoreHttpRequestMessageOperationFilter>();
Working for me. thanks "Morgan"