简体   繁体   English

.NET Core WebAPI 依赖注入解析 null

[英].NET Core WebAPI dependency injection resolve null

I use .NET Core WebAPI with dependency injection and multiple authentication schemas (http basic, access keys, JWT).我将 .NET Core WebAPI 与依赖项注入和多个身份验证模式(http 基本、访问密钥、JWT)结合使用。 I inject some business services which require some authenticated user data.我注入了一些需要一些经过身份验证的用户数据的业务服务。 If user is authenticated by any of auth middleware, DI works fine.如果用户通过任何 auth 中间件进行身份验证,DI 工作正常。 If the user is not authenticated, DI cannot resolve some services.如果用户未通过身份验证,DI 将无法解析某些服务。 I need DI to return null .我需要 DI 返回null

How is that possible?这怎么可能? Code bellow will result in exception, null is not allowed as result.下面的代码将导致异常,结果不允许为null

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

            if (!Authenticated)
            {
                return null;
            }

            return _serviceFactory.CreateMasterRepository();
        });

Also, I cannot return 401 in auth middleware, because another middleware may success (expl: cannot return 401 in http basic auth middleware because next one, JWT, may success)另外,我无法在身份验证中间件中返回 401,因为另一个中间件可能会成功(expl:无法在 http 基本身份验证中间件中返回 401,因为下一个 JWT 可能会成功)

Also, I cannot add "authentication required" check after all auth middlewares because some controllers are public (no authentication / dependency injection required).此外,我无法在所有身份验证中间件之后添加“需要身份验证”检查,因为某些控制器是公共的(不需要身份验证/依赖项注入)。

Any advice?有什么建议吗? Thanks!谢谢!

There's no problem in registering an implementation as null .将实现注册null没有问题。 It's only in resolving that you will have a problem.只有解决了才会有问题。

In other words, if you register:换句话说,如果您注册:

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

And then try:然后尝试:

private readonly IMasterRepository _repository;

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

You will get an InvalidOperationException at runtime, with a message similar to:您将在运行时收到InvalidOperationException ,并显示类似于以下内容的消息:

Unable to resolve service for type 'MyApp.IMasterRepository' while attempting to activate 'MyApp.Controllers.SomeController'尝试激活“MyApp.Controllers.SomeController”时无法解析“MyApp.IMasterRepository”类型的服务

However, there is a simple workaround.但是,有一个简单的解决方法。 Rather than injecting the interface, inject an IEnumerable of that interface:与其注入接口,不如注入该接口的IEnumerable

private readonly IMasterRepository _repository;

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

You might think it should be FirstOrDefault , however there will indeed be a single item containing the null you registered.您可能认为它应该是FirstOrDefault ,但是确实会有一个包含您注册的null项目。

This approach works because DI in ASP.Net Core supports registering multiple implementations of a given type and doesn't distinguish between null and object instances at time of registration.这种方法之所以有效,是因为 ASP.Net Core 中的 DI 支持注册给定类型的多个实现,并且在注册时不区分null和对象实例。

Do keep in mind that even though this works, it's not recommended because now the _repository variable is potentially nullable, and a null check must be used every time it is accessed.请记住,即使这有效,也不建议这样做,因为现在_repository变量可能为空,并且每次访问时都必须使用空检查。 For example: if (_repository != null) { _repository.DoSomething(); }例如: if (_repository != null) { _repository.DoSomething(); } if (_repository != null) { _repository.DoSomething(); } or _repository?.DoSomething(); if (_repository != null) { _repository.DoSomething(); }_repository?.DoSomething(); . . Most people do not expect to write code like that.大多数人不希望编写那样的代码。

This covers the DI part of the question.这涵盖了问题的 DI 部分。 But if indeed the issue is strictly with auth then ste-fu's answer describes a more appropriate approach.但是,如果问题确实出在 auth 上,那么ste-fu 的回答描述了一种更合适的方法。

The default DI framework does not allow for the factory delegate to return null by design.默认的 DI 框架不允许工厂委托按设计返回 null。

Consider null object pattern by creating a NullObject derived from the interface通过创建从接口派生的 NullObject 来考虑空对象模式

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

    public NullRepository () { }

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

that does nothing when invoked.调用时什么也不做。

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

Checking for null now becomes检查空值现在变成

//ctor
public SomeClass(IMasterRepository repository) {

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

    //...
}

To my mind, this sounds like a problem with your dependency setup.在我看来,这听起来像是您的依赖项设置有问题。

All your auth Middleware should be setting the ClaimsPrincipal on the HttpContext as part of the Invoke method if the authentication is successful.如果身份验证成功,您所有的身份验证中间件都应该在HttpContext上设置ClaimsPrincipal作为Invoke方法的一部分。

Whilst a service may need to be able to access the ClaimsPrincipal in order to function correctly, you can do this by injecting IHttpContextAccessor in the constructor and enabling it in the ConfigureServices method in Startup.cs .虽然服务可能需要能够访问ClaimsPrincipal才能正常运行,但您可以通过在构造函数中注入IHttpContextAccessor并在Startup.csConfigureServices方法中启用它来实现这一点。

Getting the DI container to return null would just mean that you have to make lots of null checks all over your code.让 DI 容器返回 null 仅仅意味着您必须在整个代码中进行大量的 null 检查。 Ensuring that the ClaimsPrincipal is correctly set means that you can leverage the [Authorize] attribute to control access to particular Controllers or methods or setup Policy-based authorization which should return the correct status codes after all the auth middleware has run.确保正确设置ClaimsPrincipal意味着您可以利用[Authorize]属性来控制对特定控制器或方法的访问或设置基于策略的授权,该授权应在所有身份验证中间件运行后返回正确的状态代码。

While you could create a wrapper class that is able to hold the service instance or null -OR- you could inject a dummy service in case the user is not authenticated, it's not recommended from a clean-code perspective.虽然您可以创建一个能够保存服务实例或null的包装类 - 或者 - 您可以注入一个虚拟服务,以防用户未通过身份验证,但从干净代码的角度来看,不建议这样做。 Both solutions would be a code smell as for the first: you would have to place null checks everywhere, even in places where you'd expect the service.两种解决方案与第一种解决方案一样都是代码异味:您必须在任何地方进行空检查,即使是在您期望提供服务的地方也是如此。 And the latter: it seems like the code could actually use the service, but it's not clear a dummy service will be provided.后者:似乎代码实际上可以使用该服务,但尚不清楚是否会提供虚拟服务。

To keep your code clean, I'd just move the public routes, that are not requiring those services, to a separate controller class (with the same route as they have now) that does not depend on the service.为了保持您的代码干净,我只是将不需要这些服务的公共路由移动到不依赖于服务的单独控制器类(与它们现在具有相同的路由)。 This way, you can just register the repository as-is and avoid magic trickery.这样,您就可以按原样注册存储库并避免魔术。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM