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

WCF 现实世界应用,非 Hello World 系列,第二部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2016年9月18日

CPOL

6分钟阅读

viewsIcon

13620

downloadIcon

102

通过团队合作在企业中进行 WCF 开发以实现真正的 RAD

引言

这是我上一篇文章 "WCF 现实世界应用,非 Hello World" 的扩展。因此,我推测您在阅读本文之前已经阅读过它,或者已经熟悉类似的rible项目结构:通常,对于每个服务,您将建立三个程序集,分别用于契约、实现和客户端 API。

背景

"WCF 现实世界应用,非 Hello World" 演示了如何在企业环境中通过团队协作有效地管理 WCF 服务开发,以快速开发和交付体面的 Web 服务。该文章还解释了“为什么”以及“如何”做到这一点。

先决条件

  • Visual Studio 2013/2015
  • 通过 NuGet 安装 xUnit
  • 通过 NuGet 安装 Visual Studio 的 xUnit 运行器
  • 通过 NuGet 安装 Fonlow Testing

提示

NuGet 包的 DLL 文件已包含在仓库中。

使集成测试更容易

我希望在 IDE 或独立运行器中运行集成测试,并且服务应该自动启动和停止,而无需以管理员身份运行。

基本项目结构

Project sturcture

除了 RealWorldServices 文件夹中的服务代码外,我创建了一个 WCF 应用程序项目 "WcfService1" 来包含所有服务程序集,例如 RealWorldService 和 RealWorldImp。请注意,我已经删除了 VS IDE 创建的所有脚手架 C# 代码,因此该项目只是一个轻量级的“主程序”/启动器/外观,用于在 IDE、IIS Express 和 IIS 中启动服务。正如您可能在 "WCF 现实世界应用,非 Hello World" 中看到的那样,对于在 IIS 中托管服务,拥有 WCF 应用程序项目并非强制要求,因为 CLR 会读取 Web.config 并相应地加载 WCF 库代码。

步骤

  1. 创建一个 WCF 应用程序项目。
  2. 删除 IDE 生成的所有 C# 代码。
  3. 添加对 WCF 服务项目的引用,例如 RealWorldService 和 RealWorldImp。

Web.config 告诉 IIS 在不使用 SVC 文件的情况下激活服务

  <system.serviceModel>
    <serviceHostingEnvironment>
      <serviceActivations>
        <!--This is to replace the standalone svc file whic is the legacy of asp.net web app.-->
        <add relativeAddress = "RealWorldServices/RealWorld.svc" service = "Fonlow.Demo.RealWorldService.Service1"/>
      </serviceActivations>
    </serviceHostingEnvironment>
    <services>
      <service name="Fonlow.Demo.RealWorldService.Service1" behaviorConfiguration="authBehavior">

        <!-- Service Endpoints. A Service may provide multiple endpoints -->
        <!-- Not need to define host. Relative  -->
        <endpoint address="" binding="basicHttpsBinding" contract="Fonlow.Demo.RealWorldService.IService1" bindingConfiguration="httpsBindingConfig">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>

当您运行 WcfService1 时,它将默认由 IIS Express 启动,并且 web.config 中提到的服务将被激活。

当您开发更多服务时,可以继续向 WcfService1 项目添加引用。

备注

  • 添加对新服务实现程序集的引用仅用于在 Visual Studio IDE 中修改 Web.config 时进行智能感知。对于生产环境,WCF 应用程序的程序集与服务程序集没有静态绑定是完全可以的。服务主机将在启动时读取 Web.config 并在运行时加载相应服务的程序集。在生产环境中,您甚至不需要部署虚拟的 WCF 应用程序程序集。
  • 为了在 IIS Express 中进行测试时支持 HTTPS,您需要在 WCF 应用程序项目的属性中进行一些调整,正如 Scott Hanselman 的文章 "在开发时使用 SSL 在 IIS Express 中更容易" 中所述。请注意,HTTPS 的“魔术”起始端口号是 44300。

更健壮的客户端代理

如果您进行 WCF 编程时间够长,很可能在客户端遇到过此错误消息,而没有提供具体/有用的错误信息

"通信对象 System.ServiceModel.Channels.ServiceChannel 处于故障状态,无法用于通信。"

这通常发生在服务运行时抛出异常,而不是您的服务操作代码抛出异常时。

MSDN 建议不要在客户端代理类中使用 using 语句,即使生成的代理类实现了 IDisposable 接口。因此,此解决方法不符合 MSDN 的编程规则;换句话说,这是一个例外情况,违反了规则。虽然有效,但该解决方法可能会使您的应用程序代码看起来重复、冗长且笨拙。

我的博客 "正确处理 WCF 通道以应对 WCF 缺陷。后续。" 进一步讨论了这些问题和替代解决方案。因此,在生成代理类(使用 svcutil.exe)之后,最好将代理客户端类包装在“安全通道”中。

    /// <summary>
    /// http://webandlife.blogspot.com/2012/08/proper-disposal-of-wcf-channels-against.html
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <remarks>Better to put this into another component</remarks>
    public class SafeChannel<T> : IDisposable where T : ICommunicationObject, IDisposable
    {
        public SafeChannel(T channel)
        {
            Instance = channel;
        }
        public static IDisposable AsDisposable(T client)
        {
            return new SafeChannel<T>(client);
        }
        public T Instance { get; private set; }
        bool disposed;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    Close();
                }
                this.disposed = true;
            }
        }
        void Close()
        {
            bool success = false;
            try
            {
                if (Instance.State != CommunicationState.Faulted)
                {
                    Instance.Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                    Instance.Abort();
            }
        }
    }

    public class RealWorldProxy : SafeChannel<Fonlow.RealWorldService.Clients.Service1Client>
    {
        public RealWorldProxy(string endpointName)
            : base(new Fonlow.RealWorldService.Clients.Service1Client(endpointName))
        {

        }
    }

集成测试(自动设置和拆卸服务)

开发人员可以通过多种方式构建集成测试

  1. 在同一个 VS 解决方案中设置多个启动项目。
  2. 将服务托管在 IIS 中,并在 VS IDE 或独立运行器中运行集成测试套件。您可以将服务代码附加到相应的 w3wp.exe 实例进行调试。
  3. 在 A.Sln 中创建服务项目,在 B.Sln 中创建测试套件,这样您就可以在由 VS IDE 启动的 IIS Express 中托管的服务和客户端代码之间进行单步调试。

然而,有时我希望将测试套件和服务代码放在同一个 VS 解决方案文件中,并通过自动设置和拆卸服务来运行测试。

通常,我会在 NUnit 或 xUnit 上编写集成测试套件,而不是 MS Test,这样我就可以在没有安装 Visual Studio 的托管计算机上部署期间运行测试套件。

 

    public class TestConstants
    {
        public const string IisExpressAndInit = "IISExpressStartup";
    }

    [CollectionDefinition(TestConstants.IisExpressAndInit)]
    public class IisCollection : ICollectionFixture<Fonlow.Testing.IisExpressFixture>
    {
        // This class has no code, and is never created. Its purpose is simply
        // to be the place to apply [CollectionDefinition] and all the
        // ICollectionFixture<> interfaces.
    }

    [Collection(TestConstants.IisExpressAndInit)]
    public class IntegrationTest
    {
        const string realWorldEndpoint = "DefaultBinding_RealWorld";

        [Fact]
        public void TestGetData()
        {
            using (RealWorldProxy client = new RealWorldProxy(realWorldEndpoint))
            {
                client.Instance.ClientCredentials.UserName.UserName = "test";
                client.Instance.ClientCredentials.UserName.Password = "tttttttt";
                Assert.True(client.Instance.GetData(1234).Contains("1234"));
            }

 

IIsExpressFixture 负责在开始时启动 IIS Express,并在装有相同 XUnit.CollectionAttribute 的所有测试类(在 TestRealWorldIntegration.dll 程序集中)完成所有测试用例后停止服务。

在 TestRealWorldIntegration 的 app.config 中,

  <appSettings>
    <add key="Testing_UseIisExpress" value="True" />
    <add key="Testing_HostSite" value="WcfService1" />
    <add key="Testing_HostSiteApplicationPool" value="Clr4IntegratedAppPool" />
    <add key="Testing_SlnRoot" value="C:\VSProjectsMy\HelloWorldAuth" />
    <add key="Testing_BaseUrl" value="https://:44300/" />
  </appSettings>

appSettings 定义了一些由 IisExpressAndInit 使用的键,用于启动 IIS Express 并指示 IIS Express 运行 WcfService1。

 提示

在 Visual Studio 2012、2013 和 2015 Update 1 之前,IIS Express 使用 %userprofile%\documents\iisexpress\config\applicationhost.config 来处理所有由 Visual Studio 在首次运行 Web 应用程序或服务时注册的托管站点。在 Visual Studio 2015 Update 1 中,IIS Express 使用 YourVsSlnRootFolder\.vs\config\applicationhost.config。这就是为什么必须在测试程序集的 app.config 中定义 Sln 根文件夹。

 

我会定期运行所有测试,包括单元测试和集成测试。

您将看到 IIS Express 由 IIsExpressAndInit 启动。

 

关注点

多年来,我编写了许多 WCF 服务,审查/修复了其他开发人员留下的遗留代码,并使用了来自其他供应商的 Web 服务。我发现糟糕或效率低下的做法导致了混乱的解决方案,不必要地消耗了公司的大量资金和开发人员的宝贵时间。

虽然我坚信问题的本质在于项目管理和公司文化,特别是使用敏捷实践或 Scrum 框架时存在的心态不当,但我认为开发人员自己应该乐于编写最少量的代码,并尽可能自动化开发工作。

WCF 和 MVC 都非常复杂、全面且成熟,我们应该更深入地研究,而不是编写笨拙但能运行的代码,从而产生巨大的技术债务。多学习,少写代码,多交付。

© . All rights reserved.