Return 404 from MVC routehandler - c#

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.

Related

IIS server running MVC - System.Threading.LockRecursionException

I have a basic MVC website, all updates installed. It is running under Microsoft-IIS/8.5 with Windows server 2012 on AWS. Error listed below.
The problem seems to be located in a couple heavily used API on the site. The caller send a JSON object to the function and returns a JSON object. This works in testing and normally on the site but when I look into my ELMAH logs, I see a lot of the below errors. My code does not call any locks as shown below so it is my opinion it is happen before the code gets to my function.
I noticed in the error it mentions the routing collection, I don't do any custom routing or change it after the site starts.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Server variables:
string="HTTP_CONTENT_LENGTH:264;HTTP_CONTENT_TYPE:application/json;HTTP_ACCEPT:application/json;HTTP_HOST:my.site.com;HTTP_USER_AGENT:libcurl-agent/1.0"
Error message:
application="program"
host="WIN-thing"
type="System.Threading.LockRecursionException"
message="Recursive read lock acquisitions not allowed in this mode."
source="System.Core"
detail="System.Threading.LockRecursionException: Recursive read lock acquisitions not allowed in this mode.
at System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker timeout)
at System.Threading.ReaderWriterLockSlim.TryEnterReadLock(TimeoutTracker timeout)
at System.Web.Routing.RouteCollection.GetReadLock()
at System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext)
at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)"
SUDO code for controller: The register function is really very simple, you calls a stored procedure that returns a few fields of data and then returns a JSON packet with the values need (two fields). The received JSON is again, a couple fields.
example register packet: {"Address":"value","company":"name"}
example result packet: {"success":true,"value":"updated 1"}
namespace ApMonitor.Controllers
{
[OutputCache(Duration = 0, NoStore = true)]
[AllowAnonymous]
public class ApiController : Controller
{
// passed via json data
public async Task<JsonResult> Register(Models.ApRegisterModel apReg)
{
try
{
using (var db = new ApplicationDbContext())
{
// get device data from SQL (one call)
var result = await DataLink.RegisterDevice(db, apReg);
// return json success fail
if(string.IsNullOrEmpty(result.DevicePw))
{
return DataLink.Fail("Unknown", string.Empty);
} else {
return DataLink.Success(result.DeviceData);
}
}
} catch (Exception ex)
{
DataLink.ProcessError(ex, "API Controller - Register");
}
}
public async Task<JsonResult> Log()
{
try
{
var input = new System.IO.StreamReader(Request.InputStream).ReadToEnd();
apEvent = Newtonsoft.Json.JsonConvert.DeserializeObject<Models.ApEventModel>(input);
using (var db = new ApplicationDbContext())
{
deviceId = await DataLink.GetDeviceId(db, apEvent.Address);
foreach(var e in apEvent.Device)
db.Events.Add(new Event() { /* ... */ });
// save data to DB
var cnt = await db.SaveChangesAsync();
return DataLink.Success(string.Format("updated {0}", cnt));
}
} catch (Exception ex)
{
DataLink.ProcessError(ex, "API Controller - Log");
}
return DataLink.Fail("Unknown", string.Empty);
}
// moved from another class called DataLink
internal static JsonResult Success(string value)
{
var result = new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { success = true, value = value }
};
return result;
}
internal static JsonResult Fail(string reason, string value)
{
var result = new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { success = false, reason = reason, value = value }
};
return result;
}
}
}

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";
}
}

handling my unauthorized requests , with an http 200 response + custom message

I have the following custom authorization class inside my asp.net mvc web application, which i call before my action methods:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
public string Model { get; set; }
public string Action { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
//code goes here................
if (!repository.can(ADusername, Model, value)) // implement this method based on your tables and logic
{ return false; }
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
else
{
var viewResult = new ViewResult();
viewResult.ViewName = "~/Views/Errors/_Unauthorized.cshtml";
filterContext.Result = viewResult;
}
// base.HandleUnauthorizedRequest(filterContext);
}
}
and i call this custom authorization before my action method as follow:-
[CheckUserPermissions(Action = "Read", Model = "Accounts")]
public ActionResult Index(){
Currently as seen in the above code when the request is not authorized , I will return a JSON or a partial view depending on request type (if it is Ajax request or not).
And inside my code I always take care of handling the json returned from the custom authorization class inside the onsuccess script as follow:-
function addrecords(data) {
if (data.IsSuccess == "Unauthorized") {
jAlert(data.description, 'Unauthorized Access');
}
else if (data.IsSuccess) {
jAlert(data.description, 'Creation Confirmation');
}
Currently my approach is working well, but I start thinking if I should continue with the fact that I am NOT retuning 401 http response for unauthorized requests ? and instead of that I am returning an http 200 , either as json object with status = “unauthrized” or redirect to a partial view ?
Can anyone advice ?
Thanks.
i used to do like this:
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult { Data = "LogOut" };
}
else
{
filterContext.Result = new RedirectResult("~/Home/Index");
}
and in jquery i check in generic ajaxError:
$(document).ajaxError(function(xhr, statusText, err){
if(xhr.status == 403) {
alert("Unathorized Request");
}
});
or:
$.ajaxSetup({
error: function (x, e) {
if (x.status == 403) {
alert("Unauthorized Access");
}
});
});
In your approach you have to check in every Ajax call success the response what is coming, but in this approach in unauthorized case returning 403 code will make Ajax call fail and error callback executes and we i use to write a generic error handler for Ajax and check if status code is that what u i return then show message that it is unauthorized request.
you can see details : Asp.net mvc Check User is Logged In and authorized Before Access to Page

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);
}
}
}

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

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());
}

Categories