views:

54

answers:

5

I'm trying to write REST-behaviour into my ASP.NET MVC2 app, but I'm having a hard time figuring out how to make the routes work as I want.

I'd like my routing to work like this:

/Users/Get/1 <- returns a regular HTML-based reply
/Users/Get.xml/1 <- returns the data from Get as XML
/Users/Get.json/1 <- returns the data as JSon

I've tried setting up routes like this:

routes.MapRoute("Rest", 
 "{controller}/{action}{format}/{id}" (...)

But it complains I need a separator between {action} and {format}

also the following:

routes.MapRoute("Rest",
     "{controller}/{action}.{format}/{id}" (...)

makes the /Users/Get/1 invalid (it needs to be /Users/Get./1 which is unacceptable)

Any suggestions?

-------------EDIT------------------------------------

I have one solution right now, but I'm not really happy with it:

routes.MapRoute(
            "DefaultWithFormat", // Route name
            "{controller}/{action}.{format}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", format = "HTML", id = UrlParameter.Optional } // Parameter defaults
        );
        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );

This works with both /Users/Get.whateverFormat/1 and also /Users/Get/1

Reason for this is that when I just do /Users/Get/1 (without the .format) it skips the first route, and goes to the next which doesn't include a format. To handle the return I've created an ActionFilterAttribute and override the OnActionExecuted method like this:

var type = filterContext.RouteData.Values["format"];
if (type != null && attributes != null)
{
    if (type == "HTML") return;
    if (type.ToString().ToLower() == "xml" && attributes.Any(a => a.AllowedTypes.Any(a2 => a2 == ResponseType.XML)))
    {
        filterContext.Result = new XmlResult(filterContext.Controller.ViewData.Model);
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.ContentType = "text/xml";
        return;
    }
    if (type.ToString().ToLower() == "json" && attributes.Any(a => a.AllowedTypes.Any(a2 => a2 == ResponseType.JSON)))
    {
        filterContext.Result = new JsonResult() { Data = (filterContext.Controller.ViewData.Model), JsonRequestBehavior = JsonRequestBehavior.AllowGet };
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.ContentType = "text/json";
        return;
    }
}

And I also have a ResponseTypeAttribute which allows me to decorate the actions with what returntype they should allow:

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public sealed class ResponseTypeAttribute : Attribute
{
    List<ResponseType> allowedTypes;

    public List<ResponseType> AllowedTypes
    {
        get { return allowedTypes; }
        set { allowedTypes = value; }
    }

    public ResponseTypeAttribute(params ResponseType[] allowedTypes)
    {
        this.allowedTypes = new List<ResponseType>();
        this.allowedTypes.AddRange(allowedTypes);
    }


}

public enum ResponseType
{
    XML, JSON
}

The XmlResult is just a simple object serializer.

A: 

Brad Wilson made a talk with title Advanced ASP.NET MVC2 where he has shown an example how to do exactly what you want. You can download the slides and example code here:

http://bradwilson.typepad.com/blog/talks.html

(It is the first talk on the page, and if I remember well restful urls is the first topic in the talk.)

apolka
doesn't really help me since the slide says: "Demo - Supporting REST-like URLs" and there are no examples of it in the sample code :)
Yngve B. Nilsen
I have seen this video: http://events.boostweb20.com/Events/SeattleCodeCamp2010/#state=sessionCode%242003-4. If you have time look at it. :) It does show the demo about the restful urls....
apolka
Now that's a pretty sweet solution! :D
Yngve B. Nilsen
A: 

Have you tried setting a default for {format} of html?

Jordan S. Jones
A: 

Maybe this is an option (using '/' instead of '.'):

routes.MapRoute(
        "Rest1",
        "Users/Get/{format}/{id}",
        new { controller = "Users", action = "Get", format = "HTML" }
        );

And then

public class UsersController : Controller {
   public ActionResult Get(string format, int id) {
      switch (format) {
         case "json":
            break;
         case "xml":
            break;
         default:
            break;
      }
      return new ContentResult(); // Or other result as required.
   }
}
John
of course, but that's not my question ;)
Yngve B. Nilsen
A: 

Another idea:

routes.MapRoute(
    "Rest1",
    "Users/Get/{id}.{format}",
    new { controller = "Users", action = "Get", format = "HTML" }
    );

And then in controler method add some code for retreaving id and format

meeron
well then /Users/Get/1 wont work... I would need to write "/Users/Get/1." in order for the route to work...
Yngve B. Nilsen
A: 

You could use regular expressions in your route, then define that "." as an optional character in your route.

Radu094