如何在涉及异步调用的 ASP.NET Core 中初始化作用域注入类

Joh*_*amm 6 c# dependency-injection asp.net-core

我需要在 ASP.NET Core 的范围级别初始化一个注入类 - 初始化涉及异步方法调用。您不会在构造函数中执行此操作,也不会在属性访问器中执行此操作。

asp.net 核心应用程序中常见的 DI 使用是获取当前用户。我通过创建一个IUserContext抽象并在作用域级别注入它来实现这一点:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在努力找出如何正确初始化CurrentUser属性。

由于不再有任何方法可以UserManager同步地从类中获取用户,因此在初始化 时从属性获取器中运行异步方法CurrentUser,或者从构造函数中运行异步方法(UserManagerASP 类上不再有任何同步方法).NET 核心)。

我觉得这样做的正确方法是在每个请求上以某种方式在注入的实例上运行一次初始化方法,因为它是有范围的(可能使用动作过滤器/中间件/控制器基类(或者可能在依​​赖注入AddScoped方法本身作为工厂方法?)

这似乎是一个非常普遍的问题,我想知道其他人是如何解决这个问题的。

Nko*_*osi 4

在这种情况下,您将需要放弃该属性并使用异步方法。

这也意味着用户可以使用异步延迟初始化

/// <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)) { }
}
Run Code Online (Sandbox Code Playgroud)

现在可以重构上下文以使用延迟初始化,

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在需要的地方使用

User user = await context.GetCurrentUser();
Run Code Online (Sandbox Code Playgroud)

现在,财产仍然可以像这样使用

public Task<User> CurrentUser => currentUser.Value;
Run Code Online (Sandbox Code Playgroud)

因为 getter 是一种方法,但在我个人看来这不是一个非常直观的设计。

User user = await context.CurrentUser;
Run Code Online (Sandbox Code Playgroud)

如果太早访问可能会产生不良结果。

我之所以提及它只是因为示例中显示的原始上下文的设计。