I want to add new user to my local database after register in Stormpath. In doc https://docs.stormpath.com/dotnet/aspnetcore/latest/registration.html#registration is section about post-registration handler. I have problem becouse i can't use UserRepository in StartUp file.
I have error:
Unable to resolve service for type
'AppProject.Repositories.IUserRepository' while attempting to
activate 'AppProject.Startup'
.
public void ConfigureServices(IServiceCollection services, IUserRepository userRepository)
{
services.AddStormpath(new StormpathOptions()
{
Configuration = new StormpathConfiguration()
{
Client = new ClientConfiguration()
{
ApiKey = new ClientApiKeyConfiguration()
{
Id = "xxxxxxxxxxx",
Secret = "xxxxxxxxx"
}
}
},
PostRegistrationHandler = (context, ct) =>
{
return MyPostRegistrationHandler(context, ct, userRepository);
}
});
}
private Task MyPostRegistrationHandler(PostRegistrationContext context, CancellationToken ct, IUserRepository userRepository)
{
userRepository.Add(new User(context.Account.Email, context.Account.FullName, context.Account.GivenName, context.Account.Surname, context.Account.Username));
userRepository.SaveChangesAsync();
return Task.FromResult(0);
}
In this scenario, I don't think it can resolve dependency of IUserRepository in StartUp. You can try something like this.
1) Add an extension method.
public static IServiceProvider AddServices(this IServiceCollection services)
{
services.AddTransient<IUserRepository, UserRepository>();
// rest of the things.
return services.BuildServiceProvider();
}
2) Get the userRepository instance like like this.
IServiceCollection services = new ServiceCollection();
services.AddServices();
var provider = services.BuildServiceProvider();
var userRepository = provider.GetRequiredService<IUserRepository>();
ConfigurationServices will not have IUserRepository input parameter.
Related
I had previously asked a question that was answered properly, but the problem is that when my custom AuthenticationStateProvider is registered as a scoped
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
I get the following error:
System.InvalidOperationException: GetAuthenticationStateAsync was called before SetAuthenticationState
But, when it is registered as a singleton, it works correctly, However, the single instance creates for the lifetime of the application domain by AddSingelton, and so this is not good.(Why? Because of :))
What should I do to register my custom AuthenticationStateProvider as a scoped, but its value is not null?
Edit:
According to #MrC aka Shaun Curtis Comment:
It's my CustomAuthenticationStateProvider:
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
: base(loggerFactory) =>
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
try
{
var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
return await ValidateUserAsync(userManager, authenticationState?.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
{
if (principal is null)
{
return false;
}
var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
if (!int.TryParse(userIdString, out var userId))
{
return false;
}
var user = await userManager.FindUserAsync(userId);
return user is not null;
}
}
And it's a program configuration and service registration:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
#region Authentication
//Authentication
services.AddDbContextFactory<ApplicationDbContext>(options =>
{
options.UseSqlServer(
Configuration.GetConnectionString("LocalDBConnection"),
serverDbContextOptionsBuilder =>
{
var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
serverDbContextOptionsBuilder.CommandTimeout(minutes);
serverDbContextOptionsBuilder.EnableRetryOnFailure();
})
.AddInterceptors(new CorrectCommandInterceptor()); ;
});
//add policy
services.AddAuthorization(options =>
{
options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin));
options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
});
// Needed for cookie auth.
services
.AddAuthentication(options =>
{
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.SlidingExpiration = false;
options.LoginPath = "/";
options.LogoutPath = "/login";
//options.AccessDeniedPath = new PathString("/Home/Forbidden/");
options.Cookie.Name = ".my.app1.cookie";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = context =>
{
var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
return cookieValidatorService.ValidateAsync(context);
}
};
});
#endregion
//AutoMapper
services.AddAutoMapper(typeof(MappingProfile).Assembly);
//CustomAuthenticationStateProvider
services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
.
.
}
Don't worry about the AddSingelton in the Blazor apps. Scoped dependencies act the same as Singleton registered dependencies in Blazor apps (^).
Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped-registered services behave like Singleton services.
The Blazor Server hosting model supports the Scoped lifetime across HTTP requests (Just for the Razor Pages or MVC portion of the app) but not across SignalR connection/circuit messages among components that are loaded on the client.
That's why there's a scope.ServiceProvider.GetRequiredService here to ensure the retrived user is fetched from a new scope and has a fresh data.
Actually this solution is taken from the Microsoft's sample.
Your problem is probably here:
var scope = _scopeFactory.CreateScope();
/...
var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
You create a new IOC container and request the instance of IUsersService from that container.
If IUsersService is Scoped, it creates a new instance.
IUsersService requires various other services which the new container must provide.
public UsersService(IUnitOfWork uow, ISecurityService securityService, ApplicationDbContext dbContext, IMapper mapper)
Here's the definition of those services in Startup:
services.AddScoped<IUnitOfWork, ApplicationDbContext>();
services.AddScoped<IUsersService, UsersService>();
services.AddScoped<IRolesService, RolesService>();
services.AddScoped<ISecurityService, SecurityService>();
services.AddScoped<ICookieValidatorService, CookieValidatorService>();
services.AddScoped<IDbInitializerService, DbInitializerService>();
IUnitOfWork and ISecurityService are both Scoped, so it creates new instances of these in the the new Container. You almost certainly don't want that: you want to use the ones from the Hub SPA session container.
You have a bit of a tangled web so without a full view of everything I can't be sure how to restructure things to make it work.
One thing you can try is to just get a standalone instance of IUsersService from the IOC container using ActivatorUtilities. This instance gets instantiated with all the Scoped services from the main container. Make sure you Dispose it if it implements IDisposable.
public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
private readonly IServiceProvider _serviceProvider;
public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
: base(loggerFactory) =>
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(scopeFactory));
protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get an instance of IUsersService from the IOC Container Service to ensure it fetches fresh data
IUsersService userManager = null;
try
{
userManager = ActivatorUtilities.CreateInstance<IUsersService>(_serviceProvider);
return await ValidateUserAsync(userManager, authenticationState?.User);
}
finally
{
userManager?.Dispose();
}
}
private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
{
if (principal is null)
{
return false;
}
var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
if (!int.TryParse(userIdString, out var userId))
{
return false;
}
var user = await userManager.FindUserAsync(userId);
return user is not null;
}
}
For reference this is my test code using the standard ServerAuthenticationStateProvider in a Blazor Server Windows Auth project.
public class MyAuthenticationProvider : ServerAuthenticationStateProvider
{
IServiceProvider _serviceProvider;
public MyAuthenticationProvider(IServiceProvider serviceProvider, MyService myService)
{
_serviceProvider = serviceProvider;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
MyService? service = null;
try
{
service = ActivatorUtilities.CreateInstance<MyService>(_serviceProvider);
// Do something with service
}
finally
{
service?.Dispose();
}
return base.GetAuthenticationStateAsync();
}
}
I want to read my data from database and control it, and I need to do this in the request pipeline at startup.
So I have to do dependency injection at startup.
This is my (DI)
public Startup(IConfiguration configuration,IAuthHelper authHelper)
{
Configuration = configuration;
AuthHelper = authHelper;
}
public IConfiguration Configuration { get; }
public IAuthHelper AuthHelper;
I encounter this error
An error occurred while starting the application.
InvalidOperationException: Unable to resolve service for type 'Laboratory.Core.Services.Interfaces.IAuthHelper' while attempting to activate 'Laboratory.Startup'.
I used service like this
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var siteDirectory = AuthHelper.GetSiteSetting().MediaPath;
var fileServerOptions = new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine
(env.WebRootPath, $#"{siteDirectory}User Picture\")),
RequestPath = "/ServerFiles"
};
app.UseFileServer(fileServerOptions);
}
This is my service
public class AuthHelper : IAuthHelper
{
private readonly LaboratoryContext _context;
private readonly IRazorPartialToStringRenderer _renderer;
private readonly IHttpContextAccessor _httpContext;
private readonly IHttpClientFactory _clientFactory;
public AuthHelper(LaboratoryContext context, IRazorPartialToStringRenderer renderer, IHttpContextAccessor httpContext, IHttpClientFactory clientFactory)
{
_context = context;
_renderer = renderer;
_httpContext = httpContext;
_clientFactory = clientFactory;
}
public TableSiteSetting GetSiteSetting()
{
try
{
return _context.TableSiteSettings.AsNoTracking().FirstOrDefault();
}
catch (SqlException)
{
return new TableSiteSetting() { StaticIp = "ServerConnectionError" };
}
catch (Exception)
{
return new TableSiteSetting() { StaticIp = "ServerError" };
}
}
}
Thanks for any help.
Your service can't be injected in Startup constructor because it has not been added yet to the dependency injection container. But you can inject it to the Configure method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAuthHelper authHelper)
{
...
}
I assume you have already registered the service in ConfigureServices
services.AddSingleton<IAuthHelper, AuthHelper>(); // Or scoped/transient depends what your service does.
You can get dbcontext service in program.cs and do what ever you like to your database data.
for example I use this approach to seed my database:
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
await ApplicationDbContextSeed.SeedSampleDataAsync(context)
}
host.Run();
I have this test client sending RequestToken:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
Address = disco.TokenEndpoint,
GrantType = "password",
ClientId = "My_Client",
ClientSecret = "mysecret",
Parameters =
{
{ "username", "user#entity.com" },
{ "password", "userpassword" },
{ "logged_entity_id", "143" },
{ "scope", "MyAPI" }
}
});
Now each user has a list of entity and I want to reject the token request if the value in the parameter "logged_entity_id" does not exist in the user's list of entity.
I was initially planning on checking it via IsActiveSync in my CustomProfileService but I can't seem to access the raw parameters in IsActiveSync method.
public class CustomProfileService : IProfileService
{
protected UserManager<User> _userManager;
public CustomProfileService(UserManager<User> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>
{
new Claim("LoggedEntityId", context.ValidatedRequest.Raw["logged_entity_id"])
};
context.IssuedClaims.AddRange(claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = _userManager.GetUserAsync(context.Subject).Result;
// var entityId = Can't access logged_entity_id parameter here
context.IsActive = user != null && user.DeletingDate == null && user.entities.Contains(entityId);
return Task.FromResult(0);
}
}
I'm not really sure if this is where I should check and reject it.
In asp.net core you can register a dependency using the built-in dependency injection container. The dependency injection container supplies the IHttpContextAccessor to any classes that declare it as a dependency in their constructors:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
...
}
Then in your class ,for example , in the implement of IProfileService :
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomProfileService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
Then in IsActiveAsync method get the value by :
var id = _httpContextAccessor.HttpContext.Request.Form["logged_entity_id"].ToString();
You can implement ICustomTokenValidator to validate token's request on your own way
You can run custom code as part of the token issuance pipeline at the token endpoint. This allows e.g. for
adding additional validation logic
changing certain parameters (e.g.token lifetime) dynamically
public class CustomValidator : ICustomTokenRequestValidator
{
public Task<TokenValidationResult> ValidateAccessTokenAsync(TokenValidationResult result)
{
throw new NotImplementedException();
}
public Task<TokenValidationResult> ValidateIdentityTokenAsync(TokenValidationResult result)
{
throw new NotImplementedException();
}
}
and in your startup.cs:
services.AddIdentityServer(options =>
{
...
})
.AddCustomTokenRequestValidator<CustomValidator>();
I am attempting to seed my database with the following code:
Startup.Configure:
app.UseCors("AllowAll")
.UseMiddleware<JwtBearerMiddleware>()
.UseAuthentication()
.SeedDatabase() <= here
.UseHttpsRedirection()
.UseDefaultFiles()
.UseMvc()
.UseSpa(SpaApplicationBuilderExtensions => { });
SeedDatabase method:
public static IApplicationBuilder SeedDatabase(this IApplicationBuilder app)
{
IServiceProvider serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider;
try
{
UserManager<ApplicationUser> userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
RoleManager<IdentityRole> roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
IConfiguration configuration = serviceProvider.GetService<IConfiguration>();
ThePLeagueContext dbContext = serviceProvider.GetService<ThePLeagueContext>();
DataBaseInitializer.SeedUsers(userManager, roleManager, configuration, dbContext);
DataBaseInitializer.SeedTeams(dbContext);
}
catch (Exception ex)
{
ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
return app;
}
Everything worked fine until I added ThePLeagueContext dbContext = serviceProvider.GetService<ThePLeagueContext>(); and then the DataBaseInitializer.SeedTeams(dbContext)
DataBaseInitializer.SeedTeams(dbContext):
public static async void SeedTeams(ThePLeagueContext dbContext)
{
List<Team> teams = new List<Team>();
// 7 because we have 7 leagues
for (int i = 0; i < 7; i++)...
if (dbContext.Teams.Count() < teams.Count)
{
foreach (Team newTeam in teams)
{
await dbContext.Teams.AddAsync(newTeam);
await dbContext.SaveChangesAsync();
}
}
}
When I attempt to seed the database with the above code I get the following exception:
System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.'
My database context is registered with the LifeTime of Scoped.
Two workarounds I found:
When I change my database context to Transient the seeding issue goes away. This however causes other issues in the application so I cannot use Transient
When I call DatabaseInitializer.SeedTeams(dbContext) from inside the DatabaseInitializer.SeedUsers(...) method, this also works, I have no clue why.
DatabaseInitializer.SeedUsers(...) method:
public async static void SeedUsers(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration, ThePLeagueContext dbContext)
{
string[] roles = new string[] { AdminRole, SuperUserRole, UserRole };
foreach (string role in roles)
{
if (!roleManager.Roles.Any(r => r.Name == role))
{
IdentityRole newRole = new IdentityRole
{
Name = role,
NormalizedName = role.ToUpper()
};
await roleManager.CreateAsync(newRole);
if (role == AdminRole)
{
await roleManager.AddClaimAsync(newRole, new Claim(Permission, ModifyPermission));
}
else if (role == SuperUserRole)
{
await roleManager.AddClaimAsync(newRole, new Claim(Permission, RetrievePermission));
}
else
{
await roleManager.AddClaimAsync(newRole, new Claim(Permission, ViewPermission));
}
}
}
ApplicationUser admin = new ApplicationUser()...
ApplicationUser sysAdmin = new ApplicationUser()...;
PasswordHasher<ApplicationUser> password = new PasswordHasher<ApplicationUser>();
if (!userManager.Users.Any(u => u.UserName == admin.UserName))
{
string hashed = password.HashPassword(admin, configuration["ThePLeagueAdminInitPassword"]);
admin.PasswordHash = hashed;
await userManager.CreateAsync(admin);
await userManager.AddToRoleAsync(admin, AdminRole);
}
if (!userManager.Users.Any(u => u.UserName == sysAdmin.UserName))
{
string hashed = password.HashPassword(sysAdmin, configuration["ThePLeagueAdminInitPassword"]);
sysAdmin.PasswordHash = hashed;
await userManager.CreateAsync(sysAdmin);
await userManager.AddToRoleAsync(sysAdmin, AdminRole);
}
SeedTeams(dbContext);
}
Is there any way I can use two separate static async methods to seed the database and keep my context as scoped?
So I like to keep things ordered and seperated. Therefore I'd do something like:
public static class SeedData
{
public static void Populate(IServiceProvider services)
{
ApplicationDbContext context = services.GetRequiredService<ApplicationDbContext>();
if (!context.SomeDbSet.Any())
{
// ...code omitted for brevity...
);
context.SaveChanges();
}
}
public static class IdentitySeedData
{
public static async Task Populate(IServiceProvider services)
{
UserManager<ApplicationUser> userManager = services.GetService<UserManager<ApplicationUser>>();
RoleManager<IdentityRole> roleManager = services.GetService<RoleManager<IdentityRole>>();
IConfiguration configuration = services.GetService<IConfiguration>();
ApplicationDbContext context = services.GetRequiredService<ApplicationDbContext>();
if (!context.Users.Any())
{
// ...code omitted for brevity...
await userManager.CreateAsync(sysAdmin);
await userManager.AddToRoleAsync(sysAdmin, AdminRole);
);
context.SaveChanges();
}
}
And then the one to top it off:
public static class DatabaseInitializer
{
public static void Initialize(IServiceProvider services)
{
IdentitySeedData.Populate(services).Wait();
SeedData.Populate(services);
}
}
Disclaimer: I haven't run the code. So if it requires some tweaking let me know. I'll make the adjustments. It's a bit time-consuming to test this out.
Brand new to ASP.Net Core. Having to create an asp.net core 2.2 project with Identity (and have users seeded).
I can't find any documentation on how to do this exactly.
I was able to find the code to create Identity Roles (compiles anyway, haven't gotten to where I can run it yet:
private static async Task CreateUserTypes(ApplicationDbContext authContext, IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string[] roleNames = { "Administrator", "Data Manager", "Interviewer", "Respondent" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
}
Now, I need to create some users. But the with the weird microsoft syntax to do that I can't find (been googling for 2 days).
Here's what does not work:
private static async Task CreateRootUser(Models.CensORContext context, ApplicationDbContext authContext, IServiceProvider serviceProvider)
{
//Create the root ADMIN user for all things admin.
UserManager<ApplicationDbContext> userManager = serviceProvider.GetRequiredService<UserManager<ApplicationDbContext>>();
IdentityUser user = new IdentityUser()
{
UserName = "admin#admin.admin",
Email = "admin#admin.admin"
};
var NewAdmin = await userManager.CreateAsync(user, "password");
}
The error I see is:
Argument1: cannot convert from 'Microsoft.AspNetCore.Identity.IdentityUser' to 'ApplicationDbContext'
Does that mean? Obviously, I don't have the right userManager. But, how do I get the right one that takes a user as the 1st parameter and a string (password) for the 2nd?
In addition, the examples that come up in Google searches have an ApplicationUser object that I do not have (and don't need?). Not defined in the examples as to how I get it.
Owen
OK. Got past syntax error, but now I'm getting a runtime error:
NullReferenceException: Object reference not set to an instance of an object. on the call to CreateAsync. Here's the new code:
private static async Task CreateRootUser(Models.CensORContext context, ApplicationDbContext authContext, IServiceProvider serviceProvider)
{
//Create the root ADMIN user for all things admin.
var userStore = new UserStore<IdentityUser>(authContext);
UserManager<IdentityUser> userManager = new UserManager<IdentityUser>(userStore, null, null, null, null, null, null, serviceProvider, null);
// = serviceProvider.GetRequiredService<UserManager<ApplicationDbContext>>();
IdentityUser user = new IdentityUser()
{
UserName = "admin#admin.admin",
Email = "admin#admin.admin"
};
var result = await userManager.CreateAsync(user, "password");
}
Going to be looking into what the other parameters are to the create userManager and how to get them from the serviceProvider?
--Owen
Figured out how to do it. The key was finding the correct serviceprovider to pass in and the right syntax for creating the userManager. The other answers I've found through google all replace the IdentityUser with their own ApplicationUser that was muddying the water. Here's the working function (hope this helps someone):
private static async Task CreateRootUser(Models.CensORContext context, ApplicationDbContext authContext, IServiceProvider serviceProvider)
{
//Create the root ADMIN user for all things admin.
var userStore = new UserStore<IdentityUser>(authContext);
UserManager<IdentityUser> userManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
//new UserManager<IdentityUser>(userStore, null, null, null, null, null, null, serviceProvider, null);
// = serviceProvider.GetRequiredService<UserManager<ApplicationDbContext>>();
IdentityUser user = new IdentityUser()
{
UserName = "admin#admin.admin",
Email = "admin#admin.admin"
};
var result = await userManager.CreateAsync(user, "password");
result = await userManager.AddToRoleAsync(user, "Administrator");
}
Your main issue seems to be dependency injection. Have a look at this link for more information. As long as you inject your DbContext and UserManager in the right way and the rest of the code should be fine.
Here is an example. You can set up a separate service for seeding to ensure you decouple your code from the rest.
public class UserSeeder
{
private readonly UserManager<IdentityUser> userManager;
private readonly ApplicationDbContext context;
public UserSeeder(UserManager<IdentityUser> userManager, ApplicationDbContext context)
{
this.userManager = userManager;
this.context = context;
}
public async Task `()
{
string username = "admin#admin.admin";
var users = context.Users;
if (!context.Users.Any(u => u.UserName == username))
{
var done = await userManager.CreateAsync(new IdentityUser
{
UserName = username,
Email = username
}, username);
}
}
}
You then have to add this class as a scoped (since your DbContext is scoped) by using services.AddScoped<UserSeeder>() in your startup. You can now simply inject your UserSeeder in any service (except singletons) and call your UserSeeder function. For instance, You can inject UserSeeder in the home controller and call it index action. This way the seeding is checked and added initially. However, this will only work IF you go to the home page first. Alternatively, you can set up a middleware like this in your startup class:
app.Use(async (context, next) => {
await context.RequestServices.GetService<UserSeeder>().SeedAsync();
await next();
});
Note that both of these ways, you are calling the database every time. You can plan on where to place it. You can also make sure this is only called once with the help of a boolean (could be in a singleton). But note that this would only run on application startup.
Here's how I seed my Admin user (learned from EF Core in Action book):
This is the User class:
public class User : IdentityUser<long>
{
//add your extra properties and relations
}
The long type specifies the primary key type. If you use the default IdentityUser class it's going to be string (uniqueidentifier in SQL).
This is the Role class:
public class Role : IdentityRole<long>
{
public static string Admin = "Admin";
}
It can be empty, I use static strings to avoid magic strings in my code.
This is the DbContext:
public class ApplicationDbContext : IdentityDbContext<User, Role, long>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
//your DbSets and configurations
//...
}
If you're going to use Identity, you need to use IdentityDbContext and specify your custom User and Role class and the type of primary key you're using.
This code adds Identity to the program:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddIdentity<User, Role>(options =>
{
//you can configure your password and user policy here
//for example:
options.Password.RequireDigit = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//...
}
This is an extension method to seed data:
public static class SeedData
{
public static IWebHost SeedAdminUser(this IWebHost webHost)
{
using (var scope = webHost.Services.CreateScope())
{
try
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Database.EnsureCreated();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
if (!userManager.Users.Any(u => u.Email == "admin#domain.com"))
{
roleManager.CreateAsync(new Role()
{
Name = Role.Admin
})
.Wait();
userManager.CreateAsync(new User
{
UserName = "Admin",
Email = "admin#domain.com"
}, "secret")
.Wait();
userManager.AddToRoleAsync(userManager.FindByEmailAsync("admin#domain.com").Result, Role.Admin).Wait();
}
}
catch (Exception ex)
{
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding user.");
//throw;
}
}
return webHost;
}
}
And finally use it in your Program.cs:
CreateWebHostBuilder(args)
.Build()
.SeedAdminUser()
.Run();