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

设计 WCF 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (9投票s)

Oct 31, 2014

CPOL

9分钟阅读

viewsIcon

39473

如果您需要创建一个 WCF 服务,但不确定如何开始或如何组织您的 Visual Studio 解决方案,那么希望本文能给您一些想法。

引言

如果您需要创建一个 WCF 服务,但不确定如何开始或如何组织您的 Visual Studio 解决方案,那么希望本文能给您一些想法。设计 WCF 服务没有正确或错误的方式,但一些想法总是受欢迎的,特别是如果您以前从未创建过。

背景

本文不是WCF 的介绍。假定读者熟悉 WCF 的基本概念以及何时何地使用 WCF 服务。如果您不熟悉 WCF 基础知识,本论坛有大量文章应该能帮助您快速入门。

我已将一个完整的 WCF 解决方案上传到 Github,其中包括一个虚构的法警公司(现称为执法代理人)的 WCF 客户端、服务和宿主。客户端和宿主都是非常简单的控制台应用程序,纯粹用于演示目的。WCF 服务(名为 BailiffServices )遵循我将在本文中描述的相同结构和设计,因此请随意查看该解决方案以进一步澄清我在此文章中描述的任何内容。

WCF 服务的基本结构

我遵循了三层架构。WCF 服务定义了业务层和数据层。WCF 客户端(不在本文讨论范围之内)定义了用户界面并消费 WCF 服务提供的服务。

  • UI 层 - 由 WCF 客户端应用程序定义和实现(不在本文讨论范围之内)
  • 业务层 - 由 WCF 服务定义和实现(将在本文中描述)
  • 数据库层 - 由 WCF 服务定义和实现(将在本文中描述)

以下屏幕截图显示了 WCF 服务的整体结构。它主要由三个项目组成,每个项目都有一个相关的测试工具项目(用于练习该项目包含的功能)。因此,构成解决方案的项目总数为六个。

  • BusinessTier - 是解决方案的主要项目,实现 WCF 接口和服务契约。所有业务逻辑都封装在此项目中。
  • DataTier - 提供数据处理功能并与后端数据库系统通信。所有与数据库相关的功能都封装在此项目中。
  • Common - 提供 BusinessTierDataTier 项目使用的共享(或通用)功能。

除了这些项目,解决方案还包含用于练习其他项目功能的测试工具项目。可以使用任何单元测试框架,但在我上传到 Github 的代码中,我使用了 NUnit

  • BusinessTier.Tests - 用于测试 BusinessTier 项目中包含功能的测试工具项目。
  • DataTier.Tests - 用于测试 DataTier 项目中包含功能的测试工具项目。
  • Common.Tests - 用于测试 Common 项目中包含功能的测试工具项目。

在进一步详细介绍每个项目的结构之前,值得指出的是,我每个类定义使用一个文件。

三个主要项目(BusinessTierDataTierCommon)中的每一个都包含一个扩展名为 .snk 的文件。这是您对程序集签名时创建的文件。只有签名的程序集才能放入全局程序集缓存(GAC)。但是,对程序集签名并将其添加到 GAC 并不是强制要求。

构建 WCF 服务

解决方案还包括一个构建脚本,如下屏幕截图所示。

要运行这些构建脚本,您需要您的电脑上安装 Nant

如果从您的开发电脑本地运行构建,您需要首先运行批处理文件 PrepareBuild.Build.bat(它会依次执行脚本 PrepareBuild.Build.xml)。这会创建一个与您的解决方案文件夹同名但带有 .Build 扩展名的文件夹,例如 BailiffServices.Build。然后,您从 BailiffServices.Build 文件夹运行批处理文件 FullBuild.Build.bat。如果您正在使用诸如 CruiseControl.NET 等持续集成服务器,您可以通过附加文件 FullBuild.Build.xml(实际上是一个 Nant 构建脚本)来连接您的 WCF 服务。

业务层

BusinessTier 项目是三层架构的业务层。因此,它定义了应用程序的业务规则。在 WCF 服务的上下文中,它定义了接口和服务契约。下面的屏幕截图显示了项目的文件夹结构。

项目按功能组织。因此有与接口、服务契约和辅助函数相关的文件夹。

  • Helpers - 包含辅助函数的定义。这是一种贯穿整个解决方案的设计模式,用于定义松散耦合的对象。它们中的大部分在 Common 项目中定义,因为它们由 BusinessTierDataTier 项目共享。
  • Interfaces - 包含 WCF 服务接口的定义。
  • Services - 包含 WCF 服务契约的定义,包括基服务契约类 ServiceBase
  • app.config - 定义 WCF 服务所需的配置,并定义绑定、服务、行为等内容。当 WCF 服务发布时,此文件构成应用程序的 web.config

辅助函数

此文件夹包含一个名为 DatabaseManager.cs 的文件。它提供了业务层和数据层之间的单点通信,即 BusinessTier 项目和 DataTier 项目。业务层逻辑所需的所有数据库通信都通过 DatabaseManager 辅助类进行路由。

数据层

DataTier 项目是三层架构的数据层。因此,它定义并实现了应用程序的所有数据库交互,例如所有数据库查询和与后端数据库的通信。下面的屏幕截图显示了项目的文件夹结构。

需要注意的是,所有数据库函数都调用存储过程。应用程序中没有包含 SQL 语句。SQL 查询的更改不应涉及 WCF 服务的重新编译和重新部署。您所有的 SQL 查询都应封装在数据库中。

特定实体(Client、Debtor)的所有数据库交互都在单个类和文件中定义。

app.config 仅包含 WCF 服务所需的连接字符串。基数据层类 DataTierBase 也在此项目中定义。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="sqlserver1" connectionString="Data Source=server-sql;Initial Catalog=bailiff;User ID=bailiffservices;Password=********;" providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

通用代码

Common 项目包含 BusinessTierDataTier 项目使用的通用(或共享)代码。

项目按功能组织。因此有与属性、实体、枚举、扩展和辅助类相关的文件夹。

  • Attributes - 包含应用程序使用的 Attribute 类的定义。
  • Entities - 包含应用程序使用的实体类的定义。这些是与应用程序业务领域相关的类,包括 ClientEntityDebtorEntity 等类。这些类的实例被返回给 WCF 客户端应用程序。
  • 在会计系统中,您会期望找到与 PurchaseOrderEntity、JournalEntity 等相关的实体类。
  • Enums - 包含应用程序使用的 Enum 类的定义。
  • Extensions - 包含应用程序使用的 Extension 类的定义。
  • Helpers - 包含应用程序使用的 Helper 类的定义。
  • app.config - 定义项目所需的配置。在本例中,它是应用程序错误日志和信息日志的名称。
  • ReflectPropertyInfo.cs - 包含类 ReflectPropertyInfo 的定义,该类将实体类的实例映射到其 SqlDataReader 表示形式。
  • UnexpectedServiceFault.cs - 包含类 UnexpectedServiceFault 的定义,该类用于异常处理的一部分。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ErrorLog" value="bailiffservices_Error.log"/>
    <add key="InfoLog" value="bailiffservices_Info.log"/>
  </appSettings>
</configuration>

服务的详细演练

既然我们已经了解了整体解决方案结构以及各个文件夹和文件的组织方式,那么了解它们是如何工作的将会很有用。我们将通过选取一个 WCF 服务,并全程跟踪它,以了解各个部分如何/何时被调用以及它们如何相互关联。为了清晰起见,我删除了一些代码段,以使解释尽可能简单易懂。完整的源代码可以在 Github 上找到。

我将使用 WCF 服务 GetClientInfo() 来达到这些目的。

[ServiceContract]
public interface IClient
{
    [OperationContract, FaultContract(typeof(UnexpectedServiceFault))]
    ClientEntity GetClientInfo(string clientid);
}

函数 GetClientInfo() 返回类型为 ClientEntity 的实例。对于需要返回实体的任何服务契约(它们实际上都是 POCO 类型类),它们都定义在 Common 项目的 Entities 文件夹中。ClientEntity 类在文件 ClientEntity.cs 中定义。

public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        DatabaseManager dm = new DatabaseManager();
        return dm.GetClientInfo(clientid);
    }
    catch (Exception ex)
    {
        throw new FaultException<UnexpectedServiceFault>(
            new UnexpectedServiceFault { ErrorMessage = ex.Message, Source = ex.Source, StackTrace = ex.StackTrace, Target = ex.TargetSite.ToString() }, 
            new FaultReason(string.Format(CultureInfo.InvariantCulture, "{0}", FaultReasons.GetClientInfo)));
    }
}

服务契约实现是唯一抛出异常的地方。在其他所有地方,异常都使用 throw 关键字简单地抛到调用堆栈的更高层。当异常最终冒泡到服务契约时,它将被传递给客户端进行处理。在整个异常处理过程中,异常都会被记录。

public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        ClientData data = new ClientData();
        ClientEntity result = data.GetClientInfo(clientid);
        return result;
    }
    catch (Exception ex)
    {
        throw;
    }
}
public ClientEntity GetClientInfo(string clientid)
{
    try
    {
        ClientEntity result = null;
        SqlConnection connection;
        using (connection = new SqlConnection(ConnectionString))
        {
            connection.Open();
            SqlCommand cmd;
            using (cmd = new SqlCommand("sp_get_clientinfo", connection))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@ClientId", clientid);
                SqlDataReader reader = cmd.ExecuteReader();
                if (reader.HasRows)
                {
                    reader.Read();
                    result = ReflectPropertyInfo.ReflectType<ClientEntity>(reader);
                }
            }
            connection.Close();
        }
        return result;
    }
    catch (Exception ex)
    {
        throw;
    }
}

此函数执行一个存储过程,以检索与指定 ClientEntity 相关的数据。SqlDataReader 首先被映射到 ClientEntity 的实例,然后从函数返回。

  1. GetClientInfo() 的接口定义位于 BusinessTier 项目中的 Interfaces/IClient.cs 文件中。
  2. GetClientInfo() 的服务契约实现在 BusinessTier 项目中的 Services/ClientService.cs 文件中。
  3. 服务契约 GetDebtorInfo() 调用数据库助手,它是一个数据库代码的包装函数。这个数据库包装器实际上是 DatabaseManager 助手,它包装了所有从 BusinessTierDataTier 的数据库相关调用。DatabaseManager 的实现在 BusinessTier 项目的 Helpers/DatabaseManager.cs 中可以找到。
  4. 数据库辅助函数 GetClientInfo() 接着调用在 DataTier 项目的 ClientData.cs 文件中 ClientData 类中定义的数据库函数 GetClientInfo()

一旦我们查询数据库获取了相应的数据并将其返回给客户端,WCF 服务就完成了它的任务,没有其他事情可做。通过查看仅仅一个 WCF 服务,我们已经设法涵盖了整个 WCF 解决方案,并了解了各个部分如何协同工作以及它们如何相互关联。

WCF 服务还提供了以下功能,为了清晰起见,本文已省略。但是,您可以通过访问 Github 上的完整源代码来了解它们的实现方式。

  • 错误日志记录
  • 信息日志记录

摘要

希望本文能为您设计自己的 WCF 服务提供一个起点。您不必完全复制我在此处描述的内容,但请随意使用适合您自己的 WCF 服务应用程序的任何部分。如果您希望我对本文中的任何内容进行进一步阐述,请随时留言。

© . All rights reserved.