This is something I toyed with a couple of months ago as a proof of concept.  I hope that some aspect of this feature makes its way into the core of ASP.NET MVC.  It rose from experimenting with some ideas for data driven portable areas, and wanting to leverage an IoC container of my own choosing (independent of the consuming application).  I wanted to prove that this was possible in ASP.NET MVC 2.  The code for this lives in an MvcContrib fork.

The idea is simple: a new factory called AreaControllerFactory.  It inherits from DefaultControllerFactory, which remains the fallback means of producing a Controller.

public class AreaControllerFactory : DefaultControllerFactory

{

    private IControllerFactoryRegistry _registry;

 

    public AreaControllerFactory(IControllerFactoryRegistry registry)

    {

        _registry = registry;

    }

 

    public override IController CreateController(RequestContext requestContext, string controllerName)

    {

        string areaName = ((Route)requestContext.RouteData.Route).DataTokens["area"] as string;

 

        IControllerFactory factory = _registry.FactoryFor(areaName);

 

        if (factory == null)

            return base.CreateController(requestContext, controllerName);

 

        return factory.CreateController(requestContext, controllerName);

    }

}

 

The AreaControllerFactory depends on an IControllerFactoryRegistry object to manage the controller factories.  The FactoryFor() method returns a ControllerFactory that has been registered for a specific area using RouteData.

The IControllerFactoryRegistry interface looks like this:

public interface IControllerFactoryRegistry

{

    void Register(string areaName, IControllerFactory factory);

    void Default(IControllerFactory factory);

    IControllerFactory FactoryFor(string areaName);

}

 

I have created a default implementation of this interface that uses a simple dictionary internally.  That code isn’t particularly interesting.  Up to now this is pretty intuitive, but I think the real slick part is how everything comes together with the ControllerFactoryRegistrar class.  It is a singleton wrapper for an IControllerFactoryRegistry object.

Right now, the Area Controller Factories get set up by calling ControllerFactoryRegistrar.BootStrap().  It takes the current ControllerFactory and uses it as a default option.  Then, it builds up a registry for all of the PortableAreas in the AppDomain.  A new AreaControllerFactory is created with the registry, and set as the new ControllerFactory.  Now, the creation of a controller will depend on what area it belongs to.

Non-portable areas can be registered with individual controller factories as well.  Just call ControllerFactoryRegistrar.Register(areaName, factory).

All of this currently fits into MVC 2.  With Area Controller Factories, Portable Areas have a new option for managing dependencies within its controllers.

Thursday, July 29, 2010 8:06:27 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1] -


Every so often, I like to take a moment to reflect on my own stupidity.  Up until last night, I was running my blog on an old machine running Windows XP SP3.  Was I aware of the impending doom?  Absolutely.  Did I do anything about it?  No, but I certainly thought about it.  “I should back up my blog one of these days.”

What’s wrong with hosting anything in this manner?  Everything.  Thunder storm?  BAM! Power outage = downtime.  Surge protection: none.  This gets better with every detail.  Max concurrent users?  Not enough.  UPS?  Non-existent.  Level of amateurism: max.

I think it’s funny that I got away with it for so long.  I was very lucky to be able to harvest some data off of that machine, and restore my blog to a newer, safer place.  From now on, my goal will be to not end up like this guy or this guy.

Saturday, June 12, 2010 11:39:29 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -


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

OpenIdPortableArea is a pluggable component that enables support for OpenId.  This post is a short walkthrough for implementing OpenId in a sample application.

Step 1:  Ducks all in a row

First, download and unzip the latest release (0.1.0.3 as of today, found at http://openidportablearea.codeplex.com/).

image

Open Visual Studio and create a new ASP.NET MVC 2 project.  In the Solution Explorer, right click on the project and go to Add, New Folder and call it “lib” (the name doesn’t really matter, just what I usually use).  Copy all of the .dll and .xml files from the files you extracted, and paste them into the new folder in your project.  Then, add a reference for DotNetOpenAuth.dll, MvcContrib.dll, and OpenIdPortableArea.dll.

 image

image

Next, create an Areas folder in your solution and copy the Views\Web.config into it.  This is important because without it, you’ll get a YSOD.  If you forget this and try to run the application it will throw a clearly worded exception during app start.

image

Up to this point, the portable area gets hooked up automatically as long as your Application_Start calls AreaRegistration.RegisterAllAreas().

Step 2:  The Old Switcheroo

If you run the application, you’ll notice that the [ Log On ] link still sends you to ~/Account/LogOn.

image

The OpenIdPortableArea comes with its own login status widget.  To substitute that behavior, open Site.master and add <%@ Import Namespace="OpenIdPortableArea.UI" %> to the top of the page, and then add <%= Html.LoginStatusWidget() %> where the RenderPartial(“LogonUserControl”) was.

image

Now, when you run the application you should be able to click that link and go to ~/OpenId.

image

Oh yeah!  Don’t forget to change your web.config to <forms loginUrl="~/OpenId" timeout="2880"/>.  That way, when you use [Authorize] it will redirect to the correct Url.

Step 3:  Know Thy User

If you enter an OpenId and click “Login” you will be sent to that OpenId provider to authenticate with them.  The provider then redirects you back to the site, and you arrive on ~/OpenId/Success.  If you were already authenticated with the provider, they might have automatically redirected you (i.e. you may have gone from the Login page to the Success page).

When the provider sends the user back to the site, the portable area sends an AuthenticatedMessage through MvcContrib’s Bus.  It contains information about the authenticated user, and allows you the opportunity to handle it explicitly.  To handle this message, create a class that inherits from MessageHandler<AuthenticatedMessage>.  In this class, override the Handle() method.  This is where you can employ your own domain to associate your user with their OpenId.  There are two simple scenarios to code for.  You already know the user, or you don’t.

using System;

using System.Web;

using MvcContrib.PortableAreas;

using OpenIdPortableArea.Helpers;

using OpenIdPortableArea.Messages;

using SampleApplication.Models;

 

namespace SampleApplication.Services.PortableAreaMessageHandlers

{

    public class AuthenticatedMessageHandler

        : MessageHandler<AuthenticatedMessage>

    {

        UserRepository _userRepository = new UserRepository();

 

        public override void Handle(AuthenticatedMessage message)

        {

            User user = _userRepository

                .FindByOpenId(message.ClaimedIdentifier);

 

            if (user != null)

            {

                OpenIdHelpers.Login(user.Name,

                    user.Email,

                    new TimeSpan(0, 5, 0),

                    true);

            }

            else

            {

                user = new User

                {

                    OpenId = message.ClaimedIdentifier,

                    Email = message.ClaimsResponse.Email,

                    Name = message.ClaimsResponse.FullName

                };

 

                HttpContext.Current.Session.Add(

                    "user",

                    user);

 

                message.ReturnUrl = "~/Home/Register";

            }

        }

    }

}


In this example, I use a UserRepository* to try and find a user by the ClaimedIdentifier (their OpenId).  If a user is found, the OpenIdHelpers class provides a simpler call for issuing a FormsAuthenticationTicket either by passing an instance of FormsAuthenticationTicket, or passing a few parameters.  If a user wasn’t found, a new one gets created and stuffed into session until the user gets redirected.  The ReturnUrl is also set to ~/Home/Register.  More on that later (Step 4).

*By the way, I cheated and hard coded this to return a user with my own OpenId.  In reality, your UserRepository will really be able to query the database for a user with the matching OpenId.

Notice the message has a ClaimsResponse property containing information about the user.  Yup, you have (some) control over that, as well.  It, too, is a message waiting to be handled.  This time, it’s a ClaimsRequestMessage.  Here’s an example:

using OpenIdPortableArea.Messages;

using MvcContrib.PortableAreas;

using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;

 

namespace SampleApplication.Services.PortableAreaMessageHandlers

{

    public class ClaimsRequestMessageHandler

        : MessageHandler<ClaimsRequestMessage>

    {

        public override void Handle(ClaimsRequestMessage message)

        {

            message.Claim.Email = DemandLevel.Require;

            message.Claim.FullName = DemandLevel.Require;

        }

    }

}


It's pretty straight forward. Set each member to the desired DemandLevel.  Now, whenever the OpenId Provider redirects the authenticated user back to the site, hopefully these demands will be honored and you can pre-populate your new user’s information.

In order to make ClaimsRequests work, you must also add the following settings to your ~/Web.config (otherwise message.Claim will be null):

<configuration>
   <configSections>
      <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection" requirePermission="false" allowLocation="true"/>
   </configSections>
   <dotNetOpenAuth>
      <openid>
         <relyingParty>
            <behaviors>
               <!-- The following OPTIONAL behavior allows RPs to use SREG only, but be compatible
                    with OPs that use Attribute Exchange (in various formats). -->
               <add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" />
            </behaviors>
         </relyingParty>
      </openid>
   </dotNetOpenAuth>
</configuration>
 


*If you don’t care about the ClaimsRequest, and aren’t using ClaimsResponse in the message handler for AuthenticatedMessage, then this setting shouldn’t matter.

WAIT!  Creating the message handlers is great, but the MvcContrib.Bus needs to know about these classes.  Add the following lines to Application_Start() in Global.asax.cs:

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

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

 

Step 4:  Paperwork

In the future, I may try to alleviate this implementation burden in OpenIdPortableArea.  But for now, you’ve got to do it (cheers!).  Relax, it isn’t so bad.

Create a Register() method on your HomeController, and create a view for it.  Make the view strongly typed for (at least) your User.  Then, use the templated helper Html.EditorForModel() to get the form on the page.

HomeController:

private UserRepository _userRepository = new UserRepository();

 

[HttpGet]

public ActionResult Register()

{

    User user = (User)Session["user"];

    return View(user);

}

 

[HttpPost]

public ActionResult Register(User user)

{

    bool exists = _userRepository.FindAll().Any(u => u.Name == user.Name);

    if (exists)

    {

        ModelState.AddModelError("", "User already exists with that name.");

        return View(user);

    }

 

    if (ModelState.IsValid)

    {

        _userRepository.Add(user);

        _userRepository.Save();

 

        // Issue forms authentication ticket.

        OpenIdHelpers.Login(user.Name, string.Empty, new TimeSpan(5, 0, 0, 0), false);

 

        return RedirectToAction("Index", "Home");

    }

    else

    {

        return View(user);

    }

}


Views/Home/Register.aspx:

image

The idea is pretty simple.  Show the user their information (filling in as much as possible via ClaimsResponse), and they click the Register button.  On the POST, perform validation on the user.  If everything checks out, add the user to the repository and authenticate them.  Lastly, redirect to wherever you please (or… ~/Home/Index).

Summary

Now, if you run your sample application you should be able to login with OpenId freely.

image

The [ Logout ] link at the top points to ~/OpenId/Logout.  By default, the portable area will handle this nicely for you.  However, if you wish to intervene there are two messages LoggingOutMessage and LoggedOutMessage.  They’ll be a topic for another post.

Here’s the short To Do list for implementing this portable area:

  1. Add Files & References
  2. Add LoginStatusWidget to MasterPage
  3. Change web.config forms loginurl to ~/OpenId
  4. Create a MessageHandler<> for AuthenticatedMessage
  5. Add the message handler to MvcContrib.Bus
  6. Add User Registration functionality
Wednesday, April 28, 2010 1:27:06 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
MvcContrib | OpenId | OpenIdPortableArea

I added the new build of MvcContrib to OpenIdPortableArea today, and committed the new embedded resource routes.  It worked great, and I’m working on some other things with the portable area.

I noticed that the newly embedded images started displaying slightly slower than before.  Maybe it’s all in my head, but it got me thinking about caching nonetheless.  The embedded JavaScript, images, and CSS files are never going to change.  As implemented right now, each request for embedded content requires that the mime type be determined, then locate the resource in the assembly, and then returns a FilePathResult() from the stream of the extracted resource.  With caching, we can prevent this operation from happening on every request.

After thinking of this, I quickly pitched it to Steve Michelotti.  I suggested the possiblity of including [OutputCache(CacheProfile="EmbeddedResource")] on the EmbeddedResourceController’s Index action.  He thought this was an interesting approach, because it leaves the actual cache settings up to the programmer using this particular feature.

So, as with any good idea I gave it a shot.  I found that it worked great, but there was a catch.  Using the OutputCache.CacheProfile property means you absolutely must provide that cache profile in the web.config.  You get a yellow screen of death without it.  With this in mind, adding such a cache profile requirement is just one more mandatory configuration to remember.  How can we make it just work.  Maybe the programmer using this code has some insane reason not to cache these resources.  Whatever the case may be, it would be nice to allow the consumer to opt into the caching mechanism and allow everything to work without it.

First Idea

I quickly created a class called OptionalOutputCacheAttribute, which inherited directly from System.Web.Mvc.OutputCacheAttribute.  The OutputCacheAttribute class uses OnResultExecuting() to serve the cached version of the page.  My plan was to check and see if that particular cache profile exists, and bail if it doesn’t.  Sounds good in theory, but fails in practice.  Why, you say?  Because the OutputCacheAttribute.CacheSettings property is marked internal.  Crap.  So much for that idea.

EDIT: On second thought, this is probably not the real reason. The OutputCacheAttribute doesn't expose its internal OutputCacheParameters object, nor does it expose an Enabled property. It is more likely that ASP.NET deals with caching a little deeper than we can get to, and I suspect it just doesn't support the idea of "maybe cache...". If the OutputCache attribute exists, then cache it. Period.

Second Idea

I have a backup plan, because there just has to be a way to make this work.  Keeping the OptionalOutputCacheAttribute class, what else is there to work with?  I found a way to make it work, but some might find it… unsavory.

The attribute takes a moment and inspects the application’s web.config.  Here’s the code:

/// <summary>

///     Provides optional chaching

/// </summary>

public class OptionalOutputCacheAttribute : OutputCacheAttribute

{

    /// <summary>

    ///     Contains a record of what CacheProfiles have been looked for

    /// </summary>

    protected static Dictionary<string, bool> CacheProfiles { get; set; }

 

    /// <summary>

    ///     Constructor

    /// </summary>

    public OptionalOutputCacheAttribute()

    {

        CacheProfiles = new Dictionary<string, bool>();

    }

 

    /// <summary>

    ///     Determines if the target CacheProfile exists before trying

    ///     to handle the caching of the page.

    /// </summary>

    /// <param name="filterContext"></param>

    public override void OnResultExecuting(ResultExecutingContext filterContext)

    {

        if (!CacheProfiles.Keys.Contains(CacheProfile))

        {

            XDocument xdoc =

                XDocument.Load(AppDomain.CurrentDomain.BaseDirectory + "\\web.config");

 

            var profileExists =

                xdoc.Descendants("outputCacheProfiles")

                    .Descendants(XName.Get("add"))

                    .Any(node => node.Attribute(XName.Get("name")).Value == CacheProfile);

 

            CacheProfiles.Add(CacheProfile, profileExists);

        }

 

        if (CacheProfiles[CacheProfile])

            base.OnResultExecuting(filterContext);

    }

}


Using Linq to Xml, it’s fairly trivial to parse the web.config.  It loads perfectly into an XDocument.  Then, it’s easy to see if the “outputCacheProfiles” node has any “add” nodes with a “name” attribute equivalent to the name of the cache profile.

As a way of reducing the frequency of web.config parses, the attribute keeps a static Dictionary.  The first thing to check is if this particular cache profile has been looked up.  If it hasn’t, we need to load the web.config and do the search.  The result is added to the dictionary for later use.

After all of this parsing nonsense, we index into the dictionary and use the bool value.  If the dictionary says that the cache profile exists, we allow the base class (System.Web.Mvc.OutputCacheAttribute) to execute its OnResultExecuting() method.

The Outcome

Using this new OptionalOutputCacheAttribute, we can decorate the EmbeddedResourceController with it:

[OptionalOutputCache(CacheProfile="EmbeddedResource")]

public ActionResult Index(string resourceName, string resourcePath)

{

    // ....

}


My initial tests for this code have proved successful.  What does everyone else think of this?  I was a little iffy on the whole web.config parsing thing (still am).  However, I think this point of extensibility or configurability is more favorable than either A) not providing any support for caching embedded resources, or B) arbitrarily determining cache settings.

Thursday, April 15, 2010 12:48:35 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
ASP.NET MVC | Caching | MvcContrib | OpenIdPortableArea

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

While working with areas I found that there was some occasional unexpected behavior with the order that routes get mapped.  Calling AreaRegistration.RegisterAllAreas() doesn’t give you any control over which area gets registered first.  If this becomes an issue, you may be so inclined to individually register routes.  Here is an approach:

public static void RegisterAreas(RouteCollection routes)

{

    routes.RegisterArea<BlogAreaRegistration>();

}

 

protected void Application_Start()

{

    //AreaRegistration.RegisterAllAreas();

    RegisterAreas(RouteTable.Routes);

    RegisterRoutes(RouteTable.Routes);

}


I created a RegisterAreas method just like RegisterRoutes.  Here, I can clearly register each Area one by one in the order that I choose.  This is done via an extension method on the RoutesCollection type.

public static class Extensions

{

    public static void RegisterArea<T>(this RouteCollection routes)

        where T : AreaRegistration

    {

        // instantiate the area registration

        AreaRegistration area = Activator.CreateInstance<T>();

 

        // create a context, which is just the name and routes collection

        AreaRegistrationContext context =

            new AreaRegistrationContext(area.AreaName, routes);

 

        // register it!

        area.RegisterArea(context);

    }

}


This extention method creates an instance of the AreaRegistration, and then creates an AreaRegistrationContext, and then calls RegisterArea on the area.

I find that this approach demystifies RegisterAllAreas and focuses on intention over convenience.  Registering areas individually is not always necessary, but it can be helpful to reduce routing confusion.

Wednesday, March 31, 2010 9:55:01 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] -
Areas | ASP.NET MVC | Routing

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: 39
This Year: 15
This Month: 0
This Week: 0
Comments: 4