Search code examples
c#asp.net-coreasp.net-core-identity

Asp.Net Core 2.2 - Unable to resolve service for type '...SignInHelper`1[...ApplicationUser]'


Trying to log in using the razor login page, I get the following error:

Unable to resolve service for type 'DAL.Models._Auth.SignInHelper`1[DAL.Models._Auth.ApplicationUser]' while attempting to activate 'AuthTest.Areas.Identity.Pages.Account.LoginModel'.

Here is my SignInHelper class:

public class SignInHelper<T> where T : ApplicationUser
{
    private readonly SignInManager<T> _signInManager;

    public SignInHelper(SignInManager<T> signInManager)
    {
        _signInManager = signInManager;
    }

    public async Task SignInUserAsync(T user, bool isPersistent, IEnumerable<Claim> customClaims)
    {
        var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
        if (customClaims != null && claimsPrincipal?.Identity is ClaimsIdentity claimsIdentity)
        {
            claimsIdentity.AddClaims(customClaims);
        }
        await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
            claimsPrincipal,
            new AuthenticationProperties { IsPersistent = isPersistent });
    }
}

My ApplicationUser class:

public class ApplicationUser : IdentityUser<Guid>
{
    public string FullName { get; set; }

    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }

    public virtual ICollection<IdentityUserClaim<Guid>> Claims { get; set; }

    public virtual ICollection<IdentityUserLogin<Guid>> Logins { get; set; }

    public virtual ICollection<IdentityUserToken<Guid>> Tokens { get; set; }


}

And my Login page model:

[AllowAnonymous]
public class LoginModel : PageModel
{
    private readonly MyDbContext _context;
    private readonly SignInHelper<ApplicationUser> _signInHelper;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly ILogger<LoginModel> _logger;

    public LoginModel(MyDbContext context,
        SignInManager<ApplicationUser> signInManager,
        SignInHelper<ApplicationUser> signInHelper,
        ILogger<LoginModel> logger)
    {
        _context = context;
        _signInHelper = signInHelper;
        _signInManager = signInManager;
        _logger = logger;
    }

    [BindProperty]
    public LoginViewModel Input { get; set; }

    [BindProperty]
    public bool IsVerifiedUser { get; set; }

    public bool IsAdmin { get; set; }

    public IList<AuthenticationScheme> ExternalLogins { get; set; }

    public string ReturnUrl { get; set; }

    [TempData]
    public string ErrorMessage { get; set; }

    public async Task OnGetAsync(string returnUrl = null)
    {
        if (!string.IsNullOrEmpty(ErrorMessage))
        {
            ModelState.AddModelError(string.Empty, ErrorMessage);
        }

        returnUrl = returnUrl ?? Url.Content("~/");

        // Clear the existing external cookie to ensure a clean login process
        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

        ReturnUrl = returnUrl;
    }

    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl = returnUrl ?? Url.Content("~/");

        if (ModelState.IsValid)
        {
            var user = await _signInManager.UserManager.Users.FirstOrDefaultAsync(u => u.UserName == Input.UserName || u.Email == Input.UserName);
            if (user == null)
            {
                ModelState.AddModelError(string.Empty, "Username or Password is not correct.");
                return Page();
            }

            var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: true);
            if (result.Succeeded)
            {
                if (IsVerifiedUser)
                {
                    if (string.IsNullOrWhiteSpace(Input.Database))
                    {
                        ModelState.AddModelError(nameof(Input.Database), "Database name is required.");
                        return Page();
                    }
                    else
                    {
                        return await SignInAsync(user, Input.Database, returnUrl);
                    }
                }
            }
        }
        return Page();
    }

    private async Task<IActionResult> SignInAsync(ApplicationUser user, string databaseName, string returnUrl = null)
    {
        var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            var customClaims = new[]{
                    new Claim(ClaimConstants.DatabaseClaim, databaseName)
                };
            await _signInHelper.SignInUserAsync(user, Input.RememberMe, customClaims);

            HttpContext.Session.SetString("DbProviderType", Input.Provider.ToString());
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
    }
}

In login.cshtml beside the user name and password inputs, I have a dropdown to select from the user accessible databases.

And here is my middleware configuration:

public class Startup
{
    public IConfiguration Configuration { get; }
    private readonly IHostingEnvironment _hostingEnvironment;

    public Startup(IConfiguration configuration, IHostingEnvironment env)
    {
        Configuration = configuration;
        _hostingEnvironment = env;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        Inflector.Inflector.SetDefaultCultureFunc = () => new CultureInfo("EN-US");

        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => false;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddDistributedMemoryCache();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromSeconds(3000);
            options.Cookie.HttpOnly = true;
        });

        services.AddDbContext<MyDbContext>(options =>
            options.UseSqlServer(Configuration["ConnectionStrings:Cn1"], b => b.MigrationsAssembly("DAL")));

        services.AddIdentity<ApplicationUser, ApplicationRole>()
            .AddEntityFrameworkStores<MyDbContext>()

            .AddDefaultTokenProviders()
            .AddDefaultUI(UIFramework.Bootstrap4);

        services.Configure<IdentityOptions>(options =>
        {
            options.User.RequireUniqueEmail = true;
        });


        // Adds IdentityServer.
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryPersistedGrants()
            .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources())
            .AddInMemoryApiResources(IdentityServerConfig.GetApiResources())
            .AddInMemoryClients(IdentityServerConfig.GetClients())

            .AddAspNetIdentity<ApplicationUser>()
            .AddProfileService<ProfileService>();

        string applicationUrl = Configuration["ApplicationUrl"].TrimEnd('/');

        services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = applicationUrl;
                options.SupportedTokens = SupportedTokens.Jwt;
                options.RequireHttpsMetadata = false;
                options.ApiName = IdentityServerConfig.ApiName;
            });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                .AddJsonOptions(
                    options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        );

        services.AddTransient<SecondDbContext>(provider => provider.GetService<ApplicationDbContextFactory>().Create());
        services.AddScoped<ApplicationDbContextFactory>();


        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = IdentityServerConfig.ApiFriendlyName, Version = "v1" });
            c.OperationFilter<AuthorizeCheckOperationFilter>();
            c.AddSecurityDefinition("oauth2", new OAuth2Scheme
            {
                Type = "oauth2",
                Flow = "password",
                TokenUrl = $"{applicationUrl}/connect/token",
                Scopes = new Dictionary<string, string>()
                {
                   { IdentityServerConfig.ApiName, IdentityServerConfig.ApiFriendlyName }
                }
            });
        });

        var mappingConfig = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile<AutoMapperProfile>();
        });

        IMapper mapper = mappingConfig.CreateMapper();
        services.AddSingleton(mapper);

        services.AddScoped<IAccountManager, AccountManager>();

        // Auth Handlers
        services.AddSingleton<IAuthorizationHandler, ViewUserAuthorizationHandler>();
        services.AddSingleton<IAuthorizationHandler, ManageUserAuthorizationHandler>();
        services.AddSingleton<IAuthorizationHandler, ViewRoleAuthorizationHandler>();
        services.AddSingleton<IAuthorizationHandler, AssignRolesAuthorizationHandler>();

        services.AddTransient<IRepositoryFactory, RepositoryFactory>();
        services.AddScoped(typeof(SqlRepository<>));

        services.AddTransient(typeof(IService<>), typeof(BaseService<>));
        services.AddTransient(typeof(IServiceWithTypeId<,>), typeof(BaseService<,>));

        services.AddTransient<IServiceFactory, ServiceFactory>();

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, MyDbContext context, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime, IServiceProvider serviceProvider)
    {
        loggerFactory.AddFile(Configuration.GetSection("Logging"));

        Utilities.ConfigureLogger(loggerFactory);
        EmailTemplates.Initialize(env);

        app.UseSession();
        app.UseIdentityServer();

        app.UseMvc();
    }
}

So what service am using wrong ?


Solution

  • You've forgotten to register SignInHelper<ApplicationUser> in ConfigureServices. Here's an example of how to register it as a scoped dependency:

    services.AddScoped<SignInHelper<ApplicationUser>>();