MVC Portable Areas – Deploying Static Files

This is the second post in a series related to build and deployment considerations as I've been exploring MVC Portable Areas:

As I've been digging more into portable areas, one of the things I've liked best is the deployment story which enables my *.aspx, *.ascx pages to be compiled into the assembly as embedded resources rather than having to maintain all those files separately. In traditional web forms, that was always the thing to prevented developers from utilizing *.ascx user controls across projects (see this post for using portable areas in web forms).  However, though the aspx pages are embedded, the supporting static files (e.g., images, css, javascript) are not. Most of the demos available online today tend to brush over this issue and focus solely on the aspx side of things. But to create truly robust portable areas, it's important to have a good story for these supporting files as well.  I've been working with two different approaches so far (of course I'd really like to hear if other people are using alternatives).


For the approaches below, the scenario really isn't that important. It could be something as trivial as this partial view:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<img src="<%: Url.Content("~/images/arrow.gif") %>" /> Hello World!

The point is that there needs to be careful consideration for any scenario that links to an external file such as an image, *.css, *.js, etc. In the example shown above, it uses the Url.Content() method to convert to a relative path. But this method won't necessary work depending on how you deploy your portable area.

One approach to address this issue is to build your portable area project with MSDeploy/WebDeploy so that it is packaged properly before incorporating into the host application.

All of the *.cs files are removed and the project is ready for xcopy deployment – however, I do not need the "Views" folder since all of the mark up has been compiled into the assembly as embedded resources. Now in the host application we create a folder called "Modules" and deploy any portable areas as sub-folders under that:

At this point we can add a simple assembly reference to the Widget1.dll sitting in the ModulesWidget1bin folder. I can now render the portable image in my view like any other portable area. However, the problem with that is that the view results in this:


It couldn't find arrow.gif because it looked for /images/arrow.gif and it was actually located at /images/Modules/Widget1/images/arrow.gif. One solution is to make the physical location of the portable configurable from the perspective of the host like this:

  <add key="Widget1" value="ModulesWidget1"/>

Using the section is a little cheesy but it could be better formalized into its own section. In fact, if were you willing to rely on conventions (e.g., "Modules{areaName}") then then config could be eliminated completely. With this config in place, we could create our own Html helper method called Url.AreaContent() that "wraps" the OOTB Url.Content() method while simply pre-pending the area location path:

public static string AreaContent(this UrlHelper urlHelper, string contentPath)  
    var areaName = (string)urlHelper.RequestContext.RouteData.DataTokens["area"];
    var areaPath = (string)ConfigurationManager.AppSettings[areaName];
    return urlHelper.Content("~/" + areaPath + "/" + contentPath);

With these two items in place, we just change our Url.Content() call to Url.AreaContent() like this:

<img src="<%: Url.AreaContent("/images/arrow.gif") %>" /> Hello World!

and the arrow.gif now renders correctly:

Since we're just using our own Url.AreaContent() rather than the built-in Url.Content(), this solution works for images, *.css, *.js, or any externally referenced files.  Additionally, any images referenced inside a css file will work provided it's a relative reference and not an absolute reference.

An alternative to this approach is to build the static file into the assembly as embedded resources themselves. I'll explore this in another post (linked at the top).