Dispose ApplicationDbContext in ASP.NET Core - c#

I have a web application and host it in Azure but the problem is that the SQL pool gets full fast regardless of tier.
I was thinking that maybe I need to dispose ApplicationDbContext, is this required? would this help?
The following is a part of my class with the method Index utilizing most of SQL time:
private readonly ApplicationDbContext _context;
private readonly IEmailSender _emailSender;
public MessagesController(ApplicationDbContext context, IEmailSender emailSender)
{
_context = context;
_emailSender = emailSender;
}
// GET: Message
[Authorize]
public async Task<IActionResult> Index()
{
var user = _context.Users.Where(u => u.Email.Equals(User.Identity.Name)).Select(u =>
new { u.Name, u.Subdomain, u.PhotoURL }).FirstOrDefault();
ViewData["Name"] = user.Name;
ViewData["Subdomain"] = user.Subdomain;
ViewData["PhotoURL"] = (user.PhotoURL == null) ? "../../img/avatar.png" : user.PhotoURL;
List<Message> messages = await _context.Messages.Where(m => m.UserName.Equals(User.Identity.Name))
.Select(m => new Message { ID = m.ID, DateTime = m.DateTime, Text = m.Text }).ToListAsync();
return View(messages);
}
Should I call _context.dispose() although I'm using the same context in other ActionResult methods?

No, you don't have to call _context.dispose() - the IoC container will release resources based on the lifetime setting which was used while registering them.
What is more, you wrote that "I'm using the same context in other ActionResult methods?". That's not true - well at least it shouldn't be true. The context should be created per request. So that while calling each of the controller actions your are generating new request and container is creating new context to be injected (each time when new request is done the controller is created once again).
Here is how you should register your context:
services.AddDbContext<SchoolContext>(options => options.UseSqlServer(CONNECTION_STRING));
By this you are using default lifetime for DbContext - scoped.

Related

I am facing issue thread related issue in asp.net core identity in asp.net core?

Error Message :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
public async Task<UserSearchDto> GetSingle(string userId, string baseUrl)
{
var user =await _userManager.FindByIdAsync(userId);
if (user != null)
{
UserSearchDto userSearches = new UserSearchDto
{
data
};
return userSearches;
}
}
In above service FindByIdAsync throwing this exeption
while i am debugging step by step then i am not facing this error
my setup in startup file as below
services.AddTransient<IAuthService, AuthService>();
Even i changed above service method but its not working
why it requires more time to perform or there is any another issue?
Edit
these manager are passed in service
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
this is ctor
public AuthService(UserManager<ApplicationUser> _userManager,
RoleManager<ApplicationRole> _roleManager,
IConfiguration configuration) : base(configuration)
{
this._userManager = _userManager;
this._roleManager = _roleManager;
}
User manage and role manager are used from Microsoft.AspNetCore.Identity
services.AddDbContext<Db>(options =>
{
options.UseSqlServer(mySqlConnectionStr);
}
);
The DbContext has a scoped service lifetime, coupled to an asp.net request. Thus services using the context should preferably also have a scoped service lifetime.
I can recommend you such approach (TModel can be yours UserSearchDto):
// Or your db context directly in class but this is better
private readonly IServiceScopeFactory _factory;
public async Task<TModel> FindByIdAsync(ulong id)
{
using var scope = _factory.CreateScope();
// your context gets here
await using var userManager = scope.ServiceProvider.GetRequiredService<UserManagerContext>();
// this is important
var entities = userManager.Set<TModel>().AsNoTracking();
// data should be filled after FindByIdAsync(ulong id), not in this method
return await entities.FirstOrDefaultAsync(t => t.Id == id);
}

Blazor Server and EF Core: A second operation was started on this context instance before a previous operation completed

I have problem with ef core. I have two services which read data from database. On one page is call first service and on second page is called second service. When i click to button for create a new program i got error. I call it normally from page with inject service. Can anybody help me with it?
Show in application
builder.Services.AddDbContextPool<Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Connection"));
});
TestService1:
public class TestService1 : ITestService1
{
private readonly Context _context;
private readonly IMapper _mapper;
public TestService1(Context context, IMapper mapper)
{
_kreativgangContext = kreativgangContext;
_mapper = mapper;
}
public virtual async Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter)
{
var model = new AllProgramViewModel();
var data = _context.Programs.Where(x => (EF.Functions.Like(x.Name ?? "", "%" + filter.Name + "%") || string.IsNullOrEmpty(filter.Name)))
.Select(x => new Core.Models.Program() { ID = x.ID, Name = x.Name, Order = x.Order });
result.Model.TotalCount = await data.CountAsync();
result.Model.Items = data.Select(x => _mapper.Map<AllProgramItemViewModel>(x));
return model;
}
}
public interface ITestService1
{
public Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter);
}
Test service 2:
public class TestService2 : ITestService2
{
private readonly Context _context;
public TestService2(Context context)
{
_context = context;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
var model = new NewProgramViewModel();
List<ProgramOrderViewModel> items = _context.Programs.Select(x => new Core.Models.Program() { Order = x.Order, ID = x.ID })
.Select(x => new ProgramOrderViewModel()
{
ID = x.ID,
Order = x.Order
}).ToList();
return await Task.FromResult(model);
}
}
public interface ITestService2
{
public Task<NewProgramViewModel> HandleAsync();
}
Error:
Error: System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Mitar.Kreativgang.Admin.Handlers.TestService2.HandleAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Handlers\TestService2.cs:line 26
at Mitar.Kreativgang.Admin.Pages.Program.ProgramNew.OnInitializedAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Pages\Program\ProgramNew.razor:line 114
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
This is a known and documented pitfall, explained in ASP.NET Core Blazor Server with Entity Framework Core (EFCore). In Blazor Server, the DI scope is the user circuit - essentially the user session. That means that a scoped service like TestService2 or a DbContext will remain in memory for a long time and end up reused by multiple methods and actions.
As the docs explain :
Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a circuit. One example of user state is data held in dependency injection (DI) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core.
You need to register and use a DbContextFactory (or PooledDbContextFactory) instead of a DbContextPool, and create a new DbContext instance right where it's used.
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
or
builder.Services.AddPooledDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
The service constructors should accept the factory instead of a context :
public TestService2(AddDbContextFactory<ContactContext> factory)
{
_factory = factory;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
using var context=_factory.CreateContext())
{
...
}
}
Component Scope
To limit a DbContext's scope to a single component it's not enough to just inject the DbContextFactory. The DbContext instance needs to be explicitly disposed when the user navigates away from the component. To do this, the component needs to implement IDisposable. This is explained in the section Scope to the component lifetime
#implements IDisposable
#inject IDbContextFactory<ContactContext> DbFactory
...
#code
{
ContactContext? Context;
public void Dispose()
{
Context?.Dispose();
}
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
...
}
}

Why is AppTenant null only while seeding data?

I'm currently fiddeling around with Ben Fosters Saaskit.
I have extended the ApplicationUser with a AppTenantId property and created a custom UserStore, which uses the AppTenant to identify the user:
public class TenantEnabledUserStore : IUserStore<ApplicationUser>, IUserLoginStore<ApplicationUser>,
IUserPasswordStore<ApplicationUser>, IUserSecurityStampStore<ApplicationUser>
{
private bool _disposed;
private AppTenant _tenant;
private readonly ApplicationDbContext _context;
public TenantEnabledUserStore(ApplicationDbContext context, AppTenant tenant)
{
_context = context;
_tenant = tenant;
}
/*... implementation omitted for brevity*/
}
If a user registers or logs in, this works fine. The AppTenant is set correctly. The problem occurs, when SeedData.Initialize(app.ApplicationServices); is called at the end of my Statup.Configure() method:
public static class SeedData
{
public async static void Initialize(IServiceProvider provider)
{
using (var context = new ApplicationDbContext(
provider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
var admin = new ApplicationUser
{
AppTenantId = 1,
Email = "foo#bar.com",
UserName = "Administrator",
EmailConfirmed = true
};
if(!context.Users.Any(u => u.Email == admin.Email))
{
var userManager = provider.GetRequiredService<UserManager<ApplicationUser>>();
await userManager.CreateAsync(admin, "Penis123#");
}
context.SaveChanges();
}
}
}
The usermanager is is calling the custom userstore, but now AppTenant is null.
When the code finally reaches
public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return _context.Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.AppTenantId == _tenant.AppTenantId, cancellationToken);
}
I am facing a System.InvalidoperationException, because AppTenant is passed as null in the constructor of above mentioned userstore.
What am I doing wrong? Am I seeding the wrong way or do I forget something fundamental here?
Update:
For now I have taken the crowbar-approach, avoided the usermanager and created my own instance of a userstore with a mock AppTenant:
if (!context.Users.Any(u => u.Email == admin.Email))
{
var userStore = new TenantEnabledUserStore(context, new AppTenant
{
AppTenantId = 1
});
await userStore.SetPasswordHashAsync(admin, new PasswordHasher<ApplicationUser>().HashPassword(admin, "VeryStrongPassword123#"), default(CancellationToken));
await userStore.SetSecurityStampAsync(admin, Guid.NewGuid().ToString("D"), default(CancellationToken));
await userStore.CreateAsync(admin, default(CancellationToken));
}
Nontheless, I'm still interested in a more clean approach, that doesn't feel that hacky.
When using Saaskit, you configure an AppTenantResolver that determines how to set the TenantContext<T> based on the provided HttpContext. It then stores the retrieved TenantContext<T> in the Items property of the HttpContext. This is a Scope level cache, so the tenant is only stored there for the duration of the request.
When you inject an AppTenant into a class it attempts to resolve it from HttpContext.Items. If no tenant is found, then it injects null instead.
When you call SeedData.Initialize(app.ApplicationServices), you are not in the context of a request, and so the AppTenantResolver middleware has not run, and will not have resolved an AppTenant.
Unfortunately, not having the full details of your code, it's hard to say exactly how to fix your issue. You would need to make sure you create a new Scope in your SeedData method and resolve an AppTenant within that scope so that subsequent calls to the IoC will allow it to be inserted.

'Cannot access a disposed object' error on UserManager.ResetPasswordAsync

I'm getting the following error when executing userManager.ResetPasswordAsync:
An unhandled exception occurred while processing the request.
ObjectDisposedException: Cannot access a disposed object.
Object name: 'TestDb'.
Microsoft.Data.Entity.DbContext.get_ServiceProvider()
I simplified the code so that it's easier to read. I'm calling the userManager twice in the controller lifetime. Once for generating the token and once for resetting the password:
private readonly UserManager<ApplicationUser> userManager;
// controller's constructor
public AuthController(UserManager<ApplicationUser> userManager) {
this.userManager = userManager;
}
[AllowAnonymous, HttpPost]
public async Task<ActionResult> ForgotPass(ForgotPassViewModel model) {
//model checks
var user = new UserQuery(db).GetUserByUserName(model.UserName);
//check if user exists
var token = await userManager.GeneratePasswordResetTokenAsync(user);
var url = $"{config.Url}/auth/resetpass?user={user.Id}&token={WebUtility.UrlEncode(token)}";
// send email with the reset url
model.Success = "An email has been sent to your email address";
return View(model);
}
[AllowAnonymous, HttpPost]
public async Task<ActionResult> ResetPass(ResetPassViewModel model) {
//model checks
var user = new UserQuery(db).GetUserById(model.UserId);
//error occurs here:
var result = await userManager.ResetPasswordAsync(user, model.Token, model.Password);
//check result
model.Success = "Password successfully reset";
return View(model);
}
Later Edit:
Here's a function from the UserQuery (as requested in comments below). I am indeed using the 'using' wrapper:
public ApplicationUser GetUserByUserName(string userName) {
using (var db = this.dbContext) {
var user = (from u in db.Users
where u.UserName == userName
select u).SingleOrDefault();
return user;
}
}
The using construct is a syntactic sugar around a
DbContext context = null;
try
{
context = new DbContext();
...stuff inside the using block ...
}
finally
{
if(context!=null)
context.Dispose()
}
It's same as calling
using(DbContext context = new DbContext())
{
...stuff inside the using block ...
}
block. This makes sure that the object is disposed as soon as possible and even when an exception happens (finally block is always called).
The DbContext in ASP.NET Core (specifically the ASP.NET Core Identity registration of it) is registered as with scoped life time, this means that the same reference will be returned each for the duration of the one request.
But when you prematurely dispose it (either with using block or by calling .Dispose() method yourself) before the request ends, it blows up when another method tries to access it.
The scoped life time is the recommended one, as the DbContext can use considerable amount of memory when it is very long living, because DbContext tracks changes of all records until you dispose it.
So in traditional applications without dependency injection or simple tutorials you create it with new and dispose it as soon as possible. But in an Web Application a request is pretty short-lived and scoped life-time keeps handle for most of the cases. There may be some corner cases where transient (AddTransient method in ASP.NET Core IoC container) lifetime is better.
If you really need transient resolution you could create a factory method and inject it to your services, something like:
services.AddTransient<Func<MyDbContext>>( (provider) => new Func<MyDbContext>( () => new MyDbContext()));
and inject it in your services/controller:
public class MyService
{
public readonly Func<MyDbContext> createMyContext;
public MyService(Func<MyDbContext> contextFactory)
{
this.createContext = contextFactory;
}
public User GetUserById(Guid userId)
{
// note we're calling the delegate here which
// creates a new instance every time
using(var context = createContext())
{
return context.User.FirstOrDefault(u => u.Id = userId);
}
}
}
This won't cause the issue, but is more complicated than necessary. And if you need transactions this may not play well as the transactions are per DbContext instance and Identity will always use the scoped one
It seems the method userManager.ResetPasswordAsync() used some delay load property of the user variable. Since the user variable is out of the DB query scope, so that the property is not accessible.
I replaced my custom User queries with the built in userManager queries that do the same thing and it works now:
In ForgotPass function:
var user = await userManager.FindByEmailAsync(model.UserName);
In ResetPass function:
var user = await userManager.FindByIdAsync(model.UserId);
I'll update the answer once I know exactly why my initial approach didn't work.

How to mock out the UserManager in ASP.NET 5

I am writing a UI for managing users in an ASP.NET 5 app. I need to show any errors returned by the UserManager in the UI. I have the IdentityResult errors being passed back in the view model but I am a touch adrift when it comes to testing my code.
What is the best way to Mock the UserManager in ASP.NET 5?
Should I be inheriting from UserManager and overriding all the methods I am using and then injecting my version of UserManager into an instance of the Controller in my test project?
I have managed it with the help of the MVC Music Store sample application.
In my Unit Test class, I set up the database context and UserManager like this:
public class DatabaseSetupTests : IDisposable
{
private MyDbContext Context { get; }
private UserManager<ApplicationUser> UserManager { get; }
public DatabaseSetupTests()
{
var services = new ServiceCollection();
services.AddEntityFramework()
.AddInMemoryDatabase()
.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase());
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MyDbContext>();
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
var serviceProvider = services.BuildServiceProvider();
Context = serviceProvider.GetRequiredService<MyDbContext>();
UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
}
....
}
Then I can use the UserManager in my unit tests, for example:
[Fact]
public async Task DontCreateAdminUserWhenOtherAdminsPresent()
{
await UserManager.CreateAsync(new ApplicationUser { UserName = "some#user.com" }, "IDoComplyWithTheRules2016!");
...
}
If your Dependency Injector is not able to resolve an IHttpContextAccessor then you will not be able to create a UserManager instance due to it being dependent on it.
I think (and this is just an assumption), that with Asp.Net 5, the UserManager does take care of refreshing cookie based claims when you change them (claims, roles...) for a user and therefore requires some HttpContext for login / logout actions and cookie access.

Categories

Resources