I got some great feedback on mapping PortableAreas to MasterPages and ContentPlaceHolders:

“Wouldn't this need to be repeated for every PA that was used by the host? Could this be changed to just globally map "PageTitle" to Title, "BodyContent" to Content, etc.?” – Steve Michelotti

Yeah, he’s right.  Seems ridiculous to have to map each area individually when they’ll all be using the same configuration.  Enter MapAll():

PortableAreaContent.MapAll()

    .Master("~/Views/Shared/PA.master")

    .Title("PageTitle")

    .Body("BodyContent");

 

PortableAreaContent.Map<AnotherPortableAreaMap>()

    .JavaScript("ScriptContent");

 

AreaRegistration.RegisterAllAreas();


You’ll notice that I changed “Content” to “PortableAreaContent” in the above snippet.  I did that because I thought that “Content” was too vague, and that the new name expressed more intent.

The MapAll() method basically works the same as the Map<>() method, except that it sets the default values for all other PortableAreaMaps.  The Map<>() method is still available, and in the above snippet it is used to configure a special mapping for a JavaScript ContentPlaceHolder. 

Adding a way to globally set MasterPages and ContentPlaceHolders further simplified the mapping scenarios, while still leaving it robust for slightly more complicated ones.

Sunday, May 23, 2010 6:11:49 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
MvcContrib | Portable Areas

Some of the most difficult aspects of consuming PortableAreas (PAs) are MasterPages and ContentPlaceHolders.  The embedded views of a PA are hard coded to use Site.Master, which may be inconsistent with the host application.  It works, but it can be much better.  After some discussion on twitter with Eric Hexter and Steve Michelotti, I had an idea for solving the problem.  I’ll soon submit a pull request.

Host Application – Wiring Things Up

In your application, you surely have your own MasterPage.  That MasterPage has ContentPlaceHolders, probably with IDs that conform to the coding standards of your particular project.  The goal is to use a PA, and make it feel like a seamless part of your application.  To do that, you need some way to tie everything together.

Hypothetically, you wish to utilize “SomePortableArea” with your application.  Inside of your Global.asax.cs, during Application_Start() you can map the PA to your MasterPage and its ContentPlaceHolders like this:

Content.Map<SomePortableAreaMap>()

    .Master("~/Views/Shared/MyApplication.master")

    .Title("PageTitle")

    .Body("BodyContent");

 

AreaRegistration.RegisterAllAreas();


The Content static class has a Map<>() method that returns an instance for the given map type.  The type you provide depends on the PA you’re working with.  The imaginary SomePortableArea.dll has a class called SomePortableAreaMap.  Thus, the example calls Map<SomePortableAreaMap>() to get the map.

PortableAreaMap has three extension methods:

Master() - Tells the PortableArea what MasterPage to use.
Title() - Tells the PortableArea what the Title ContentPlaceHolderID is in the MasterPage.
Body() - Tells the PortableArea what the Body ContentPlaceHolderID is in the MasterPage.


*In order for the mappings to work, they must be done before the areas are registered.

Here is a look at the markup in “MyApplication.Master”:

image

Creating PortableAreas – Providing a Map Class

As the author of a PortableArea, now you can feel confident that people won’t send nasty grams about how your PA ruined their conventions!  Even more good news: it’s not that difficult for you to implement.  Imagine that you’re the author of “SomePortableArea.”  You would define a class like this:

public class SomePortableAreaMap : PortableAreaMap

{

    public SomePortableAreaMap()

    {

        DefaultMasterPageLocation = "~/Views/Shared/Site.Master";

        DefaultBodyID = "MainContent";

        DefaultTitleID = "TitleContent";

    }

}


Inherit from PortableAreaMap, and provide some default values in the constructor.  These default values come from the MasterPage that you have in your project (mostly for intellisense & View generation reasons):

image

There is just one part left, and that is to override a new method in SomePortableAreaRegistration:

public class SomePortableAreaRegistration : PortableAreaRegistration

{

    // ... other code

 

    public override PortableAreaMap GetMap()

    {

        return Content.Map<SomePortableAreaMap>();

    }

 

    // ... other code

}


This just grabs the Map for your portable area, using the SomePortableArea type defined earlier.

Extensibility

One way that you might be able to extend the portable area is to use another <asp:Content> tag in your views, perhaps ContentPlaceHolderID="JavaScriptContent" to make sure all of your area’s JavaScript gets rendered to the bottom of the page.  You could then add a method to SomePortableAreaMap, allowing the host to map it to one of their ContentPlaceHolders:

public SomePortableAreaMap JavaScript(string name)

{

    Add("JavaScriptContent", name);

    return this;

}


Summary

Host applications are able to configure a PA to use their MasterPages by calling chainable methods.

PortableAreas provide a PortableAreaMap, making the former possible.  By providing a map class, the PA can more easily support another ContentPlaceHolder (i.e. for JavaScript).  Then, the consuming application can create a special MasterPage, or nest MasterPages to accommodate the PA, all the while retaining their freedom of conventions.

Hopefully this is easy to understand.  The steps seem fairly intuitive, and simple to me (but then again, I’m biased… after all, I wrote it!).  What do you think?

Friday, May 21, 2010 12:27:58 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
MvcContrib | Portable Areas

I recently spiked out a new feature for MvcContrib PortableAreas to automatically wire up IMessageHandlers.  It’s a small feature, but I think it makes PortableAreas just a little easier.  Previously, you would have had to manually wire up each individual handler in the Global.asax.cs like this:

MvcContrib.Bus.AddMessageHandler(typeof(AuthenticatedMessageHandler));


Now, that won’t be necessary.  By virtue of descending from MessageHandler<>, the class will be registered automatically.

Enjoy!

Tuesday, May 18, 2010 11:19:55 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
MvcContrib | Portable Areas

One of the hokey things about the OpenIdPortableArea is that the ProvidersWidget contains some hard coded CSS, JavaScript, and Urls to images on the Internet.  Steve Michelotti pushed a change to MvcContrib that provides a way to resolve embedded static resources.  Since the OpenIdPortableArea project could really use this feature, I eagerly downloaded it and tried it out.  Here are my thoughts…

Getting the EmbeddedResourceController hooked up isn’t terrible.  First I compiled the MvcContrib source, and then dropped in & re-added a reference to the MvcContrib.dll.  Then, in my PortableArea project I added the following route to the OpenIdAreaRegistration:

context.MapRoute(

    "OpenId-EmbeddedResource",

    "OpenId/Content/{resourceName}",

    new { controller = "EmbeddedResource", action = "Index" },

    null,

    new string[]{ "MvcContrib.PortableAreas" }

);


One of the tricky things to remember is to include the namespace parameter of “MvcContrib.PortableAreas” while mapping the route.  Without this, the routing engine won’t find the controller properly.

After wiring up this route I added a providers.css file and embedded it.

image

Then, I updated the reference in the ProvidersWidget:

<link href="/OpenId/Content/content.styles.providers.css" rel="stylesheet" type="text/css" />


This works!  However, you may have noticed that the Url for the CSS file includes “content.styles.providers.css”.  This is due to the way MvcContrib currently resolves static resources.  All embedded resources are registered by their namespace, or location within the project.  While this isn’t a big deal, it can be somewhat limiting.  I would like to have a little more control over what the route to this embedded resource will look like, but what other options are there?

If I were to choose a route for the OpenId providers.css file, I think it would look something like:

<link href="/OpenId/Styles/providers.css" rel="stylesheet" type="text/css" />


In order to use a Url like that, the providers.css file would have to be located under OpenIdPortableArea.OpenId.  I might have lots of resources, though.  It would be tidier to keep them in their own folder.  Time to be creative.

The EmbeddedResourceController has an Index method that takes a string parameter called “resourceName”.  This parameter is eventually used to resolve the embedded resource based on its namespace.  Unfortunately, there’s no way to help MvcContrib find the correct namespace for the desired resource through routing alone.  To solve this problem, I used simple inheritance.

public class StylesController : EmbeddedResourceController

{

    public ActionResult Resource(string resourceName, string resourceLocation)

    {

        return Index(string.Format("{0}.{1}", resourceLocation, resourceName));

    }

}


I created a new Controller called Styles, and inherited from EmbeddedResourceController.  It has one action method called Resource.  The method takes two parameters: “resourceName” and “resourceLocation”.  It uses the parameters to build the resource’s actual name.  Inheriting from EmbeddedResourceController buys us the ability to call the Index action with the proper resource name.

Finally, a new route can be implemented (which is actually simpler):

context.MapRoute(

    "OpenId-StylesResource",

    "OpenId/Styles/{resourceName}",

    new

    {

        controller = "Styles",

        action = "Resource",

        resourceLocation = "Content.Styles"

    }

);


This new controller gives us the opportunity to adjust the resource name, as well as the freedom to organize embedded static resources the way we want inside of our portable areas.  Perhaps the EmbeddedResourceController could be expanded to incorporate this point of extensibility.

Tuesday, April 13, 2010 12:58:40 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
Embedded Resources | MvcContrib | OpenIdPortableArea | Portable Areas

I’m proud to announce my official entrance to the world of Open Source projects.  I just published OpenIdPortableArea on CodePlex.  It is a pluggable component for ASP.NET MVC that takes advantage of MvcContrib’s Portable Areas.  With relatively few requirements, you are able to add basic OpenId support to your applications.  For details on how to consume this Portable Area, check out the documentation I slaved over!

Proudly, this project is using mercurial.  It includes one sample project to date.  Please, take a few minutes to check it out and provide feedback.

CodePlex

DotNetOpenAuth

Thursday, April 08, 2010 10:48:12 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
CodePlex | Mercurial | MvcContrib | OpenId | OpenIdPortableArea | Portable Areas

John Nelson

mugshot I am a passionate C# Developer working in ASP.NET on an e-commerce solution for ticketing software. I work across all of the application layers, including server side functionality, and client side programming with jQuery and MS Ajax. Although my full time job is in WebForms, I spend many of my off hours working with MVC. I am especially interested in productivity and good programming practices.

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
johncoder.com
Statistics
Total Posts: 41
This Year: 17
This Month: 0
This Week: 0
Comments: 4