【问题标题】:C# .Net Core dependency injection, inject multiple parameters to constructorC# .Net Core 依赖注入,向构造函数注入多个参数
【发布时间】:2017-03-28 07:22:43
【问题描述】:

我有AuthenticationStrategy 类,我将在控制器构造函数中注入它。

我有两个IAuthenticationProvidersInternalAuthenticationProviderExternalAuthenticationProvider
AuthenticationStrategy 构造函数中,我想注入所有提供者。
示例代码:

public class AuthenticationStrategy
{
    private readonly Dictionary<string, IAuthenticationProvider> _authenticationProviders;

    public AuthenticationStrategy(IAuthenticationProvider[] authenticationProviders)
    {
        if (authenticationProviders == null)
        {
            throw new ArgumentNullException("AuthenticationProviders");
        }

        _authenticationProviders = authenticationProviders
            .ToDictionary(x => nameof(x), x => x);
    }
}

如何使用依赖注入注入多个提供程序? 示例代码:

services.AddScoped<IAuthenticationProvider, InternalAuthenticationProvider>();
services.AddScoped<IAuthenticationProvider, ExternalAuthenticationProvider>();
services.AddScoped<AuthenticationStrategy>();

有什么想法吗?

【问题讨论】:

  • 您是否遇到任何错误?当您尝试上述代码时会发生什么?
  • IAuthenticationProvider[] authenticationProviders 在 AuthenticationStrategy 构造函数中为空。

标签: c# .net dependency-injection constructor .net-core


【解决方案1】:

我认为将Dictionary 存储在您的策略中是一种代码问题,因为它看起来像一个反模式Service Locator。您可能需要 introduce the factory 基于密钥的身份验证提供程序。这是.Core 依赖注入中所需的方法,但是您可以使用其他IoC containers with similar features(例如,命名依赖)。

所以,你的代码可能是这样的:

public enum AuthType
{
    Internal,
    External,
}

public interface IAuthenticationProviderResolver
{
    IAuthenticationProvider GetAuthByType(AuthType type);
}

public class ProviderResolver : IAuthenticationProviderResolver
{
    private readonly IServiceProvider _serviceProvider;

    public RepositoryResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IAuthenticationProvider GetAuthByName(AuthType type)
    {
         switch (type) 
         {
             case AuthType.Internal:
                 return _serviceProvider.GetService<InternalAuthenticationProvider>();
             case AuthType.External:
                 return _serviceProvider.GetService<ExternalAuthenticationProvider>();
             default:
                 throw new ArgumentException("Unknown type for authentication", nameof(type))
         }
    }
}

现在您只需照常注册课程:

services.AddSingleton<IAuthenticationProviderResolver, ProviderResolver>();
services.AddScoped<InternalAuthenticationProvider>();
services.AddScoped<ExternalAuthenticationProvider>();
services.AddScoped<AuthenticationStrategy>();

以及策略内部的用法:

public class AuthenticationStrategy
{
    private readonly IAuthenticationProviderResolver _resolver;

    public AuthenticationStrategy(IAuthenticationProviderResolver resolver)
    {
        if (resolver== null)
        {
            throw new ArgumentNullException("Provider Resolver");
        }

        _resolver = resolver;
    }

    public void MakeDecision()
    {
        _resolver.GetAuthByType(authType).Authenticate();
    }
}

【讨论】:

  • 2 件事:1) 在 IAuthenticationProvider GetAuthByName(string name) 中松散地键入使用名称(字符串),我不确定它是否是更好的方法。 2) 如果 IAuthenticationProvider 的具体实现超过 2 个怎么办? - 提前致谢!
  • 命名依赖是一种常见的方法,与Distionary 非常相似。您仍然可以使用更多参数或with a enum 定义自己的签名
  • @YawarMurtaza 更新为enum,因为它是一种更安全的方法,并添加了一些switch。如果您对解决方案有更多想法,请分享。对我来说,最好关闭其他 IoC 容器而不是原生容器。
  • 这使它成为强类型,这很好并且回答了我的第一点。现在,如果我们有另一个 IAuthenticationProvider 实现,我们将不得不编辑这个类以添加另一个 switch-case 语句并添加另一个违反开闭原则的枚举值。
  • @YawarMurtaza 我不同意你的观点。策略应定义AuthType,解析器应提供该类型所需的身份验证。如果我们将所有这些存储在内部策略中,一段时间后它将像God 对象。仍然认为某些第三方 IoC 更受欢迎,而不是这样的解决方法
【解决方案2】:

一种选择是使 AuthenticationStrategy 通用。那么你可能会因类型而异

config.Scan(assembly =>
{
    assembly.AssemblyContainingType(typeof(AuthenticationProvider));
    assembly.ConnectImplementationsToTypesClosing(typeof(IAuthenticationProvider<>));
});

上面的代码会扫描 dll,因此您也不必逐个注册。

【讨论】:

    【解决方案3】:

    如果您坚持使用 OOTB 依赖注入设置,即不使用第三方容器,那么一种选择是在您的构造函数参数中显式,如下所示:

    public class AuthenticationStrategy
    {
        public AuthenticationStrategy(
            IInternalAuthenticationProvider internal,
            IExternalAuthenticationProvider external)
        {
            ...
        }
    }
    

    IInternalAuthenticationProviderIExternalAuthenticationProvider 接口只不过是这样的标记接口:

    public interface IInternalAuthenticationProvider : IAuthenticationProvider { }
    public interface IExternalAuthenticationProvider : IAuthenticationProvider { }
    

    所以你的 DI 设置现在看起来像这样:

    services.AddScoped<IInternalAuthenticationProvider , InternalAuthenticationProvider>();
    services.AddScoped<IExternalAuthenticationProvider , ExternalAuthenticationProvider>();
    services.AddScoped<AuthenticationStrategy>();
    

    【讨论】:

    • 这是我现在正在使用的解决方案。但我仍然想摆脱构造函数中的显式参数,因为将来它可能是更具体的提供者。
    【解决方案4】:

    假设您在 Visual Studio 2017 中使用 Asp.Net Core 项目类型

    假设您有以下接口定义:

       public interface IAuthenticationProvider 
        {
        }
    

    像这样实现类:

    public class WindowsAuthentication : IAuthenticationProvider { }
    
    
    public class NTMLAuthentication : IAuthenticationProvider { }
    
    
    public class KerberosAuthentication : IAuthenticationProvider { }
    
    
    public class CustomAuthentication : IAuthenticationProvider { }
    

    到目前为止一切顺利。现在要解决实现相同接口的类型的依赖关系,我将使用自定义解析器类及其接口:

    public interface IAuthenticationResolver
    {
        IAuthenticationProvider GetProvider(Type type);
    }
    

    及其实现:

    public class AuthenticationResolver : IAuthenticationResolver
        {
            private readonly IServiceProvider services;
            public AuthenticationResolver(IServiceProvider services)
            {
                this.services = services;
            }
    
            public IAuthenticationProvider GetProvider(Type type)
            {            
                return this.services.GetService(type) as IAuthenticationProvider;
            }
        }
    

    在您的Startup 类中,在ConfigureServices 下注册这些类型

     services.AddTransient<IAuthenticationResolver, AuthenticationResolver>();
                services.AddTransient<WindowsAuthentication>();
                services.AddTransient<KerberosAuthentication>();
                services.AddTransient<NTMLAuthentication>();
                services.AddTransient<CustomAuthentication>();
    

    当然,如果你需要的话,你可以使用 Scoped。

    一切就绪后,返回到注入依赖项的控制器/客户端类:

     public class HomeController : Controller
     {
            private readonly Dictionary<string, IAuthenticationProvider> authProvidersDictionary;
    
            public HomeController(IAuthenticationResolver resolver)
            {
                System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly();
                this.authProvidersDictionary = new Dictionary<string, IAuthenticationProvider>();
    
                foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
                {
                    if (ti.ImplementedInterfaces.Contains(typeof(IAuthenticationProvider)))
                    {                   
    
                        this.authProvidersDictionary.Add(ti.Name, resolver.GetProvider(ti.UnderlyingSystemType));
                    }
                }            
            }
    }
    

    希望这会有所帮助!

    【讨论】:

      【解决方案5】:

      这个问题本身就是依赖注入的反模式示例,它是 .net 核心选择的“金锤”工具。

      您的类应该能够访问两个身份验证提供程序,而无需依赖注入的不相交代码。

      .net 依赖注入中的不相交代码:

      • 在应用启动时注册注入的对象
      • 注入对象的对象构造与匿名方法中的对象实现分离
      • .net 核心的应用程序代码的其余部分从未直接调用对象构造。 .net 核心框架调用构造函数。

      刚毕业的初级开发人员应该能够查看任何方法中的任何一行,并快速找到该方法的调用方式、调用位置以及调用方法的原因——无需了解数十个(数百个? ) .net 核心框架的小微特征。

      使用模拟类进行单元测试很容易通过依赖注入以外的方式实现。

      【讨论】:

        猜你喜欢
        • 2016-01-01
        • 2020-12-20
        • 1970-01-01
        • 2011-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-29
        • 2019-04-20
        相关资源
        最近更新 更多