5

I am developing some REST resources using the ASP.NET MVC 3 platform. So far, its been great, I love the flexibility of MVC 3, and it is a breeze to build REST services with it. One area where I have been having a difficult time are the route constraints in Global.asax. For one, they never seem to work properly (including one ALWAYS returns 404, even if the input most definitely meets the requirements of the constraint...but thats another question).

Second, and more importantly, the result returned when a constraint fails is always an HTML page. Assuming the constraints work, having the result be HTML really throws a wrench in the mix when all of the consumers of these REST services will be accepting data types, such as application/json, text/xml, possibly bson, etc. I really need to be able to address errors in our clients directly, rather than simply blowing up and logging the html for sideband resolution.

Is it possible to change what gets returned in response to a route constraint? Is it possible to make whats returned dynamic, such that if the client issuing the request only accepts application/bson, we can generate an acceptable response, rather than simply generating a canned response of a single mime type?

jrista
  • 32,447
  • 15
  • 90
  • 130
  • Can you explain why you couldn't use a catch-all route that maps to a controller under your control? For example, my blog engine (FunnelWeb) uses this to show a Search page instead of a 404. see http://wallaceturner.com/blah/blah2 this could trivially be changed to return a JSON string. – wal Mar 05 '12 at 23:31
  • Hadn't considered of the catch-all route. I'll have to check it out...however I'm curious if I would have error information from previous routes? If a route fails due to THAT ROUTES constraints, I want to send back a message that indicates the problem, so the client will know what went wrong. The catch-all route may be a partial solution, but if I can't access any information that explains why routing failed, it would remain just a partial fix. – jrista Mar 09 '12 at 01:51
  • `but if I can't access any information that explains why routing failed` Can't your unit tests cover this? routeDebugger etc? its impossible to determine what route you think your clients(not under your control) were trying to go down. Actually, I kind of see a situation you might want to handle - to see if we're on the same page could you give an example where the catch-all wouldn't work? it sounds like you might need to have catch-all's for each of your routes/controller pairs... – wal Mar 09 '12 at 04:13
  • 1
    We use REST primarily for "RPC"...services (resources) that are primarily used by other code...applications or other services. If, at runtime, for whatever reason, a call is made to a rest service that violates a route constraint...either because it included user-generated data, or was an existing product calling into an updated service that broke contract...or whatever, we need to know why the request failed. In some cases, we may be able to correct the issue automatically in code...in the worst case, we would log the issue for manual resolution by operations staff later. – jrista Mar 09 '12 at 08:13
  • 1
    Since its CODE making the request, and not a human, we need the body of error responses to be lightweight and easily processed by code. An HTML error page is great if a human is going to see it and manually deal with the issue. Its terrible if you need to process the error with code and take the appropriate automated action. Hence the need to replace all default HTML bodies in error messages generated by ASP.NET MVC with JSON bodies. The nature and details of the errors are still important...its just the format they are being rendered into that needs to change. – jrista Mar 09 '12 at 08:16

2 Answers2

3

Errlusion might be of some help here.

You may be able to write some custom logic to check the HTTP Accept header and/or use [SomeContext].Request.IsAjaxRequest() to see if JSON or BSON would be appropriate to return -- or just return JSON or BSON for all types of requests if you prefer.

kendaleiv
  • 5,793
  • 3
  • 28
  • 38
3

About returning a code error instead of going to a controller you have to implemet a custom RouteHandler. This link resumes all thing you can put the finger on ...and that you might modify this way. About "adapting" the return type...you can do this in the controller. It is enough to put som if and in some cases you return Json(...) and in other cases you return View or PartialView.

However it is nice to do this in a controller filter...!

I implemented a Control filter that allows the controller to negotiate the return type with the client. I is very simple...the client just declare the type that i would like to receive either in a route parameter (a piece of the url, or of the query string) or by posting a field that contains this info. The use is very simple...you rcontroller can return a View thai the "default" return type if no infos come from the client. Then the filter "change" automatically the result type before the View is invoked transformig it in what the client required. The possibilties handled by the filter are a view whose name is chosen by the client or Json

The code is here(It contains some controls on the "hint" provided by the client to prevent attacks by malicious users):

public class AcceptViewHintAttribute : ActionFilterAttribute
{
    private JsonRequestBehavior jsBehavior;
    public AcceptViewHintAttribute(JsonRequestBehavior jsBehavior = JsonRequestBehavior.DenyGet)
    {
        this.jsBehavior = jsBehavior;
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        string hint = filterContext.RequestContext.HttpContext.Request.Params["ViewHint"];
        if (hint == null) hint = filterContext.RequestContext.RouteData.Values["ViewHint"] as string;
        if (!string.IsNullOrWhiteSpace(hint) && hint.Length<=100 && new Regex(@"^\w+$").IsMatch(hint) )
        {


                ViewResultBase res = filterContext.Result as ViewResultBase;
                if (res != null)
                {
                    if (hint == "json")
                    {
                        JsonResult jr = new JsonResult();
                        jr.Data = res.ViewData.Model;
                        jr.JsonRequestBehavior = jsBehavior;
                        filterContext.Result = jr;
                    }
                    else
                    {
                        res.ViewName = hint;
                    }
                }

        }
        base.OnActionExecuted(filterContext);
    }
}
Francesco Abbruzzese
  • 4,139
  • 1
  • 17
  • 18
  • Thanks for the post. The specifics about how to return content based on the client's Accept header is something I've taken care of...the question is more about whether that is possible in the context of errors being returned by ASP.NET MVC itself, at the point of routing, before requests ever get to my own controllers. If I have a constraint on a route, and the constraint is not met, and no other route matches, an error is returned with an HTML body. Is there some way to change the error handling path there, and customize what is returned, rather than simply leave it up to ASP.NET to decide? – jrista Mar 07 '12 at 22:55
  • 1
    You can put a default routing rule at the end of your list that cathc all unmatched requests (the ones that woul cause an error) and route them to a UNIQUE Controller that is in charge of deciding how to display the error. Consider you can return EVERYTHING also Http error headers from a controller...so this way you are free to choose HOW TO HANDLE errors, and you have put all error handling into an unique place implementation of the match all rule can be done with a custom Rout Handler ...Hope this is what you need – Francesco Abbruzzese Mar 08 '12 at 09:11
  • Yes I agree, @jrista this is what I said in my comment below your question! – wal Mar 09 '12 at 01:41
  • Thanks for the help. I believe a custom route handler is the way we'll need to go. – jrista Mar 12 '12 at 21:18