Currently .NET 6 does not use startup.cs and only uses program.cs. I am trying to move everything over to use only program.cs but have some issues at the CreateRoles() portion.
In my .NET 3.1 I changed
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
to
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDBContext>();
I have CreateRoles(serviceProvider).Wait(); in my Configure function and
public async Task CreateRoles(IServiceProvider serviceProvider)
{
var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
string[] roleNames = { "UserManager", "Manager", "Worker", "User" };
IdentityResult identityResult;
foreach(var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
identityResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
var usermanager = await UserManager.FindByEmailAsync("UserManager#UserManager.com");
if (usermanager == null)
{
var userManager = new IdentityUser { UserName = "UserManager#UserManager.com", Email = "UserManager#UserManager.com", EmailConfirmed = true };
var createUserManager = await UserManager.CreateAsync(userManager,"UserManager2022!");
if (createUserManager.Succeeded)
{
await UserManager.AddToRoleAsync(userManager, "UserManager");
}
}
}
I removed the function name and added its contents directly to program.cs and removed
CreateRoles(serviceProvider).Wait(); but now my project won't even run. The project is able to build successfully but when running it wont connect to web server. If I was to go back to having both startup.cs and program.cs the project runs with no issues.
This is currently what I have on program.cs.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddDbContext<ApplicationDBContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default Connection")));
builder.Services.AddDbContext<ManagementContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default Connection")));
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false).AddRoles<IdentityRole>().AddEntityFrameworkStores<ApplicationDBContext>();
builder.Services.AddTransient<IProductInterface,ProductRepo>();
var app = builder.Build();
using (var serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<ApplicationDBContext>();
//context.Database.EnsureDeleted(); //Add to clear db at startup.
context.Database.Migrate();
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();;
app.UseAuthorization();
var UserManager = app.Services.GetRequiredService<UserManager<IdentityUser>>();
var RoleManager = app.Services.GetRequiredService<RoleManager<IdentityRole>>();
string[] roleNames = { "UserManager", "Manager", "Worker", "User" };
IdentityResult identityResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
identityResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
var usermanager = await UserManager.FindByEmailAsync("UserManager#UserManager.com");
if (usermanager == null)
{
var userManager = new IdentityUser { UserName = "UserManager#UserManager.com", Email = "UserManager#UserManager.com", EmailConfirmed = true };
var createUserManager = await UserManager.CreateAsync(userManager, "UserManager2022!");
if (createUserManager.Succeeded)
{
await UserManager.AddToRoleAsync(userManager, "UserManager");
}
}
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
I know a solution would just be to create a startup.cs and program.cs but I want to use just program.cs.
Do you want something like below ?
After you call builder.Build() the app.Services resolves as an IServiceProvider.
var app = builder.Build();
CreateRolesAsync(app.Services);
...
app.Run();
async Task CreateRolesAsync( IServiceProvider serviceProvider)
{..}
Related
I need to access UserManager instance to seed IdentityUser data, I am doing in program.cs file
Below is given code snippet
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services
.AddDefaultIdentity<MyIdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
});
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapControllers();
app.MapControllerRoute(
name: "default",
pattern: "{controller = Home}/{action = Index}/{Id?}"
);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
var scopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<MyIdentityUser>>();
SeedInitialData.SeedData(roleManager, userManager);
app.Run();
and I receive this exception
InvalidOperationException: No service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' has been registered.
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
Please help me, how to find this issue. regards
I read many articles, and I tried some of them, none of them worked for me.
I create a simple demo to show how to seed data to identity in asp.net core, You can refer to it.
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
//.........
builder.Services.AddIdentity<IdentityUser,IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
var app = builder.Build();
// Configure the HTTP request pipeline.
//........
app.UseAuthorization();
using (var scope = app.Services.CreateScope())
{
//Resolve ASP .NET Core Identity with DI help
var userManager = (UserManager<IdentityUser>)scope.ServiceProvider.GetService(typeof(UserManager<IdentityUser>));
var roleManager = (RoleManager<IdentityRole>)scope.ServiceProvider.GetService(typeof(RoleManager<IdentityRole>));
// do you things here
MyIdentityDataInitializer.SeedData(userManager, roleManager);
}
//........
app.Run();
}
}
MyIdentityDataInitializer class
public static class MyIdentityDataInitializer
{
public static void SeedData(UserManager<IdentityUser> userManager,RoleManager<IdentityRole> roleManager)
{
SeedRoles(roleManager);
SeedUsers(userManager);
}
public static void SeedUsers(UserManager<IdentityUser> userManager)
{
if (userManager.FindByNameAsync("user1").Result == null)
{
IdentityUser user = new IdentityUser();
user.UserName = "user1";
user.Email = "user1#localhost";
IdentityResult result = userManager.CreateAsync(user, "Password123!!!").Result;
if (result.Succeeded)
{
userManager.AddToRoleAsync(user,
"NormalUser").Wait();
}
}
if (userManager.FindByNameAsync("user2").Result == null)
{
IdentityUser user = new IdentityUser();
user.UserName = "user2";
user.Email = "user2#localhost";
IdentityResult result = userManager.CreateAsync(user, "Password123!!!").Result;
if (result.Succeeded)
{
userManager.AddToRoleAsync(user,
"Administrator").Wait();
}
}
}
public static void SeedRoles(RoleManager<IdentityRole> roleManager)
{
if (!roleManager.RoleExistsAsync("NormalUser").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "NormalUser";
IdentityResult roleResult = roleManager.CreateAsync(role).Result;
}
if (!roleManager.RoleExistsAsync("Administrator").Result)
{
IdentityRole role = new IdentityRole();
role.Name = "Administrator";
IdentityResult roleResult = roleManager.CreateAsync(role).Result;
}
}
}
Now when I run my project, The data will be seeded successfully.
I have a UserService in my ASP.NET Core project of my Blazor web assembly solution (.NET 6) in which I'm trying to access the current logged in user to determine the ID of the company they work for.
UserService.cs
public class UserService
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IHttpContextAccessor _httpContextAccessor;
public UserService(UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContextAccessor)
{
_userManager = userManager;
_httpContextAccessor = httpContextAccessor;
}
private ClaimsPrincipal User => _httpContextAccessor.HttpContext?.User;
public IEnumerable<UserDto> GetUsers()
{
var users = new List<UserDto>();
var companyId = GetCompanyId();
users = _userManager.Users.Select(u => new UserDto()
{
CompanyId = u.CompanyId,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName,
Id = u.Id,
DeleteDate = u.DeleteDate
}).Where(u => u.DeleteDate == null && u.CompanyId == companyId.Result).ToList();
return users;
}
public async Task<int> GetCompanyId()
{
var user = await _userManager.GetUserAsync(User);
return user.CompanyId;
}
}
As you can see I've injected IHttpContextAccessor into the constructor for my UserService and I've also registered builder.Services.AddHttpContextAccessor(); in my Program.cs file (code also shown below).
Whenever my code hits the return user.CompanyId; line of my UserService, it errors with a null reference exception as the user is always null. I'm logged in and successfully authenticated by the point I try and get it to run this code.
Am I missing something from my Program.cs file or I have done something wrong in the UserService itself?
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddProfileService<ProfileService>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddTransient<IUnitOfWork, UnitOfWork>();
builder.Services.AddTransient<UserService>();
builder.Services.AddScoped<DialogService>();
builder.Services.AddControllersWithViews();
builder.Services.AddHttpContextAccessor();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapBlazorHub();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
to get userId by using contextAccessor try below code, instead of usermanager
var userId=_httpContextAccessor.User.Identity.Name
I write an application where ASP.NET Core Identity is responsible for authentication and authorization on the server. I also use JWT tokens in this purpose. Everything works fine, besides authorization based on roles. Simple [Authorize] attribute works, I need to be logged in to get the resource, but when I use [Authorize(Roles="Administrator")] none gets authorization.
private static async Task CreateUsers(
RoleManager<IdentityRole> roleManager,
UserManager<ApplicationUser> userManager)
{
string role_Administrator = "Administrator";
string role_RegisteredUser = "RegisteredUser";
await roleManager.CreateAsync(new IdentityRole(role_Administrator));
await roleManager.CreateAsync(new IdentityRole(role_RegisteredUser));
var user_Admin = new ApplicationUser()
{
SecurityStamp = Guid.NewGuid().ToString(),
UserName = "Admin",
Email = "admin#test.com",
};
await userManager.CreateAsync(user_Admin, "Pass4Admin");
await userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);
await userManager.AddToRoleAsync(user_Admin, role_Administrator);
}
And example method from controller
[HttpGet("{id}")]
[Authorize(Roles = "Administrator")]
public IActionResult Get(int id)
{
return Ok();
}
I thought Identity is able to check roles assigned to authenticated user basing on data from created tables like aspnetroles and aspnetuserroles when the request comes from a client application, but apparently I don't understand something about this mechanism and I should configure it somehow.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().ToTable("AppUsers");
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddEntityFrameworkMySql();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"))
);
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 7;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(opts =>
{
opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = true;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Auth:Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])),
ValidAudience = Configuration["Auth:Jwt:Audience"],
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateAudience = true
};
cfg.IncludeErrorDetails = true;
});
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole",
policy => policy.RequireRole("Administrator"));
});
services.AddAutoMapper(typeof(WebMappingProfile));
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var dbContext = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
var roleManager = serviceScope.ServiceProvider.GetService<RoleManager<IdentityRole>>();
var userManager = serviceScope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
dbContext.Database.Migrate();
DbSeeder.Seed(dbContext, roleManager, userManager);
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
add role into your token as claim if you use custom jwt middleware
login action and token generator :
var role = await _userManager.GetRolesAsync(user);
IdentityOptions _options = new IdentityOptions();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim("UserID",user.Id.ToString()),
new Claim(_options.ClaimsIdentity.RoleClaimType,role.FirstOrDefault())
}),
Expires = DateTime.UtcNow.AddDays(_applicationSettings.Token_Expire_Day),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_applicationSettings.JWT_Secret)),
SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
User class:
public class User: IdentityUser
{
}
StartUp.cs:
public class Startup
{
private readonly IConfiguration _config;
public Startup(IConfiguration config)
{
_config = config;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<User, IdentityRole>(cfg =>
{
cfg.User.RequireUniqueEmail = true;
}).AddEntityFrameworkStores<DutchContext>();
services.AddDbContext<DutchContext>(cfg =>
{
cfg.UseSqlServer("Data Source=.;Initial Catalog=DutchDatabase;Integrated Security=True;MultipleActiveResultSets=true;");
});
services.AddAutoMapper();
services.AddTransient<IMailService, NullMailService>();
services.AddScoped<IDutchRepository, DutchRepository>();
services.AddScoped<IAccountRepository, AccountRepository>();
services.AddScoped<IOrganizationRepository, OrganizationRepostory>();
services.AddScoped<UserManager<User>, UserManager<User>>();
services.AddMvc()
.AddJsonOptions(opt => opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
//app.UseDefaultFiles();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseNodeModules(env);
app.UseAuthentication();
app.UseMvc(cfg =>
{
cfg.MapRoute("Default",
"{controller}/{action}/{id?}",
new { controller = "Account", Action = "LogIn" });
});
CreateRoles(serviceProvider).Wait();
}
private async Task CreateRoles(IServiceProvider serviceProvider)
{
//initializing custom roles
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
string[] roleNames = { "Admin", "Tester", "Developer" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
//create the roles and seed them to the database: Question 1
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
IdentityUser user = await UserManager.FindByEmailAsync("subratkr#gmail.com");
if (user == null)
{
user = new IdentityUser()
{
UserName = "admin#gmail.com",
Email = "admin#gmail.com",
};
await UserManager.CreateAsync(user, "Test#123");
}
await UserManager.AddToRoleAsync(user, "Admin");
IdentityUser user1 = await UserManager.FindByEmailAsync("tester#gmail.com");
if (user1 == null)
{
user1 = new IdentityUser()
{
UserName = "tester#gmail.com",
Email = "tester#gmail.com",
};
await UserManager.CreateAsync(user1, "Test#123");
}
await UserManager.AddToRoleAsync(user1, "Tester");
IdentityUser user2 = await UserManager.FindByEmailAsync("dev#gmail.com");
if (user2 == null)
{
user2 = new IdentityUser()
{
UserName = "dev#gmail.com",
Email = "dev#gmail.com",
};
await UserManager.CreateAsync(user2, "Test#123");
}
await UserManager.AddToRoleAsync(user2, "Developer");
}
}
Issue getting exception:
InvalidOperationException: No service for type
'Microsoft.AspNetCore.Identity.UserManager.
on line:
var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
Can anyone help please? Thanks.
Use your IdentityUser implementation, the User class
var UserManager = serviceProvider.GetRequiredService<UserManager<User>>();
Because in the ConfigureServices method, you have declare to the identity provider to use that class services.AddIdentity<User, IdentityRole>
I've build a background task in my ASP.NET Core 2.1 following this tutorial: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#consuming-a-scoped-service-in-a-background-task
Compiling gives me an error:
System.InvalidOperationException: 'Cannot consume scoped service 'MyDbContext' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'
What causes that error and how to fix it?
Background task:
internal class OnlineTaggerMS : IHostedService, IDisposable
{
private readonly CoordinatesHelper _coordinatesHelper;
private Timer _timer;
public IServiceProvider Services { get; }
public OnlineTaggerMS(IServiceProvider services, CoordinatesHelper coordinatesHelper)
{
Services = services;
_coordinatesHelper = coordinatesHelper;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Run every 30 sec
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private async void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Console.WriteLine("Online tagger Service is Running");
// Run something
await ProcessCoords(dbContext);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private async Task ProcessCoords(MyDbContext dbContext)
{
var topCoords = await _coordinatesHelper.GetTopCoordinates();
foreach (var coord in topCoords)
{
var user = await dbContext.Users.SingleOrDefaultAsync(c => c.Id == coord.UserId);
if (user != null)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
//expire time = 120 sec
var coordTimeStamp = DateTimeOffset.FromUnixTimeMilliseconds(coord.TimeStamp).AddSeconds(120).ToUnixTimeMilliseconds();
if (coordTimeStamp < now && user.IsOnline == true)
{
user.IsOnline = false;
await dbContext.SaveChangesAsync();
}
else if (coordTimeStamp > now && user.IsOnline == false)
{
user.IsOnline = true;
await dbContext.SaveChangesAsync();
}
}
}
}
public void Dispose()
{
_timer?.Dispose();
}
}
Startup.cs:
services.AddHostedService<OnlineTaggerMS>();
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<TutorDbContext>();
DbInitializer.Initialize(context);
}
catch(Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Full startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// ===== Add Identity ========
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<TutorDbContext>()
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
//return 401 instead of redirect
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Version = "v1", Title = "xyz", });
// Swagger 2.+ support
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(security);
});
services.AddHostedService<OnlineTaggerMS>();
services.AddTransient<UsersHelper, UsersHelper>();
services.AddTransient<CoordinatesHelper, CoordinatesHelper>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IServiceProvider serviceProvider, IApplicationBuilder app, IHostingEnvironment env, TutorDbContext dbContext)
{
dbContext.Database.Migrate();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "xyz V1");
});
CreateRoles(serviceProvider).GetAwaiter().GetResult();
}
private async Task CreateRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<User>>();
string[] roleNames = { "x", "y", "z", "a" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
var _user = await UserManager.FindByEmailAsync("xxx");
if (_user == null)
{
var poweruser = new User
{
UserName = "xxx",
Email = "xxx",
FirstName = "xxx",
LastName = "xxx"
};
string adminPassword = "xxx";
var createPowerUser = await UserManager.CreateAsync(poweruser, adminPassword);
if (createPowerUser.Succeeded)
{
await UserManager.AddToRoleAsync(poweruser, "xxx");
}
}
}
You need to inject IServiceScopeFactory to generate a scope. Otherwise you are not able to resolve scoped services in a singleton.
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
Edit:
It's perfectly fine to just inject IServiceProvider and do the following:
using (var scope = serviceProvider.CreateScope()) // this will use `IServiceScopeFactory` internally
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
The second way internally just resolves IServiceProviderScopeFactory and basically does the very same thing.
For Entity Framework DbContext classes there is a better way now with:
services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer(...))
Then you can just inject the factory in your singleton class like this:
IDbContextFactory<MyDbContext> myDbContextFactory
And finally use it like this:
using var myDbContex = _myDbContextFactory.CreateDbContext();
Though #peace answer worked for him, if you do have a DBContext in your IHostedService you need to use a IServiceScopeFactory.
To see an amazing example of how to do this check out this answer How should I inject a DbContext instance into an IHostedService?.
If you would like to read more about it from a blog, check this out .
I found the reason of an error. It was the CoordinatesHelper class, which is used in the the background task OnlineTaggerMS and is a Transient - so it resulted with an error. I have no idea why compiler kept throwing errors pointing at MyDbContext, keeping me off track for few hours.
You still need to register MyDbContext with the service provider. Usually this is done like so:
services.AddDbContext<MyDbContext>(options => {
// Your options here, usually:
options.UseSqlServer("YourConnectionStringHere");
});
If you also posted your Program.cs and Startup.cs files, it may shed some more light on things, as I was able to quickly setup a test project implementing the code and was unable to reproduce your issue.