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.