Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/330.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# DI,从工厂解决服务实现_C#_Asp.net Core_.net Core - Fatal编程技术网

C# DI,从工厂解决服务实现

C# DI,从工厂解决服务实现,c#,asp.net-core,.net-core,C#,Asp.net Core,.net Core,我有一个接口和它的多个实现: 接口IFoo{bool CanFoo();} 类Foo1:IFoo{bool CanFoo()=>true;} 类Foo2:IFoo{bool CanFoo()=>false;} 我想在IServiceCollection中注册它们,并提供如下自定义实现工厂: services.AddTransient<Foo1>(); services.AddTransient<Foo2>(); services.AddTransient<IFoo

我有一个接口和它的多个实现:

接口IFoo{bool CanFoo();}
类Foo1:IFoo{bool CanFoo()=>true;}
类Foo2:IFoo{bool CanFoo()=>false;}
我想在
IServiceCollection
中注册它们,并提供如下自定义实现工厂:

services.AddTransient<Foo1>();
services.AddTransient<Foo2>();
services.AddTransient<IFoo>(sp =>
{
    if (someMagicCondition)
        return sp.GetService<Foo1>();
    else
        return sp.GetService<Foo2>();
});
服务
.AddTransient()
.AddTransient();
//最后登记工厂
services.AddTransient(提供者=>
{
var registrations=provider.GetServices();//异常
返回registrations.FirstOrDefault(r=>r.CanFoo());
};
因此,正如您可能看到的,这个想法是解决第一个可用的实现。但是,此代码会导致
StackOverflowException
,因为
provider.GetServices()
也会尝试解决工厂,即使它没有实现接口,也会导致无休止的循环

有几个问题:

  • 在这种情况下,是否期望服务提供商尝试解决工厂问题
  • 如果可能的话,我如何克服这个问题
  • 更新


    工厂首先参与的原因是,必须在运行时根据输入数据选择正确的服务,输入数据可能会随着每个新请求而变化。因此,我不能将其缩小到应用程序启动期间的特定服务注册。

    以下代码首先注册
    Foo1
    用于
    IFoo
    然后将
    IFoo
    的服务替换为
    Foo2

    services
      .AddTransient<IFoo, Foo1>()
      .AddTransient<IFoo, Foo2>();
    
    然后使用工厂方法使用条件注册

    serviceCollection.AddTransient(factory =>
    {
        //get the key here. you can use factory.GetService
        // to get another service and extract Key from it.
         switch(key)
         {
             case "Foo1":
                  return factory.GetService<Foo1>();
                  break;
             case "Foo2":
                  return factory.GetService<Foo2>();
                  break;
             default:
                  return null;
          }
    }
    
    serviceCollection.AddTransient(工厂=>
    {
    //在这里获取密钥。您可以使用factory.GetService
    //获取另一个服务并从中提取密钥。
    开关(钥匙)
    {
    案例“Foo1”:
    return factory.GetService();
    打破
    案例“Foo2”:
    return factory.GetService();
    打破
    违约:
    返回null;
    }
    }
    
    您可以注册工厂,如

            serviceCollection.AddTransient(factory =>
           {
               Func<string, IFoo> mytype = key =>
               {
                   switch (key)
                   {
                       case "Foo1":
                           return factory.GetService<Foo1>();
                       case "Foo2":
                           return factory.GetService<Foo2>();
                       default:
                           return null;
                   }
               };
               return mytype;
           })
    

    如注释中所述,您的代码失败,因为对于依赖项注入容器,您的工厂是
    IFoo
    实例的有效源。因此,当要求解析
    IFoo
    的所有服务时,工厂也会被要求解析

    如果您希望这样做,那么您必须以某种方式分离类型。例如,您可以创建一个标记接口
    IActualFoo
    ,用于注册您的服务:

    services.AddTransient<IActualFoo, Foo1>()
    services.AddTransient<IActualFoo, Foo2>();
    
    因此,工厂将根据某些条件逻辑创建正确的实例。虽然经常使用这种方法,但它需要工厂内的全部责任,以及在设计时有限的一组可能的
    IFoo
    实现。因此,如果您希望以后动态添加实现,这将不起作用

    您还可以注册一组
    IFoo
    工厂。因此,不要让
    IFoo
    实现决定它是否是正确的实现,而是将每个
    IFoo
    实现的逻辑移到工厂:

    services.AddSingleton<IFooFactory, Foo1Factory>();
    services.AddSingleton<IFooFactory, Foo2Factory>();
    services.AddSingleton<IFoo>(sp =>
    {
        var factories = sp.GetServices<IFooFactory>();
        return factories.FirstOrDefault(f => f.CanFoo())?.CreateFoo();
    });
    
    当然,除了在工厂中更新
    Foo
    实现之外,您还可以传递服务提供商并解析它们(如果它们已注册).

    看起来实际的问题是如何根据某些条件选择服务实现。引发异常是因为
    GetServices
    返回注册生成的所有服务,包括工厂方法本身。当工厂调用
    GetServices
    时,它会递归调用自己,直到出现异常为止这是StackOverflowException

    另一个问题是
    GetServices()
    实际上会创建服务实例,但只使用一个。另一个实例会被丢弃。这会导致垃圾对象,如果服务本身很昂贵或控制资源(如数据库连接),则可能会很昂贵

    如果直接注册服务,并且factory方法在决定需要哪种类型后使用所需类型调用
    GetService
    ,则可以避免递归:

    services
        .AddTransient<Foo1>()
        .AddTransient<Foo2>();
    
    // register the factory last
    services.AddTransient<IFoo>(provider =>
    {
        var type=PickFooType();
    
        return provider.GetService(type);
    };
    
    更新-基于上下文的依赖项解析

    从问题的更新来看,问题似乎不是如何创建工厂,而是如何根据每个请求的上下文(参数值、环境变量、数据等)选择服务。这称为基于上下文的依赖项解析。它在高级DI容器中可用,如.NET Core的DI实现和抽象,但不可用

    正确的添加方法需要在每个请求之前添加中间件来替换DI解析步骤。快速而肮脏的方法是将工厂函数本身添加为服务,并在需要时调用它。这就是Rahul在回答中所展示的

    这意味着
    AddTransient
    必须注册一个接受解析所需参数的函数:

    services.AddTransient(provider =>
    {
        IFoo resolver(MyParam1 param1,MyParam2 param2)
        {
            var type=PickFooType(param1,param2);    
            return provider.GetService(type);
        }
    
        return resolver;
    };
    
    这将注册一个
    Func
    。控制器可以通过构造函数或:

    通过注册工厂而不是
    IFoo
    接口,我们可以通过接口再次注册服务

    services
        .AddTransient<IFoo,Foo1>()
        .AddTransient<IFoo,Foo2>();
    

    创建服务时,它不会返回它们的注册。当您调用它时,它会尝试通过实例化任何已注册的类型和工厂来创建服务。如果您在工厂内调用它,则会得到一个递归调用。不过,这段代码相当奇怪。您是否打算在每次需要单个
    IFoo
    但仅您是否保留了其中一个?看起来您正在尝试在DI中实现过滤。您想要解决的实际问题是什么?@PanagiotisKanavos正确,这是一个想法。我意识到这并不完美,但我需要将处理请求的能力委托给服务。有什么建议吗?您可以通过
    服务获得注册
    
    public interface IFooFactory
    {
        bool CanFoo();
        IFoo CreateFoo();
    }
    
    public Foo1Factory : IFooFactory
    {
        public bool CanFoo() => true;
        public IFoo CreateFoo() => new Foo1();
    }
    public Foo2Factory : IFooFactory
    {
        public bool CanFoo() => false;
        public IFoo CreateFoo() => new Foo2();
    }
    
    services
        .AddTransient<Foo1>()
        .AddTransient<Foo2>();
    
    // register the factory last
    services.AddTransient<IFoo>(provider =>
    {
        var type=PickFooType();
    
        return provider.GetService(type);
    };
    
    Type PickPaymentProvider()
    {
        var activeProvider=LoadActiveProvider();
        switch (activeProvider)
        {
            case 'Paypal':
                return typeof(Foo1);
            case 'Visa' :
                return typeof(Foo2);
            ...
        }
    }        
    
    services.AddTransient(provider =>
    {
        IFoo resolver(MyParam1 param1,MyParam2 param2)
        {
            var type=PickFooType(param1,param2);    
            return provider.GetService(type);
        }
    
        return resolver;
    };
    
    public IActionResult MyAction([FromServices] resolver,int id,MyParam1 param1...)
    {
        MyParam2 param2=LoadFromDatabase(id);
        IFoo service=resolver(param1,param2);
        var result=service.DoSomeJob();
        return OK(result);
    }
    
    services
        .AddTransient<IFoo,Foo1>()
        .AddTransient<IFoo,Foo2>();
    
    services.AddTransient(provider =>
    {
        IFoo resolver(MyParam1 param1,MyParam2 param2)
        {
            var firstMatch=provider.GetServices<IFoo>()
                                   .First(svc=>svc.CanFoo(param1,param2));
            return firstMatch;
        }
    
        return resolver;
    };