在“WCF 服务库”项目中实现自托管的依赖注入





5.00/5 (1投票)
互联网上有许多关于通过 .svc 文件标记 [在 WCF 服务应用程序中] 注入依赖项的内容,这在你有 WCF 服务应用程序时很容易实现。但是,即使在你的 WCF 库项目(没有 .svc 文件)中,它也同样容易实现。
引言
有许多方法可以使你的应用程序松散耦合。使用依赖注入就是其中一种方法。
松散耦合的应用程序有助于使你的应用程序易于维护、可扩展,并且还有助于测试你的代码等。本文将逐步引导你将 IOC 容器实现到你的 WCF 服务库项目中。最后,我们将添加自托管组件。在这里,我们将使用 Autofac 作为 IOC 容器。
背景
为了使应用程序松散耦合,它应该遵守面向对象编程中 SOLID 原则的规范。
- 单一职责原则:一个类应该只有一个职责,也就是说,软件规范中只有一个部分的更改能够影响类的规范。
- 开闭原则:“软件实体……应该对扩展开放,对修改关闭。”
- 里氏替换原则:“程序中的对象应该能够被其子类型的实例替换,而不会改变程序的正确性。”另请参阅契约式设计。
- 接口隔离原则:“多个客户端特定的接口优于一个通用接口。”
- 依赖倒置原则:应该“依赖于抽象,[而不是]具体实现。”
依赖注入有助于实现“依赖倒置原则”
来源:https://en.wikipedia.org/wiki/SOLID
对于 IOC 容器,我们将使用 Autofac 和 Autofac.WCF
来源:https://autofaccn.readthedocs.io/en/latest/integration/wcf.html
你需要的东西
- Visual Studio(我使用的是 VS Community Edition 2017)
- Autofac
- Autofac.WCF
- 还有一颗开放的心:)
Using the Code
我将使用非常简单的代码作为示例。我的重点是让事情易于理解。
第 1 步:创建一个解决方案并添加 WCF 库项目
我们将解决方案命名为 "DependencyInjectionWcfWithSelfHosting
",WCF 服务库项目命名为 "TestService
"。
第 2 步:在 WCF 服务项目中添加脚手架代码
- 删除 app.config,以及系统创建的
IService1
、Service1.cs 文件(你可以重命名这些文件,但我喜欢从一个干净的开始) - 右键单击你的
TestService
项目,添加一个名为 "DisplayService
" 的 WCF 服务类新项。
它应该看起来像这样
IDisplayService.cs
[ServiceContract]
public interface IDisplayService
{
[OperationContract]
string DisplayInputValue(string input);
}
DisplayService.cs
public class DisplayService : IDisplayService
{
public string DisplayInputValue(string input)
{
return input;
}
}
如你所见,代码没有什么花哨之处,只是将传入的输入原样返回。
但请稍等,我们可以做一些事情来用一些 string
消息来装饰该输入,例如
如果我传入 "Sunny
" 作为输入,我可以向传入的输入添加一些前缀 string
,如 "You've entered
",并将其返回为 "You've entered Sunny
"。
好的,我可以直接将输入 string
与 "You've entered
" 拼接起来,然后从 DisplayInputValue()
返回。
public string DisplayInputValue(string input)
{
return "You've entered" + input;
}
但是,为了解决 WCF 服务中的依赖注入问题,我们将尝试从一些实用程序类中完成拼接部分。然后,通过构造函数注入其实例。
第 3 步:为实用程序类添加足够的代码
- 在
TestService
项目中添加名为“Utilities”的文件夹,并在其中添加一个名为“StringMixer
”的类,代码如下public class StringMixer : IStringMixer { /// <summary> /// This method deals with adding ""You've entered" before the passed input /// </summary> /// <param name="value"></param> /// <returns></returns> public string MixStringValue(string value) { return $"You've entered {value}"; } }
- 从 "
StringMixer
" 类中提取接口。这将是 "IStringMixer
"。这将有助于我们在 "DisplayService
" 中进行构造函数注入。public interface IStringMixer { string MixStringValue(string value); }
我们很快就会用到这些实用程序。
第 4 步:添加托管支持
- 将控制台应用程序添加到解决方案中,并将其命名为 "
Host
"。 - 转到控制台应用程序中的 program.cs,并在
Main()
中添加以下代码。确保你已添加 "TestService
" 和 "System.ServiceModel
" 的引用。
using System;
using System.ServiceModel;
using TestService;
namespace Host
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(DisplayService));
host.Open();
Console.WriteLine("Host has started");
Console.ReadLine();
}
}
}
现在,我们最近添加的控制台应用程序中可能有一个 app.config 文件。在其中添加以下 ServiceModel
部分。
<system.serviceModel>
<services>
<service name="TestService.DisplayService">
<endpoint address=""
binding="basicHttpBinding"
contract="TestService.IDisplayService"
name="TestServiceEndPoint" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
name="TestServiceMexEndPoint"/>
<host>
<baseAddresses>
<add baseAddress="https:///TestService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
或者
如果你愿意,可以通过右键单击 app.config 并单击“编辑 WCF 配置”来手动创建自己的服务模型配置,然后按照向导中的指示进行操作(确保它会运行)。
- 运行你的 "
Host
" 应用程序以确保一切正常 - 你可能会收到访问权限错误,只需以管理员模式运行 Visual Studio,然后运行你的
Host
应用程序
如果你成功走到这一步,恭喜你。你已经完成了一半!
第 5 步:验证我们目前所做的工作是否有效
- 尝试在你的
WCFTestClient
中打开 URL "https:///TestService",并确保它工作正常。
第 6 步:完成未竟的任务
正如我在第 3 点末尾提到的,我们将把实用程序类“StringMixer
”通过构造函数注入到“DisplayService
”中。
- 转到 "
TestService
" 项目,打开你的显示服务类并添加一个构造函数。 - 使用
StringMixer
的接口作为抽象(即 "IStringMixer
")来注入其依赖项。
DisplayService.cs 现在可能看起来像这样
public class DisplayService : IDisplayService
{
private readonly IStringMixer _mixer;
public DisplayService(IStringMixer mixer)
{
_mixer = mixer;
}
public string DisplayInputValue(string input)
{
return _mixer.MixStringValue(input);
}
}
我们将从主机应用程序注入此依赖项。让我们开始一场激动人心的冒险。
第 7 步:在 "Host" 项目中设置 Autofac 和 Autofac.Wcf
- 在 "
Host
" 项目中添加 "Autofac
" 和 "Autofac.Wcf
" 作为 NuGet 包 - 在 "
Host
" 项目中添加一个名为 "Bootstrapper
" 的类。在这个类中,我们将在 Autofac 容器中注册我们的依赖项。public static ContainerBuilder RegisterContainerBuilder() { ContainerBuilder builder = new ContainerBuilder(); builder.Register(c => new StringMixer()).As<IStringMixer>(); builder.Register(c => new DisplayService (c.Resolve<IStringMixer>())).As<IDisplayService>(); return builder; }
引用这意味着每当 Autofac 在基于它们的抽象(接口)进行构造函数注入时看到这些依赖项的引用,它就会将它们注册的类注入其中。
例如
"builder.Register(c => new StringMixer()).As<IStringMixer>();"
这意味着,无论何处引用 IStringMixer,Autofac 都会注入 StringMixer 作为其实现。
第 8 步:稍微调整 "Host" 项目
- 通过添加以下代码修改 program.cs 文件中的代码
static void Main(string[] args) { using (IContainer container = Bootstrapper.RegisterContainerBuilder().Build()) { ServiceHost host = new ServiceHost(typeof(DisplayService)); IComponentRegistration registration; if (!container.ComponentRegistry.TryGetRegistration (new TypedService(typeof(IDisplayService)), out registration)) { Console.WriteLine("The service contract has not been registered in the container."); Console.ReadLine(); Environment.Exit(-1); } host.AddDependencyInjectionBehavior<IDisplayService>(container); host.Open(); Console.WriteLine("Host has started"); Console.ReadLine(); host.Close(); Environment.Exit(0); }
第 9 步:运行宿主应用程序。这一次,你获得了依赖注入的力量。
感谢你的耐心。我们已经完成了这些步骤!
(图片来源:unsplash 上的 daniel-cheung)
关注点
- 你可以从 "
Host
" 项目中删除 app.config 文件,并在你的宿主应用程序中编写与EndPoint
配置相关的代码。仅供参考,我已添加对 "http
" 和 "netNamedPipe
" 的支持。static void Main(string[] args) { using (IContainer container = Bootstrapper.RegisterContainerBuilder().Build()) { var httpLocation = "https:///DisplayService"; Uri address = new Uri(httpLocation); var netNamedPipeLocation = "net.pipe:///DisplayService/"; ServiceHost host = new ServiceHost(typeof(DisplayService)); host.AddServiceEndpoint(typeof(IDisplayService), new BasicHttpBinding(), httpLocation); host.AddServiceEndpoint(typeof(IDisplayService), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), netNamedPipeLocation); IComponentRegistration registration; if (!container.ComponentRegistry.TryGetRegistration (new TypedService(typeof(IDisplayService)), out registration)) { Console.WriteLine("The service contract has not been registered in the container."); Console.ReadLine(); Environment.Exit(-1); } host.AddDependencyInjectionBehavior<IDisplayService>(container); host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true, HttpGetUrl = address }); // Add MEX endpoint host.AddServiceEndpoint( ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexNamedPipeBinding(), netNamedPipeLocation + "mex" ); host.Open(); Console.WriteLine("The host has been opened."); Console.ReadLine(); host.Close(); Environment.Exit(0); } }
- 你也可以在 WAS 托管中使用本文提供的信息。
- 除了 Autofac,你可以使用任何 IOC 容器,或者自己创建一个用于依赖注入的容器。
- 并非总是需要为你的类提供接口,但它提供了更好的抽象和组合而不是继承。尽管如此,还是有某些方法可以使用 IOC 容器为这样的类(我指的是没有接口的类)注入依赖项。
参考文献
- 依赖注入有助于实现“依赖倒置原则”
- 来源:https://en.wikipedia.org/wiki/SOLID
- 对于 IOC 容器,我们将使用 Autofac 和 Autofac.WCF 来源:https://autofaccn.readthedocs.io/en/latest/integration/wcf.html
- (图片来源:unsplash 上的 daniel-cheung)
- https://alexmg.com/posts/self-hosting-wcf-services-with-the-autofac-wcf-integration
历史
- 29/07/2019
- 为代码片段添加了格式
- 更新了图片