Getting a stack overflow when adding IMembershipService to constructor

Topics: Customizing Orchard
May 24, 2015 at 2:24 AM
Edited May 24, 2015 at 2:27 AM
Need some help here please. I'm trying to upgrade from Orchard 1.7.2 to 1.9 and have come across some code that did work, but is now breaking in 1.9.

My troubleshooting appears to lead me in the direction that adding an IMembershipService dependency to the FormsAuthenticationService is causing a stack overflow.

Here is what I've done to troubleshoot.

I've copied the code from the stock FormsAuthenticationService into my own class LmsFormsAuthenticationService and have suppressed it, like so.
    [OrchardSuppressDependency("Orchard.Security.Providers.FormsAuthenticationService")]
    public class LmsFormsAuthenticationService  : IAuthenticationService
    {

This is working fine with the following constructor
        public LmsFormsAuthenticationService(ShellSettings settings, IClock clock, IContentManager contentManager, IHttpContextAccessor httpContextAccessor) {

However, if I add code to bring in the IMembershipService, it starts giving me stack overflows.
        public LmsFormsAuthenticationService(ShellSettings settings, IClock clock, IContentManager contentManager, IHttpContextAccessor httpContextAccessor, IMembershipService membershipService) {
I'm not exactly sure how to troubleshoot this.

At the end of the day, I need to be able to create new membership accounts from my new Forms Authentication Service, and the only way I can think to do that is to use the MembershipService. Is there some other way to get a handle on the MembershipService without including it in the constructor? I tried using a public property like I've seen for the ILogger(), but that didn't seem to work.

Would appreciate any help possible.

Here is the repeating loop that's causing the stack overflow.
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextImplementation.WorkContextImplementation(Autofac.IComponentContext componentContext) Line 15   C#
Orchard.Framework.dll!Orchard.Environment.WorkContextModule.Load.AnonymousMethod__0(Autofac.IComponentContext ctx) Line 19  C#
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.HttpContextScopeImplementation.HttpContextScopeImplementation(System.Collections.Generic.IEnumerable<Orchard.Environment.IWorkContextEvents> events, Autofac.ILifetimeScope lifetimeScope, System.Web.HttpContextBase httpContext, object workContextKey) Line 83 C#
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.CreateWorkContextScope(System.Web.HttpContextBase httpContext) Line 48    C#
Orchard.Framework.dll!Orchard.Mvc.MvcModule.HttpContextBaseFactory(Autofac.IComponentContext context) Line 54   C#
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextImplementation.WorkContextImplementation(Autofac.IComponentContext componentContext) Line 15   C#
Orchard.Framework.dll!Orchard.Environment.WorkContextModule.Load.AnonymousMethod__0(Autofac.IComponentContext ctx) Line 19  C#
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.HttpContextScopeImplementation.HttpContextScopeImplementation(System.Collections.Generic.IEnumerable<Orchard.Environment.IWorkContextEvents> events, Autofac.ILifetimeScope lifetimeScope, System.Web.HttpContextBase httpContext, object workContextKey) Line 83 C#
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.CreateWorkContextScope(System.Web.HttpContextBase httpContext) Line 48    C#
Orchard.Framework.dll!Orchard.Mvc.MvcModule.HttpContextBaseFactory(Autofac.IComponentContext context) Line 54   C#
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextImplementation.WorkContextImplementation(Autofac.IComponentContext componentContext) Line 15   C#
Orchard.Framework.dll!Orchard.Environment.WorkContextModule.Load.AnonymousMethod__0(Autofac.IComponentContext ctx) Line 19  C#
[External Code] 
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.HttpContextScopeImplementation.HttpContextScopeImplementation(System.Collections.Generic.IEnumerable<Orchard.Environment.IWorkContextEvents> events, Autofac.ILifetimeScope lifetimeScope, System.Web.HttpContextBase httpContext, object workContextKey) Line 83 C#
Orchard.Framework.dll!Orchard.Environment.WorkContextAccessor.CreateWorkContextScope(System.Web.HttpContextBase httpContext) Line 48    C#
Orchard.Framework.dll!Orchard.Mvc.MvcModule.HttpContextBaseFactory(Autofac.IComponentContext context) Line 54   C#
Developer
May 24, 2015 at 10:44 AM
Edited May 24, 2015 at 10:45 AM
Looks like you got a circular dependency going on that causes the SO exception. At the same time, you seem to be violating the single responsibility principle, since the authentication service's intended purpose I believe is to authenticate users, not create them. However, if you think it's justified, you can probably work around the circular dependency issue by injecting a Lazy<IMembershipService>, so that an actual instance is only resolved the first time it's actually used, giving the IoC container a chance to register all components first. But unless I'm confusing the role of the authentication service, I'd advise against creating users from it.
May 24, 2015 at 3:51 PM
Thanks. Lazy loading it may have solved the immediate problem. At least the SO went away.

Let me take step back and explain my scenario and see if some of your Orchard heavies have a better way than my hackish implementation.

For my Orchard, I have two user stores. The first is the Orchard one, which basically contains just the Admin and a few Content Editor users. The 2nd is an external system that the customers authenticate against. When an existing customer on the external system first comes to my website, there will not be an orchard account for them. I do not, however, want to prompt them to sign up since we are using their credentials and user data from the external system. So when they first sign in, I check their credentials against the Orchard user store (same functionality as FormsAuthentication). If no user is found, however, I need to authenticate them against the external store. If they authenticate, then I seem to remember that I needed to create then an actual Orchard account due to the way orchard authentication and authorization works. So what I ended up having to do was create the account in the GetAuthenticatedUser() call. There is one added wrinkle, in that externally authenticated users have their own version of the IUser record, which has some meta data retrieved from the external site. So I'm having to save this new user record into the WorkContextAccessor.GetContext().CurrentUser for that information to be seen in other pages.

So is there a better way to authenticate against two user stores, and have different IUser records depending on the user store I authenticate against? Or is there a way to tell Orchard to get User information from a different source than the Orchard database? Would I need to also implement my own version of IMembershipService and tweak the GetUser() or ValidateUser() methods? Now that I look at it, it seems like the logical place...