Membership reboot replace Ninject with Simple Injector

643 Views Asked by At

I need add membership reboot (RavenDb) into the project that use IOC Simple Injector

Ninject implementation

var config = MembershipRebootConfig.Create();
kernel.Bind<MembershipRebootConfiguration<HierarchicalUserAccount>>().ToConstant(config);
kernel.Bind<UserAccountService<HierarchicalUserAccount>>().ToSelf();   kernel.Bind<AuthenticationService<HierarchicalUserAccount().To<SamAuthenticationService<HierarchicalUserAccount>>();
kernel.Bind<IUserAccountRepository<HierarchicalUserAccount>>().ToMethod(ctx => new BrockAllen.MembershipReboot.RavenDb.RavenUserAccountRepository("RavenDb"));
kernel.Bind<IUserAccountQuery>().ToMethod(ctx => new BrockAllen.MembershipReboot.RavenDb.RavenUserAccountRepository("RavenDb"));

Simple Injector implementation

container.Register(MembershipRebootConfig.Create);
container.Register<UserAccountService<HierarchicalUserAccount>>();
container.Register<AuthenticationService<HierarchicalUserAccount>, SamAuthenticationService<HierarchicalUserAccount>>();
container.Register<IUserAccountRepository<HierarchicalUserAccount>>(() => new RavenUserAccountRepository("RavenDb"), Lifestyle.Singleton);
container.Register<IUserAccountQuery>(() => new RavenUserAccountRepository("RavenDb"));

On row

container.Register<UserAccountService<HierarchicalUserAccount>>();

I have an error For the container to be able to create UserAccountService, it should contain exactly one public constructor, but it has 2. Parameter name: TConcrete

Thanks for your help.

2

There are 2 best solutions below

2
On BEST ANSWER

Simple Injector forces you to let your components to have one single public constructor, because having multiple injection constructors is an anti-pattern.

In case the UserAccountService is part of your code base, you should remove the constructor that should not be used for auto-wiring.

In case the UserAccountService is part of a reusable library, you should prevent using your container's auto-wiring capabilities in that case as described here. In that case you should fallback to wiring the type yourself and let your code call into the proper constructor, for instance:

container.Register<UserAccountService<HierarchicalUserAccount>>(() =>
    new UserAccountService<HierarchicalUserAccount>(
        container.GetInstance<MembershipRebootConfiguration<HierarchicalUserAccount>>(),
        container.GetInstance<IUserAccountRepository<HierarchicalUserAccount>>()));
0
On

I'm just going to include here how I converted the Ninject configuration to Simple Injector for the Single Tenant sample in the MembershipReboot repository (which I cloned). I thought that might be beneficial for anyone who was searching for how to go about this, as it may save them some time.

Firstly, the configuration in the Single Tenant sample's NinjectWebCommon class is:

var config = MembershipRebootConfig.Create();
kernel.Bind<MembershipRebootConfiguration>().ToConstant(config);
kernel.Bind<DefaultMembershipRebootDatabase>().ToSelf();
kernel.Bind<UserAccountService>().ToSelf();
kernel.Bind<AuthenticationService>().To<SamAuthenticationService>();
kernel.Bind<IUserAccountQuery>().To<DefaultUserAccountRepository>().InRequestScope();
kernel.Bind<IUserAccountRepository>().To<DefaultUserAccountRepository>().InRequestScope();

Now, I'll set out the whole SimpleInjectorInitializer class, which started with the one which was added to the project via the SimpleInjector.MVC3 Nuget package, and follow up with comments:

public static class SimpleInjectorInitializer
{
    /// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
    public static void Initialize()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();

        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());

        InitializeContainer(container);

        container.Verify();

        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
    }

    private static void InitializeContainer(Container container)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<DefaultMembershipRebootDatabase, BrockAllen.MembershipReboot.Ef.Migrations.Configuration>());

        var config = MembershipRebootConfig.Create();
        container.Register(() => config, Lifestyle.Singleton);
        container.Register(() => new DefaultMembershipRebootDatabase(), Lifestyle.Scoped);

        container.Register<IUserAccountQuery, DefaultUserAccountRepository>(Lifestyle.Scoped); // per request scope. See DefaultScopedLifestyle setting of container above.
        container.Register<IUserAccountRepository, DefaultUserAccountRepository>(Lifestyle.Scoped);

        container.Register(() => new UserAccountService(container.GetInstance<MembershipRebootConfiguration>(), container.GetInstance<IUserAccountRepository>()));
        container.Register<AuthenticationService, SamAuthenticationService>();

        var iUserAccountQueryRegistration = container.GetRegistration(typeof(IUserAccountQuery)).Registration;
        var iUserAccountRepositoryRegistration = container.GetRegistration(typeof(IUserAccountRepository)).Registration;

        iUserAccountQueryRegistration.SuppressDiagnosticWarning(DiagnosticType.TornLifestyle, "Intend for separate Objects");
        iUserAccountRepositoryRegistration.SuppressDiagnosticWarning(DiagnosticType.TornLifestyle, "Intend for separate Objects");
    }
}

Scoping the config to a Singleton with a factory func is pretty much the same as Ninject's ToConstant. DefaultMembershipRebootDatabase is the obvious departure, but I honestly don't think it matters whether MR's DefaultMembershipRebootDatabase is scoped a transient or per web request. It calls SaveChanges every time an operation is performed e.g. Registering a user. It does not use larger, per request-bound tansactions. So, using the same DefaultMembershipRebootDatabase context later in the same request is no going to cause any weird MR issues.

HOWEVER, some thought will need to given to what happens if you want to create a Domain User during the same operation as you create a MR UserAccount. (A Domain User may contain more information beyond password stuff, like first and last names, DOB etc.). Tying an MR UserAccount to a Domain User (with additional user info such a name, address etc.) is a common use case. So what happens if the creation of the Domain User fails after creation of the MR UserAccount succeeded? I don't know. Perhaps as part of the rollback, you delete the MR user. But the registration email will already have been sent. So, these are the issues that you face here.

As you can see, in the Simple Tenant sample, Brock registers both IUserAccountRepository and IUserAccountQuery to DefaultUserAccountRepository. This is obviously by design and so we have to do that as well, if we want to use MR's UserAccountService and AuthenticationService. Thus, we need to suppress the Diagnostic warnings which would otherwise prevent the Container from Verifying.

Hope that all helps and by all means let me know if there are problems with my registrations.

Cheers