Posts
203
Comments
1120
Trackbacks
51
MVC Portable Areas Enhancement – Embedded Resource Controller

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:

  1. External image files that are referenced in an <img> tag
  2. External files referenced like css or JavaScript files
  3. 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!

posted on Tuesday, April 13, 2010 9:23 PM Print
Comments
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Jeremy Bull
4/23/2010 4:48 PM
Portable Areas are looking better and better. This was a key missing piece. Thank you. But how do I put a master page in a portable area?
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Steve
4/24/2010 9:00 PM
As of yet, no one has come up with a solution that embeds the master page inside the portable area. The virtual path to the portable area currently must point to something that physically exists on disk which typically is in the host.
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Jeremy Bull
5/20/2010 2:17 PM
I saw your new post about Master Pages (http://bit.ly/aWocy3) -- I'll add some thoughts there.

I have another suggestion: We don't want ".Areas." in the middle of all our namespaces, so we're using ReSharper to set "Namespace Provider = false" on the Areas folder. We're also changing the portable area assembly namespace so that the area name does not repeat in the class namespaces. The portable area works fine like this, but the Embedded Resource Controller can't find anything. What I need is the ability to override the VirtualRoot and Namespace in the call to the AssemblyResourceStore ctor inside RegisterAreaEmbeddedResources.
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Steve
5/21/2010 7:21 AM
@Jeremy - Interesting. I'll have a look at it this weekend.
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
packer
11/25/2011 12:21 PM
I am using MVC 2 on VS2010 and IIS 7. I created a portable area with
some images embedded into it. I tried to create routing rule two ways at
the time of portable area registeration. Following are the two ways I
tried.


Method 1

=======
context.MapRoute("ResourceRoute", "login/resource/{resourceName}",
new { controller = "EmbeddedResource", action = "Index" },
new string[] { "MvcContrib.PortableAreas" });

context.MapRoute(
"login",
"login/{controller}/{action}",
new { controller = "login", action = "index" }); RegisterAreaEmbeddedResources();

In this case I tried to access the image using Url.Resource()



Method 2
=======

context.MapRoute(
"login",
"login/{controller}/{action}",
new { controller = "login", action = "index" }); RegisterDefaultRoutes(context);RegisterAreaEmbeddedResources();

In this case I tried to access the image using Url.Content()



Following are scenarios where things are working fine.

1. When I run the application on my local development server (Ctrl + F5). 2. When run application after publishing it on my localhost. 3. When I run the application on different development server (Ctrl + F5), say on my friend's box.

But it does not render image (right now I have only images as static
resource. No CSS or JS) if I publish on someone else's system, say
localhost on my friend's box. Everything else is working fine except for
the image rendering.


I have tried all possible means to get it rendered in the situation it is failing but all in vain. I need some help and that is why I asked such a long question hoping that maybe I may get some pointer here.
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Steve Michelotti
12/4/2011 8:45 PM
@packer - Hard to know without seeing all of the code. How are you referencing the images? absolute paths, etc.? Are you specifically getting 404 on the image requests? If so, what path is it showing for those?
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Naeem Akbar
1/17/2012 5:02 AM
Fantastic blog. Just what I was looking for. I have it working with MVC3.. Note for any one doing so..
1)<img src="<%= Url.Resource("images.globe.png") %>" />
shld be
<img src="@Url.Resource("images.globe.png")" />
2) Make sure you include the MVCContrib namespace to your razor view
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
FunkMonkey33
12/6/2012 7:46 PM
Hi Steve,

Thanks for the great article. I'm using this technique to great success, except in the case of static HTML files.

I keep getting a 500 error when it tries to serve a static HTML file. Here is my route registration:

private void RegisterRoutes(AreaRegistrationContext context)
{
context.MapRoute(
AreaName + "_scripts",
base.AreaRoutePrefix + "/Scripts/{resourceName}",
new { controller = "EmbeddedResource", action = "Index", resourcePath = "Scripts" },
new[] { "MvcContrib.PortableAreas" }
);

context.MapRoute(
AreaName + "_html",
base.AreaRoutePrefix + "/html/{resourceName}",
new { controller = "EmbeddedResource", action = "Index", resourcePath = "html" },
new[] { "MvcContrib.PortableAreas" }
);

context.MapRoute(
AreaName + "_content",
base.AreaRoutePrefix + "/Content/{resourceName}",
new { controller = "EmbeddedResource", action = "Index", resourcePath = "content" },
new[] { "MvcContrib.PortableAreas" }
);

context.MapRoute(
AreaName + "_images",
base.AreaRoutePrefix + "/images/{resourceName}",
new { controller = "EmbeddedResource", action = "Index", resourcePath = "images" },
new[] { "MvcContrib.PortableAreas" }
);

context.MapRoute(
AreaName + "_default",
base.AreaRoutePrefix + "/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "PortableAreaDemo.PortableAreas.Areas.Demo.Controllers", "MvcContrib" }
);

}

I'm thinking this could have something to do with the HTML files not being complete (i.e. no <html>, <head> or <body> tags).

Thanks!
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
Steve Michelotti
12/7/2012 9:47 AM
@FunkMonkey33 - hmm, hard to know without looking at it. Are you able to get any more detailed information on the 500? I doubt it's because lack of <html> tags. Are you able to hit the URL directly from the browser?
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
FunkMonkey33
12/10/2012 2:22 PM
It turns out it has nothing to do with the html files being partial (starting with a Div tag, rather than with an html tag).

It turns out it is something within the Embedded Resource Controller. I requested the same static HTML file (action set to Embedded Resource), directly from the browser url, and I get the same error message whether it's a partial html file, or a fully-formed html file.

Here is the stack trace:

[KeyNotFoundException: The given key was not present in the dictionary.]
System.Collections.Generic.Dictionary`2.get_Item(TKey key) +9624813
MvcContrib.PortableAreas.EmbeddedResourceController.GetContentType(String resourceName) +78
MvcContrib.PortableAreas.EmbeddedResourceController.Index(String resourceName, String resourcePath) +208
lambda_method(Closure , ControllerBase , Object[] ) +135
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +208
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +55
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +263
System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +19
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +191
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
System.Web.Mvc.Controller.ExecuteCore() +116
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8920797
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
FunkMonkey33
12/10/2012 6:50 PM
Okay, I found the problem by removing the DLL reference to MvcContrib, and instead downloading the source code, and adding a project reference to MvcContrib instead.

When the 500 happened, I was able to break and see the problem. Basically the EmbeddedResourceController's InitializeMimeTypes did not include a MIME type for html. I added an html mime type to the dictionary, and now it works.

How might I go about requesting this change to the main branch?
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
FunkMonkey33
12/19/2012 2:48 PM
Any issues consuming an MVC3 Portable Area from an MVC 4 application? I'm trying to do that and my PortableAreaRegistration's RegisterArea method never gets called.

Thanks.

Aaron
Gravatar
# re: MVC Portable Areas Enhancement – Embedded Resource Controller
FunkMonkey33
12/19/2012 6:54 PM
It seems that in an MVC4 application, the AreaRegistration.RegisterAllAreas() method in Application_Start (in global.asax.cs) doesn't wind up calling the RegisterArea() method of my PortableAreaRegistration class. Not sure why.

Gravatar
# Portable areas
Ramesh Bas
5/1/2013 6:07 PM
You can use Portable Areas created by MvCContrib project. Here is a a complete solotion and some documentation:

http://portalareas.azurewebsites.net/

Thanks

Ramesh Bas
Gravatar
# 404 errors on embedded resources
Tinus Van Eck
11/13/2013 6:50 AM
I keep getting 404 errors on ANY embedded resources besides the views. Even in the Quicklinks area sample project!

I was able to track it down to what appears to be IIS. It seems that in the sample project, if the project's web settings are set to IIS Express, the images come up. In Local IIS they DON'T!

Why is this?

Post Comment

Title *
Name *
Email
Comment *  
Verification

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud