Tuesday, 20 October 2009

Wiring Up The Membership Provider With StructureMap

I spent yesterday retrofitting into an ASP.Net MVC site that I wrote earlier in the year. I ran into a number of minor issues that made me stop and think, and decided to post them here for future reference.

I had already added a constructor to each of my controller classes that allowed services to be passed in as parameters (as it improves testability), so was pretty well prepared for letting StructureMap AutoWire the services as needed.

The site consumed implementations of three interfaces I had defined:

  • IHoldSettingsForTheSite
  • ILogEvents
  • IAmTheSiteRepository

I configured default instances of the implementations in the BootStrapStructureMap procedure which I called from the Application_Start method in the Global.asax:

   1: private void BootStrapStructureMap()
   2:         {          
   3:             ObjectFactory.Initialize(rep =>
   4:             {
   5:                 rep.ForRequestedType<IHoldSettingsForTheSite>().TheDefaultIsConcreteType<Settings>();
   6:                 rep.ForRequestedType<ILogEvents>().TheDefault.Is.OfConcreteType<EventLogging>().WithCtorArg("sourceName").EqualToAppSetting("ApplicationName");
   7:                 rep.ForRequestedType<IAmTheSiteRepository>().TheDefault.Is.OfConcreteType<Repository>().WithCtorArg("connectionString").EqualTo(ConfigurationManager.ConnectionStrings["OpenFields.MainSite.Data"].ConnectionString);
   8:             });            
   9:         }
Good times… all was fine until I tried to call a method on a controller that required authentication.The following exception was raised:
StructureMap Exception Code:  202
No Default Instance defined for PluginFamily AssenblyName.Controllers.IFormsAuthentication, AssenblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Where AssemblyName is the name of my applications assembly.

I’d originally built the site using the “out of the box” AccountController class which is added by default in the ASP.Net MVC project template. This class calls a MembershipProvider object to provide for account authentication. I’d configured the application to use the ActiveDirectoryMembershipProvider in production and I wired up a dummy MembershipProvider class for use during development and testing.

Within the AccountController.cs file there are a number of other objects declared:

  • IMembershipService
  • IFormsAuthentication
  • AccountMembershipService (Implements IMembershipService)
  • FormsAuthenticationService (Implements IFormsAuthentication)
Personally I think its a bit smelly holding them all in one file but they’re helper objects built to aid testability of the AccountController class and won’t be used anywhere else. I configured default instances of these interfaces and pointed them to these to the Services objects that implemented them giving me this:
   1: rep.ForRequestedType<IFormsAuthentication>().TheDefaultIsConcreteType<FormsAuthenticationService>();
   2: rep.ForRequestedType<IMembershipService>().TheDefaultIsConcreteType<AccountMembershipService>();
When I next ran the app, the following exception was then raised:

StructureMap Exception Code:  202
No Default Instance defined for PluginFamily System.Web.Security.MembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
The cause of this was from not declaring an instance of a MembershipProvider model. This really threw me initially as I knew that the the wiring up of the MembershipProvider was handled by the membership section of the web.config file:
   1: <membership defaultProvider="DUMMY">
   2:   <providers>
   3:     <clear />
   4:     <add name="DUMMY" type="OpenFields.Dummy.MembershipProvider, OpenFields.Dummy" />
   5:     <add name="AD" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="ADConnectionString" />        
   6:   </providers>
   7: </membership>
On further investigation, I soon realised that although the membership provider wiring is still handled by this config section and that the MembershipProvider is an abstract class, StructureMap still injects the implementation to use. (OMG this is powerful!) Basically, we’ve got to tell StructureMap to use the default membership provider for the application. This can be easily achieved by adding the following line into my BootStrapStructureMap method:
  rep.ForRequestedType<MembershipProvider>().TheDefault.IsThis(Membership.Provider);
I couldn’t find many posts on this scenario so I hope this helps anyone in this same predicament!

6 comments:

  1. This would be little tricky to do in web.config?

    ReplyDelete
  2. Thanks!
    Of course, now with the latest version, the syntax should be:
    x.For().Use(Membership.Provider);

    ReplyDelete
  3. No worries Jason. I'll update this post for the latest version of Structure Map this week! :-)

    ReplyDelete
  4. I would say "this is article is life saver"

    ReplyDelete
  5. this article helped me. Thank you

    ReplyDelete
  6. I have been trying to get this set up work but have some really strange problems! When ever the user is updated via the repository, a call to the dbcontext just throws System.ObjectDisposedException! The update does wrap the dbcontext object in a using statement but this is standard practice isn't it? Using entity framework by the way.

    ReplyDelete