X-HTTP-Method-Override gives NotFound (404) on ASP.NET Web API - c#

I am trying to implement HTTP method override following the steps described here. Basically, I am creating a DelegatingHandler, similar to the following, and adding it as a message handler on Application_Start.
public class MethodOverrideHandler : DelegatingHandler
{
readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
const string _header = "X-HTTP-Method-Override";
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Check for HTTP POST with the X-HTTP-Method-Override header.
if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
return base.SendAsync(request, cancellationToken);
}
}
I have the following methods defined on my Controller:
persons/{id}, GET
persons/{id}, PUT
persons/{id}, DELETE
I can call them through their "native" methods and they work as expected. However, when I try to call them through a POST, sending the X-HTTP-Method-Override header with "DELETE" or "PUT", it gives a Not Found (404) error. It is important to add that, when it gives this error, it never reaches the MethodOverrideHandler -- I have put a Breakpoint which is never hit; it does hit the Breakpoint when I call normal DELETE and PUT.
I even tried adding another method:
persons/{id}, POST
When I do this, I get a Method Not Allowed (405) instead.
I thought that message handlers were run BEFORE the Routing and Controller dispatchers. Why is this giving me 404?
I do not think this is related, but I am not using default Web API routing. Instead, I am mapping using a custom Attribute, assigned to each method, like this:
routes.MapHttpRoute(
String.Format("{0}_{1}", operation.Name, service.ServiceId),
String.Format("{0}/{1}", service.RoutePrefix, routeTemplateAttribute.Template),
defaults,
new { httpMethod = GetHttpMethodConstraint(operation) });
[HttpDelete, RouteTemplate("persons/{id}")]
public HttpResponseMessage DeletePerson(string id)
{
// ...
}
EDIT: GetHttpMethodConstraint code is below.
private static HttpMethodConstraint GetHttpMethodConstraint(MethodInfo methodInfo)
{
var methodResolver = HttpMethodResolver.FromMethodInfo(methodInfo);
return new HttpMethodConstraint(methodResolver.Resolve());
}
internal class HttpMethodResolver
{
private MethodInfo _methodInfo;
private HttpMethodResolver(MethodInfo methodInfo)
{
_methodInfo = methodInfo;
}
public static HttpMethodResolver FromMethodInfo(MethodInfo methodInfo)
{
return new HttpMethodResolver(methodInfo);
}
public string[] Resolve()
{
var verbs = new List<HttpMethod>();
if (MethodHasAttribute<HttpGetAttribute>())
{
verbs.Add(HttpMethod.Get);
}
else if (MethodHasAttribute<HttpPostAttribute>())
{
verbs.Add(HttpMethod.Post);
}
else if (MethodHasAttribute<HttpDeleteAttribute>())
{
verbs.Add(HttpMethod.Delete);
}
else if (MethodHasAttribute<HttpPutAttribute>())
{
verbs.Add(HttpMethod.Put);
}
else
{
throw new ServiceModelException("HTTP method attribute should be used");
}
return verbs.Select(v => v.Method).ToArray();
}
private bool MethodHasAttribute<T>() where T : Attribute
{
return GetMethodAttribute<T>() != null;
}
private T GetMethodAttribute<T>() where T : Attribute
{
return _methodInfo.GetCustomAttributes(typeof(T), true).FirstOrDefault() as T;
}
}

I think I'm having the same problem. It does look like the route constraints are checked before any message handlers.
So I created a custom constraint that knows to check for an overridden HTTP method:
class OverrideableHttpMethodConstraint : HttpMethodConstraint
{
public OverrideableHttpMethodConstraint(HttpMethod httpMethod) : base(httpMethod)
{
}
protected override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
IEnumerable<string> headerValues;
if (request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase) &&
request.Headers.TryGetValues("X-HTTP-Method-Override", out headerValues))
{
var method = headerValues.FirstOrDefault();
if (method != null)
{
request.Method = new HttpMethod(method);
}
}
return base.Match(request, route, parameterName, values, routeDirection);
}
}

I have tried to reproduce your error but I wasn't able to. Here, you can download my simple project with your message handler: https://dl.dropbox.com/u/20568014/WebApplication6.zip
I would like to point out that message handlers run before the action selection logic is performed. So, in your case, probably something else causes the problem and I think you should look at your other message handlers, your message handler's registration code, etc because the problem occurs due to the fact that your message handler never runs.
Also, I think your IRouteConstraint implementation, GetHttpMethodConstraint, looks suspicious to me.
Here is my registration code for the message handler:
protected void Application_Start(object sender, EventArgs e) {
var config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(
"DefaultHttpRoute",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new MethodOverrideHandler());
}

Related

How to prioritize Web Api Controllers over IHttpHandler?

I have a legacy project that has a single IHttpHandler implementing class that routes all the requests using a huge switch statement etc.. I am trying to introduce Attribute Routing with ApiControllers but the first one always has the priority. Is it possible to configure the system (either code or IIS) so that Web ApiControllers have priority over my single IHttpHandler implementing class? In IIS, I put my AttributeRouting first and then there are all the aspx ones but still the Web Api Controller is not getting processed first..no matter what I do (having them under the same project). I don't want to introduce a separate project.
Edit: There is a IHttpModule that decides based on what is after api/ to route it to specific ashx file. One of them is the one described..
Edit 2: More specifically: If the uri doesn't have a list of filtered things [file,message,property ...] it is routed to Resource.aspx
so api/file, api/message, api/property would be handle from other .ashx files - otherwise the traffic goes to Resource.ashx...
As a result the requests that have api/endpoint1, api/endpoint2, api/endpoint3
will all go to Resource.aspx. The question is how to route api/endpoint3 to the API Controller described below.
Thanks
Simplified Code Architecture:
//SolutionName/Api/MyModule.cs (Legacy Code)
//this routes based on what is after api/ to Resource.ashx or other ashx files
public class MyModule : IHttpModule {
//if url doesn't contain [file,message,property ...] route to Resource.ashx
}
//SolutionName/API/Resource.ashx (Legacy Code)
//this is hit at any request solutionname/api/anything
public class DefaultHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context) {
String APIBranch = parse(context);
switch(APIBranch)
{
case "endpoint1": methodOne(); break;
case "endpoint2": methodTwo(); break;
[...]
default: throw Exception(); break;
}
}
}
//SolutionName/API/App_Start/AttributeRoutingHttpConfig.cs
public static class AttributeRoutingHttpConfig
{
public static void RegisterRoutes(HttpRouteCollection routes)
{
// See http://github.com/mccalltd/AttributeRouting/wiki for more options.
// To debug routes locally using the built in ASP.NET development server, go to /routes.axd
routes.MapHttpAttributeRoutes();
}
public static void Start()
{
RegisterRoutes(GlobalConfiguration.Configuration.Routes);
}
}
//SolutionName/API/Controllers/MyController.cs
//this should have been hit for a GET on solutionname/api/endpoint3/id
[RoutePrefix("endpoint3")]
public class MyController : ApiController
{
private IModelDao modelDao;
MyController(IModelDao modelDao){
this.modelDao = modelDao;
}
[Route("{id}")]
[HttpGet]
public Model GetSomething(int id)
{
Model model = modelDao.GetSomething(id);
return model;
}
}
I've found two solutions to this problem. The first is to modify module that rewrites urls by inserting check if Web API routing system can handle request. The second is to add another module to application, that will direct requests to Web API Handler using HttpContext.RemapHandler().
Here's code:
First solution.
If your module looks like this:
public class MyModule: IHttpModule
{
public void Dispose(){}
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
}
Then you need to change it like this:
public void Init(HttpApplication context)
{
context.BeginRequest += (object Sender, EventArgs e) =>
{
HttpContext httpContext = HttpContext.Current;
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod),
httpContext.Request.Url);
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null) //enough if WebApiConfig.Register is empty
return;
string currentUrl = httpContext.Request.Url.LocalPath.ToLower();
if (currentUrl.StartsWith("/api/endpoint0") ||
currentUrl.StartsWith("/api/endpoint1") ||
currentUrl.StartsWith("/api/endpoint2"))
{
httpContext.RewritePath("/api/resource.ashx");
}
};
}
Second solution.
Module for remapping handlers:
public class RemappingModule: IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.PostResolveRequestCache += (src, args) =>
{
HttpContext httpContext = HttpContext.Current;
string currentUrl = httpContext.Request.FilePath;
if (!string.IsNullOrEmpty(httpContext.Request.QueryString.ToString()))
currentUrl += "?" + httpContext.Request.QueryString;
//checking if url was rewritten
if (httpContext.Request.RawUrl != currentUrl)
{
//getting original url
string url = string.Format("{0}://{1}{2}",
httpContext.Request.Url.Scheme,
httpContext.Request.Url.Authority,
httpContext.Request.RawUrl);
var httpRequestMessage = new HttpRequestMessage(
new HttpMethod(httpContext.Request.HttpMethod), url);
//checking if Web API routing system can find route for specified url
IHttpRouteData httpRouteData =
GlobalConfiguration.Configuration.Routes.GetRouteData(httpRequestMessage);
if (httpRouteData != null)
{
//to be honest, I found out by experiments, that
//context route data should be filled that way
var routeData = httpContext.Request.RequestContext.RouteData;
foreach (var value in httpRouteData.Values)
routeData.Values.Add(value.Key, value.Value);
//rewriting back url
httpContext.RewritePath(httpContext.Request.RawUrl);
//remapping to Web API handler
httpContext.RemapHandler(
new HttpControllerHandler(httpContext.Request.RequestContext.RouteData));
}
}
};
}
}
These solutions work when method WebApiConfig.Register is empty, but if there were routes with templates like "api/{controller}" then any path with two segments starting with "api" would pass the check, even if there're no controllers with specified name and your module can do something userfull for this path. In this case you can, for example, use method from this answer to check if controller exists.
Also Web API routing system will accept route even if found controller don't handle requests for current http method. You can use descendant of RouteFactoryAttribute and HttpMethodConstraint to avoid this.
UPD Tested on this controllers:
[RoutePrefix("api/endpoint1")]
public class DefaultController : ApiController
{
[Route("{value:int}")]
public string Get(int value)
{
return "TestController.Get: value=" + value;
}
}
[RoutePrefix("api/endpoint2")]
public class Endpoint2Controller : ApiController
{
[Route("segment/segment")]
public string Post()
{
return "Endpoint2:Post";
}
}

Need to log asp.net webapi 2 request and response body to a database

I am using Microsoft Asp.net WebApi2 hosted on IIS. I very simply would like to log the request body (XML or JSON) and the response body for each post.
There is nothing special about this project or the controller processing the post. I am not interested in using logging frameworks like nLog, elmah, log4net, or the built-in tracing features of web API unless it is necessary to do so.
I am simply wanting to know where to put my logging code and how to get the actual JSON or XML from the incoming and outgoing request and response.
My controller post method:
public HttpResponseMessage Post([FromBody])Employee employee)
{
if (ModelState.IsValid)
{
// insert employee into to the database
}
}
I would recommend using a DelegatingHandler. Then you will not need to worry about any logging code in your controllers.
public class LogRequestAndResponseHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
// log request body
string requestBody = await request.Content.ReadAsStringAsync();
Trace.WriteLine(requestBody);
}
// let other handlers process the request
var result = await base.SendAsync(request, cancellationToken);
if (result.Content != null)
{
// once response body is ready, log it
var responseBody = await result.Content.ReadAsStringAsync();
Trace.WriteLine(responseBody);
}
return result;
}
}
Just replace Trace.WriteLine with your logging code and register the handler in WebApiConfig like this:
config.MessageHandlers.Add(new LogRequestAndResponseHandler());
Here is the full Microsoft documentation for Message Handlers.
There are multiple approaches to generically handle Request/Response logging for every WebAPI method calls:
ActionFilterAttribute:
One can write custom ActionFilterAttribute and decorate the controller/action methods to enable logging.
Con: You need to decorate every controller/methods (still you can do it on base controller, but still it doesn't address cross cutting concerns.
Override BaseController and handle logging there.
Con: We are expecting/forcing the controllers to inherit from a custom base controller.
Using DelegatingHandler.
Advantage: We are not touching controller/method here with this approach. Delegating handler sits in isolation and gracefully handles the request/response logging.
For more indepth article, refer this http://weblogs.asp.net/fredriknormen/log-message-request-and-response-in-asp-net-webapi.
One of the option you have is using creating a action filter and decorating your WebApiController/ApiMethod with it.
Filter Attribute
public class MyFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.Request.Method == HttpMethod.Post)
{
var postData = actionContext.ActionArguments;
//do logging here
}
}
}
WebApi controller
[MyFilterAttribute]
public class ValuesController : ApiController{..}
or
[MyFilterAttribute]
public void Post([FromBody]string value){..}
Hope this helps.
Getting access to request message is easy. Your base class, ApiController contains .Request property, which, as name suggests, contains the request in parsed form. You simply examine it for whatever you're looking to log and pass it to your logging facility, whichever it may be. This code you can put in the beginning of your action, if you need to do it for just one or a handful.
If you need to do it on all actions (all meaning more than a manageable handful), then what you can do is override .ExecuteAsync method to capture every action call for your controller.
public override Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken
)
{
// Do logging here using controllerContext.Request
return base.ExecuteAsync(controllerContext, cancellationToken);
}
This seems to be a pretty old thread but worh sharing another solution.
You can add this method in your global.asax file which will be triggered every after HTTP request ends.
void Application_EndRequest(Object Sender, EventArgs e)
{
var request = (Sender as HttpApplication).Request;
var response = (Sender as HttpApplication).Response;
if (request.HttpMethod == "POST" || request.HttpMethod == "PUT")
{
byte[] bytes = request.BinaryRead(request.TotalBytes);
string body = Encoding.UTF7.GetString(bytes);
if (!String.IsNullOrEmpty(body))
{
// Do your logic here (Save in DB, Log in IIS etc.)
}
}
}
This is really old topic but I spent much time(search the internet) to do these thing so I will just post my solution here.
Concept
Override ExecuteAsync of APicontroller method for tracking Inbound request,in my solution I create Base_ApiController as a parent of my project's API controllers .
Use System.Web.Http.Filters.ActionFilterAttribute to track Outbound response of api controller
***(Additional)***Use System.Web.Http.Filters.ExceptionFilterAttribute to log when exception occure.
1. MyController.cs
[APIExceptionFilter] // use 3.
[APIActionFilter] // use 2.
public class Base_APIController : ApiController
{
public bool IsLogInbound
{
get
{ return ConfigurationManager.AppSettings["LogInboundRequest"] =="Y"? true:false ; }
}
/// <summary>
/// for logging exception
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override Task<HttpResponseMessage> ExecuteAsync(
HttpControllerContext controllerContext,
CancellationToken cancellationToken
)
{
// Do logging here using controllerContext.Request
// I don't know why calling the code below make content not null Kanit P.
var content = controllerContext.Request.Content.ReadAsStringAsync().Result.ToString(); // keep request json content
// Do your own logging!
if (IsLogInbound)
{
try
{
ErrLog.Insert(ErrLog.type.InboundRequest, controllerContext.Request,
controllerContext.Request.RequestUri.AbsoluteUri
, content);
}
catch (Exception e) { }
}
// will not log err when go to wrong controller's action (error here but not go to APIExceptionFilter)
var t = base.ExecuteAsync(controllerContext, cancellationToken);
if (!t.Result.IsSuccessStatusCode)
{
}
return t;
}
2. APIActionFilter.cs
public class APIActionFilter : System.Web.Http.Filters.ActionFilterAttribute
{
public bool LogOutboundRequest
{
get
{ return ConfigurationManager.AppSettings["LogInboundRequest"] == "Y" ? true : false; }
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
try {
var returndata = actionExecutedContext.Response.Content.ReadAsStringAsync().Result.ToString();
//keep Json response content
// Do your own logging!
if (LogOutboundRequest)
{
ErrLog.Insert(ErrLog.type.OutboundResponse, actionExecutedContext.Response.Headers,
actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
+ "/"
+ actionExecutedContext.ActionContext.ActionDescriptor.ActionName
, returndata );
}
} catch (Exception e) {
}
}
}
}
3. APIExceptionFilter.cs
public class APIExceptionFilter : ExceptionFilterAttribute
{
public bool IsLogErr
{
get
{ return ConfigurationManager.AppSettings["LogExceptionRequest"] == "Y" ? true : false; }
}
public override void OnException(HttpActionExecutedContext context)
{
try
{
//Do your own logging!
if (IsLogErr)
{
ErrLog.Insert(ErrLog.type.APIFilterException, context.Request,
context.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
+ "/"
+ context.ActionContext.ActionDescriptor.ActionName
, context.Exception.ToString() + context.Exception.StackTrace);
}
}catch(Exception e){
}
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
else {
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
}

Web Api Required Parameter

Using ASP.NET Web API. Is there a way to automatically return a status code 400 if a parameter is null? I found this question but that is a global solution that is applied to all methods, I want to do this on a per method per parameter basis.
So, for example, this is what I am currently doing:
public HttpResponseMessage SomeMethod(SomeNullableParameter parameter)
{
if (parameter == null)
throw new HttpResponseException(HttpStatusCode.BadRequest);
// Otherwise do more stuff.
}
I would really just like to do something like this (notice the required attribute):
public HttpResponseMessage SomeMethod([Required] SomeNullableParameter parameter)
{
// Do stuff.
}
The approach I ended up using was to create a custom filter that I registered globally. The filter checks all request parameters for the RequiredAttribute. If the attribute is found then it checks if the parameter was passed with the request (not null) and returns status code 400 if it was null. I also added a cache to the filter to store the required parameters for each request to avoid the reflection hit on future calls. I was pleasantly surprised to find that this works for value types as well since the action context stores the parameters as objects.
EDIT - Updated solution based on tecfield's comment
public class RequiredParametersFilter : ActionFilterAttribute
{
// Cache used to store the required parameters for each request based on the
// request's http method and local path.
private readonly ConcurrentDictionary<Tuple<HttpMethod, string>, List<string>> _Cache =
new ConcurrentDictionary<Tuple<HttpMethod, string>, List<string>>();
public override void OnActionExecuting(HttpActionContext actionContext)
{
// Get the request's required parameters.
List<string> requiredParameters = this.GetRequiredParameters(actionContext);
// If the required parameters are valid then continue with the request.
// Otherwise, return status code 400.
if(this.ValidateParameters(actionContext, requiredParameters))
{
base.OnActionExecuting(actionContext);
}
else
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
}
private bool ValidateParameters(HttpActionContext actionContext, List<string> requiredParameters)
{
// If the list of required parameters is null or containst no parameters
// then there is nothing to validate.
// Return true.
if (requiredParameters == null || requiredParameters.Count == 0)
{
return true;
}
// Attempt to find at least one required parameter that is null.
bool hasNullParameter =
actionContext
.ActionArguments
.Any(a => requiredParameters.Contains(a.Key) && a.Value == null);
// If a null required paramter was found then return false.
// Otherwise, return true.
return !hasNullParameter;
}
private List<string> GetRequiredParameters(HttpActionContext actionContext)
{
// Instantiate a list of strings to store the required parameters.
List<string> result = null;
// Instantiate a tuple using the request's http method and the local path.
// This will be used to add/lookup the required parameters in the cache.
Tuple<HttpMethod, string> request =
new Tuple<HttpMethod, string>(
actionContext.Request.Method,
actionContext.Request.RequestUri.LocalPath);
// Attempt to find the required parameters in the cache.
if (!this._Cache.TryGetValue(request, out result))
{
// If the required parameters were not found in the cache then get all
// parameters decorated with the 'RequiredAttribute' from the action context.
result =
actionContext
.ActionDescriptor
.GetParameters()
.Where(p => p.GetCustomAttributes<RequiredAttribute>().Any())
.Select(p => p.ParameterName)
.ToList();
// Add the required parameters to the cache.
this._Cache.TryAdd(request, result);
}
// Return the required parameters.
return result;
}
}
Set [Required] on a property in your model and then check the ModelState to see if it IsValid.
This will allow all the required properties to be tested at the same time.
See the "Under-Posting" section # Model validation in WebAPI
we can use the BindRequired, which is from Microsoft.AspNetCore.Mvc.ModelBinding namespace.
public async Task<ActionResult<IEnumerable<Numbers>>> GetAll([BindRequired, FromQuery]string[] numbers)
{
var result = await _service.GetAllDetails(numbers);
return Ok(result);
}
after that your swagger will look like below.
A solution for asp.net core...
[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var requiredParameters = context.ActionDescriptor.Parameters.Where(
p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredModelAttribute>() != null).Select(p => p.Name);
foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, StringComparer.Ordinal)))
{
if (argument.Value == null)
{
context.ModelState.AddModelError(argument.Key, $"The argument '{argument.Key}' cannot be null.");
}
}
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
context.Result = new BadRequestObjectResult(errors);
return;
}
base.OnActionExecuting(context);
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredModelAttribute : Attribute
{
}
services.AddMvc(options =>
{
options.Filters.Add(typeof(CheckRequiredModelAttribute));
});
public async Task<IActionResult> CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken)
{
//...
}
The accepted solution takes it upon itself to report back any errors. A more appropriate approach for MVC5 is to let the controller handle (via model validation) the reporting of any errors, aka something like this:
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class ValidateParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
var descriptor = context.ActionDescriptor;
if (descriptor != null)
{
var modelState = context.ModelState;
foreach (var parameterDescriptor in descriptor.GetParameters())
{
EvaluateValidationAttributes(
suppliedValue: context.ActionArguments[parameterDescriptor.ParameterName],
modelState: modelState,
parameterDescriptor: parameterDescriptor
);
}
}
base.OnActionExecuting(context);
}
static private void EvaluateValidationAttributes(HttpParameterDescriptor parameterDescriptor, object suppliedValue, ModelStateDictionary modelState)
{
var parameterName = parameterDescriptor.ParameterName;
parameterDescriptor
.GetCustomAttributes<object>()
.OfType<ValidationAttribute>()
.Where(x => !x.IsValid(suppliedValue))
.ForEach(x => modelState.AddModelError(parameterName, x.FormatErrorMessage(parameterName)));
}
}
You may then plug it in universally via WebApiConfig.cs:
config.Filters.Add(new ValidateParametersAttribute());

Return 404 from MVC routehandler

I have a list of IP's that keep crawling our live site which throw exceptions when they use certain URL's with no parameters (because of the MVC routing). I want to block those IP addresses and return a 404 not found page as soon as they can be picked up, but I don't want to do it in IIS as I want to log the encounters within our application.
I've written a catch-all type routehandler that uses a custom MvcHandler to check the list and modify the response:
public class ApplicationRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
BlacklistedIPAddressHandler handler = new BlacklistedIPAddressHandler(Cache.WebsiteCache.GetBlacklistedIPList(), requestContext);
return handler;
}
}
public class BlacklistedIPAddressHandler : MvcHandler
{
List<IPBlacklistModel> blacklist;
public BlacklistedIPAddressHandler(List<IPBlacklistModel> Blacklist, RequestContext requestContext) : base(requestContext)
{
blacklist = Blacklist;
}
protected override IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state)
{
var ip = httpContext.Request.UserHostAddress;
if (blacklist != null &&
blacklist.Where(x => x.IP_ADDRESS.Contains(ip)).Count() > 0)
{
httpContext.Response.ClearHeaders();
httpContext.Response.Clear();
httpContext.Response.StatusCode = 404;
httpContext.Response.SuppressContent = true;
httpContext.Response.End();
}
return base.BeginProcessRequest(httpContext, callback, state);
}
}
It's then implemented as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = #"[^\.]*" }
).RouteHandler = new Helpers.Routing.ApplicationRouteHandler();
}
I keep getting a Server cannot append header after HTTP headers have been sent error when I do this. Anybody perhaps know why?
I suppose you should remove httpContext.Response.End(); statement from your handler - as the request is passed for further processing, it should not be terminated now.
HttpResponse.End method description from MSDN:
Sends all currently buffered output to the client, stops execution of
the page, and raises the EndRequest event.

Web API audit logging

I need to audit log calls to my Web API, ideally I'd like to use an Attribute, something like:
[HttpPost, Auditing]
public dynamic MyAPICall()
The Attribute should be able to intercept the API call before and after execution in order to log the parameters and also, how long the API call took to run.
With MVC I could create an ActionFilterAttribute derivative and override OnActionExecuted and OnActionExecuting.
Is the equivalent possible in the Web API world?
Http message handler should be a good extensible point for such purposes. Be careful though, there can be some issues with concurrent request content reading. For instance, Model Binder may try to read request content while LoggingHandler is reading it and fail to deserialize a model. To prevent such issues just add Wait call to the LogRequestLoggingInfo method.
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log the request information
LogRequestLoggingInfo(request);
// Execute the request
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
// Extract the response logging info then persist the information
LogResponseLoggingInfo(response);
return response;
});
}
private void LogRequestLoggingInfo(HttpRequestMessage request)
{
if (request.Content != null)
{
request.Content.ReadAsByteArrayAsync()
.ContinueWith(task =>
{
var result = Encoding.UTF8.GetString(task.Result);
// Log it somewhere
}).Wait(); // !!! Here is the fix !!!
}
}
private void LogResponseLoggingInfo(HttpResponseMessage response)
{
if (response.Content != null)
{
response.Content.ReadAsByteArrayAsync()
.ContinueWith(task =>
{
var responseMsg = Encoding.UTF8.GetString(task.Result);
// Log it somewhere
});
}
}
}
You can read more about it here.
I would use a message handler rather than attributes.
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
LogResponse(response);
return response;
});
}
private void LogRequest(HttpRequestMessage request)
{
(request.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
Logger.Info("{4:yyyy-MM-dd HH:mm:ss} {5} {0} request [{1}]{2} - {3}", request.GetCorrelationId(), request.Method, request.RequestUri, x.Result, DateTime.Now, Username(request));
});
}
private void LogResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
(response.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
Logger.Info("{3:yyyy-MM-dd HH:mm:ss} {4} {0} response [{1}] - {2}", request.GetCorrelationId(), response.StatusCode, x.Result, DateTime.Now, Username(request));
});
}
private string Username(HttpRequestMessage request)
{
var values = new List<string>().AsEnumerable();
if (request.Headers.TryGetValues("my-custom-header-for-current-user", out values) == false) return "<anonymous>";
return values.First();
}
}
I think you will be interested to take a look at Web API tracing http://www.asp.net/web-api/overview/testing-and-debugging/tracing-in-aspnet-web-api. It allows you to look into the internal mechanism of Web API.
In your case, I assume you're particularly interested in what's the input and output of actions. So you can right your TraceWriter like following sample to filter out the redundant information:
public class ActionAuditor : ITraceWriter
{
private const string TargetOperation = "ExecuteAsync";
private const string TargetOpeartor = "ReflectedHttpActionDescriptor";
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
{
var rec = new TraceRecord(request, category, level);
traceAction(rec);
if (rec.Operation == TargetOperation && rec.Operator == TargetOpeartor)
{
if (rec.Kind == TraceKind.Begin)
{
// log the input of the action
}
else
{
// log the output of the action
}
}
}
}
I've worked on a library that allows you to log interactions with ASP.NET Web API Controllers by using Action Filters.
It can record action method calls with caller info, arguments, output, duration, exceptions and more.
Take a look at Audit.WebApi.
You can quickly create a sample project that uses this library with the following commands:
> dotnet new -i Audit.WebApi.Template
> dotnet new webapiaudit

Categories