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
Related
I am trying to abstract away the create of an AmazonS3Client. For context this is done using this mechanism rather than the standard DI mechanism as the credentials needed for the client are only known at runtime after the credentials have been retrieved to assume a role. These credentials have a temporary access token.
I have created the following factory, which aims to return the same client to the caller until the credentials used within the client are invalid. After this I dispose of the client and instantiate a new one.
I want to ensure this is thread safe. As in caller one could have got the client and was about to use it. At the same time caller two tries to get a client, which was then deemed to have invalid credentials. The logic below then tries to dispose of the client. So I am concerned on the impact on caller one.
To add the following code belongs in a singleton service within .net core.
public class AmazonS3ClientFactory : IAmazonS3ClientFactory
{
private readonly IAmazonAssumeRoleService assumeRoleService;
private readonly Config config;
public AmazonS3ClientFactory(
IOptions<Config> config,
IAmazonAssumeRoleService assumeRoleService)
{
if (string.IsNullOrWhiteSpace(config?.Value.AssumedRoleArn))
{
throw new ArgumentNullException(nameof(config));
}
this.config = config?.Value;
this.assumeRoleService = assumeRoleService;
}
private IAmazonS3 Client { get; set; }
public async Task<IAmazonS3> GetClient(RegionEndpoint region)
{
if (this.Client != null && this.assumeRoleService.CredentialsExistAndAreValid())
{
return this.Client;
}
else
{
this.Client?.Dispose();
var credentials= await this.assumeRoleService.AssumeRoleAsync(this.config.AssumedRoleArn);
this.Client = new AmazonS3Client(assumeRoleCredentials, region);
return this.Client;
}
}
}
yes its thread safe
i asked same question from amazon's employee and his answer was yes
just enjoy :D
I am making a DLL to consume a REST API in aspnetcore.
Ideally, I would like it to be accessed this way:
API api = new API(clientInfo);
api.Module.Entity.Action(params);
But I am struggling to make that a reality. I can't make anything static because more than 1 session might be instanced at the same time. I can't pass the session around except by reference otherwise session state(cookies etc.) might change in the copy. Is there a design pattern I should be using?
public class API
{
private Session _session;
public API(ClientInfo clientInfo)
{
_session = new Session(clientInfo);
}
}
The session serves as middleware for the client, stores login data in case the client needs to repeat login, handles some errors/retries and exposes client methods.
public class Session
{
private Client _client;
private string _path;
public Session(ClientInfo clientInfo)
{
_client= new Client(clientInfo);
_path = clientInfo.Path;
}
public HttpResponseMessage Get(string name, string arguments = "")
{
return _client.Get(_path, name, arguments);
}
...
}
The client actually performs the calls.
public class Client
{
public HttpResponseMessage Get(string path, string endpointName, string arguments)
{
return GetClient().GetAsync(path + endpointName + arguments).Result;
}
private HttpClient GetClient(){...}
...
}
Personally, I just build a simple client for my APIs, with methods corresponding to the endpoints the API has:
public class FooClient
{
private readonly HttpClient _httpClient;
public FooClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<GetFooResult> Get(int id)
{
...
}
// etc
}
The HttpClient dependency is provided by registering a typed client in Startup.cs:
services.AddHttpClient<FooClient>(c =>
{
// configure client
});
And I add an IServiceCollection extension to encapsulate this and any other setup logic:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
{
...
}
}
Then, in my Startup, I can simply do something like:
services.AddFooClient(Configuration.GetValue<string>("FooUri"));
This is extremely helpful for setting up automatic error handling, retry policies, etc. via Polly, as you can then set up all that configuration just once in the extension.
Now, getting to your issue of persisting things like auth tokens, you have a few possibilities. I tend to prefer persisting auth tokens as claims, in which case you can simply retrieve the claim and pass it into methods on your client that need it:
var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));
If you handle things that way, you can bind your client in any scope, including singleton.
An alternative approach would be to actually persist the auth token in your client, but this has to be done with care. You should definitely avoid using singleton scope, unless you're employing something like a ConcurrentDictionary and even then, ensuring that the right token is always used could be a bit gnarly.
Assuming you're using a request scope, you can store the token directly as an ivar or something, but you'd still need to persist it some place else beyond that, or you'd still need to re-auth for each request. If you were to store it in the session, for example, then you could do something like:
services.AddScoped<FooClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();
var httpClient = httpClientFactory.Create("ClientName");
var session = httpContextAccessor.HttpContext.Session;
var client = new FooClient(httpClient);
client.SetAuthToken(session["FooAuthToken"]);
});
However, even then, I'd still say it's better to pass the auth token into the method than do any of this. It's more explicit about which actions require auth versus those that do not, and you always know exactly what's coming from where.
One of your biggest problems will be the reuse of the HttpClient. This is a known problem for "pre-Core" days. Luckily, its been addressed and as of Net Core 2.1 we now have an HttpClientFactory which allows you to spin up as manage HttpClients as you need and they're handled for you as part of the framework.
https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore
With this in mind, theres nothing stopping you from using DI to inject an IHttpClientFactory which will provide you with the necessary access to the pipeline you need. Other than that, its entirely up to you how you design the code which "manages" your access to the REST resources. Maybe some sort of Repository Pattern? (Purely guess work really without knowing your architecture etc)
On an ASP.NET Core project I have the following on Startup:
services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));
services.AddTransient<IValidationService, ValidationService>();
services.AddTransient<IValidator<Model>, ModelValidator>();
The ValidationService is as follows:
public interface IValidationService {
Task<List<Error>> ValidateAsync<T>(T model);
}
public class ValidationService : IValidationService {
private readonly IServiceProvider _provider;
public ValidationService(IServiceProvider provider) {
_provider = provider;
}
public async Task<List<Error>> ValidateAsync<T>(T model) {
IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();
return await validator.ValidateAsync(model);
}
}
And the ModelValidator is as follows:
public class ModelValidator : AbstractValidator<Model> {
public ModelValidator(Context context) {
// Some code using context
}
}
When I inject a IValidationService in a controller and use it as:
List<Error> errors = await _validator.ValidateAsync(order);
I get the error:
System.ObjectDisposedException: Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur is you
are calling Dispose() on the context, or wrapping the context 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: 'Context'.
Any idea why I am having this error when using Context inside ModelValidator.
How to fix this?
UPDATE
So I changed the code to:
services.AddScoped<IValidationService, ValidationService>();
services.AddScoped<IValidator<Model>, ModelValidator>();
But I get the same error ...
UPDATE - Seed Data Code inside Configure method on Startup
So on Configure method I have:
if (hostingEnvironment.IsDevelopment())
applicationBuilder.SeedData();
And the SeedData extension is:
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static void SeedData(this IApplicationBuilder builder) {
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
What am I missing?
UPDATE - A possible solution
Changing my Seed method to the following seems to work:
using (IServiceScope scope =
_provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
Context context = _provider.GetService<Context>();
// Insert data in database
}
Just a guess in what causes your error:
You are using DI and async calls. If, somewhere in your call stack, you return a void instead of Task, you get the described behavior. At that point, the call is ended and the context disposed. So check if you have an async call that returns a void instead of Task. If you change the return value, the ObjectDisposedException is probably fixed.
public static class DataSeedExtensions {
private static IServiceProvider _provider;
public static async Task SeedData(this IApplicationBuilder builder) { //This line of code
_provider = builder.ApplicationServices;
_type = type;
using (Context context = (Context)_provider.GetService<Context>()) {
await context.Database.MigrateAsync();
// Insert data code
}
}
And in configure:
if (hostingEnvironment.IsDevelopment()){
await applicationBuilder.SeedData();
}
Blog post on how to fix this error: Cannot access a disposed object in ASP.NET Core when injecting DbContext
I had a similar issue working with asp.net core. I have an async POST method in my controller and when it returns void I will have this exception. After I changed the POST method return a TASK the problem was solved.
Change from:
public async void PostAsync([FromBody] Model yourmodel)
To
public async Task PostAsync([FromBody] Model yourmodel)
Update for ASP.NET Core 2.1
In ASP.NET Core 2.1 the methods changed slightly. The general method is similar to the 2.0, just the methods name and return types have been changed.
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.Seed();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return new WebHostBuilder()
...; // Do not call .Build() here
}
Applies for ASP.NET Core 2.0
With ASP.NET Core 2.0 there have been some changes in how EF Core tools (dotnet ef migrations etc.) determine the DbContext and connection string at design time.
The below answer leads that the migrations and seeding are applied when calling any of the dotnet ef xxx commands.
The new pattern for getting a design time instance for the EF Core tools is by using an BuildHostWeb static method.
As per this announcement, EF Core will now use the static BuildWebHost method which configures the whole application, but doesn't run it.
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
// Tools will use this to get application services
public static IWebHost BuildWebHost(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}
Replace this in your old Main method
public static void Main(string[] args)
{
var host = BuildWebHost(args)
.Seed();
host.Run();
}
Where Seed is an extension method:
public static IWebHost Seed(this IWebHost webhost)
{
using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
{
// alternatively resolve UserManager instead and pass that if only think you want to seed are the users
using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
}
}
}
public static class SeedData
{
public static async Task SeedAsync(ApplicationDbContext dbContext)
{
dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
}
}
Old Answer, still applies to ASP.NET Core 1.x
There is a semi-official pattern on how to seed Entity Framework Core in ASP.NET Core application you should apply, because during application startup there is no Request and hence no RequestServices (which resolves scoped services).
In essence it boils down to creating a new scope, resolve the types you need and dispose the scope again once you're finished.
// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var db = serviceScope.ServiceProvider.GetService<AppDbContext>();
if (await db.Database.EnsureCreatedAsync())
{
await SeedDatabase(db);
}
}
One of the reasons directly resolving a service via app.ApplicationServices.GetService<MyService>() is that ApplicationServices is the application (or lifetime) scope provider and the services resolved here stay alive until the application is shut down.
Usually the scoped container will resolve from it's parent container, if the object already exists there. So if you instantiate the DbContext this way in the application, it will be available in ApplicationServices container and when a request happens, a child container will be created.
Now when resolving the DbContext it won't be resolved as scoped, because it already exists in the parent container, so the instance of the parent container will be returned instead. But since it has been disposed during the seeding, it won't be accessible.
A scope container is nothing else then a singleton container with limited lifetime.
So never resolve scoped services in Application startup w/o using the pattern above of first creating a scope and resolving from it.
If you are using any async void please replace it with async Task
Had the same issue. Hope this helps someone. In addition to making the method async and return a Task, you need to make sure that the method will also be awaited wherever you are calling it.
the problem is that DBContext is scoped per request by default, but you have things that depend on it scoped as transient, so they do not have the same scope and DBContext may be disposed before you are done using it
Similar to Yang Zhang, I had to change my controller function
From:
public IActionResult MyFunc([FromBody]string apiKey)
To:
public async Task<IActionResult> MyFunc([FromBody]string apiKey)
I'd like to share my solution for those who are trying to start a background task in their controllers. That means you want to start a task and don't want to wait for the result like audit logging to database. If you are creating a task and try to do database operations in that task you will receive this error;
Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
Already explained in details. Find it here
In my case, it wasn't an Async problem, but the code had a
using (DataContext dc=dataContext) {}
block, and of course, the context was disposed after that.
In my case the controller method was async and it was returning a task but inside that I had 2 await calls. First await calls gets some data from a service and second await call writes to the DB using EF. I had to remove the await from this second call and only then it worked. I didn't remove async/await from method signatures. I just called the second method without await.
I was facing a similar error and later was able to resolve it.
I was calling the async method without using await.
old code
var newUser = _repo.Register(newUserToCreate);
with the fix made
var newUser = await _repo.Register(newUserToCreate);
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.
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>();