Adding headers to only certain calls in Swashbuckle - c#

I'm trying to set up Swagger for our API using Swashbuckle. Every one of our controllers inherits from a BaseController that has ~8 parameters(userId, language, platform, etc) on it. They each have read only variables with their get function set to read the value from the headers pulled in. Ex:
protected int UserID
{
get
{
return int.Parse(HttpContext.Current.Request.Headers["user_Id"]);
}
}
When I first installed Swagger, it would only give options to pass in parameters that are listed in the method. Those parameters always exclude the ~8 parameters that are set in BaseController, so if a controller uses the user ID, the user ID isn't listed in the method signature. As user ID, won't show up on the Swagger-generated UI.
Right now, I've been experimenting using the SwaggerOperation attribute like so:
[SwaggerOperation("UserValidate(UserID)")]
public int UserValidate()
{
return this._UserValidateService.UserValidate(this.UserID);
And I have a custom swagger parameter that checks all of the methods if their SwaggerOperation name contains "UserID", and if so add the UserID header to that call.
My problem with this is that I have to manually go through every call and add the swagger operation, and if the call ever changes, we have to remember to also change the SwaggerOperation.
I also thought about just adding the 8 to every API call and marking them as optional, but that sounds like it would bloat the UI horribly.
Please advise on the situation. Are there options I haven't considered? Are they Swashbuckle functionalities I've missed that will fix my issues?
Thanks in advance.

If you are using swashbuckle>=5, you can add operation filter as below. It will add a header to only selected requests:
public class AddHeaderParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.ApiDescription.HttpMethod.Equals("POST") && context.ApiDescription.RelativePath.Contains("login"))
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "UserId",
In = ParameterLocation.Header,
Schema = new OpenApiSchema
{
Type = "String",
},
Required = true
});
}
}
Above adds a required header t only POST operation which has action name as login.

Related

Disable model validation for single .NET Core API action

I have an API controller for performing autosaves on an application I am developing. It uses the same viewmodel as the view, which has a number of required fields. The autosave controller may need to save a model that is not considered valid if the user has not completed the form when it is saved. By default, an .NET Core controller declared with the [ApiController] attribute will automatically force validation. I know I can disable this like so in Startup.cs:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
But this will apply to all API controllers in the project. Is it possible to disable this default validation for only one controller or action? Everything I've found so far has directed me to use the code above, but that doesn't accomplish what I'm looking for.
You can override the default InvalidModelStateResponseFactory:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory =
AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper.InvalidModelStateResponseFactory;
});
The InvalidModelStateResponseFactory below checks for OptionalValidationAttribute on a controller action, and searches for a form/query parameter flag which controls the validation is enabled/disabled:
// Code taken from https://github.com/dotnet/aspnetcore/blob/5747cb36f2040d12e75c4b5b3f49580ef7aac5fa/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs#L23
// and is modified to optionally disable validation for controller action methods decorated with OptionalValidationAttribute
public static class AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper
{
public static Func<ActionContext, IActionResult> InvalidModelStateResponseFactory => actionContext =>
{
var shouldEnableDataValidationarameterName = ((OptionalValidationAttribute)((ControllerActionDescriptor)actionContext.ActionDescriptor)
.MethodInfo.GetCustomAttributes(typeof(OptionalValidationAttribute), true)
.SingleOrDefault())?.ShouldEnableDataValidationParameterName;
var isValidationEnabled = true;
if (shouldEnableDataValidationarameterName != null)
{
var httpContextRequest = actionContext.HttpContext.Request;
var shouldEnableDataValidationValue = httpContextRequest.Form[shouldEnableDataValidationarameterName]
.Union(httpContextRequest.Query[shouldEnableDataValidationarameterName]).FirstOrDefault();
isValidationEnabled = shouldEnableDataValidationValue?.ToLower() == bool.TrueString.ToLower();
}
if (!isValidationEnabled)
{
return null;
}
var problemDetailsFactory = actionContext.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionContext.HttpContext, actionContext.ModelState);
ObjectResult result;
if (problemDetails.Status == 400)
{
// For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400.
result = new BadRequestObjectResult(problemDetails);
}
else
{
result = new ObjectResult(problemDetails)
{
StatusCode = problemDetails.Status,
};
}
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
}
The OptionalValidationAttribute:
[AttributeUsage(AttributeTargets.Method)]
public class OptionalValidationAttribute : Attribute
{
public OptionalValidationAttribute(string shouldEnableDataValidationParameterName)
{
ShouldEnableDataValidationParameterName = shouldEnableDataValidationParameterName;
}
public string ShouldEnableDataValidationParameterName { get; }
}
Example usage on an controller action:
[HttpPost]
[OptionalValidation(shouldEnableDataValidationParameterName: nameof(shouldEnableDataValidation))] // C# 11 needed to use nameof for a method parameter
public async Task<IActionResult> Update(
[FromForm] int id,
[FromForm] string name,
[FromForm] bool shouldEnableDataValidation
)
{
...
}
I would suggest you to approach this differently: Disabling the model validation will mean that there isn’t any validation for this action; not now, not later. Just because you do not require validation right now that doesn’t mean that you won’t need some kind of validation later on.
If you used some custom handling to disable the validation altogether for that action, then all you are doing is creating an actual exception into your application which will make it more complex. Developers looking at this later might not expect this behavior and could spend a good amount of time trying to figure out why the validation isn’t running when it’s working for every other action.
So instead, consider just duplicating the model so that each action has its own model: Action A has the original model with the validation attributes, requiring the values to be filled. And action B has a copy of that model without any validation attributes.
While this may seem wasteful, this does give you a few more benefits:
If you later require validation for some fields on action B, you could just add some validation attributes back. You didn’t have to disable the automatic validation completely, so individual validation attributes will just continue to work if you add them to the model.
Having separate models allows both actions to evolve independently. There’s already a good indicator that the actions do two different things: One requires the values, the other doesn’t. So it’s not unlikely that the models might need to diverge further in the future. For example, you might want to add a property to only one model but not the other.
As already mentioned above, you can stick to the default behavior and keep a consistent development experience.
Similar to Poke's answer, I would recommend using a different model for the action you want not to be validated. Instead of creating a copy of the model, however, I would just derive from the validated model and add the [ValidateNever] attribute, e.g.
[ValidateNever]
public class MyUnvalidatedModel : MyValidatedModel {
}
This will allow you to avoid a lot of duplication, while still giving you an unvalidated version of your model.

Getting 415 Unsupported media type when executing from address line in browser provifing JSON as parameter for route in .NET Core 3

I'm executing the URL
https://localhost:44310/api/Licensee/{"name":"stan"}
in address field of my browser, getting the error
"title": "Unsupported Media Type", "status": 415
which is described as
... the origin server is refusing to service the request because the payload
is in a format not supported by this method on the target resource.
The suggested troubleshot is
... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...
I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.
The controller looks like this.
[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
return Ok(await Licensee.GetLicenseeByParameters(parameters));
}
I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.
services.AddControllers()
.AddJsonOptions(_ =>
{
_.JsonSerializerOptions.AllowTrailingCommas = true;
_.JsonSerializerOptions.PropertyNamingPolicy = null;
_.JsonSerializerOptions.DictionaryKeyPolicy = null;
_.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.
How can I resolve this (or at least troubleshoot further)?
The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time
Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:
[HttpGet("/api/licensee")]
public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
{
return Json(parameters);
}
The first time the DTO is:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
}
What you need is to send a HTTP Request as below:
GET /api/licensee?name=stan&note=it+works
And later you decide to change the LicenseeParameters:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
public List<SubNode> Children{get;set;} // a complex array
}
You don't have to change the controller signature. Just send a payload in this way:
GET /api/licensee?name=stan&note=it+works&children[0].nodeName=it&children[1].nodeName=minus
The conversion is : . represents property and [] represents collection or dictionary.
In case you do want to send a json string within URL, what you need is to create a custom model binder.
internal class LicenseeParametersModelBinder : IModelBinder
{
private readonly JsonSerializerOptions _jsonOpts;
public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
{
this._jsonOpts = jsonOpts.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var name= bindingContext.FieldName;
var type = bindingContext.ModelType;
try{
var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
bindingContext.Result = ModelBindingResult.Success(obj);
}
catch (JsonException ex){
bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
}
return Task.CompletedTask;
}
}
and register the model binder as below:
[HttpGet("/api/licensee/{parameters}")]
public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
{
return Json(parameters);
}
Finally, you can send a json within URL(suppose the property name is case insensive):
GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.

ASP.NET C# ApiController and Static Config Class

I have an issue with an ASP.NET C# API application.
It uses the AuthApiAttribute class to check authorization, using 2 HTTP header to authenticate the query.
Once I have validated the credentials, I put some configuration linked to those credentials in a class with static attributes. That class is named ApiKeyConfig. That parts works correctly.
My problem is when the ApiController handles the response, the value of the attributes of ApiKeyConfig are the values of the previous API call.
So if I call the API 4 time with userA, userB, userC and userA again, the result will be:
Call for userA: Has no info if server is fresh, last call if not
Call for userB: Will have info of userA
Call for userC: Will have info of userB
Call for userA: Will have info of userC
I was expecting the static values of the ApiKeyConfig class not to survive from one query to another. I thought it would be static for the query API call only.
And from that behaviour, I suppose that the AuthApiAttribute class call is done AFTER the controller method has executed ?
In my controller, I have defined [AuthApi] above my public class CustomerController : ApiController.
So what would be the best way to pass to my controller configuration that are specific to the API-key for the current call ?
Also, is there a way to prevent values to be kept from API call to API call ? Like in this case, what would I do to make sure ApiKeyConfig don't have the value of the previous request?
Edit:
My AuthApiAttribute class:
public class AuthApiAttribute : Attribute, IAuthorizationFilter
{
public bool AllowMultiple => true;
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
// I have the logic to check if user is valid
// [...]
List<ApiKey> keys; // Is assigned the valid API keys, skip that code below to avoid long comment
// I have some logic here to stock the valid keys in "keys"
// [...]
foreach (ApiKey apikey in keys)
{
if (key == apikey.key && auth == apikey.auth)
{
// FIXME: Would need to do somehting here to assign that key to something for me to be able to use that value once I'm in the controller's method
return response;
}
}
response.Result.StatusCode = System.Net.HttpStatusCode.Forbidden;
response.Result.Content = new StringContent("Access forbidden. Make sure your credentials are valid.");
return response;
}
}
The class ApiKeyConfig is just a class with attributes representing the settings of the API key in use (somewhat like a user's profile)
Here, an example of a Controller in which I want to refer to the ApiKey for the current request.
[AuthApi]
public class CustomerController : ApiController
{
public Models.Response Get(string id)
{
// FIXME: Here, I want to access the value of ApiKey for the current session.
try
{
// I have some logic here to get the Customer requested
// [...]
return new Models.Response
{
Status = "Success",
Data = Customer
};
}
catch (Exception e)
{
return new Models.Response
{
Status = "Error",
Message = e.Message,
Stack = e.StackTrace
};
}
}
}
Solution:
Based on Athanasios Kataras answer, in AuthApiAttribute:
actionContext.ControllerContext.Configuration.Properties.TryAdd("apikey", apikey);
And then, in my Controller's Method accessing this value with:
Configuration.Properties.TryGetValue("apikey", out object config);
ApiKeyConfig keyConfig = (ApiKeyConfig)config;
if (keyConfig.value.Equals(""))
{
// Handle session undefined
}
You should not use static variables for these types of communication.
When you have multiple concurrent users, the static variable might change in your authorization, before the request is handled by the controller. This will lead to bugs that can't be easily identified.
Maybe you could use something like this to share data between filters and controllers. WebApi: how to pass state from filter to controller?
Also make sure that you extend the https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api authorization attribute for your authorization action, as this will certainly run before your controller.

How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?

I have a custom attribute extension of System.Web.Http.Filters.ActionFilterAttribute that I am using for logging with Web API controllers. I am experiencing an issue that indicates that the attribute object is being reused call to call. Data in my public properties from an initial call will appear in the logged information for a subsequent call and so on.
I read in this post that I "should never store instance state in an action filter that will be reused between the different methods." He goes on to say,"This basically means that the same instance of the action filter can be reused for different actions and if you have stored instance state in it it will probably break."
My custom attribute is apparently "break" ing. Thus began my search to answer the question …
How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?
An example is given in the post I referenced above of how data should be passed method to method using the HttpContext.Items dictionary. That's great and I can see how that would help but I'm not using ASP.net MVC'sSystem.Web.Http.Mvc.ActionFilterAttribute – which the poster uses in his answer. I'm doing Web API and the context object passed into the OnActionExecuting method is of type HttpActionContext and not of type ActionExecutingContext. I do not have access to the HttpContext.Items dictionary through the passed context, however, I believe that it is safe to access the HttpContext like this:
HttpContext.Current.Items[key]
Is that safe?
I do not have access to that dictionary in the constructor however and since that is where I receive my parameterized message string as a positional parameter, I am seemingly dependent on stored instance state.
So what to do?
In this post – also dependent on ASP.net MVC's System.Web.Http.Mvc.ActionFilterAttributeand its ActionExecutingContext– the poster uses the ActionParameters property of that context object to get at the parameters passed to the attribute, but I cannot find any equivalent in Web API's HttpActionContext. If I could, this would seem to be the answer! But alas…
How can I safely get to the positional parameter value passed into my constructor and the named parameter value passed in through a public property within the OnActionExecuting method?
Posts I have researched:
Are ActionFilterAttributes reused across threads? How does that work?
MVC Action Filter and Multiple Threads
passing action method parameter to ActionFilterAttribute in asp.net mvc
Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
System.Web.Mvc.ActionFilterAttribute vs System.Web.Http.Filters.ActionFilterAttribute
Web Api 2 HttpContext or HttpActionContext
Background: In the constructor, I pass a parameterized message string that includes placeholders for the values of arguments passed to the method the attribute is applied to. I also have an optional LogAllResponses property that is set through a named parameter to the attribute that I use to decide how much information I will log. The public properties that receive these values are set through the constructor and attribute invocation like this:
[LogAction("Retrieve information for all ad week items with storeId: {storeId}.", LogAllResponses = false)]
The important parts of the implementation of my action filter appear below:
public class LogActionAttribute : ActionFilterAttribute
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string ParameterizedMessage { get; set; }
public bool LogAllResponses { get; set; } = true;
public LogActionAttribute(string parameterizedMessage)
{
ParameterizedMessage = parameterizedMessage;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
HttpContext.Current.Items["__Parameterized_Message__"] = ParameterizedMessage;
HttpContext.Current.Items["__Log_All_Responses__"] = LogAllResponses.ToString();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var contextualizedMessage = HttpContext.Current.Items["__Parameterized_Message__"] as string ?? "";
var logAllResponsesAsString = HttpContext.Current.Items["__Log_All_Responses__"] as string ?? "";
var logAllResponses = logAllResponsesAsString.CompareIgnoreCase("true") == 0;
// convert argument values with ID suffixes to identifiable names
var arguments = actionExecutedContext.ActionContext.ActionArguments;
//foreach (var arg in arguments)
// ...
// replace the placeholders in the parameterized message string with actual values
// log the contextualized message
//Log.Debug(...
base.OnActionExecuted(actionExecutedContext);
}
}

Adding header to Swagger Markup [duplicate]

How can I add individual headers on different controllers.
E.g.:
Controller Name: Controller1,
Custom header: Header1
Controller Name: Controller2,
Custom header: Header2
The headers should be displayed for all the apis under the specific controller
This can be solved by adding an OperationFilter to your swagger configuration.
First you have to provide a class that implements IOperationFilter. The Applymethod receives an Operation parameter which contains the controller name in the tagfield. When the Swagger UI is rendered, the Applymethod will be called for each method in the API. You could even provide individual parameters for each API method, as Operation also contains the operationId.
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
operation.parameters = new List<Parameter>();
if (operation.tags[0]?.CompareTo("Example") == 0)
{
operation.parameters.Add(new Parameter
{
name = "X-ExampleParam",
#in = "header",
#default = "42", // optional default value, can be omitted
type = "string",
description = "My special parameter for the example API",
required = true
});
}
else if (operation.tags[0]?.CompareTo("Whatever") == 0)
{
// add other header parameters here
}
}
}
In the debugger, with a controller named ExampleController, it looks like this:
The result in the Swagger UI is a special parameter that is only applied to the API of my Example controller:
Tell Swagger to use your OperationFilter by adding one line in the Register method of the SwaggerConfig class:
public class SwaggerConfig
{
public static void Register(HttpConfiguration config)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
//GlobalConfiguration.Configuration
config
.EnableSwagger(c =>
{
... // omitted some lines here
c.OperationFilter<AddRequiredHeaderParameter>(); // Add this line
... // omitted some lines here
})
}
The idea to this solution is based on ShaTin's answer: How to send custom headers with requests in Swagger UI?
This is not an answer but stackoverflow wont' let me just make a comment on the solution from jps. Just wanted to add this is what I needed for my using clauses in jps's answer to get this to work in regular .net:
using Swashbuckle.Application;
using Swashbuckle.Swagger;
using System.Collections.Generic;
using System.Web.Http.Description;

Categories

Resources