'Cannot access a disposed object' error on UserManager.ResetPasswordAsync - c#

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.

Related

EF Core - API Site: A second operation was started on this context before a previous operation completed

I know there are plenty of other threads covering this topic, but none of the solutions appear to have helped. Hence the creation of this one. Sorry for the lengthy Post!
The problem:
I am running an .Net Core API Website using EF Core. Naturally I am ensuring the context is Transient and created per request. Passing the Context through classes to ensure it's not recreated or executing two queries at the same time. I can debug and follow my code through without error. But once in production environment, I still run into this problem, and I've exhausted all of the top google result solutions...
The actual error:
A second operation was started on this context 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.
services.AddDbContext<APIContext>((serviceprovider, options) =>
{
options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection"));
options.UseInternalServiceProvider(serviceprovider);
});
I've tried:
Several different ways of registering my context, using all the different constructors like below. I've even tried creating as a DbContextPool instead with no luck.
services.AddDbContext<APIContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")), ServiceLifetime.Transient, ServiceLifetime.Transient);
Every one of my controllers or middleware, have the DBContext passed via the constructor as DI. Despite a few Async calls (all using await). I can not find any area that may cause this error. I've added a code sample below following a recent error, code removed for brevity.
To provide some context, every request enters my middleware for security, it's the catch in here that notifies me of the error, so I'm not sure if that's the cause? Am I registering the Interface /Middleware correctly?
//Register Request logger
services.AddTransient<IRequestLogger, RequestLoggerConcrete>();
//Constructor for Request Logger
public RequestLoggerConcrete(APIContext context)
{
_context = context;
}
//Method Called in Middleware
public async void InsertLoggingData(LoggerEntity loggerTB)
{
try
{
loggerTB.LoggerID = 0;
_context.Loggers.Add(loggerTB);
await _context.SaveChangesAsync();
}
catch (Exception)
{
throw;
}
}
//The Middleware Constructor
public ApiKeyValidatorsMiddleware(RequestDelegate next, IValidateRequest ivalidaterequest, IRequestLogger irequestlogger)
{
_next = next;
_IValidateRequest = ivalidaterequest;
_IRequestLogger = irequestlogger;
}
//Middleware Execution
public async Task Invoke(HttpContext httpContext)
{
try
{
..... Validation - API Code Code Removed ....
_IRequestLogger.InsertLoggingData(loggertb);
await _next.Invoke(httpContext);
}
catch(Exception ex) {
...Notify Email Removed...
}
}
//Controller Init
public SMSController(APIContext db)
{
_db = db;
}
//Sample Method
public ActionResult GetMessages()
{
return Json(SMSMessages.GetMessages(_db));
}
//Class Code
public static SMSMessageSimple GetMessages(APIContext _db)
{
var lst = (from sms in _db.SMSs
where sms.SentResultCode == null
&& sms.SentToDevice == false
select new SMSMessage()
{
message = sms.Message,
recepient = sms.ToNumber,
id = sms.SMSID.ToString()
}).ToArray();
var DistinctGuids = lst.Select(s => Convert.ToInt32(s.id)).Distinct();
DateTime dtNow = System.DateTime.Now.ToGMT();
_db.SMSs.Where(w => DistinctGuids.Contains(w.SMSID)).Update(u => new SMSEntity()
{
SentToDevice = true,
ModifiedBy = "API",
ModifiedDate = dtNow
});
return new SMSMessageSimple(lst);
}
I get this error on methods that don't even make any calls to the Database, yet on some, the log is inserted but the error persists.. Could it be the app pool? If two separate requests are made at the same time?
Any guidance is appreciated!!

Need async entrypoint for custom configuration of DbContext

Net Core and EF core does not support AAD tokens out of the box like full framework. There are a workaroudn were you can set access token on the SqlConnection. Retrieving the token is a async operation. So I need a generic entrypoint that are async. In constructor of my DbContext I can inject and execute stuff, but I cant do it async so it not good enough.
Any ideas? Thanks
internal class DbTokenConfig : IDbContextConfig
{
private readonly ITokenProvider _tokenProvider;
public DbTokenConfig(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public async Task Config(MyDbContext context)
{
var conn = context.Database.GetDbConnection() as SqlConnection;
conn.AccessToken = await _tokenProvider.GetAsync();
}
}
I need a async entrypoint were I can execute it, generic offcourse so any service that inject a DbContext will get it applied
edit: So basicly when doing
public class MyCommandHandler : ICommandHandler<MyCommand>
{
private readonly DbContext _ctx;
public MyCommandHandler(DbContext ctx)
{
_ctx = ctx;
}
public async Task Handle(MyCommand cmd)
{
await _ctx.Set<Foo>().ToListAsync(); //I want my access token to be applied before it opens connection
}
}
edit: Working solution
.AddDbContext<MyDbContext>(b => b.UseSqlServer(Configuration.GetConnectionString("MyDb")))
.AddScoped<DbContext>(p =>
{
var ctx = new AuthenticationContext("https://login.microsoftonline.com/xxx");
var result = ctx.AcquireTokenAsync("https://database.windows.net/", new ClientCredential("xxx", "xxx"))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
var db = p.GetService<MyDbContext>();
((SqlConnection)db.Database.GetDbConnection()).AccessToken = result.AccessToken;
return db;
})
Just need to make the keys configurable, create a abstraction etc
There's a Github issue about this, so this is definitely not unclear. The issue is closed because there's no built-in support currently, a different issue tracks this.
The original issue describes a clever workaround though. First of all, UseSqlBuilder has an overload that accepts an existing DbConnection. This connection can be configured with an AAD token. If it's closed, EF will open and close it as needed. One could write :
services.AddDbContext<MyDBContext>(options => {
SqlConnection conn = new SqlConnection(Configuration["ConnectionString"]);
conn.AccessToken = (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/")
.Result;
options.UseSqlServer(conn);
});
The tricky part is how to dispose that connection.
The clever solution posted by Brian Ball is to implement an interface on the DbContext, and register that as the service that's used by controllers with a factory function. The DbContext still gets registered using its concrete type. The factory function gets that context and sets the AAD token to its connection :
services.AddDbContext<MyDbContext>(builder => builder.UseSqlServer(connectionString));
services.AddScoped<IMyDbContext>(serviceProvider => {
//Get the configured context
var dbContext = serviceProvider.GetRequiredService<MyDbContext>();
//And set the AAD token to its connection
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
if(connection == null) {/*either return dbContext or throw exception, depending on your requirements*/}
connection.AccessToken = //code used to acquire an access token;
return dbContext;
});
This way, the context's lifetime is still managed by EF Core. AddScoped<IMyDbContext> acts as a filter that takes that context and sets the AAD token
Next problem is how to write that //code used to acquire an access token; so it doesn't block.
This isn't so much of a problem because, according to the docs :
The AzureServiceTokenProvider class caches the token in memory and retrieves it from Azure AD just before expiration.
This code could be extracted into a factory method, and even get injected as a dependency.
Moving the goal posts
The main problem is that constructors can't be asynchronous yet so constructor injection can't retrieve tokens asynchronously.
What can be done though, is to register an asynchronous Func<> factory or service that's called in a controller's asynchronous actions instead of the constructor. Let's say :
//Let's inject configuration too
//Defaults stolen from AzureServiceTokenProvider's source
public class TokenConfig
{
public string ConnectionString {get;set;};
public string AzureAdInstance {get;set;} = "https://login.microsoftonline.com/";
public string TennantId{get;set;}
public string Resource {get;set;}
}
class DbContextWithAddProvider
{
readonly AzureServiceTokenProvider _provider;
readonly TokenConfig _config;
readonly IServiceProvider _svcProvider;
public DbContextWithAddProvider(IServiceProvider svcProvider, IOption<TokenConfig> config)
{
_config=config;
_provider=new AzureServiceTokenProvider(config.ConnectionString,config.AzureAdInstance);
_svcProvider=svcProvider;
}
public async Task<T> GetContextAsync<T>() where T:DbContext
{
var token=await _provider.GetAccessTokenAsync(_config.Resource,_config.TennantId);
var dbContext = _svcProvider.GetRequiredService<T>();
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
connection.AccessToken = token;
return dbContext;
}
}
This service should be registered as a singleton as it doesn't keep any state except the cached token, which we do want to keep around.
This can now be injected in a constructor, and called in an async action :
class MyController:Controller
{
DbContextWithAddProvider _ctxProvider;
public MyController(DbContextWithAddProvider ctxProvider)
{
_ctxProvider=ctxProvider;
}
public async Task<IActionResult> Get()
{
var dbCtx=await _ctxProvider.GetContextAsync<MyDbContext>();
...
}
}
I went through a similar though process almost 2 years ago where, in my last job, we decided to implement dynamic refreshing of the credentials for a DbContext object which it retrieved from Key Vault on the applications initial startup and then cached the credentials, if a connection failed then it was assumed that the credentials had changed or expired and it would retrieve them again and refresh the SqlConnection object (happy-path scenario, obviously there are other reasons for a connection to fail).
The problem then, and in this case, is that IServiceCollection has no asynchronous method available which allow you to invoke asynchronous delegates, so you have to use .Result when registering a service with asynchronous logic as a prerequisite.
What you could do is create a SqlConnection object with your access token and pass that to SqlServerDbContextOptionsExtensions.UseSqlServer within the AddDbContext<T> service registration in ConfigureServices. This ensures that every DbContext which is created will have an access token assigned, and with it being scoped by default it will have a new token per request.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<ITokenProvider, TokenProvider>();
services.AddScoped<ISqlConnectionProvider, SqlConnectionProvider>();
services.AddDbContext<TestDbContext>((provider, options) =>
{
var connectionTokenProvider = provider.GetService<ITokenProvider>();
var sqlConnectionProvider = provider.GetService<ISqlConnectionProvider>();
var accessToken = connectionTokenProvider.GetAsync().Result; // Yes, I consider this to be less than elegant, but marking this delegate as async & awaiting would result in a race condition.
var sqlConnection = sqlConnectionProvider.CreateSqlConnection(accessToken);
options.UseSqlServer(sqlConnection);
});
}
The interface for ISqlConnectionProvider is
internal interface ISqlConnectionProvider
{
SqlConnection CreateSqlConnection(string accessToken);
}
In the implementation of ISqlConnectionProvider you'd have to
Inject an IOptions<T> object which contains the connection string details
Build or assign the connection string
Assign the access token
Return the SqlConnection object

EF Core multiple HTTP requests throws an error

I cannot seem to find an answer to this question.
So in the frontend when the user loads a page we call an API for each item on that page (10 items). So that equals 10 API calls.
Most of the calls work but there are always a few that fail when trying to query the database resulting in the following error:
InvalidOperationException: A second operation started on this
context before a previous operation completed. Any instance members
are not guaranteed to be thread safe.
Now I understand that Entity Framework is not thread safe but I am unsure how to get around this error.
Everywhere where I am using a DBContext it is always injected in using the built in .net core Ioc container.
Here is the DI setup
services.AddScoped<IOmbiContext, OmbiContext>();
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
All of my repositories are setup in a Transient scope with the Context as Scoped according to this article: https://learn.microsoft.com/en-us/aspnet/core/data/entity-framework-6
Now I have tried changing the context to Transient and it still happens.
How can I avoid this?
More Information
The API Method:
[HttpGet("movie/info/{theMovieDbId}")]
public async Task<SearchMovieViewModel> GetExtraMovieInfo(int theMovieDbId)
{
return await MovieEngine.LookupImdbInformation(theMovieDbId);
}
Which eventually calls the following where the exception is being thrown:
public async Task<RuleResult> Execute(SearchViewModel obj)
{
var item = await PlexContentRepository.Get(obj.CustomId); <-- Here
if (item != null)
{
obj.Available = true;
obj.PlexUrl = item.Url;
obj.Quality = item.Quality;
}
return Success();
}
PlexContentRepository
public PlexContentRepository(IOmbiContext db)
{
Db = db;
}
private IOmbiContext Db { get; }
public async Task<PlexContent> Get(string providerId)
{
return await Db.PlexContent.FirstOrDefaultAsync(x => x.ProviderId == providerId); <-- Here
}
If you use Entity Framework Core usually you do not need to add your Database Context as an additional service
I recommend to setup your DbContext in the Startup.cs as following:
services.AddEntityFrameworkSqlServer()
.AddDbContext<OmbiContext>();
Followed by a Controller class for your API calls taking the DBContext as constructor parameter.
public class ApiController : Controller
{
protected OmbiContext ctx;
public ApiController(OmbiContext dbctx)
{
ctx = dbctx;
}
public async Task<IActionResult> yourAsyncAction()
{
// access ctx here
}
}

Do IDisposable objects get disposed at the end of a Nancy request using the RequestContainer?

I'm registering a DbContext onto the TinyIoCContainer that's passed into the ConfigureRequestContainer method on the DefaultNancyBootstrapper.
Whilst this works fine, I've noticed that the Dispose method on the context is never called once a request has completed. I'd expect the DbContext to be disposed of after a request to close the connection (we're using SQLite).
Q: Are disposable instances actually disposed at the end of a request within the TinyIoCContainer?
Bootstrapper
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
container.Register<IContext>((_,__) =>
{
// Code here to get connection string
return new Context(new SQLiteConnection(connString), true);
});
}
Context
public interface IContext : IDisposable
{
...
}
public class Context : DbContext, IContext
{
...
public new void Dispose()
{
base.Dispose(); // This never gets called
}
}
Update
The marked answer was ultimately correct. I basically had to do something like so:
if (string.IsNullOrEmpty(context.Request.UserHostAddress))
{
container.Register<IContext>((_,__) => null);
}
else
{
// Get username from request headers
// Build up SQLite connection string based off username
var dbContext = new Context(new SQLiteConnection(connString));
container.Register<IContext>(dbContext);
}
I think its because you're using the manual factory registration, it expects you to control lifetime yourself. You probably don't want to be using that anyway, as you are creating a new context every time you ask for one with the code you have there - switch it to an instance registration and you should be ok.
container.Register<IContext>(new Context(new SQLiteConnection(connString), true));
Haven't used TinyIoC a lot but this page says that registration per request is different, not sure if that should always be the case.
https://github.com/grumpydev/TinyIoC/wiki/Registration---lifetimes

AddScoped service is not retaining settings throughout the request

I have created an Interface
public interface ICurrentUser
{
Task<bool> Set(UserAuth user);
User Get();
}
and a class
public class CurrentUserSvc : Interface.ICurrentUser
{
private User _u;
private UserAuth _ua;
private AppDbContext db;
public CurrentUserSvc(AppDbContext db) {
this.db = db;
}
public User Get()
{
return _u;
}
public async Task<bool> Set(UserAuth ua)
{
_ua = ua; // this is the default EntityFramework IdentityUser
_u = await db.AppUsers // this is my applicaiton's 'extra settings'
// user used to ensure passowrd fields are
// not passed about everywhere
.Where(u => u.UserID == _ua.UserID)
.SingleAsync();
return true;
}
}
In Startup.cs I set
services.AddScoped<ICurrentUser, CurrentUserSvc>();
// I also add a service which will be used later in a scoped
// lifecycle (though I've also tried transient on that one)
services.AddScoped<IProductDbSvc, ProductDbSvc>();
Later I call to a piece of middleware:
public async Task<Task> Invoke(HttpContext hc)
{
if (hc.User.Identity.IsAuthenticated) {
UserAuth iu = await _um.FindByIdAsync(hc.User.GetUserId());
await _cus.Set(iu);
}
// the values are definitely set correctly here.
// I have inspected them during debug
return _next(hc);
}
Later still I try to access the content of the CurrentUserSvc I try to access the current user via the GET
public ProductDbSvc(AppDbContext db, ICurrentUser cu){
this.db = db;
this.cu = cu;
// the values in cu are NULL here. Get() returns null
this.CurrentUser = cu.Get();
}
but the result of Get() is null I was expecting that a Scoped param would retain the values set earlier in the request lifecycle.
What am I missing? Is there some other way to ensure the scoped-singleton retains the user data throughout the application's lifecycle.
UPDATE: I've created a generic project that illustrates this problem generically. https://github.com/AlexChesser/AspnetIdentitySample
check out the repo
build and run in visualstudio or DNX
register a local user
try to view the service on http://localhost:5000/api/currentuser
You'll notice that within the DEBUG output you can see that the correct user details are set, but within the actual controller itself the values returned are null.
UPDATE 2 the working sample is on this branch in github https://github.com/AlexChesser/AspnetIdentitySample/tree/dependencyinjectionscoped
UPDATE 3 turns out scoped parameters can be injected into the INVOKE method of custom middleware as well. https://github.com/AlexChesser/AspnetIdentitySample/commit/25b010a5ae45678c137b2ad05c53ccd659a29101 altering the invoke method will allow for scoped parameters to be injected correctly.
public async Task Invoke(HttpContext httpContext,
ICurrentUserService cus,
UserManager<ApplicationUser> um)
{
if (httpContext.User.Identity.IsAuthenticated)
{
ApplicationUser au = await um.FindByIdAsync(httpContext.User.GetUserId());
await cus.Set(au);
}
await _next(httpContext);
}
UPDATE 4 - I discovered an issue with my middleware signature last night which is pretty important. Code above has been edited to the correct form. Specifically the method was Task<Task> and return _next(...)
This was resulting in a "whitescreen" death on certain page loads (async called badly will not throw a stack trace)
By altering to a Task and using await next(...) the code functions properly and eliminates the intermittent whitescreen death caused by badly implemented async in dotnet5.
DbContext is a scoped service and as well as your CurrentUserSvc is a scoped service. Middlewares are instantiated only once for the whole running time of the app, so they are singleton essentially. So you need to remove both DbContext and CurrentUserSvc from being constructor injected here.
Instead you can use HttpContext's RequestServices property (which returns a IServiceProvider) to resolve both the DbContext and CurrentUserSvc services.
In the middleware, inject a dependency to IServiceProvider, rather than ICurrentUser. Then in the Invoke get the current user via serviceProvider.GetRequiredService<ICurrentUser>();

Categories

Resources