How to register two classes that inherit from IdentityUser with different DbContexts (.net 6 web api)

ahmed mahjoub 0 Reputation points
2023-10-27T21:30:57.01+00:00

I want to use builder.Services.AddIdentity() as follows:

Both User and Employee classes inherit from IdentityUser. AppDbContext and EmployeeDbContext inherit from IdentityDbContext. As you might have guessed, Users and Employees are stored in different databases and they have different properties (thats why I didnt want to use the Role feature). I want to be able to use UserManager<User> and UserManager<Employee> so I cant handle logic of both types in the same method and make complex logic. But this here does not work. When I run the API project I get the error: "System.InvalidOperationException: 'Scheme already exists: Identity.Application'". Apparently an asp.net api doesnt support registering AddIdentity twice. Do you have a solution for my use case. I want to use AspNetCore.Identity for 2 different IdentityUser in 2 different databases in the same .net web api application.

builder.Services.AddIdentity<User, IdentityRole>()
    .AddTokenProvider<DataProtectorTokenProvider<User>>("name1")
    .AddEntityFrameworkStores<AppDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddIdentity<Employee, IdentityRole>()     .AddTokenProvider<DataProtectorTokenProvider<Employee>>("name2")     .AddEntityFrameworkStores<EmployeeDbContext>()   
.AddDefaultTokenProviders();

builder.Services.AddAuthentication(options => {     
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => {     options.TokenValidationParameters = new TokenValidationParameters     {         ValidateIssuerSigningKey = true,         ValidateIssuer = true,         ValidateAudience = true,         ValidateLifetime = true,         ClockSkew = TimeSpan.Zero,         ValidIssuer = builder.Configuration["JwtSettings:Issuer"],         builder.Configuration["JwtSettings:IssuerEmployees"] },         
ValidAudience = builder.Configuration["JwtSettings:Audience"],       
  IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]))     }; });
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,614 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
343 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 66,706 Reputation points
    2023-10-27T22:56:00.2833333+00:00

    you can multiple providers, but they each need a unique authentication scheme (name), that is you can not use the default for both. you will also need to define authentication polices because you are not using the default setup.


  2. JasonPan - MSFT 5,981 Reputation points Microsoft Vendor
    2023-10-30T06:31:11.92+00:00

    Hi @ahmed mahjoub,

    Thanks for your feedback, I learned that the search results provided by search engines such as Bing are not substantially helpful to you, but let me know more about your needs, the following updated answers, please check it out, if there are others who need help, you can let me know.

    UPDATE

    Here is my project structure.

    User's image

    Test Result

    Animation1

    CustomSqlRoleProvider.cs

    using AspCore7_Web_Identity.Areas.Identity.Data;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    
    namespace AspCore7_Web_Identity
    {
        public class CustomSqlRoleProvider : IRoleStore<IdentityRole>
        {
            private readonly MyDbContext _context;
    
            public CustomSqlRoleProvider(MyDbContext context)
            {
                _context = context;
            }
    
            public async Task<IdentityResult> CreateAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                _context.Roles.Add(role);
                await _context.SaveChangesAsync(cancellationToken);
    
                return IdentityResult.Success;
            }
    
            public async Task<IdentityResult> UpdateAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                _context.Roles.Update(role);
                await _context.SaveChangesAsync(cancellationToken);
    
                return IdentityResult.Success;
            }
    
            public async Task<IdentityResult> DeleteAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                _context.Roles.Remove(role);
                await _context.SaveChangesAsync(cancellationToken);
    
                return IdentityResult.Success;
            }
    
            public Task<string> GetRoleIdAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                return Task.FromResult(role.Id);
            }
    
            public Task<string?> GetRoleNameAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                return Task.FromResult(role.Name);
            }
    
            public Task SetRoleNameAsync(IdentityRole? role, string? roleName, CancellationToken cancellationToken)
    
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
                if (roleName == null) throw new ArgumentNullException(nameof(roleName));
    
                role.Name = roleName;
                return Task.CompletedTask;
            }
    
            public Task<string?> GetNormalizedRoleNameAsync(IdentityRole role, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
    
                return Task.FromResult(role.NormalizedName);
            }
    
            public Task SetNormalizedRoleNameAsync(IdentityRole? role, string? normalizedName, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (role == null) throw new ArgumentNullException(nameof(role));
                if (normalizedName == null) throw new ArgumentNullException(nameof(normalizedName));
    
                role.NormalizedName = normalizedName;
                return Task.CompletedTask;
            }
    
            public async Task<IdentityRole?> FindByIdAsync(string roleId, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (roleId == null) throw new ArgumentNullException(nameof(roleId));
    
                return await _context.Roles.FindAsync(new object[] { roleId }, cancellationToken);
            }
    
            public async Task<IdentityRole?> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName));
    
                return await _context.Roles.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, cancellationToken);
            }
            public async Task<IList<IdentityRole>> GetAllRolesAsync(CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
    
                // Assuming _context.Roles is the roles DbSet
                var roles = await _context.Roles.ToListAsync(cancellationToken);
    
                return roles;
            }
            public void Dispose()
            {
                // Dispose of any unmanaged resources if necessary.
                _context?.Dispose();
            }
        }
    }
    
    

    MultiProviderRoleService.cs

    using Microsoft.AspNetCore.Identity;
    
    namespace AspCore7_Web_Identity
    {
        public class MultiProviderRoleService
        {
            private readonly RoleManager<IdentityRole> _defaultRoleManager;
            private readonly CustomSqlRoleProvider _customSqlRoleProvider;
    
            public MultiProviderRoleService(
                RoleManager<IdentityRole> defaultRoleManager,
                CustomSqlRoleProvider customSqlRoleProvider)
            {
                _defaultRoleManager = defaultRoleManager;
                _customSqlRoleProvider = customSqlRoleProvider;
            }
    
            public async Task<IEnumerable<IdentityRole>> GetAllRolesAsync()
            {
                var defaultRoles = _defaultRoleManager.Roles.ToList();
                var customRoles = await _customSqlRoleProvider.GetAllRolesAsync(CancellationToken.None);
    
                return defaultRoles.Concat(customRoles);
            }
        }
    }
    
    

    Settings in Program.cs

    var connectionString = builder.Configuration.GetConnectionString("SqlDataContextConnection") ?? throw new InvalidOperationException("Connection string 'SqlDataContextConnection' not found.");
    
    builder.Services.AddDbContext<SqlDataContext>(
        options => options.UseSqlServer(connectionString, sqlServerOptionsAction: sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure();
        })
    );
    builder.Services.AddIdentity<Users, IdentityRole>().AddTokenProvider<DataProtectorTokenProvider<Users>>("name1").AddEntityFrameworkStores<SqlDataContext>().AddDefaultTokenProviders();
    
    
    
    builder.Services.AddDbContext<MyDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("MyConnection")));
    
    builder.Services.AddIdentityCore<Users>().AddRoles<IdentityRole>() // If you want roles for Employees.
        .AddTokenProvider<DataProtectorTokenProvider<Users>>("name2").AddEntityFrameworkStores<MyDbContext>();
    
    builder.Services.AddTransient<CustomSqlRoleProvider>();
    
    builder.Services.AddTransient<MultiProviderRoleService>();
    

    Test Method

            [HttpGet]
            public async Task<IActionResult> testrole() {
                var roles = await _roleService.GetAllRolesAsync();
                return Ok();
            }
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Jason


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.