I came across a situation where I wanted to use MvcContrib’s Pagination features.  My project consists of an AutoMapper Attribute that performs the actual mapping of my domain to its view model counterpart.

Given that my view is of type IEnumerable<TagDisplayModel>, and my controller passes LazyPagination<Tag> to View(), something like Html.Grid(Model).AutoGenerateColumns().Render() works as expected.  However, adding Html.Page((IPagination)Model) to our view fails.

AutoMapper is a delicate worker, doing only as it knows how to do.  It knows how to handle IEnumerable just fine, performing a mapping for each item in the enumeration.  However, it knows nothing about MvcContrib’s IPagination interface, nor how to map to a concrete one.  When we try to specify a mapping from IPagination<Tag> to IPagination<TagDisplayModel>, nothing seems to happen (or a type casting error… I forget).

Luckily, AutoMapper’s API provides a hook for such occasions.  Enter: ConstructUsing().  This method provides a hook for us to specify how it should resolve the destination class.  MvcContrib Pagination, by default, uses LazyPagination<T>.  It works, but the translation gets hairy if we try to keep up with it.  If the source does not execute the contained IQueryable and perform the actual pagination, it will be lost during the mapping process.  Instead, we can use CustomPagination, whose constructor takes an IEnumerable<T>, as well as paging information.

Because the actual code is too ugly (and the method signature too long to fit in this blog), I have put it up on CodePaste.NET. (BEWARE:  The resulting generic code is not for the faint of heart)

Using this extension method, create a map for your paginated types:

AutoMapper.Mapper

    .CreateMap<IPagination<Tag>, IPagination<TagDisplayModel>>()

    .ConstructPagination();

 

Then, the following action method will work as you would expect it to:

[AutoMap(typeof(IPagination<Tag>),typeof(IPagination<TagDisplayModel>))]

public ActionResult Index(int page = 1)

{

    var tags = _readonlySession.All<Tag>().AsPagination(page);

    return View(tags);

}

 

Thus, allowing you to call <%= Html.Pager((IPagination)Model) %> inside the view without error (also remember that your view is IEnumerable<TagDisplayModel>.

Tuesday, August 10, 2010 9:35:00 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
ASP.NET MVC | AutoMapper | MvcContrib

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

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