Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency injection of custom IdentityUser class #11232

Closed
mmaderic opened this issue Jun 14, 2019 · 7 comments
Closed

Dependency injection of custom IdentityUser class #11232

mmaderic opened this issue Jun 14, 2019 · 7 comments
Labels
area-identity Includes: Identity and providers Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. question

Comments

@mmaderic
Copy link

mmaderic commented Jun 14, 2019

Hi,

I need help understanding how this could be done.
What I am trying to achieve is dependency injection of UserManager service in unreferenced assembly from main project. Everything is working if built in IdentityUser is used as type, but when trying to do the same with custom one, it can't build the service, no mater what I try.

Here is the execution chain with the built in IdentityUser class that works fine:
Executable project startup class, ConfigureServices method call:

 public void ConfigureServices(IServiceCollection services)
 {     
    // removed other services for brevity
    IdentityServiceLoader.LoadIdentityService(services, Config.GetConnectionString("Identity"));
 }

Executable project is referencing library assembly (application layer) containing implementation of LoadIdentityService method.

LoadIdentityService then loads dynamically another library assembly, containing IIdentityService implementation (infrastructure layer) and calls RegisterIdentityService interface method.

    public sealed class IdentityServiceLoader
    {
        public static void LoadIdentityService(IServiceCollection services, string connectionString)
        {            
            var loader = PluginLoader.CreateFromAssemblyFile(Path.GetFullPath(Const.InfrastructurePath), PluginLoaderOptions.PreferSharedTypes);
            var assembly = loader.LoadDefaultAssembly();

            var serviceType = assembly.GetExportedTypes().Single(x => typeof(IIdentityService).IsAssignableFrom(x));       
            var service = (IIdentityService)Activator.CreateInstance(serviceType);

            service.RegisterIdentityService(services, connectionString);
        }
    }
}

And finally infrastructure layer is implementing identityservice self registration into main project services collection.

     public class IdentityService : IIdentityService
     {
         public void RegisterIdentityService(IServiceCollection services, string connectionString)
         {
            services.AddDbContext<ApplicationIdentityDbContext>(options =>
                options.UseSqlServer(connectionString));

            services.AddIdentity<IdentityUser, IdentityRole>(options =>
                {
                    options.User.RequireUniqueEmail = true;
                    options.Password.RequireNonAlphanumeric = false;
                })
                .AddEntityFrameworkStores<ApplicationIdentityDbContext>()
                .AddDefaultTokenProviders();         
        }
     }

The end user of this dependency injection is the identityservice itself, trying to use the service in its command handler implementation.

public sealed class CreateApplicationUserHandler : ICreateApplicationUserHandler
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IMediator _mediator;

        public CreateApplicationUserHandler(UserManager<IdentityUser> userManager, IMediator mediator)
        {
            _userManager = userManager;
            _mediator = mediator;
        }
    }

If used instead of IdentityUser with custom ApplicationUser (implemented at identity service assembly) it throws this exception:

System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler2[Vigab.Application.Actions.Commands.CreateApplicationUserCommand,System.Guid] Lifetime: Transient ImplementationType: Vigab.Infrastructure.IdentityService.Handlers.Commands.CreateApplicationUserHandler': Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager1[Vigab.Infrastructure.IdentityService.ApplicationUser]' while attempting to activate 'Vigab.Infrastructure.IdentityService.Handlers.Commands.CreateApplicationUserHandler'.)

@mkArtakMSFT mkArtakMSFT added area-servers question area-identity Includes: Identity and providers and removed area-servers labels Jun 14, 2019
@blowdart
Copy link
Contributor

@HaoK thoughts?

@HaoK HaoK self-assigned this Jul 1, 2019
@HaoK
Copy link
Member

HaoK commented Jul 2, 2019

Including the working code isn't as helpful as including the code that is causing the error, can you include that as well?

@HaoK HaoK added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Jul 12, 2019
@HaoK HaoK removed their assignment Jul 12, 2019
@mmaderic
Copy link
Author

mmaderic commented Jul 12, 2019

@HaoK Hi, yes sure.
Here is the non working example with custom ApplicationUser class, extending built in IdentityUser.
Code snippets 1 and 2 remain the same.

public class IdentityService : IIdentityService
     {
         public void RegisterIdentityService(IServiceCollection services, string connectionString)
         {
            services.AddDbContext<ApplicationIdentityDbContext>(options =>
                options.UseSqlServer(connectionString));

            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
                {
                    options.User.RequireUniqueEmail = true;
                    options.Password.RequireNonAlphanumeric = false;
                })
                .AddEntityFrameworkStores<ApplicationIdentityDbContext>()
                .AddDefaultTokenProviders();         
        }
     }

    public class ApplicationUser : IdentityUser
    {
        public string Name { get; set; }
        public string Surname { get; set; }
    }
    public sealed class CreateApplicationUserHandler : ICreateApplicationUserHandler
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly IMediator _mediator;

        public CreateApplicationUserHandler(UserManager<ApplicationUser> userManager, IMediator mediator)
        {
            _userManager = userManager;
            _mediator = mediator;
        }
    }

@HaoK
Copy link
Member

HaoK commented Jul 12, 2019

Does everything work if you add a reference to the unreferenced assembly that contains the types?

@mmaderic
Copy link
Author

mmaderic commented Jul 12, 2019

It does work if called from ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
        {       
            var idService = new IdentityService();
            idService.RegisterIdentityService(services, Config.GetConnectionString("Identity"));
        }

@mmaderic
Copy link
Author

mmaderic commented Jul 13, 2019

OK I assume now it is dependency injection issue after all. GetService returns null.

    public sealed class CreateApplicationUserHandler : ICreateApplicationUserHandler
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly IMediator _mediator;

        public CreateApplicationUserHandler(IServiceProvider provider, IMediator mediator)
        {
            _userManager = (UserManager<ApplicationUser>)provider.GetService(typeof(UserManager<ApplicationUser>));
            _mediator = mediator;
        }
  }

@mmaderic
Copy link
Author

I have managed to solve the issue.
The problem is if you want to use service as self implementation from not referenced assembly execution context from main project.

For example:
services.AddTransient<ApplicationUser>(); will not enable ApplicationUser instance injection, if this service is registered from service itself and not being referenced from main project. In order to make this work, we need to define interface at any project that is referenced from main project and use the interface registration.

services.AddTransient<IApplicationUser, ApplicationUser>();

The same works for UserManager class, where we need to extend the built in UserManager class as partial class and implement custom IUserManager interface. In that case we are able to use custom ApplicationUser class.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-identity Includes: Identity and providers Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. question
Projects
None yet
Development

No branches or pull requests

4 participants