C# .Net Core 依赖注入,向构造函数注入多个参数

6

我有一个名为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>();

有什么想法吗?

你有收到任何错误吗?当你尝试以上代码时会发生什么? - Nkosi
AuthenticationStrategy 构造函数中的 IAuthenticationProvider[] authenticationProviders 为 null。 - Algirdas
5个回答

2

我认为将Dictionary存储在您的策略内部是一种代码气味,因为它看起来像一种反模式服务定位器。您可能需要基于键引入认证提供程序的工厂。这是.Core依赖注入中的理想方法,但您也可以使用其他具有类似功能(例如命名依赖项)的IoC容器。

所以,您的代码可以是这样的:

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)如果有多于2个的IAuthenticationProvider具体实现怎么办? - 先谢谢! - Yawar Murtaza
@YawarMurtaza更新为enum,因为这是更加类型安全的方法,并添加了一些switch。如果您对解决方案有更多想法,请分享。对我来说,最好切换到其他IoC容器而不是本机容器。 - VMAtm
@YawarMurtaza 我在这里不同意你的观点。策略应该定义AuthType,而解析器应该提供该类型需要的身份验证。如果我们将所有这些内容存储在策略中,一段时间后它就会像是一个“上帝”对象。仍然认为某些第三方IoC比这种解决方法更可取。 - VMAtm
有趣。你能否提供一些支持你想法的参考资料? - Yawar Murtaza
@YawarMurtaz 这只是我的理解,基于Mark Seaman的书《.Net中的DI》,其中包含有关命名依赖项的示例。 - VMAtm
显示剩余2条评论

1

一种选择是使AuthenticationStrategy成为通用的。然后您可以使用不同的类型进行区分。

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

代码会扫描dll,因此您不需要注册每一个dll。

1
如果您坚持使用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>();

这是我目前正在使用的解决方案。但是我仍然想摆脱构造函数中的显式参数,因为未来可能会有更具体的提供者。 - Algirdas

1
假设您正在使用 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>();

当然,如果需要的话,您可以使用Scopped。
完成这一切后,返回到注入依赖项的控制器/客户端类:
 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));
                }
            }            
        }
}

希望这有所帮助!

0

这个问题本身就是一个反模式的例子,说明依赖注入成为 .net core 中首选的“万能工具”。

你的类应该能够访问这两个身份验证提供程序,而不需要使用依赖注入的分离代码。

.net 依赖注入中的分离代码:

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

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

使用模拟类进行单元测试可以轻松实现,而不必使用依赖注入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接