65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2019年7月28日

CPOL

5分钟阅读

viewsIcon

21513

downloadIcon

333

互联网上有许多关于通过 .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

你需要的东西

  1. Visual Studio(我使用的是 VS Community Edition 2017)
  2. Autofac
  3. Autofac.WCF
  4. 还有一颗开放的心:)

Using the Code

我将使用非常简单的代码作为示例。我的重点是让事情易于理解。

第 1 步:创建一个解决方案并添加 WCF 库项目

我们将解决方案命名为 "DependencyInjectionWcfWithSelfHosting",WCF 服务库项目命名为 "TestService"。

第 2 步:在 WCF 服务项目中添加脚手架代码

  • 删除 app.config,以及系统创建的 IService1Service1.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)

关注点

  1. 你可以从 "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);
                    }
                }
  2. 你也可以在 WAS 托管中使用本文提供的信息。
  3. 除了 Autofac,你可以使用任何 IOC 容器,或者自己创建一个用于依赖注入的容器。
  4. 并非总是需要为你的类提供接口,但它提供了更好的抽象和组合而不是继承。尽管如此,还是有某些方法可以使用 IOC 容器为这样的类(我指的是没有接口的类)注入依赖项。

参考文献

历史

  • 29/07/2019
    • 为代码片段添加了格式
    • 更新了图片
© . All rights reserved.