Intercept webapi json formatting errors - c#

I'd like to have a way to intercept the exception that occurs when you send in malformed json to a webapi endpoint, so that I can return a semantic error code as opposed to just 500. (e.g. "Fix your broken JSON or go to hell")

You can create your custom validation filter attribute by deriving from ActionFilterAttribute:
public class ValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext
.Request
.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
}
}
Now, you may either decorate your actions with it:
[HttpGet]
[ValidationFilter()]
public string DoSomethingCool()
or register it globally via your config:
config.Filters.Add(new ValidationFilterAttribute());

Related

Can you override the body deserialization web api

When I attempt to override serialization settings for a controller they are used for creating the response but ignored when parsing the body.
I need to override the json serialization settings for each requests in WebAPI. I am trying to change the serialization settings based on the route in use. Example: if it is a V1 api use SerializationSettingsA, if it is a V2 api use SerializationSettingsB.
I have tried multiple approaches including overriding the IContentNegotiator and IHttpControllerActivator hoping to set the serialization settings for the context but in all case it does not work. The behavior I am seeing is that the override serialization settings are used when crating the response body but not when parsing the request. Is there some other settings that need overriding for changing how the request body is parsed.
class PerControllerConfigActivator : IHttpControllerActivator
{
private static readonly DefaultHttpControllerActivator Default = new DefaultHttpControllerActivator();
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
controllerDescriptor.Configuration = HttpConfigurationFactory.CreateDefaultConfiguration();
controllerDescriptor.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Insert(0,
new VersionedPropertyConverter(request.RequestUri.AbsoluteUri));
var result = Default.Create(request, controllerDescriptor, controllerType);
return result;
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
configuration.MapHttpAttributeRoutes();
configuration.Formatters.Clear();
configuration.Services.Replace(typeof(IHttpControllerActivator), new PerControllerConfigActivator());
app.UseWebApi(configuration);
}
}
Borrowing some ideas from here, you could use custom input formatters with a specific content-type header on the request itself.
First you need to create an input formatter for the overrided version
public class MyNonDefaultJsonFormatter : InputFormatter
{
public MyNonDefaultJsonFormatter()
{
this.SupportedMediaTypes.Clear()
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/non-default-json");
}
public override bool CanRead(InputFormatterContext context)
{
return base.CanRead(context);
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext)
{
//Do some custom deserializing on context.HttpContext.Request.Body
}
}
Then you need to register the input formatter in configure services
services.AddMvc(options =>
{
options.InputFormatters.Add(new MyNonDefaultJsonFormatter());
}
Then specify the Content-Type as "application/non-default-json" when making your request.
As another idea, you may be able to remove the default JSON deserializer all together and instead register two serializers of your own, both accepting standard application/json, but with type checking in the CanReadType override that allows you to differentiate. I haven''t had a chance to test this out myself, but if I do will report back here.

Can a web api attribute return a value

I am currently doing some authorization in a web api. I have created a custom AuthorizeAttribute as follows :-
public class CustomAuthorization : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
}
}
Now in the HandleUnauthorizedRequest i am validation my token. The token contains a user object. What i want is to return the decryted user object to the web api controller as below: -
[HttpPost()]
[HttpOptions]
[CustomAuthorization]
public PolicyListReturnType GetPolicyList(PolicyListTypeDto listTypeDto)
{
//Get user object here;
}
Is this a good method or is there another way of doing this.

Handle Deserialization Exception in Formatter of Delegating Handler

I'm using web API and implementing delegating handler.
I have a customization of Json serializer / deserializer which is registered as a formatter in the API configuration.
var globalFormatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = globalFormatters.JsonFormatter;
jsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
jsonFormatter.SerializerSettings.Converters.Add(...)
As for exception handling, I've added an ExceptionFilterAttribute, and also added it as a filter in the configuration.
public class MethodAttributeExceptionHandling : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var errorHandler = new ErrorHandler();
var response = errorHandler.ProcessError(actionExecutedContext);
actionExecutedContext.Response = response;
}
}
This seemed to be working well, until I encountered a deserialization exception,
which did not get caught in my filter.
I've read the exception handling documentation Here which mentions serialization exception are not caught by the filter (It does not mention serialization however), and I couldn't find any solution to catch it and handle it properly.
In case it will help someone else,
In order to deal with this issue I implemented an ActionFilterAttribute, where in my implementation of the OnActionExecuting I'm checking if the model state is invalid, and do error handling stuff if so.
That's the spot just before the action execution and after the object deserialization.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
//Do error handling Stuff...
}
}

Add custom header to all responses in Web API

Simple question, and I am sure it has a simple answer but I can't find it.
I am using WebAPI and I would like to send back a custom header to all responses (server date/time requested by a dev for syncing purposes).
I am currently struggling to find a clear example of how, in one place (via the global.asax or another central location) I can get a custom header to appear for all responses.
Answer accepted, here is my filter (pretty much the same) and the line i added to the Register function of the WebApi config.
NOTE: The DateTime stuff is NodaTime, no real reason just was interested in looking at it.
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Content.Headers.Add("ServerTime", Instant.FromDateTimeUtc(DateTime.Now.ToUniversalTime()).ToString());
}
Config Line:
config.Filters.Add(new ServerTimeHeaderFilter());
For that you can use a custom ActionFilter (System.Web.Http.Filters)
public class AddCustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add("customHeader", "custom value date time");
}
}
You can then apply the filter to all your controller's actions by adding this in the configuration in Global.asax for example :
GlobalConfiguration.Configuration.Filters.Add(new AddCustomHeaderFilter());
You can also apply the filter attribute to the action that you want without the global cofiguration line.
Previous answers to this question don't address what to do if your controller action throws an exception. There are two basic ways to get that to work:
Add an exception filter:
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
public class HeaderAdderExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Response == null)
context.Response = context.Request.CreateErrorResponse(
HttpStatusCode.InternalServerError, context.Exception);
context.Response.Content.Headers.Add("header", "value");
}
}
and in your WebApi setup:
configuration.Filters.Add(new HeaderAdderExceptionFilter());
This approach works because WebApi's default exception handler will send the HttpResponseMessage created in a filter instead of building its own.
Replace the default exception handler:
using System.Net;
using System.Net.Http;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Results;
public class HeaderAdderExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
HttpResponseMessage response = context.Request.CreateErrorResponse(
HttpStatusCode.InternalServerError, context.Exception);
response.Headers.Add("header", "value");
context.Result = new ResponseMessageResult(response);
}
}
and in your WebApi setup:
configuration.Services.Replace(typeof(IExceptionHandler), new HeaderAdderExceptionHandler());
You can't use both of these together. Okay, well, you can, but the handler will never do anything because the filter already converted the exception into a response.
Super important to note that as written, this code will send all the exception details to the client. You probably don't want to do this in production, so check out all the available overloads on CreateErrorResponse() and pick which one suits your needs.
Julian's answer led me to have to create the filter but only using the the System.Web (v4) and System.Web.Http (v5) namespace (MVC packages were not part of this particular project this was used on.)
using System.Web;
using System.Web.Http.Filters;
...
public class AddCustomHeaderActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
actionExecutedContext.ActionContext.Response.Headers.Add("name", "value");
}
}
And add it to the global.asax to have it used on every controller/action
GlobalConfiguration.Configuration.Filters.Add(new AddCustomHeaderActionFilterAttribute());
Neither of the above two solutions worked for me. They wouldn't even compile. Here's what I did. Added:
filters.Add(new AddCustomHeaderFilter());
to RegisterGlobalFilters(GlobalFilterCollection filters) method in FiltersConfig.cs and then added
public class AddCustomHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
actionExecutedContext.HttpContext.Response.Headers.Add("ServerTime", DateTime.Now.ToString());
}
}
It can be done by the messagehandler easily, it will handle both ok response and exception case.
public class CustomHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// add header to request if you want
var response = await base.SendAsync(request, cancellationToken);
response.Headers.Add("cutomKey", "cutomValue");
return response;
}
}
Add it in the config
config.MessageHandlers.Add(new CustomHeaderHandler());
According to my requirement, below single line of code serves the purpose.
System.Web.HttpContext.Current.Response.Headers.Add("Key", "Value")
I combined the normal and exception path in one class:
public class CustomHeaderAttribute : FilterAttribute, IActionFilter, IExceptionFilter
{
private static string HEADER_KEY { get { return "X-CustomHeader"; } }
private static string HEADER_VALUE { get { return "Custom header value"; } }
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
return (new CustomHeaderAction() as IActionFilter).ExecuteActionFilterAsync(actionContext, cancellationToken, continuation);
}
public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
return (new CustomHeaderException() as IExceptionFilter).ExecuteExceptionFilterAsync(actionExecutedContext, cancellationToken);
}
private class CustomHeaderAction: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response != null)
{
actionExecutedContext.Response.Content.Headers.Add(HEADER_KEY, HEADER_VALUE);
}
}
}
private class CustomHeaderException : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Response == null)
{
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, context.Exception);
}
context.Response.Content.Headers.Add(HEADER_KEY, HEADER_VALUE);
}
}
}
Nothing fancy but at least it gives me one place to control my additional headers. For now it's just static content but you could always hook it up to some sort of dictionary generator/factory.
I had the same problem while trying to add a new header to the whole controller, just add "services.AddHttpContextAccessor();" to startup.cs then create your controller
public class EnController : Controller{
public EnController(IHttpContextAccessor myHttpAccessor)
{
myHttpAccessor.HttpContext.Response.Headers.Add("Content-Language", "en-US");
}
... more methods here...
}

Custom authorizations in Web.API

My understanding of ASP.NET MVC is that for authorizations I should use something like -
public class IPAuthorize : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext) {
//figure out if the ip is authorized
//and return true or false
}
But in Web API, there is no AuthorizeCore(..).
There is OnAuthorization(..) and the general advice for MVC is not to use OnAuthorization(..).
What should I use for custom authorizations in Web API?
Authorization is done in an authorization filter - that mean you derive from System.Web.Http.AuthorizeAttribute and implement the IsAuthorized method.
You don't implement authorization in a normal action filter because they run later in the pipeline than authorization filters.
You also don't implement authentication in a filter (like parsing a JWT) - this is done even earlier in an extensibility point called MessageHandler.
The method we use for is an custom ApiAuthorize attribute that inherits from System.Web.Http.AuthorizeAttribute. for example:
public class ApiAuthorizeAttribute : AuthorizeAttribute
{
readonly CreditPointModelContext _ctx = new CreditPointModelContext();
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if(Authorize(actionContext))
{
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
{
try
{
//boolean logic to determine if you are authorized.
//We check for a valid token in the request header or cookie.
}
catch (Exception)
{
return false;
}
}
}

Categories

Resources