views:

150

answers:

3

Consider this MapRoute:

MapRoute(
    "ResultFormat",
    "{controller}/{action}/{id}.{resultFormat}",
    new { controller = "Home", action = "Index", id = 0, resultFormat = "json" }
);

And it's controller method:

public ActionResult Index(Int32 id, String resultFormat)
{
    var dc = new Models.DataContext();

    var messages = from m in dc.Messages where m.MessageId == id select m;

    if (resultFormat == "json")
    {
        return Json(messages, JsonRequestBehavior.AllowGet); // case 2
    }
    else
    {
        return View(messages); // case 1
    }
}

Here's the URL scenarios

  • Home/Index/1 will go to case 1
  • Home/Index/1.html will go to case 1
  • Home/Index/1.json will go to case 2

This works well. But I hate checking for strings. How would implement an enum to be used as the resultFormat parameter in the controller method?


Some pseudo-code to explain the basic idea:

namespace Models
{
    public enum ResponseType
    {
        HTML = 0,
        JSON = 1,
        Text = 2
    }
}

The MapRoute:

MapRoute(
    "ResultFormat",
    "{controller}/{action}/{id}.{resultFormat}",
    new {
        controller = "Home",
        action = "Index",
        id = 0,
        resultFormat = Models.ResultFormat.HTML
    }
);

The controller method signature:

public ActionResult Index(Int32 id, Models.ResultFormat resultFormat)
+2  A: 

IMHO the response format is a cross cutting concern and it's not the controller to mess with it. I would suggest you to write an ActionFilter for this job:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public sealed class RespondToAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var resultFormat = filterContext.RouteData.Values["resultFormat"] as string ?? "html";
        ViewResult viewResult = filterContext.Result as ViewResult;
        if (viewResult == null)
        {
            // The controller action did not return a view, probably it redirected
            return;
        }
        var model = viewResult.ViewData.Model;
        if (string.Equals("json", resultFormat, StringComparison.OrdinalIgnoreCase))
        {
            filterContext.Result = new JsonResult { Data = model };
        }
        // TODO: you could add some other response types you would like to handle
    }
}

which then simplifies your controller action a bit:

[RespondTo]
public ActionResult Index(int id)
{
    var messages = new string[0];
    if (id > 0)
    {
        // TODO: Fetch messages from somewhere
        messages = new[] { "message1", "message2" };
    }
    return View(messages);
}

The ActionFilter is a reusable component that you could apply to other actions.

Darin Dimitrov
I can't get your example-code to work. I've posted a new question here: http://stackoverflow.com/questions/1662216/return-jsonresult-using-an-actionfilter-on-a-controller Would be awesome if you'd take a look
roosteronacid
What particular issues did you have? As suggested as an answer in your other question you need to override OnActionExecuted instead of OnResultExecuting method because the latter runs before the controller action and the model will always be `null`.
Darin Dimitrov
A: 

Your pseudo code will work correctly. Default ModelBinder automatically converts the string in the url to Models.ResultFormat enum. But it would be better to make ActionFilter, as said Darin Dimitrov.

name1ess0ne
A: 

This is the ActionFilter I came up with:

public sealed class AlternateOutputAttribute :
                    ActionFilterAttribute, IActionFilter
{
    void IActionFilter.OnActionExecuted(ActionExecutedContext aec)
    {
        ViewResult vr = aec.Result as ViewResult;

        if (vr == null) return;

        var aof = aec.RouteData.Values["alternateOutputFormat"] as String;

        if (aof == "json") aec.Result = new JsonResult
        {
            JsonRequestBehavior = JsonRequestBehavior.AllowGet,
            Data = vr.ViewData.Model,
            ContentType = "application/json",
            ContentEncoding = Encoding.UTF8
        };
    }
}
roosteronacid