【问题标题】:.NET Core WebAPI dependency injection resolve null.NET Core WebAPI 依赖注入解析 null
【发布时间】:2018-07-13 04:28:39
【问题描述】:

我使用 .NET Core WebAPI 和依赖注入和多种身份验证模式(http 基本、访问密钥、JWT)。我注入了一些需要经过身份验证的用户数据的业务服务。如果用户通过任何 auth 中间件进行身份验证,DI 工作正常。如果用户未通过身份验证,DI 将无法解析某些服务。我需要 DI 返回null

这怎么可能?下面的代码将导致异常,null 不允许作为结果。

services.AddTransient<IMasterRepository>(serviceProvider =>
        {
            var _serviceFactory = new RepositoriesFactory(Configuration);

            if (!Authenticated)
            {
                return null;
            }

            return _serviceFactory.CreateMasterRepository();
        });

另外,我不能在 auth 中间件中返回 401,因为另一个中间件可能会成功(解释:不能在 http 基本身份验证中间件中返回 401,因为下一个 JWT 可能会成功)

此外,我无法在所有身份验证中间件之后添加“需要身份验证”检查,因为某些控制器是公共的(不需要身份验证/依赖注入)。

有什么建议吗? 谢谢!

【问题讨论】:

  • 当使用? (C# 8) 将泛型类型明确声明为可为空时是否有效?

标签: c# authentication asp.net-web-api dependency-injection .net-core


【解决方案1】:

注册一个实现为null没有问题。只有解决你才会遇到问题。

也就是说,如果你注册了:

services.AddTransient<IMasterRepository>(provider => null);

然后试试:

private readonly IMasterRepository _repository;

public SomeController(IMasterRepository repository)
{
    _repository = repository;
}

您将在运行时收到InvalidOperationException,消息类似于:

在尝试激活“MyApp.Controllers.SomeController”时无法解析“MyApp.IMasterRepository”类型的服务

但是,有一个简单的解决方法。与其注入接口,不如注入该接口的IEnumerable

private readonly IMasterRepository _repository;

public SomeController(IEnumerable<IMasterRepository> repositories)
{
    _repository = repositories.First();  // (using System.Linq)
}

您可能认为应该是FirstOrDefault,但确实会有一个项目包含您注册的null

这种方法之所以有效,是因为 ASP.Net Core 中的 DI 支持注册给定类型的多个实现,并且在注册时不区分 null 和对象实例。

请记住,尽管这可行,但不建议这样做,因为现在_repository 变量可能可以为空,并且每次访问时都必须使用空检查。例如:if (_repository != null) { _repository.DoSomething(); }_repository?.DoSomething();。大多数人不希望编写这样的代码。

这涵盖了问题的 DI 部分。但是,如果问题确实与身份验证有关,那么ste-fu's answer 描述了一种更合适的方法。

【讨论】:

  • 我看不到这个:o,如果我在代码库中看到这个,我会停止我正在做的任何事情并重构它。
  • @huysentruitw - 确实。我添加了一段来回应这不是推荐的做法。谢谢。
【解决方案2】:

默认的 DI 框架不允许工厂委托按设计返回 null。

通过创建从接口派生的 NullObject 来考虑 null object pattern

public class NullRepository : IMasterRepository {
    public static readonly IMasterRepository Empty = new NullRepository();

    public NullRepository () { }

    //...implement members that do nothing and may return empty collections.
}

调用时什么都不做。

services.AddTransient<IMasterRepository>(serviceProvider => {
    IMasterRepository result = NullRepository.Empty;
    var _serviceFactory = new RepositoriesFactory(Configuration);
    if (Authenticated) {
        result = _serviceFactory.CreateMasterRepository();
    }
    return result;
});

现在检查 null 变成了

//ctor
public SomeClass(IMasterRepository repository) {

    if(repository == NullRepository.Empty)
        //...throw

    //...
}

【讨论】:

    【解决方案3】:

    在我看来,这听起来像是您的依赖设置有问题。

    如果身份验证成功,您的所有身份验证中间件都应将HttpContext 上的ClaimsPrincipal 设置为Invoke 方法的一部分。

    虽然服务可能需要能够访问ClaimsPrincipal 才能正常运行,但您可以通过在构造函数中注入IHttpContextAccessor 并在Startup.cs 中的ConfigureServices 方法中启用它来做到这一点。

    让 DI 容器返回 null 只是意味着您必须对整个代码进行大量的 null 检查。确保 ClaimsPrincipal 设置正确意味着您可以利用 [Authorize] 属性来控制对特定控制器或方法的访问,或者设置 Policy-based authorization 在所有身份验证中间件运行后应该返回正确的状态代码。

    【讨论】:

      【解决方案4】:

      虽然您可以创建一个能够保存服务实例的包装类或null -或者-您可以注入一个虚拟服务以防用户未通过身份验证,但从干净代码的角度来看,不建议这样做。两种解决方案都像第一个一样具有代码味道:您必须在任何地方放置空检查,即使在您期望该服务的地方也是如此。后者:看起来代码实际上可以使用该服务,但不清楚是否会提供虚拟服务。

      为了保持您的代码干净,我只需将不需要这些服务的公共路由移至不依赖于服务的单独控制器类(与现在的路由相同)。这样,您可以按原样注册存储库并避免魔术。

      【讨论】:

        猜你喜欢
        • 2021-02-02
        • 2019-04-13
        • 2016-05-22
        • 1970-01-01
        • 2022-12-20
        • 2021-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多