2

In a asp.net core 2.1 MVC application I have the following code coming a bit from a Github from a nugget and some addition made by me :

    public static IServiceCollection AddIdentityMongoDbProvider<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupIdentityAction, Action<MongoIdentityOptions> setupDatabaseAction) where TUser : UserEntity where TRole : RoleEntity
    {
        services.AddIdentity<TUser, TRole>(setupIdentityAction ?? (x => { }))
            .AddRoleStore<RoleStore<TRole>>()
            .AddUserStore<UserStore<TUser, TRole>>()
            .AddDefaultTokenProviders();

        var dbOptions = new MongoIdentityOptions();
        setupDatabaseAction(dbOptions);

        var userCollection = new IdentityUserCollection<TUser>(dbOptions.DbType, dbOptions.ConnectionString);
        var roleCollection = new IdentityRoleCollection<TRole>(dbOptions.DbType, dbOptions.ConnectionString);

        // Add collections and stores in services for DI
        services.AddTransient<IIdentityUserCollection<TUser>>(x => userCollection);
        services.AddTransient<IIdentityRoleCollection<TRole>>(x => roleCollection);

        services.AddTransient<ITenantStore<TenantEntity, TUser>>(x => new TenantStore<TenantEntity, TUser>(dbOptions.DbType, dbOptions.ConnectionString, userCollection));
        services.AddTransient<ILicenseStore<LicenseEntity>>(x => new LicenseStore<LicenseEntity>(dbOptions.DbType, dbOptions.ConnectionString));

        // Identity Services
        services.AddTransient((Func<IServiceProvider, IUserStore<TUser>>)(x => new UserStore<TUser, TRole>(userCollection, roleCollection, x.GetService<ILookupNormalizer>())));
        services.AddTransient<IRoleStore<TRole>>(x => new RoleStore<TRole>(roleCollection));

        return services;
    }

So as you can see it's using the dependency injection, but i'm asking myself somes questions :

1) the userCollection and roleCollection are 'local' variable that are then passed into DI. But then how is the lifecycle of those object managed? I mean are they never disposed because they are used in DI? Or are they creating each time?

2) Is there a difference between

services.AddTransient<ILicenseStore<LicenseEntity>>(x => new LicenseStore<LicenseEntity>(dbOptions.DbType, dbOptions.ConnectionString));

And

services.AddTransient<ILicenseStore<LicenseEntity>>(new LicenseStore<LicenseEntity>(dbOptions.DbType, dbOptions.ConnectionString));

3) In the line

services.AddTransient((Func<IServiceProvider, IUserStore<TUser>>)(x => new UserStore<TUser, TRole>(userCollection, roleCollection, x.GetService<ILookupNormalizer>())));

There is the 'x.GetService()'. Is that a way to tell the constructor that the parameter needed in the constructor will be from DI? Sort of using Dependency Injection in DependecyInjection?

4) In case of yes to question 3, is it possible to do something like this?

services.AddSingletion<IMongoClient>(new MongoClient("connectionString"));
services.AddTransient<IXStore>(new XStore(x.GetService<IMongoClient>()))

The point being to achieve that the MongoClient will be a singleton (which is the recommended way)

5) What is the difference beetween :

services.AddScoped((Func<IServiceProvider, IUserStore<TUser>>)(x => new UserStore<TUser, TRole>(x.GetRequiredService<IIdentityUserCollection<TUser>>(), x.GetRequiredService<IIdentityRoleCollection<TRole>>(), x.GetService<ILookupNormalizer>())));

And

services.AddScoped<IUserStore<TUser>, UserStore<TUser, TRole>>();

Thanks a lot for the answers :)

Edit:

New way:

        services.AddSingleton<ICustomMongoClient>(x => new CustomMongoClient(dbOptions.ConnectionString));

        // Add collections and stores in services for DI
        services.AddTransient<IIdentityUserCollection<TUser>>(x => new IdentityUserCollection<TUser>(x.GetRequiredService<ICustomMongoClient>()));
        services.AddTransient<IIdentityRoleCollection<TRole>>(x => new IdentityRoleCollection<TRole>(x.GetRequiredService<ICustomMongoClient>()));


        services.AddTransient<ITenantStore<TenantEntity, TUser>>(x => new TenantStore<TenantEntity, TUser>(x.GetRequiredService<ICustomMongoClient>(), x.GetRequiredService<IIdentityUserCollection<TUser>>()));
        services.AddTransient<ILicenseStore<LicenseEntity>>(x => new LicenseStore<LicenseEntity>(x.GetRequiredService<ICustomMongoClient>()));

        // Identity Services
        services.AddTransient((Func<IServiceProvider, IUserStore<TUser>>)(x => new UserStore<TUser, TRole>(x.GetRequiredService<IIdentityUserCollection<TUser>>(), x.GetRequiredService<IIdentityRoleCollection<TRole>>(), x.GetService<ILookupNormalizer>())));
        services.AddTransient<IRoleStore<TRole>>(x => new RoleStore<TRole>(x.GetRequiredService<IIdentityRoleCollection<TRole>>()));
S.Martignier
  • 383
  • 2
  • 4
  • 17

1 Answers1

2
  1. First of all userCollection and roleCollection are instantiated only once. Both of these local variables won't be garbage collected for the following reason. They are captured by delegates created with lambdas (x => userCollection and x => roleCollection) and the delegates are added to services collection which is definitely a GC root.

  2. Yes, there is. First line compiles and latter one doesn't. You can pass constructed object only to AddSingleton. The only difference between services.AddSingleton(x => new object) and services.AddSingleton(new object) is when the object is instantiated, right now (new object) or at the first request to specified service type (x => new object).

  3. Yes.

  4. If you fix second line
    services.AddSingletion<IMongoClient>(new MongoClient("connectionString")); services.AddTransient<IXStore>(x => new XStore(x.GetService<IMongoClient>()))
    then answer is Yes. In fact DI container does this for you, calls GetService for every constructor parameter. So the following code is equivalent to yours
    services.AddSingletion<IMongoClient>(new MongoClient("connectionString")); services.AddTransient<IXStore, XStore>()

  5. Basically both examples are the same. When you use first example you manually resolving all constructor parameters using DI container. Whe you use second one the DI container resolves all constructor parameters automatically for you. So prefer second approach in this case, because it is less code. Consider using first approach only when you need instantiate objects by yourself using new or providing them with some other services and not by DI container.

Alexander
  • 9,104
  • 1
  • 17
  • 41
  • First thanks for the response, it's helping me a lot :) – S.Martignier Mar 04 '19 at 07:09
  • First thanks for the response, it's helping me a lot :) Getting back to point 1: Correct me if i'm wrong but the fact that `userCollection ` and `roleCollection ` are GC root is not a good think (or at leat not neccessary). So a better solution will be to resolve the collections using DI? What is according to you the better solution? (second comment because of 5 minutes rule, sorry) – S.Martignier Mar 04 '19 at 07:16
  • I've got some time to rethink the way it was done. I edited my question with a new way of doing it, can I have your opinion on it please? Thanks – S.Martignier Mar 04 '19 at 09:31
  • @S.Martignier It looks okay, but I need to know more about the nature of classes and so on to say more precise things, so I'll leave my opinion based on what I've seen. I believe it's better to have `Scoped` lifetime for all `Stores` because access to them is usually transactional so you would like to have the same `Store` instance per request. And you can write it shorter just by calling `services.AddTransient()`. And you can only used predefined types in generics, `AddTransient, User>` is wrong because there is no such type `T`, (IUser, User) is fine – Alexander Mar 04 '19 at 20:05
  • Thanks for the answer :) The TUser and TRole looks like generic indeed but they are not, it's because those services calls are inside an extension method that looks like : `public static IServiceCollection AddIdentityMongoDbProvider(this IServiceCollection services, Action setupIdentityAction, Action setupDatabaseAction) where TUser : UserEntity where TRole : RoleEntity` Can I ask you one last thing (question 5 in question post)? – S.Martignier Mar 05 '19 at 07:58
  • @S.Martignier Updated my answer with 5th question info – Alexander Mar 06 '19 at 16:13
  • Big big thanks for all yours answers, it's really helped me to understand DI better and to produces better code – S.Martignier Mar 07 '19 at 07:31