第6章 服务模式 在 .NET 中实现 Service Interface
上下文
您 的应用程序部署在 Microsoft Windows? 操作系统上。您决定将应用程序的某一块功能作为 ASP.NET Web Service 公开。互操作性是一个关键问题,因此您无法使用仅在 Microsoft .NET Framework 中出现的复杂数据类型。
背景
通 常,当您将音频光盘 (CD) 插入计算机时,您用来播放 CD 的程序会显示与音乐有关的各种信息。此信息可能包括曲目信息、封面画、评论等。为了演示 Service Interface 模式的实现,下面以此为例来实现一个 ASP.NET Web Service。
实现策略
Service Interface 描述了接口机制与应用逻辑的分离方法。接口负责实现并执行将要公开的服务的合约,而应用逻辑则负责以特定方式实现接口所使用的业务功能。本示例使用 ASP.NET Web Service 来实现服务接口。
注意:此处显示的应用逻辑是 Table Data Gateway 模式的示例。在典型的应用程序中,该实现还要提供一些附加的业务功能。为了重点讲述 Service Interface,本示例忽略了这些附加业务功能。
Service Interface 实现
ASP.NET Web Service 用于实现 Service Interface。通过将服务接口实现为 Web Service,任意数目的全异系统都能使用 Internet 标准(如 XML、SOAP 和 HTTP)来访问这部分功能。在创建用于支持应用程序互操作性的基础结构方面,Web Service 严重依赖于 XML 以及其他 Internet 标准的接受程度。
由于焦点在于服务使用者与提供者之间的互操作性,因 此您不能依赖在不同的平台上可能有、也可能没有的复杂类型。因此,您需要定义一个提供互操作性的合约。下面描述的方法包括使用 XML 架构定义数据传输对象、使用平台特有工具生成数据传输对象,然后依赖该平台实现使用数据传输对象的服务接口代码。这并不是唯一可以使用的方法。.NET Framework 可以自动生成所有功能块。但是,在一些情况下,它所生成的服务接口所具有的互操作不是很好。另一方面,您可以使用 Web Services 描述语言 (WSDL) 和 XML 架构来指定接口,然后使用 wsdl.exe 实用程序为您的应用程序生成服务接口。
合约
Service Interface 中指出,存在允许服务提供者与服务使用者进行互操作的合约。将该合约实现为 ASP.NET Web Service 需要完成三个方面的工作:
- 指定 XML 架构。在服务使用者与提供者之间传输的数据的定义是使用 XML 架构来指定的。服务的输入是 long 类型的简单变量;因此此方案不需要架构,因为简单类型已构建到 SOAP 规范中。但是,Web Service 的返回类型不是简单类型,因此必须使用 XML 架构来指定此类型。在本示例中,架构包含在 Recording.xsd 文件中。
- 数据传输对象。.NET framework 有一个名为 xsd.exe 的工具,只要为该工具指定一个 XML 架构,它就可以生成由实现 Web Service 的代码所使用的数据传输对象。在本示例中,数据传输对象的名称是 Recording,并且包含在 Recording.cs 文件中。
- 服务接口实现。这是一个类,该类继承自 System.Web.Services.WebServices,并指定了至少一个标记有 [WebMethod] 属性的方法。在本示例中,该类的名称为 RecordingCatalog,并且包含在RecordingCatalog.asmx.cs 文件中。该类负责发出对服务实现的调用,并负责将服务实现的输出转换为 Web Service 将使用的格式。转换数据的功能封装在名为 RecordingAssembler 的类中,并包含在RecordingCatalog.asmx.cs 文件中。该类是组装器的一个示例,而组装器是 Mapper 模式的一个变体。[Fowler03]
下图描绘了实现服务接口的各个类之间的关系。
图 1 服务接口类
Recording.xsd
将要传输到客户端的信息的定义是使用 XML 架构指定的。下列构架定义了两种复杂类型:Recording 和 Track。
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:tns="http://msdn.microsoft.com/practices" elementFormDefault="qualified" targetNamespace="http://msdn.microsoft.com/patterns" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Recording" type="tns:Recording" />
<xs:complexType name="Recording">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:long" />
<xs:element minOccurs="1" maxOccurs="1" name="title" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="artist" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="unbounded" name="Track" type="tns:Track" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Track">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="id" type="xs:long" />
<xs:element minOccurs="1" maxOccurs="1" name="title" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="duration" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Recording 类型有 ID、艺术家、标题以及大量的 Track 类型。 Track 类型还有 ID、标题以及持续时间元素。
Recording.cs
前面已提到,.NET Framework 有一个 xsd.exe 命令行工具,该工具接受 XML 架构作为输入,并输出可以在程序中使用的类。所生成的类用作 Web Service 的返回值。用于生成 Recording.cs 类的命令如下:
xsd /classes Recording.xsd
The output that was produced by running this command is shown below:
//------------------------------------------------------------------------------
// <autogenerated>
// This code was generated by a tool.
// Runtime Version: 1.0.3705.288
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </autogenerated>
//------------------------------------------------------------------------------
//
// This source code was auto-generated by xsd, Version=1.0.3705.288.
//
using System.Xml.Serialization;
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://msdn.microsoft.com/practices")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://msdn.microsoft.com/practices", IsNullable=false)]
public class Recording {
/// <remarks/>
public long id;
/// <remarks/>
public string title;
/// <remarks/>
public string artist;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("Track")]
public Track[] Track;
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://msdn.microsoft.com/practices")]
public class Track {
/// <remarks/>
public long id;
/// <remarks/>
public string title;
/// <remarks/>
public string duration;
}
RecordingCatalog.asmx.cs
在定义该类之后,您需要实现实际的 Web Service 实现。该类封装了所有 Service Interface 行为。要公开的服务通过使用 [WebMethod] 属性来显式定义。
[WebMethod]
public Recording Get(long id)
{ /* */ }
Get 方法接受 id 作为输入,并返回 Recording 对象。按照 XML 架构的说明,Recording 还可以包括许多 Track 对象。
下面是具体的实现。
using System.ComponentModel;
using System.Data;
using System.Web.Services;
namespace ServiceInterface
{
[WebService(Namespace="http://msdn.microsoft.com/practices")]
public class RecordingCatalog : System.Web.Services.WebService
{
private RecordingGateway gateway;
public RecordingCatalog()
{
gateway = new RecordingGateway();
InitializeComponent();
}
#region Component Designer generated code
//
#endregion
[WebMethod]
public Recording Get(long id)
{
DataSet ds = RecordingGateway.GetRecording(id);
return RecordingAssembler.Assemble(ds);
}
}
}
Get 方法通过调用 RecordingGateway 获得一个 DataSet。然后,它通过调用 RecordingAssembler.Assemble 方法将 DataSet 转换为所生成的 Recording 和 Track 对象。
RecordingAssembler.cs
之所以将该类作为服务接口的一部分,是因为需要将应用逻辑的输出转换为要通过 Web Service 发送出去的对象。RecordingAssembler 类负责将服务实现的返回类型(本例中为 ADO.NET DataSet)转换为上一步所生成的 Recording 和 Track 类型。
using System;
using System.Collections;
using System.Data;
public class RecordingAssembler
{
public static Recording Assemble(DataSet ds)
{
DataTable recordingTable = ds.Tables["recording"];
if(recordingTable.Rows.Count == 0) return null;
DataRow row = recordingTable.Rows[0];
Recording recording = new Recording();
recording.id = (long)row["id"];
string artist = (string)row["artist"];
recording.artist = artist.Trim();
string title = (string)row["title"];
recording.title = title.Trim();
ArrayList tracks = new ArrayList();
DataTable trackTable = ds.Tables["track"];
foreach(DataRow trackRow in trackTable.Rows)
{
Track track = new Track();
track.id = (long)trackRow["id"];
string trackTitle = (string)trackRow["title"];
track.title = trackTitle.Trim();
string duration = (string)trackRow["duration"];
track.duration = duration.Trim();
tracks.Add(track);
}
recording.Track = (Track[])tracks.ToArray(typeof(Track));
return recording;
}
}
Assembler 类通常要复杂一些。它们的工作是从一种表示法转换为另一种表示法,因此它们通常都很简单,但总是依赖于两种表示法。这种依赖性使得它们容易受这两种表示法变化的影响。
虽 然组装器很有用,但是如果已有满足您需要的可选组装器,那么您不必自己创建。但如果需要,您可以使用 XML 序列化功能来创建一个 XMLDataDocument 实例,再将它与 DataSet 关联,然后返回 XML。
应用逻辑
对于大多数企业应用而言,本示例中的应用逻辑可能过于简单。这是因为使用此模式重点是为了实现Service Interface ,以便更完整地展示实现部分,而不是作为一个代表性示例。此实现使用 Table Data Gateway 来从数据库中检索数据。Table Data Gateway 类(称为 RecordingGateway)检索 recording 记录以及与 recording 关联的 track 记录。结果以单个 DataSet 的形式返回。有关所使用的数据库架构以及 DataSet 的详细讨论,请参阅在 .NET 中使用 DataSet 实现 Data Transfer Object。
RecordingGateway.cs
该类用两个结果集填充 DataSet:recording 和 track。客户端传递所需要的 recording 记录的 ID。该类在数据库中执行两个查询以填充 DataSet。最后,该类定义 recording 与其 track 记录之间的关系。
using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
public class RecordingGateway
{
public static DataSet GetRecording(long id)
{
String selectCmd =
String.Format(
"select * from recording where id = {0}",
id);
SqlConnection myConnection =
new SqlConnection(
"server=(local);database=recordings;Trusted_Connection=yes");
SqlDataAdapter myCommand =
new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "recording");
String trackSelect =
String.Format(
"select * from Track where recordingId = {0} order by Id",
id);
SqlDataAdapter trackCommand =
new SqlDataAdapter(trackSelect, myConnection);
trackCommand.Fill(ds, "track");
ds.Relations.Add("RecordingTracks",
ds.Tables["recording"].Columns["id"],
ds.Tables["track"].Columns["recordingId"]);
return ds;
}
}
注意:这个示例并不是向 DataSet 中填入数据的唯一方式。从数据库中检索此数据的方法有很多种。例如,可以使用存储过程。
测试
单元测试的重点是测试该实现的内部方面。可以用一个单元测试来测试从数据库 (RecordingGatewayFixture) 中检索信息,而其他测试则用来测试 DataSet 到 Recording 和 Track 对象 (RecordingAssemblerFixture) 的转换。
RecordingGatewayFixture
RecordingGatewayFixture 类用于测试 RecordingGateway 的输出(这是一个 DataSet)。通过该测试可以验证在给定 ID 的情况下是否从数据库中检索到包含 recording 和 track 信息的正确 DataSet 。
using NUnit.Framework;
using System.Data;
[TestFixture]
public class RecordingGatewayFixture
{
private DataSet ds;
private DataTable recordingTable;
private DataRelation relationship;
private DataRow[] trackRows;
[SetUp]
public void Init()
{
ds = RecordingGateway.GetRecording(1234);
recordingTable = ds.Tables["recording"];
relationship = recordingTable.ChildRelations[0];
trackRows = recordingTable.Rows[0].GetChildRows(relationship);
}
[Test]
public void RecordingCount()
{
Assertion.AssertEquals(1, recordingTable.Rows.Count);
}
[Test]
public void RecordingTitle()
{
DataRow recording = recordingTable.Rows[0];
string title = (string)recording["title"];
Assertion.AssertEquals("Up", title.Trim());
}
[Test]
public void RecordingTrackRelationship()
{
Assertion.AssertEquals(10, trackRows.Length);
}
[Test]
public void TrackContent()
{
DataRow track = trackRows[0];
string title = (string)track["title"];
Assertion.AssertEquals("Darkness", title.Trim());
}
[Test]
public void InvalidRecording()
{
DataSet ds = RecordingGateway.GetRecording(-1);
Assertion.AssertEquals(0, ds.Tables["recording"].Rows.Count);
Assertion.AssertEquals(0, ds.Tables["track"].Rows.Count);
}
}
RecordingAssemblerFixture
第二个配件通过测试 DataSet 到 Recording 和 Track 对象的转换来测试 RecordingAssembler 类。
using NUnit.Framework;
using System.Data;
using System.IO;
using System.Xml;
[TestFixture]
public class RecordingAssemblerFixture
{
private static readonly long testId = 1234;
private Recording recording;
[SetUp]
public void Init()
{
DataSet ds = RecordingGateway.GetRecording(1234);
recording = RecordingAssembler.Assemble(ds);
}
[Test]
public void Id()
{
Assertion.AssertEquals(testId, recording.id);
}
[Test]
public void Title()
{
Assertion.AssertEquals("Up", recording.title);
}
[Test]
public void Artist()
{
Assertion.AssertEquals("Peter Gabriel", recording.artist);
}
[Test]
public void TrackCount()
{
Assertion.AssertEquals(10, recording.Track.Length);
}
[Test]
public void TrackTitle()
{
Track track = recording.Track[0];
Assertion.AssertEquals("Darkness", track.title);
}
[Test]
public void TrackDuration()
{
Track track = recording.Track[0];
Assertion.AssertEquals("6:51", track.duration);
}
[Test]
public void InvalidRecording()
{
DataSet ds = RecordingGateway.GetRecording(-1);
Recording recording = RecordingAssembler.Assemble(ds);
Assertion.AssertNull(recording);
}
}
运行这些测试后,就可以判断是否能够正确地从数据库中检索信息,并将数据库输出转换为数据传输对象。但是,上述测试并未测试端到端功 能,也未测试所有服务接口代码。下面的示例则用于测试完整的功能。由于它验证整个接口是否按照所期望的方式工作,因此将它称为功能测试或验收测试。下面描 述的测试将从 RecordingGateway 中检索 DataSet。然后,它使用 Web Service 发出调用,以便检索完全相同的 Recording。收到结果后,它直接将两个结果进行比较。如果二者相等,则说明 Service Interface 运行正常。
注意:下面显示的仅仅是可能的验收测试的示例。您还应注意的是,还存在执行此类测试的其他方法。这仅仅是执行测试的一种方法。
AcceptanceTest.cs
下面是服务接口的一些验收测试示例:
using System;
using System.Data;
using NUnit.Framework;
using ServiceInterface.TestCatalog;
[TestFixture]
public class AcceptanceTest
{
private static readonly long id = 1234;
private DataSet localData;
private DataTable recordingTable;
private RecordingCatalog catalog = new RecordingCatalog();
private ServiceInterface.TestCatalog.Recording recording;
[SetUp]
public void Init()
{
// get the recording from the database
localData = RecordingGateway.GetRecording(id);
recordingTable = localData.Tables["recording"];
// get the same recording from the web service
recording = catalog.Get(id);
}
[Test]
public void Title()
{
DataRow recordingRow = recordingTable.Rows[0];
string title = (string)recordingRow["title"];
Assertion.AssertEquals(title.Trim(), recording.title);
}
[Test]
public void Artist()
{
DataRow recordingRow = recordingTable.Rows[0];
string title = (string)recordingRow["artist"];
Assertion.AssertEquals(title.Trim(), recording.artist);
}
// continued
}
结果上下文
下面是将 ASP.NET Web Service 用作 Service Interface 实现的优缺点:
优点
- 分隔任务。将服务接口与应用逻辑分隔开来很重要,因为它们有可能独立变化。将接口部分作为 ASP.NET Web Service 实现有助于进行这种分隔。
- 互操作性。由于接口基于 Internet 标准(如 XML 和 SOAP),这就使得客户端使用无论哪一种操作系统都可以访问 Web Service。
- ASP.NET Web services 和 Microsoft Visual Studio.NET。 此环境使得 Web Services 的使用非常简单。本示例中演示的 xsd.exe 工具提供了一种将 XML 架构转换为 C# 或 Microsoft Visual Basic? .NET 类的工具。为了创建 Web Service,本示例使用了 Microsoft Visual Studio? .NET 开发系统中的预定义模板并生成了 RecordingCatalog.asmx.cs 文件的大部分。
缺点
- 数据转换。在许多情况下,必须进行将应用逻辑表示法转换为服务接口当前所使用的表示法的数据转换。由于使用一个依赖于这两种表示法的类将导致依赖性,就使得这种转换始终会有问题。在本例中,RecordingAssembler 类依赖于由 RecordingGateway 返回的 DataSet 以及所生成的 Recording 和 Track 类。
同步。架构和所生成的代码无法自动更新。因此,对架构进行任何更改后,都需要重新运行 xsd.exe 工具来重新生成 Recording.cs 类。
第6章 服务模式 在 .NET 中实现 Service Interface的更多相关文章
- 第6章 服务模式 在 .NET 中实现 Service Gateway(服务网关)
上下文 您正在设计企业应用程序,该程序需要使用由其他应用程序提供的服务.该服务定义了一个合约,所有服务使用者要访问该服务都必须遵守该合约.该合约定义了与此服务通信所需的技术.通信协议和消息定义等内容. ...
- 第6章 服务模式 Service Interface(服务接口)
Service Interface(服务接口) 上下文 您正在设计企业应用程序,并且需要能够通过网络使用其部分功能.此功能需要能够被各类系统使用,因此互操作性是设计的重要方面.除互操作性之外,可能还需 ...
- 第5章分布式系统模式 在 .NET 中使用 DataSet 实现 Data Transfer Object
要在 .NET Framework 中实现分布式应用程序.客户端应用程序需要显示一个窗体,该窗体要求对 ASP.NET Web Service 进行多个调用以满足单个用户请求.基于性能方面的考虑,我们 ...
- 第九章 C语言在嵌入式中的应用
上章回顾 编码的规范和程序版式 版权管理和申明 头文件结构和作用 程序命名 程序注释和代码布局规范 assert断言函数的应用 与0或NULL值的比较 内存的分配和释放细节,避免内存泄露 常量特性 g ...
- Android开发艺术探索——第二章:IPC机制(中)
Android开发艺术探索--第二章:IPC机制(中) 好的,我们继续来了解IPC机制,在上篇我们可能就是把理论的知识写完了,然后现在基本上是可以实战了. 一.Android中的IPC方式 本节我们开 ...
- 设计模式之第6章-迭代器模式(Java实现)
设计模式之第6章-迭代器模式(Java实现) “我已经过时了,就不要讲了吧,现在java自带有迭代器,还有什么好讲的呢?”“虽然已经有了,但是具体细节呢?知道实现机理岂不美哉?”“好吧好吧.”(迭代器 ...
- 使用Micrisoft.net设计方案 第二章组织模式
第二章组织模式 模式不仅依赖于它所包含的更小模式,同时也依赖包含它的更大的模式.它是描述复杂软件的系统方法. 本章的目标是让我们了解以下问题: 1.如何标识模式与模式的关系 2.如何把模式组织成模式集 ...
- 云计算的三种服务模式:SaaS/PaaS/IaaS
转载http://blog.chinaunix.net/uid-22414998-id-3141499.html 定义 云计算主要分为三种服务模式,而且这个三层的分法重要是从用户体验的角度出发的: S ...
- 云计算三种服务模式SaaS、PaaS和IaaS及其之间关系(顺带CaaS、MaaS)
云计算架构图 很明显,这五者之间主要的区别在于第一个单词,而aaS都是as-a-service(即服务)的意思,这五个模式都是近年来兴起的,且这五者都是云计算的落地产品,所以我们先来了解一下云计算是什 ...
随机推荐
- 【sqli-labs】 less37 POST- Bypass MYSQL_real_escape_string (POST型绕过MYSQL_real_escape_string的注入)
POST版本的less36 uname=1&passwd=1%df' or 1#
- Pjax无刷新跳转页面实现,支持超链接与表单提交
什么是pjax? 当你点击一个站内的链接的时候,不是做页面跳转,而是只是站内页面刷新.这样的用户体验,比起整个页面都闪一下来说, 好很多. 其中有一个很重要的组成部分, 这些网站的ajax刷新是支持浏 ...
- Objective-C类成员变量深度剖析--oc对象内存模型
目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...
- excel 处理方法
//.方法一:采用OleDB读取EXCEL文件: //打开excel 返回指定表中的所有数据 public DataSet ExcelToDS(string Path) { string strCon ...
- java操作Excel的poi 创建一个sheet页
package com.java.poi; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.us ...
- MySQL在Linux下的表名如何不区分大小写
MySQL在Linux下的表名如何不区分大小写 今天测试的时候,遇到一些问题,明明看到数据,就是查不出来;后来发现,在linux下, mysql的表名区分大小写,而在windows下是不区分,从w ...
- 【Shell编程】Shell基本语法
Shell 语法 Shell程序设计作为一种脚本语言,在Linux系统中有广泛的应用,本文记录了关于Shell程序设计的基础语法知识和常用命令,方便查询,熟练使用shell也需要经常实践,这对于完 ...
- NOIP2009 T2 Hankson的趣味题
传送门 题目描述 Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson.现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题. 今天在课堂上, ...
- react 点击事件+父子传值
接下来要做的效果是,在父组件添加两个按钮,点击后改变父组件传过去的值 父组件 import React, { Component } from 'react'; import Test from '. ...
- .net 单元测试
都说测试驱动开发,但是想写好单元测试其实不容易,不是因为测试用例难以构造,而是因为很多时候方法非常复杂 其中部分测试想要完成就十分费力,其中让人崩溃的地方主要如下: 实例私有函数 实例静态私有函数 十 ...