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;
};