Tuesday, April 17, 2012

RESTful Redirecting in ASP.NET MVC

Usually, in an ASP.NET MVC action method, if you want to send the user to a different action you would do something like this:

public ActionResult Index()
{
  return View();
}

public ActionResult Foo()
{
  return RedirectToAction("Index");
}

So now, when a user requests the "Foo" action on this controller, they get the "Index" action in response. This works. But there's something about it that doesn't sit right with me. It's a client-visible redirect. That is, the order of operations is as follows
  1. Browser requests the "Foo" resource.
  2. Server sends a 302 response, telling the browser to request the "Index" resource.
  3. Browser requests the "Index" resource.
  4. Server sends the "Index" view.
In most cases, this isn't a big deal. Nobody will ever know the difference. But I will. And on days when I'm in a RESTful purist mood, that difference bothers me.

The World Wide Web Consortium defines the 302 response code as follows:
The requested resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field.
So, if a client makes a request, and that request is good and correct but is temporarily being handled by another resource, then a 302 response tells the browser this and the browser follows along accordingly. However, that's not always the case. Sometimes you want to actually respond through a different action as part of the actual response for the initial request. Maybe the resource is an alias for another resource, for example. And the browser shouldn't be part of this equation. From the perspective of the browser, the server should just respond with the correct response.

I guess in the old Web Forms world we're talking about the difference between Response.Redirect() and Server.Transfer()? I'm actually not sure, I've so rarely used Server.Transfer() and it was so long ago that it was before I cared about the actual client/server interaction and cared only about the perceived behavior.

So how can this be done in MVC? As it turns out, there are various schools of thought on the subject. But it occurred to me today that there may be a simpler approach. (And, looking back now, it looks like this guy was thinking the same thing.) Part of the whole point of the ASP.NET MVC Framework is that these actions are independent of the rest of the web infrastructure. They're essentially atomic. They're just... methods. So can't I call one from another one?

public ActionResult Index()
{
  return View();
}

public ActionResult Foo()
{
  return Index();
}

Why yes, I can! There's a catch, though. That call to View() ended up doing something a little unexpected, but actually makes sense when you think about it. It's still trying to return the view for Foo(), not for Index(). Why? Well, that's the request to which it's responding. Unless there's some ugly reflection going on, it doesn't know or care that it was called by the Index() method. It does know and care that it's responding to a web request and translating that request into a view. And, since the request was for the Foo action, the view will try to be the Foo view.

But, with a little more explicit coding (and I love being explicit, rather than just assuming that convention will prevail... it leads to easier debugging in my experience), we can fix this:

public ActionResult Index()
{
  return View("Index");
}

public ActionResult Foo()
{
  return Index();
}

Now we're telling the View() method exactly what view we want it to process. For the Index action, there's no difference. We're just explicitly telling the view engine what to do. But now when the user makes a request for the Foo action, they also get the Index view. And they get it transparently. No redirect, no special routing, just a request and a response. The code is merely treating Foo as a hard link alias for Index.