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

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