I need to initialize an injected class at the scope level in ASP.NET Core - the initialization involves asynchronous method calls. You wouldn't do this in the constructor, nor a property accessor.
A common DI use in an asp.net core application is getting the current user. I implemented this by creating an IUserContext
abstraction and injecting it at the scoped level:
public sealed class AspNetUserContext : IUserContext
{
private readonly UserManager<User> userManager;
private readonly IHttpContextAccessor accessor;
private User currentUser;
public AspNetUserContext(IHttpContextAccessor a, UserManager<User> userManager) {
accessor = a;
if (userManager == null)
throw new ArgumentNullException("userManager");
this.userManager = userManager;
}
public string Name => accessor.HttpContext.User?.Identity?.Name;
public int Id => accessor.CurrentUserId();
public User CurrentUser {
get {
if (currentUser == null) {
currentUser = this.UserManager.FindByIdAsync(Id.ToString()).ConfigureAwait(false).GetAwaiter().GetResult();
}
return currentUser;
}
}
}
I am struggling trying to find out how to correctly initialize the CurrentUser
property.
Since there is no longer any way to get a user from the UserManager
class sychronously, I am not comfortable running an async method from within a property getter when initializing the CurrentUser
, nor from the constructor (there are no long any synchronous methods on the UserManager
class with ASP.NET Core).
I feel like the correct way to do this would be to run an initialization method on the injected instance somehow once per request since it is scoped (perhaps with an action filter/middleware/Controller base class (or perhaps in the dependency injection AddScoped
method itself as a factory method?)
This seems like a pretty common problem and I'm wondering how others have resolved this.
In this case you will need to forgo the property and have an asynchronous method.
This would also mean having an asynchronous lazy initialization for the User using
/// <summary>
/// Provides support for asynchronous lazy initialization.
/// </summary>
/// <typeparam name="T"></typeparam>
public class LazyAsync<T> : Lazy<Task<T>> {
/// <summary>
/// Initializes a new instance of the LazyAsync`1 class. When lazy initialization
/// occurs, the specified initialization function is used.
/// </summary>
/// <param name="valueFactory">The delegate that is invoked to produce the lazily initialized Task when it is needed.</param>
public LazyAsync(Func<Task<T>> valueFactory) :
base(() => Task.Run(valueFactory)) { }
}
This now makes it possible to refactor the context to use lazy initialization,
public sealed class AspNetUserContext : IUserContext {
private readonly UserManager<User> userManager;
private readonly IHttpContextAccessor accessor;
private readonly LazyAsync<User> currentUser;
public AspNetUserContext(IHttpContextAccessor accessor, UserManager<User> userManager) {
this.accessor = accessor;
if (userManager == null)
throw new ArgumentNullException(nameof(userManager));
this.userManager = userManager;
currentUser = new LazyAsync<User>(() => this.userManager.FindByIdAsync(Id.ToString()));
}
public string Name => accessor.HttpContext.User?.Identity?.Name;
public int Id => accessor.CurrentUserId();
public Task<User> GetCurrentUser() {
return currentUser.Value;
}
}
And used where needed
User user = await context.GetCurrentUser();
Now a property could have still been used like
public Task<User> CurrentUser => currentUser.Value;
as the getter is a method, but that is not a very intuitive design in my personal opinion.
User user = await context.CurrentUser;
and can have undesirable results if accessed too early.
I only mention it because of the design of the original context shown in the example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With