Unable to seed data in ASP.NET Core in a static method due to exception 'A second operation started on this context before a previous' - asp.net-core

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.

Related

Caching odata Web Api

I am developing an OData API for my Asp.net core application and i want to implement caching on this.
The problem is all my endpoints will be IQueryable with a queryable services with no execution at all. so i can't implement any caching on service level
Controller
public class TagsController : ODataController
{
private readonly ITagService _tagService;
private readonly ILogger<TagsController> _logger;
public TagsController(ITagService tagService, ILogger<TagsController> logger)
{
_tagService = tagService;
_logger = logger;
}
[HttpGet("odata/tags")]
[Tags("Odata")]
[AllowAnonymous]
[EnableCachedQuery]
public ActionResult<IQueryable<Tag>> Get()
{
try
{
return Ok(_tagService.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex, "Some unknown error has occurred.");
return BadRequest();
}
}
}
So I tried to apply an extension on EnableQuery attribute to add the caching implementation on it. so i added the following
public class EnableCachedQuery : EnableQueryAttribute
{
private IMemoryCache _memoryCache;
public EnableCachedQuery()
{
_memoryCache = new MemoryCache(new MemoryCacheOptions());
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
//var url = GetAbsoluteUri(actionContext.HttpContext);
var path = actionContext.HttpContext.Request.Path + actionContext.HttpContext.Request.QueryString;
//check cache
if (_memoryCache.TryGetValue(path, out ObjectResult value))
{
actionContext.Result = value;
}
else
{
base.OnActionExecuting(actionContext);
}
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
return;
var path = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
var cacheEntryOpts = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(15));
base.OnActionExecuted(context);
_memoryCache.Set(path, context.Result, cacheEntryOpts);
}
}
the first request completed successfully and retrieved the data correctly with filters and queries applied. then when tried to add the data to cache the context.Result holds the ObjectResult and then in the second request which should be cached the value was there but with an error in executing which means that the cached value is not the final output value that should be passed to the Result
Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'ApplicationDbContext'.
============================
Update:
public class ApplicationDbContext : IdentityDbContext<User, Account, Session>, IApplicationDbContext
{
public ApplicationDbContext(
DbContextOptions options,
IApplicationUserService currentUserService,
IDomainEventService domainEventService,
IBackgroundJobService backgroundJob,
IDomainEventService eventService,
IDateTime dateTime) : base(options, currentUserService, domainEventService, backgroundJob, dateTime) { }
public DbSet<Tag> Tags => Set<Tag>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var entityTypes = builder.Model.GetEntityTypes()
.Where(c => typeof(AuditableEntity).IsAssignableFrom(c.ClrType))
.ToList();
foreach (var type in entityTypes)
{
var parameter = Expression.Parameter(type.ClrType);
var deletedCheck = Expression.Lambda
(Expression.Equal(Expression.Property(parameter, nameof(AuditableEntity.Deleted)), Expression.Constant(false)), parameter);
type.SetQueryFilter(deletedCheck);
}
builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
builder.ApplySeedsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
}

IdentityServer4 Reject Token Request If Custom Parameter Not Valid

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>();

cache TryGetValue versus Get

This is how I am implementing my CacheManager. The problem I am facing is that TryGetValue will always return null in RemoveFromCache function. This function is called after one of the tokens has expired and so I am trying to clear that token from the List in cache, while GetAllTokens is returning full list of all tokens. AddTokenToCache is working correctly.
Its a WebAPI on ASPNET-Core 3.0
CacheManager.cs
public class CacheManager : ICacheManager
{
private IMemoryCache _cache;
public CacheManager(IMemoryCache cache) {
_cache = cache;
}
public void AddTokenToCache(string appName, string tokenString)
{
List<Token> tokens = new List<Token>();
//save this token against the application record in-memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens))
{
if (tokens == null)
tokens = new List<Token>();
}
tokens.Add(new Token
{
AppName = appName,
GeneratedAt = DateTime.Now,
TokenId = tokenString
});
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
;// .SetSlidingExpiration(TimeSpan.FromSeconds(180)); //3 minutes
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
}
public List<Token> GetAllTokens()
{
return _cache.Get<List<Token>>(CacheHelper.CacheKey_Tokens);
}
public bool RemoveFromCache(string tokenId)
{
List<Token> tokens = new List<Token>();
//remove this token from memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens)) {
return false;
}
else
{
if (tokens != null && tokens.Count > 0)
{
//_logger.LogInfo("Processing token");
//trimming quotations from the string
tokenId = tokenId.Substring(1, tokenId.Length - 2);
int index = tokens.FindIndex(t => t.TokenId == tokenId);
if (index >= 0)
tokens.RemoveAt(index);
var cacheEntryOptions = new MemoryCacheEntryOptions();
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
return true;
}
}
return false;
}
}
My calling sequence is:
AddTokenToCache (token is added successfully to cache)
GetAllToken (shows a token is added to cache)
AddTokenToCache (token is added successfully to cache)
GetAllToken (shows both tokens are added to cache)
Fire TokenExpired event which calls RemoveFromCache (tokens is null)
GetAllToken (shows both tokens are added to cache)
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILoggerManager, LoggerManager>();
services.AddMemoryCache();
services.AddDbContext<GEContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
services.AddRazorPages();
services.AddSingleton<ICacheManager, CacheManager>();
RegisterHandlerforTokenExpiredEvent(services);
//other code removed for brevity
}
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
{
var sp = services.BuildServiceProvider();
var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher
var cacheManager = sp.GetService<ICacheManager>(); //subscriber
jwtManager.TokenExpired += cacheManager.OnTokenExpired;
}
That's because you built another ServiceProvider by services.BuildServiceProvider():
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
{
var sp = services.BuildServiceProvider(); // this is a different service provider from the default one built by ASP.NET Core itself.
var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher
var cacheManager = sp.GetService<ICacheManager>(); //subscriber
// it doesn't work because the cacheManager is not the same instance that you use in the controllers
jwtManager.TokenExpired += cacheManager.OnTokenExpired;
}
As a result, the ICacheManager instance you get is NOT the same singleton that you inject in Controllers/Other Services. In other words, you'll have two different ICacheManager instance !
As a golden rule, DO NOT build another copy of ServiceProvider by services.BuildServiceProvider() in you application layer code unless you're pretty sure it's fine for you.
How to fix
Instead of building another copy of service provider and then getting another instance, you should always use IoC instead of Service Locator Pattern.
Seems that your JWTAuthenticationManager is a singleton and you want to bind the Event handler at startup-time. If that's the case, you could register an HostedService.
public class MyHostedService : IHostedService
{
private readonly IJWTAuthenticationManager _jWTAuthManager;
private readonly ICacheManager _cacheManager;
// suppose your IJWTAuthenticationManager is a singleton service
public MyHostedService(IJWTAuthenticationManager jWTAuthManager, ICacheManager cacheManager)
{
this._jWTAuthManager = jWTAuthManager;
this._cacheManager = cacheManager;
}
public Task StartAsync(CancellationToken cancellationToken)
{
this._jWTAuthManager.TokenExpired += this._cacheManager.OnTokenExpired;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this._jWTAuthManager.TokenExpired -= this._cacheManager.OnTokenExpired;
return Task.CompletedTask;
}
}
and register this service within Startup:
services.AddHostedService<MyHostedService>();
Another way that doesn't need HostedService and starts at start-up time:
Get the service and bind the event before Host.Run():
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
jwtMgr.TokenExpired = cacheMgr.OnTokenExpired;
host.Run();
}

ASP.NET Core 2.2 Create IdentityUser

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();

SignalR OnDisconnected event not persisting data to DB

I have a SignalR hub in which I'm injecting service classes which persist data to a local SQL Server instance via Castle Windsor.
The hub looks like:
[Authorize]
public class MyHub : Hub
{
private readonly IHubService _hubService;
private readonly IHubUserService _hubUserService;
private readonly IUserService _userService;
public MyHub(IHubService hubService, IHubUserService hubUserService, IUserService userService)
{
_hubService = hubService;
_hubUserService = hubUserService;
_userService = userService;
}
public async Task JoinHub(Guid hubId)
{
var hub = _hubService.GetHubById(hubId);
if (hub == null)
throw new NotFoundException(String.Format("Hub ({0}) was not found.", hubId.ToString()));
var userName = Context.User.Identity.Name;
var user = _userService.GetUserByUserName(userName);
if (user == null)
throw new NotFoundException(String.Format("User ({0}) was not found.", userName));
var hubUser = new HubUser
{
User = user,
Hub = hub,
ConnectionId = Context.ConnectionId
};
// Persist a new HubUser to the DB
hubUser = _hubUserService.InsertHubUser(hubUser);
await Groups.Add(Context.ConnectionId, hub.Id.ToString());
Clients.Group(hub.Id.ToString()).addChatMessage(userName + " has joined.");
}
public async Task LeaveHub()
{
var userName = Context.User.Identity.Name;
var hubUser = _hubUserService.GetHubUserByUserName(userName);
// Removes HubUser from the DB
_hubUserService.RemoveHubUser(hubUser);
await Groups.Remove(Context.ConnectionId, hubUser.Hub.Id.ToString());
Clients.Group(hubUser.Hub.Id.ToString()).addChatMessage(userName + " has left.");
}
public override Task OnDisconnected(bool stopCalled)
{
var userName = Context.User.Identity.Name;
var hubUser = _hubUserService.GetHubUserByUserName(userName);
// Removes HubUser from the DB
_hubUserService.RemoveHubUser(hubUser); // This line executes but does not persist anything to DB
Groups.Remove(Context.ConnectionId, hubUser.Hub.Id.ToString());
Clients.Group(hubUser.Hub.Id.ToString()).addChatMessage(userName + " has left.");
return base.OnDisconnected(stopCalled);
}
}
When calling JoinHub and LeaveHub methods from the client, everything works fine. However, when the OnDisconnected method fires, nothing is deleted from the database. I can see that the code does indeed execute, but the record remains in the DB and does not get deleted.
I'm wondering if perhaps my nhibernate session is not committing the transaction to the database due to castle windsor's dependency lifetimes or something, however, it's odd that LeaveHub executes as expected but the same code does not in the OnDisconnected method.
My dependencies are registered with the following configuration as per this blog post.
Kernel.Register(
//Nhibernate session factory
Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,
//Nhibernate session
Component.For<ISession>().UsingFactoryMethod(kernel => kernel.Resolve<ISessionFactory>().OpenSession()).LifeStyle.HybridPerWebRequestTransient()
);
and I also register an interceptor to implement a unit of work pattern:
// Unitofwork interceptor
Component.For<NhUnitOfWorkInterceptor>().LifeStyle.HybridPerWebRequestTransient()
If anyone can give any input on why the method LeaveHub works correctly and why it fails to persist anything in the OnDisconnected method, that'd be greatly appreciated.
Just an FYI Nhibernate Sessions don't do so well using async as they are not threadsafe at all. Try running things synchronously and see what you get.
Is Nhibernate set to flush on transaction commit? I can't comment becasue I am a newbie but I ran into this issue some time ago. I am not using FluentNhibernate but I am sure there is a config option to set flush on transaction commit. This is assuming you are wrapping all open session calls in a transaction. I use something like this for sessions. Also go get Nhibernate Profiler it is a godsend.
public class SessionManager : ISessionManager
{
private readonly ISessionFactory _sessionFactory;
private ISession _currentSession;
private ITransaction _currentTransaction;
public SessionManager(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
public ISession OpenSession()
{
if (CurrentSessionContext.HasBind(_sessionFactory))
{
_currentSession = _sessionFactory.GetCurrentSession();
}
else
{
_currentSession = _sessionFactory.OpenSession();
CurrentSessionContext.Bind(_currentSession);
}
CurrentSessionContext.Bind(_currentSession);
_currentTransaction = _currentSession.BeginTransaction();
return _currentSession;
}
public void Dispose()
{
try
{
if (_currentTransaction != null && _currentTransaction.IsActive)
_currentTransaction.Commit();
}
catch (Exception)
{
if (_currentTransaction != null) _currentTransaction.Rollback();
throw;
}
finally
{
if (_currentSession != null)
{
if (_currentTransaction != null) _currentTransaction.Dispose();
_currentSession.Close();
}
}
}
}
Here is my configuration, I am using it on several apps. On a side not there is a reason I don't use FluentNhibernate, The mapping by code built in is awesome and flexible. Let me know I can send you some sample mappings.
public class SessionFactoryBuilder
{
public static ISessionFactory BuildSessionFactory(string connectionString)
{
var cfg = new Configuration();
cfg.DataBaseIntegration(db =>
{
db.Dialect<MsSql2012Dialect>();
db.Driver<Sql2008ClientDriver>();
db.ConnectionString = connectionString;
db.BatchSize = 1500;
db.LogSqlInConsole = false;
db.PrepareCommands = true;
db.ConnectionReleaseMode = ConnectionReleaseMode.AfterTransaction;
db.IsolationLevel = IsolationLevel.ReadCommitted;
})
.SetProperty(Environment.CurrentSessionContextClass, "web")
.SetProperty(Environment.UseSecondLevelCache, "true")
.SetProperty(Environment.ShowSql, "true")
.SetProperty(Environment.PrepareSql, "true")
.Cache(c =>
{
c.UseQueryCache = true;
c.Provider<RtMemoryCacheProvider>();
c.DefaultExpiration = 1440;
}).SessionFactory().GenerateStatistics();
HbmMapping mapping = GetMappings();
cfg.AddDeserializedMapping(mapping, "AppName");
SchemaMetadataUpdater.QuoteTableAndColumns(cfg);
return cfg.BuildSessionFactory();
}
private static HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.AddMappings(typeof (UserMap).Assembly.GetTypes());
HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
return mapping;
}
}
Here is a neat little bit for managing SignalR dependencies with Castle. You may want to give this a try just for giggles.
public class SignalRDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver
{
private readonly IWindsorContainer _container;
public SignalRDependencyResolver(IWindsorContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
_container = container;
}
public override object GetService(Type serviceType)
{
return TryGet(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return TryGetAll(serviceType).Concat(base.GetServices(serviceType));
}
[DebuggerStepThrough]
private object TryGet(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch (Exception)
{
return null;
}
}
private IEnumerable<object> TryGetAll(Type serviceType)
{
try
{
Array array = _container.ResolveAll(serviceType);
return array.Cast<object>().ToList();
}
catch (Exception)
{
return null;
}
}
}
Put this in global asax before you set your controller factory
// SignalR
_container.Register(Classes.FromThisAssembly().BasedOn(typeof(IHub)).LifestyleTransient());
SignalRDependencyResolver signalRDependencyResolver = new SignalRDependencyResolver(_container);
Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver = signalRDependencyResolver;