你的位置:首页 > ASP.net教程

[ASP.net教程]第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 标准(如

由于焦点在于服务使用者与提供者之间的互操作性,因 此您不能依赖在不同的平台上可能有、也可能没有的复杂类型。因此,您需要定义一个提供互操作性的合约。下面描述的方法包括使用

合约

Service Interface 中指出,存在允许服务提供者与服务使用者进行互操作的合约。将该合约实现为 ASP.NET Web Service 需要完成三个方面的工作:

  • 指定 。在服务使用者与提供者之间传输的数据的定义是使用 long 类型的简单变量;因此此方案不需要架构,因为简单类型已构建到 SOAP 规范中。但是,Web Service 的返回类型不是简单类型,因此必须使用
  • 数据传输对象。.NET framework 有一个名为 xsd.exe 的工具,只要为该工具指定一个 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

将要传输到客户端的信息的定义是使用 Recording 和 Track

<?
<xs:schema 
   <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 命令行工具,该工具接受

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.
/// <remarks/> 
[System.
[System.
public class Recording { 
    /// <remarks/> 
    public long id; 
    /// <remarks/> 
    public string title; 
    /// <remarks/> 
    public string artist; 
    /// <remarks/> 
    [System.
    public Track[] Track; 
} 
/// <remarks/> 
[System.
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 对象。按照 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 转换为所生成的 RecordingTrack 对象。

RecordingAssembler.cs

之所以将该类作为服务接口的一部分,是因为需要将应用逻辑的输出转换为要通过 Web Service 发送出去的对象。RecordingAssembler 类负责将服务实现的返回类型(本例中为 ADO.NET DataSet)转换为上一步所生成的 RecordingTrack 类型。

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 类通常要复杂一些。它们的工作是从一种表示法转换为另一种表示法,因此它们通常都很简单,但总是依赖于两种表示法。这种依赖性使得它们容易受这两种表示法变化的影响。

虽 然组装器很有用,但是如果已有满足您需要的可选组装器,那么您不必自己创建。但如果需要,您可以使用

应用逻辑

对于大多数企业应用而言,本示例中的应用逻辑可能过于简单。这是因为使用此模式重点是为了实现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) 中检索信息,而其他测试则用来测试 DataSetRecordingTrack 对象 (RecordingAssemblerFixture) 的转换。

RecordingGatewayFixture

RecordingGatewayFixture 类用于测试 RecordingGateway 的输出(这是一个 DataSet)。通过该测试可以验证在给定 ID 的情况下是否从数据库中检索到包含 recordingtrack 信息的正确 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

第二个配件通过测试 DataSetRecordingTrack 对象的转换来测试 RecordingAssembler 类。

using NUnit.Framework; 
using System.Data; 
using System.IO; 
using System.
[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 标准(如
  • ASP.NET Web services Microsoft Visual Studio.NET。 此环境使得 Web Services 的使用非常简单。本示例中演示的 xsd.exe 工具提供了一种将

缺点

  • 数据转换。在许多情况下,必须进行将应用逻辑表示法转换为服务接口当前所使用的表示法的数据转换。由于使用一个依赖于这两种表示法的类将导致依赖性,就使得这种转换始终会有问题。在本例中,RecordingAssembler 类依赖于由 RecordingGateway 返回的 DataSet 以及所生成的 RecordingTrack 类。

同步。架构和所生成的代码无法自动更新。因此,对架构进行任何更改后,都需要重新运行 xsd.exe 工具来重新生成 Recording.cs 类。




去青海旅游要多少钱去青海旅游最佳路线深圳去宁夏旅游报价跟团去宁夏旅游多少钱几月去宁夏旅游最好2015年学生火车票优惠时间规定 14年放假安排时间表 2015湖南卫视春节联欢晚会节目单 2015年最新母亲节活动策划方案 来安徽做花痴 安徽赏花攻略(组图) 无奇不有的世界,这些怪异的婚礼习俗你可曾听过[三] 南粤苑特色是什么?番禺南粤苑有什么好吃的? 2015龙抬头节三亚大小洞天有什么祈福?大小洞天二月二祈福要门票吗? 霞客古渡自助游攻略?上林霞客古渡开车要多久? 新兴天露山漂流节好玩吗?2015天露山漂流节玩什么? 上林霞客古渡景区一日游攻略?霞客古渡景区路线? 新兴天露山漂流节什么时候开始?天露山漂流节几号开始? 泰式按摩怎么回事?有什么效果啊? 迪拜女人怎么样?中国男人可以娶一个迪拜女人吗? 泰国人喜欢跳什么舞啊?什么舞蹈最能代表泰国啊? 中国女人嫁给迪拜男人后生活会怎样?迪拜男人怎么样? AT28HC256E-12LM/883 Datasheet AT28HC256E-12LM/883 Datasheet AT28HC256E-12LM/883C Datasheet AT28HC256E-12LM/883C Datasheet AT28HC256E-12PC Datasheet AT28HC256E-12PC Datasheet 浙江香港二日游 浙江香港二日游 浙江香港二日游 佳木斯跟团香港两日游价格 佳木斯跟团香港两日游价格 佳木斯跟团香港两日游价格 大庆跟团香港2日游 大庆跟团香港2日游 大庆跟团香港2日游