MVC Portable Areas Enhancement – Embedded Resource Controller
- by Steve Michelotti
MvcContrib contains a feature called Portable Areas which I’ve recently blogged about. In short, portable areas provide a way to distribute MVC binary components as simple .NET assemblies where the aspx/ascx files are actually compiled into the assembly as embedded resources. This is an extremely cool feature but once you start building robust portable areas, you’ll also want to be able to access other external files like css and javascript. After my recent post suggesting portable areas be expanded to include other embedded resources, Eric Hexter asked me if I’d like to contribute the code to MvcContrib (which of course I did!). Embedded resources are stored in a case-sensitive way in .NET assemblies and the existing embedded view engine inside MvcContrib already took this into account. Obviously, we’d want the same case sensitivity handling to be taken into account for any embedded resource so my job consisted of 1) adding the Embedded Resource Controller, and 2) a little refactor to extract the logic that deals with embedded resources so that the embedded view engine and the embedded resource controller could both leverage it and, therefore, keep the code DRY. The embedded resource controller targets these scenarios: External image files that are referenced in an <img> tag External files referenced like css or JavaScript files Image files referenced inside css files Embedded Resources Walkthrough This post will describe a walkthrough of using the embedded resource controller in your portable areas to include the scenarios outlined above. I will build a trivial “Quick Links” widget to illustrate the concepts. The portable area registration is the starting point for all portable areas. The MvcContrib.PortableAreas.EmbeddedResourceController is optional functionality – you must opt-in if you want to use it. To do this, you simply “register” it by providing a route in your area registration that uses it like this: 1: context.MapRoute("ResourceRoute", "quicklinks/resource/{resourceName}",
2: new { controller = "EmbeddedResource", action = "Index" },
3: new string[] { "MvcContrib.PortableAreas" });
First, notice that I can specify any route I want (e.g., “quicklinks/resources/…”). Second, notice that I need to include the “MvcContrib.PortableAreas” namespace as the fourth parameter so that the framework is able to find the EmbeddedResourceController at runtime.
The handling of embedded views and embedded resources have now been merged. Therefore, the call to:
1: RegisterTheViewsInTheEmmeddedViewEngine(GetType());
has now been removed (breaking change). It has been replaced with:
1: RegisterAreaEmbeddedResources();
Other than that, the portable area registration remains unchanged.
The solution structure for the static files in my portable area looks like this:
I’ve got a css file in a folder called “Content” as well as a couple of image files in a folder called “images”. To reference these in my aspx/ascx code, all of have to do is this:
1: <link href="<%= Url.Resource("Content.QuickLinks.css") %>" rel="stylesheet" type="text/css" />
2: <img src="<%= Url.Resource("images.globe.png") %>" />
This results in the following HTML mark up:
1: <link href="/quicklinks/resource/Content.QuickLinks.css" rel="stylesheet" type="text/css" />
2: <img src="/quicklinks/resource/images.globe.png" />
The Url.Resource() method is now included in MvcContrib as well. Make sure you import the “MvcContrib” namespace in your views.
Next, I have to following html to render the quick links:
1: <ul class="links">
2: <li><a href="http://www.google.com">Google</a></li>
3: <li><a href="http://www.bing.com">Bing</a></li>
4: <li><a href="http://www.yahoo.com">Yahoo</a></li>
5: </ul>
Notice the <ul> tag has a class called “links”. This is defined inside my QuickLinks.css file and looks like this:
1: ul.links li
2: {
3: background: url(/quicklinks/resource/images.navigation.png) left 4px no-repeat;
4: padding-left: 20px;
5: margin-bottom: 4px;
6: }
On line 3 we’re able to refer to the url for the background property.
As a final note, although we already have complete control over the location of the embedded resources inside the assembly, what if we also want control over the physical URL routes as well. This point was raised by John Nelson in this post. This has been taken into account as well. For example, suppose you want your physical url to look like this:
1: <img src="/quicklinks/images/globe.png" />
instead of the same corresponding URL shown above (i.e., “/quicklinks/resources/images.globe.png”). You can do this easily by specifying another route for it which includes a “resourcePath” parameter that is pre-pended. Here is the complete code for the area registration with the custom route for the images shown on lines 9-11:
1: public class QuickLinksRegistration : PortableAreaRegistration
2: {
3: public override void RegisterArea(System.Web.Mvc.AreaRegistrationContext context, IApplicationBus bus)
4: {
5: context.MapRoute("ResourceRoute", "quicklinks/resource/{resourceName}",
6: new { controller = "EmbeddedResource", action = "Index" },
7: new string[] { "MvcContrib.PortableAreas" });
8:
9: context.MapRoute("ResourceImageRoute", "quicklinks/images/{resourceName}",
10: new { controller = "EmbeddedResource", action = "Index", resourcePath = "images" },
11: new string[] { "MvcContrib.PortableAreas" });
12:
13: context.MapRoute("quicklink", "quicklinks/{controller}/{action}",
14: new {controller = "links", action = "index"});
15:
16: this.RegisterAreaEmbeddedResources();
17: }
18:
19: public override string AreaName
20: {
21: get
22: {
23: return "QuickLinks";
24: }
25: }
26: }
The Quick Links portable area results in the following requests (including custom route formats):
The complete code for this post is now included in the Portable Areas sample solution in the latest MvcContrib source code. You can get the latest code now. Portable Areas open up exciting new possibilities for MVC development!