Thursday, April 5, 2012

Strongly typed links within ASP.NET MVC areas

Recently we've started to utilize concept of areas in our ASP.NET MVC application to separate different products provided by our application. We are going to have some controllers with the same names in different areas, so when linking, we'll need to specify the area name (if different than the current request's one). But we're used to strongly-typed url generation using extensions from MVC Futures like Html.ActionLink<T> with lambdas (Html.ActionLink<HomeController>(x => x.About(), "Home") etc. Unfortunately, these two requirements don't work well together.

MVC Futures extensions (known also as Microsoft.Web.Mvc) are good at getting the controller and action name from provided controller type and action lambda, but they don't get the area correctly. It's probably because there's no 100% correct way to determine in which area the controller lies. In most cases, we could guess that from the namespace - when creating area within Visual Studio, it creates the directory for controllers under Areas.AreaName.Controllers. But that's just a convention and there's no guarantee that it's always followed.

MVC Futures offers a solution - we can mark our controllers within areas with an attribute:

[ActionLinkArea("First")]
public class BillingController : Controller
{
}

This is understood by MVC Futures' strongly-typed helpers and when building a link to BillingController they'll use "First" area correctly.

Unfortunately, our requirements were more complicated. We have another area we use to expose some of our controllers through the RESTful API. And linking rules are as follows:

  • when current request is within First or Second area, we're linking as described above - target area is determined by target controller
  • but, when current request is within API area, we should link to API alternative controller (if available).

My first idea was to inherit from ActionLinkAreaAttribute and override the target area name for API calls, but unfortunately the attribute class is sealed. It means that we can't make use of that standard behavior and need to create our own.

After some fiddling with source code I've written my own versions of helper methods I need. My implementations conform to my own attribute, which allows to set up the default area name and fall back to standard MVC behavior (staying in current area) for API calls. Here's how to use it:

[LinkWithinArea("First", OrSwitchTo = "Api")]
public class BillingController : Controller
{
}

Now, whenever the helper method is building a link typed with BillingController, it'll generate link to Api area for calls from Api area or to First area for all other calls. OrSwitchTo parameter is optional - when omitted, LinkWithinArea will behave just like the built-in ActionLinkArea. No need to specify area all the time when building a links.

I've published the attribute and helpers code as a Gist, feel free to use it.

1 comment:

  1. Thanks for sharing this valuable piece of information with us. I am glad to find this stuff here. Keep up the good work.

    ReplyDelete