[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 转换为所生成的 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 类通常要复杂一些。它们的工作是从一种表示法转换为另一种表示法,因此它们通常都很简单,但总是依赖于两种表示法。这种依赖性使得它们容易受这两种表示法变化的影响。
虽 然组装器很有用,但是如果已有满足您需要的可选组装器,那么您不必自己创建。但如果需要,您可以使用
应用逻辑
对于大多数企业应用而言,本示例中的应用逻辑可能过于简单。这是因为使用此模式重点是为了实现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.[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 实现的优缺点:
优点