依赖注入与 Windows Communication Foundation






4.87/5 (10投票s)
本文展示了一种在使用 WCF 服务时启用依赖注入的替代方法。
引言
我阅读了许多关于如何将您喜欢的 IoC 容器与 Windows Communication Foundation 集成的文章。所有这些文章都有一个共同点。它们都使用 IInstanceProvider
接口,并创建该接口的自定义实现,将服务容器钩入 WCF 堆栈。本文将演示一种不同的解决方法,并希望我们最终能得到一个干净且可重用的解决方案。
背景
那么,这里的问题是什么?为什么我们要在服务器端使用 IoC 容器来管理服务实例?
为了说明这一点,我们将通过一个简单的示例来演示,其中我们有这个服务契约。
[ServiceContract]
public interface ISampleService
{
[OperationContract]
int Calculate(int value1, int value2);
}
非常简单,我们有以下实现与之匹配。
public class SampleService : ISampleService
{
public int Calculate(int value1, int value2)
{
return value1 + value2;
}
}
SampleService
类(暂未)没有传入的依赖项,因此我们唯一需要做的就是在 WCF 认为合适的时候创建一个 SampleService
类的实例。我们实际上不需要做任何事情来实现这一点,而且大部分所需信息都包含在与我们的服务对应的 *.svc 文件中。
<%@ ServiceHost Language="C#" Debug="true"
Service="WcfService1.SampleService" CodeBehind="SampleService.svc.cs" %>
在这里,我们看到的是用于创建服务实例的信息,而我真正不喜欢的部分是,我们需要在 .svc 文件中引用我们的具体实现 (SampleService
)。我更愿意只指定契约。实际上,我们可以通过将服务请求路由到一个我们需要实现的 ServiceHostFactory
来实现这一点。然后,我们可以将其更改为
<%@ ServiceHost Language="C#" Debug="true" Service="WcfService1.ISampleService"
Factory="WcfService1.CustomServiceHostFactory" %>
看起来已经好多了,但 CustomServiceHostFactory
呢?嗯,这很简单。我们只需要让它创建一个 ServiceHost
并将服务类型传递给它的构造函数。
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost
CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return base.CreateServiceHost(serviceType, baseAddresses);
}
}
就这样。它现在应该可以正常运行了。嗯,实际上不行。ServiceHost
类只能接受一个具体类型作为服务类型(或者一个单例对象,但这不是我们想要的)。
我想将契约放在 .svc 文件中毕竟不是一个好主意。让我们将其改回具体类,并暂时保留 CustomServiceHostFactory
。
现在它又可以按预期工作了,并且假设这是场景,IInstanceProvider
方法本来就应该可行。我不会解释如何实现它,因为至少有十多篇文章都讲解了这一过程。
但是,如果我们想将依赖项传递给 SampleService
呢?像这样
public class SampleService : ISampleService
{
private readonly ILogger _logger;
public SampleService(ILogger logger)
{
_logger = logger;
}
public int Calculate(int value1, int value2)
{
return value1 + value2;
}
}
如果我们尝试这样做,我们可以看到它会因以下错误消息而失败
提供的服务类型无法加载为服务,因为它没有默认(无参数)构造函数。要解决此问题,请向类型添加默认构造函数,或将类型的实例传递给主机。
好的,那没起作用,现在怎么办?我们不能传递接口,也不能传递没有无参数构造函数的具体类型。而且 IInstanceProvider
也无济于事,因为我们仍然需要给 ServiceHost
它想要的东西。
有一种解决办法,那就是添加那个无参数构造函数,并依赖一个进行属性注入或通过接受依赖项的构造函数进行构造函数注入的 IInstanceProvider
。
有没有一种方法可以只用那一个构造函数并使其工作?这可能吗?
一点点动态类型
这里的想法是在运行时创建一个实现我们的服务契约 (ISampleService
) 的类型,并创建该类型,使其只包含一个无参数构造函数。我们在这里谈论的是一种代理实现,但没有我们通常在动态代理中看到的常规方法拦截。实际上,我们这里只需要拦截一件事,那就是当 WCF 需要创建我们的服务实例时。我开始时使用了以下“手动”代理实现。
public class ManualProxy : ISampleService
{
private readonly ISampleService _target;
public static Func<object> TargetFactory;
public ManualProxy()
{
_target = (ISampleService)TargetFactory();
}
public int Calculate(int value1, int value2)
{
return _target.Calculate(value1,value2);
}
}
这是一种惰性代理,当代理类型本身创建时,它会创建其目标。如果我们回到 CustomServiceHostFactory
类,我们可以通过使用这个手动实现的代理来测试它。
哦,别忘了。让我们将 .svc 文件改回只引用服务契约。像这样
<%@ ServiceHost Language="C#" Debug="true" Service="WcfService1.ISampleService"
Factory="WcfService1.CustomServiceHostFactory" %>
CustomServiceHostFactory
看起来像这样
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost
CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ManualProxy.TargetFactory = () => new SampleService(new Logger());
return base.CreateServiceHost(typeof(ManualProxy), baseAddresses);
}
}
静态目标工厂委托将在创建 ManualProxy
时被调用,并且 ServiceHost
会很高兴,因为我们提供了一个只有无参数构造函数的具体类。我们启动系统,就成功地将依赖项注入到了我们的服务中。太棒了!!我想我们都可以想象目标工厂调用我们的服务容器而不是上面显示的 new 运算符。
从手动到自动
虽然我们已经在某种程度上解决了问题,但手动编写这些代理会非常繁琐,而这正是本文的核心。我们如何在运行时创建这些代理类型?
实际上,这很简单,因为我们可以使用 Reflection.Emit
和 TypeBuilder
来创建一个实现我们的服务接口的类型,它看起来非常像我们刚才看到的手动代理。
在 ILSpy 中查看 ManualProxy,发出 IL 代码需要一些时间来适应,但本质上我们可以发出与 ManualProxy 完全相同的代码。
需要注意的是,对 IL 和堆栈管理有基本了解会很有帮助。
我所做的是创建了一个工厂类 (ServiceProxyFactory
),它创建一个新类型,实现它,最后将目标工厂委托分配给该类型。
private static Type CreateProxyType(Type serviceType, Func<object> targetFactory)
{
TypeBuilder typeBuilder = GetTypeBuilder(serviceType);
FieldBuilder targetFactoryField = DefineTargetFactoryField(typeBuilder);
FieldBuilder targetField = DefineTargetField(serviceType, typeBuilder);
ImplementParameterlessConstructor(serviceType, typeBuilder, targetFactoryField, targetField);
ImplementMethods(serviceType, typeBuilder,targetField);
Type proxyType = typeBuilder.CreateType();
AssignTargetFactory(proxyType, targetFactory);
return proxyType;
}
代理类型使用字典进行缓存,以便我们不会为每个请求都创建代理类型。
public Type GetProxyType(Type serviceContractType, Func<object> targetFactory)
{
return _proxyCache.GetOrAdd(serviceContractType,
s => CreateProxyType(serviceContractType,targetFactory));
}
ServiceProxyFactory
中的大部分内容都非常基础,我将其留给读者仔细研究。
为了展示一些“IL 魔力”,这里是实现每个方法的代码,通过将方法调用转发给底层目标
private static void ImplementMethods(Type serviceContractType,
TypeBuilder typeBuilder, FieldBuilder targetField)
{
foreach (MethodInfo targetMethod in GetTargetMethods(serviceContractType))
{
MethodBuilder methodBuilder = GetMethodBuilder(typeBuilder, targetMethod);
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld,targetField);
for (int i = 1; i <= targetMethod.GetParameters().Length; ++i)
ilGenerator.Emit(OpCodes.Ldarg, i);
ilGenerator.Emit(OpCodes.Callvirt,targetMethod);
ilGenerator.Emit(OpCodes.Ret);
}
}
现在真正有趣的是看看我们可以用这些代码做什么。整个代码量大约为 100 行。
我们现在可以将 CustomServiceHostFactory
改为这样
public class CustomServiceHostFactory : ServiceHostFactory
{
private static readonly ServiceProxyFactory
ServiceProxyFactory = new ServiceProxyFactory();
protected override System.ServiceModel.ServiceHost
CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
Type serviceProxyType = ServiceProxyFactory.GetProxyType(
serviceType, () => new SampleService(new Logger()));
return base.CreateServiceHost(serviceProxyType, baseAddresses);
}
}
同样,我们必须想象调用服务容器而不是 new
运算符。
正如我们所见,我们现在在 WCF 中获得了对依赖注入的全方位支持,并且不必担心之前遇到的限制。
尽情享用!!