MVC Portable Areas

April 6th, 2012

Introduction:

An MVC Portable Area is really just a dll that contains the views, controllers, scripts, etc… needed to use in a website that is either a Web Forms website or an MVC website. I will cover the MVC website here.

Why Use One?

I have used portable areas in many projects since they came out. A developer can use them for a reusable widget or a complete engine. I have actually used them for both.

Set Up:

I am going to assume that you have a new project. It is just as easy to add a portable area to an existing solution.

First, let’s create a blank solution. Click on New Project, then select Other Project Types –> Visual Studio Solutions. Name it PortableAreaDemo

image

 

Next, right click on the solution in Solution Explorer and Add New Project. Choose Web and ASP.NET MVC 3 Application. I Named mine PortableAreaDemo.Mvc.

 

image

 

Choose Internet Application on the next screen:

image

 

Next, add another project. This is also going to be a ASP.NET MVC 3 Web Application. I called mine PortableAreaDemo.PortableAreas. Note, you might get an error here or might not see your solution node. If so, follow this link: http://stackoverflow.com/questions/7457935/solution-folder-not-showing-in-visual-studio-2010-how-can-i-make-it-visible

image

 

This time, just make it an empty application.

image

 

Hopefully you see something like this:

image

 

Next, go ahead and delete the Controllers, Views, Models, Scripts, and Content folders under the PortableAreas project. Also, remove the Global.asax inside that project as well.

image

 

Now, right click on the PortableAreas project and add an Area. Let’s call it Demo. This should produce something like this:

image

 

Notice now that it created an AreaRegistration child called DemoAreaRegistration. We will need to now go and add a Library Package Reference to this. Right click on References under the PortableAreas project –> Add Library Package Reference. Then click on Online –> All. Wait for it to load, then type MvcContrib in the search box. Install the package.

image

 

Now go back into DemoAreaRegistration.cs and change the parent name to PortableAreaRegistration.

image

 

Move the routes out into a private method called RegisterRoutes.  By default, if you were to call the base.RegisterAreas here, it would set up the EmbeddedResource. However, your routes get set up in the wrong order and you will lose control once your portable area gets more sophisticated. I would strongly suggest not using the built in ones here and registering them yourself. Override the RegisterArea that passes the IApplicationBus instead of the one that just has the context:

   1: public override void  RegisterArea(AreaRegistrationContext context, IApplicationBus bus)

   2: {

   3:     RegisterRoutes(context);

   4:     RegisterAreaEmbeddedResources();

   5: }

 

Then add a private method called RegisterRoutes and add the following routes into it:

   1: private void RegisterRoutes(AreaRegistrationContext context)

   2: {

   3:     context.MapRoute(

   4:         AreaName + "_scripts",

   5:         base.AreaRoutePrefix + "/Scripts/{resourceName}",

   6:         new { controller = "EmbeddedResource", action = "Index", resourcePath = "scripts" },

   7:         new[] { "MvcContrib.PortableAreas" }

   8:     );

   9:  

  10:     context.MapRoute(

  11:         AreaName + "_images",

  12:         base.AreaRoutePrefix + "/images/{resourceName}",

  13:         new { controller = "EmbeddedResource", action = "Index", resourcePath = "images" },

  14:         new[] { "MvcContrib.PortableAreas" }

  15:     );

  16:  

  17:     context.MapRoute(

  18:         AreaName + "_default",

  19:         base.AreaRoutePrefix + "/{controller}/{action}/{id}",

  20:         new { action = "Index", id = UrlParameter.Optional },

  21:         new[] { "PortableAreaDemo.PortableAreas.Areas.Demo.Controllers", "MvcContrib" }

  22:     );

  23: }

 

When all is done, your registration class should look like this:

   1: using System.Web.Mvc;

   2: using MvcContrib.PortableAreas;

   3:  

   4: namespace PortableAreaDemo.PortableAreas.Areas.Demo

   5: {

   6:     public class DemoAreaRegistration : PortableAreaRegistration 

   7:     {

   8:         public override string AreaName

   9:         {

  10:             get

  11:             {

  12:                 return "Demo";

  13:             }

  14:         }

  15:  

  16:         public override void  RegisterArea(AreaRegistrationContext context, IApplicationBus bus)

  17:         {

  18:             RegisterRoutes(context);

  19:             RegisterAreaEmbeddedResources();

  20:         }

  21:  

  22:         private void RegisterRoutes(AreaRegistrationContext context)

  23:         {

  24:             context.MapRoute(

  25:                 AreaName + "_scripts",

  26:                 base.AreaRoutePrefix + "/Scripts/{resourceName}",

  27:                 new { controller = "EmbeddedResource", action = "Index", resourcePath = "scripts" },

  28:                 new[] { "MvcContrib.PortableAreas" }

  29:             );

  30:  

  31:             context.MapRoute(

  32:                 AreaName + "_images",

  33:                 base.AreaRoutePrefix + "/images/{resourceName}",

  34:                 new { controller = "EmbeddedResource", action = "Index", resourcePath = "images" },

  35:                 new[] { "MvcContrib.PortableAreas" }

  36:             );

  37:  

  38:             context.MapRoute(

  39:                 AreaName + "_default",

  40:                 base.AreaRoutePrefix + "/{controller}/{action}/{id}",

  41:                 new { action = "Index", id = UrlParameter.Optional },

  42:                 new[] { "PortableAreaDemo.PortableAreas.Areas.Demo.Controllers", "MvcContrib" }

  43:             );

  44:         }

  45:         

  46:     }

  47: }

 

So a little explanation of this. When your application first starts, there is a little piece of code in Application_Start that will end up calling this Portable Area Registration. Area routes should always be called before your normal routes to give it a chance to find the Area name. Otherwise, the default route would think the Area name was a controller name. Here is the piece of code that calls the portable area registration:

   1: protected void Application_Start()

   2: {

   3:     AreaRegistration.RegisterAllAreas();

   4: ...

 

Ok, so now let’s create a controller in the Demo area. Let’s call it “World”. Now let’s add an action to it. Can you guess the name? Yep, that’s right “Hello”. Create a Razor partial View for it. Type “Hello World” in the view. When all is said and done, you should look something like this:

image

 

Now, here is a very IMPORTANT step. Anything that is either a view, css, javascript, image, etc must be an embedded resource. What does this mean? It means it will be added to the dll instead of being found on the file system. This is important because a portable area travels with the dll and not with the project itself. Yes, it will cause you to have to build every time you want to see a change in the page or js. This is why you must decide up front if it is worth the development time that it will take. For me, the projects that I have used them for, it has been. We wanted something reusable that we could move from solution to solution without having to rewrite.

So in order to make it embedded, right click on the Hello.cshtml file and choose Properties. Change the Build Action to Embedded Resource.

 

image

 

Next, add the reference for the PortableAreas Project to the main Mvc project.

Now add a folder called Areas under the Mvc project. Open the folder Views under the Mvc project and copy the Web.config up to the Areas folder that you just created.

You should now look like this:

image

 

So, why are we adding the folder called Areas and a web.config into it? It is because when the dll gets put into the project, it will put your portable area into this folder underneath the covers. This is where it pretends it is and will look for the views and such inside of this folder.

 

Now, in the Index.cshtml view under Home, add the line at the bottom:

   1: @Html.Action("Hello", "World", new { area = "Demo"})

 

Set the PortableAreaDemo.Mvc as the Startup Project and Press “Start Debugging”, you should now see:

image

Hopefully you got it working. If not, go back through and check the steps, otherwise, you can comment here or catch me on twitter at @mike_d_moser and I can try and help you out. Thanks and happy coding.

  • http://RobSeder.wordpress.com/ RobSeder

    Mike,

    This seems very, very brittle – and not particularly easy to put together. Do you see any practical applications for this? Or, is this just a case where this is available “just because… why not”? Thanks

    -Rob

    • http://twitter.com/mike_d_moser Mike Moser

       Hi Rob, thanks for commenting. I have used portable areas in quite a few
      practical situations. It is something that you really have to decide
      for yourself whether it is worth the development time to use.

      I actually use portable areas in two of my products. I chose to use them
      because I have built engines for these products. Now I can take those
      engines and drop them anywhere, including into each other (yes, I
      actually drop one into another one). I also used portable areas for
      widgets. These are the more frequent seen reasons to use portable
      areas. 

      The downside to developing them this way is that you cannot use the
      traditional post backs. Pretty much any call you make needs to be using
      AJAX, because you have usually put this in some page. It isn’t hard
      really to develop this way, it is just a bit different thought process.

      The other downside is that you have to compile each time you make a
      small change to see it. Now, there are work-arounds to that. One fellow
      developer and I had a separate project where the portable area was the
      site. That said, it took quite a bit to get it to a good level where we
      could develop it in a place without it needing to compile and then still
      use that in the other sites where we were dropping it in.

      • Betty

        can you expand further on why you can’t use postbacks?

        • http://twitter.com/mike_d_moser Mike Moser

           Hey Betty. Sorry for the long delay. So here is a scenario where traditional Form Posts won’t work. Let’s say you have an Album Engine which might have a place to create an album, upload photos, etc… Let’s say you want to take that Album Engine and drop it into your site and make it look like part of your site. So, I would create a page in my main site and then just call Html.Action(“Create”, “Album”, …) in the page. This would then just be the Create album control that now looks like a page in the main site (Layout, style, etc…). Now let’s say you want to validate the album name and then when getting to the server, you also want to ensure the album name is unique.

          What would we do normally? We would probably do some client validation, then Model.IsValid when it gets to the server, then validate it against the database. If any of these don’t work from the server, we would probably return the Model back to the View with the errors on it, right? Well, with a portable area, when we return the view (the Create Album view), it is just a control without any styling, layout, etc… Therefore we would actually have to handle our post like this

          form.submit(function() {
           $.post(…, form.serialize(), function(response) { … })
          });

          and then handle the errors ourselves. If the page was good, then we would either need some return url plugged in or to now send it to some convention or function pointer.

  • Binhnd

    Thanks Mike,

    @Html.Action(“Hello”, “World”, new { area = “Demo”}) 
    if I leave this code you will encounter the following error
    The controller for path ‘/’ was not found or does not implement
    IController. Description:
    An unhandled exception occurred during the execution of the current web
    request. Please review the stack trace for more information about the error and
    where it originated in the code.

    Exception Details:
    System.Web.HttpException: The controller for path ‘/’ was not found or does
    not implement IController

    And,
    Edit: > @Html.Action(“Hello”, “World”, new { area = “”}) (remove Demo)
    run successfully!
    So I made a mistake at any stage? 
    Thanks so much!

    • http://twitter.com/mike_d_moser Mike Moser

      Hi Binhnd. Can you please post your code for your registration on the Portable Areas. Especially the constructor and the routes. I think the problem might be there. Thanks.

  • Binhnd

    If use “AreaRegistration.RegisterAllAreas();”
    @Html.EditorFor(model => model.Id)   > Error
    NullReferenceException

  • Sody

    Hi Mike,

    thank your for this article. This is exactly what I was searching for.
    I’ve got a simple question. How cann I use CSS class in that PortableArea project ? Let’s say…I will add a simple style /for instance font-bold/ to DIV with text HELLO WORLD und wannt to use it in  PortableAreaDemo.Mvc project.
    Is there any chance to define also CSS styles in that PortableArea project und use it ?

    Please advice.

    Thx
    Michal

    • http://twitter.com/mike_d_moser Mike Moser

      Hi Sody,

      So the simplest thing you could do is do this:

      1. Add this to your registration right below the scripts registration:

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

      2. Add a style sheet, I called mine demo.css with this in it:

      div.helloworld {
          font-weight: bold;
      }3. Mark that style sheet embedded resource, by going right clicking on the file and going to properties.4. Change the html to look like this in the view Hello.cshtml:
      @model dynamic

      Hello World    

      5. Build and run it. – Should see Hello World in bold
      Notice the link that references the stylesheet. This now has Demo in it. What we did was tell the portable area that whenever it sees the route with Content in it, use the embedded resource controller. The thing is, you might have image urls in the stylesheet. Make sure those images are embedded and the either exist in the images folder that already has a route set up for it or you set up another route for those images. Not bad, just always remember that whatever files like css, images, or javascript all have to be embedded. Then the EmbeddedResourceController (built into the MvcContrib) will take care of it from there.

      • Sody

         Many, many thanks !