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

快速粗糙的 WCF 服务和客户端(使用 WCF 和 Winforms)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (25投票s)

2007 年 5 月 14 日

7分钟阅读

viewsIcon

205086

downloadIcon

4535

一个关于如何使用 Windows Communication Foundation 服务的示例。

引言

这是一个创建自托管 WCF 服务和客户端的完整演练。你需要 .NET 3.0 和 Visual Studio 2005 才能使其正常工作。

示例 WCF 服务

入门

打开 Visual Studio 2005 并创建一个新的 WCF 服务库项目。此项目类型列在 .NET Framework 3.0 下。这将创建一个项目和一个 C# 文件 (Class1.cs)。该 C# 文件包含一个我们将要修改的服务存根。

修改服务

首先,我们将更改页面底部附近的数据契约类。它最初看起来像这样:

[DataContract]
public class DataContract1
{
    string firstName;
    string lastName;

    [DataMember]
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }
    [DataMember]
    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

我们要做的只是更改类名。现在将类名更改为 "Patient"。

数据契约定义

数据契约是服务和客户端之间的正式协议,它抽象地描述了要交换的数据。也就是说,为了进行通信,客户端和服务不必共享相同的类型,只需共享相同的数据契约。数据契约为每个参数或返回类型精确定义了为了交换而序列化(转换为 XML)的数据。(微软

接下来,我们需要更改服务契约接口。

服务契约定义

服务契约表示两方之间有效的消息交换模式。发起通信的一方称为发起方。另一方称为服务。服务契约指定一个或多个操作契约。操作契约表示单个消息交换或相关的请求/回复消息交换。(Don Box

服务契约的名称没问题,但让我们将标记有 [OperationContract] 属性的方法更改为我们 Patient 类(数据契约)的 GetSet 方法。

[ServiceContract()]
public interface IService1
{
    [OperationContract]
    Patient GetPatient(Int32 index);

    [OperationContract]
    void SetPatient(Int32 index, Patient patient);
}

上述方法的 index 参数将用于访问 Patient 数组中的单个 Patient。在我们接下来修改 IService1 实现类之后,这一点将变得显而易见。用以下代码替换 service1 类:

[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
public class PatientService : IService1
{
    Patient[] pat = null;

    public PatientService()
    {
        pat = new Patient[3];

        pat[0] = new Patient();
        pat[0].FirstName = "Bob";
        pat[0].LastName = "Chandler";

        pat[1] = new Patient();
        pat[1].FirstName = "Joe";
        pat[1].LastName = "Klink";

        pat[2] = new Patient();
        pat[2].FirstName = "Sally";
        pat[2].LastName = "Wilson";
    }

    public Patient GetPatient(Int32 index)
    {
        if (index <= pat.GetUpperBound(0) && index > -1)
            return pat[index];
        else
            return new Patient();
    }

    public void SetPatient(Int32 index, Patient patient)
    {
        if (index <= pat.GetUpperBound(0) && index > -1)
            pat[index] = patient;
    }
}

ServiceBehavior 属性 IncludeExceptionDetailInFaults=true 会在我们搞砸某些事情时,在调试期间为我们提供更详细的错误信息。没有这个,我们会得到一个泛泛的 FaultException,它不会告诉我们任何关于真实错误的信息。

我们 PatientService 类的构造函数创建并填充了我们的 Patient 数组。
GetPatient 方法返回索引处的 Patient 对象。
SetPatient 方法将索引处的 Patient 对象设置为通过第二个参数传入的 Patient 对象。

现在我们应该有了一个可用的服务,但我们需要一个宿主来承载我们的服务。

服务宿主

右键单击服务项目并转到“属性”。将输出类型从“类库”更改为“Windows 应用程序”。

关闭属性窗口,再次右键单击项目,这次选择“添加”->“新建项”,选择“Windows 窗体”,然后单击“添加”按钮。

在窗体设计器中添加一个按钮,然后双击新添加的按钮。这将添加一个 click 事件处理程序并将您移动到代码窗口。

在顶部添加 using System.ServiceModel;。这将允许我们托管我们的服务。

然后,在 Form1 构造函数的正上方,添加 2 个新的类变量。

bool serviceStarted = false;
ServiceHost myServiceHost = null;
现在回到 button1_Click 事件处理程序代码。将下面列出的代码作为您的方法体。
if (serviceStarted)
{
    myServiceHost.Close();
    serviceStarted = false;
    button1.Text = "Start Service";
}
else
{
    Uri baseAddress = new Uri("net.tcp://:2202/PatientService");

    NetTcpBinding binding = new NetTcpBinding();

    myServiceHost = new ServiceHost(typeof(PatientService), baseAddress);
    myServiceHost.AddServiceEndpoint(typeof(IService1), binding, baseAddress);

    myServiceHost.Open();

    serviceStarted = true;
    button1.Text = "Stop Service";
}

这段代码的作用是,如果我们的服务尚未启动,则为其启动一个宿主,如果已经启动,则关闭宿主。

要托管或启动服务,您必须了解您的 ABC。不是字母表,而是服务的**A**ddress(地址)、**B**inding(绑定)和 **C**ontract(契约)。

在这种情况下,我们基本上是虚构一个地址。我们将使用 TCP 绑定(通信方法),所以地址 (URI) 的第一部分设置为 net.tcp。地址的下一部分是机器名或 IP 和端口号。Localhost 仅表示您的机器,而端口号 (2202) 只是任意选择的。在随便选择任何数字之前,您需要确保它是一个有效且开放的端口。地址的最后一部分只是我们的服务名称。

绑定是 TCP,所以我们需要创建一个 NetTcpBinding 对象。

我们的契约是 IService1,即我们的服务契约接口的名称。

我们创建一个新的 ServiceHost 对象,它接受一个“singletonInstance”类型和我们的地址作为参数。“singletonInstance”是我们契约的实现类,即 PatientService

接下来,我们向刚刚创建的宿主添加一个终结点。这会以我们的 ABC 作为参数,但由于某种原因,顺序是相反的。

宿主被打开关闭,分别用于允许或禁止客户端访问。

我们需要为我们的宿主应用程序添加一个主入口点,所以在底部,紧接在类之后但在命名空间括号内,添加以下代码。

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

返回到窗体设计器,并将按钮上的文本更改为“启动服务”。

如果您愿意,现在可以构建并运行此应用程序。您现在已经完成了一个自托管服务,但我们需要一个客户端来消费该服务。

示例客户端

右键单击解决方案,然后单击“添加”->“新建项目”->“Windows”,并选择“Windows 应用程序”。

向新窗体添加 3 个标签控件、3 个文本框和 2 个按钮。

  • label1 的 text 属性更改为“索引:”
  • label2 的 text 属性更改为“名:”
  • label3 的 text 属性更改为“姓:”
  • button1 的 text 属性更改为“获取患者”
  • button2 的 text 属性更改为“设置患者”
  • textBox1button1label1 对齐
  • textBox2button2label2 对齐
  • 最后,将 textBox3label3 对齐

最终成品应如下所示:

Screenshot - Image1.jpg

现在双击窗体的空白部分为窗体添加一个 Load 事件处理程序。然后双击两个按钮,为每个按钮添加 click 事件处理程序。

我们需要向客户端项目添加 2 个引用。选择“项目”菜单项,然后单击“添加引用”。在 .NET 选项卡下,找到并选择 System.ServiceModel,然后单击“确定”。再添加一个引用,这次选择“项目”选项卡并选择您之前创建的服务。

回到 Form1.cs,将 using System.ServiceModel;using YourService;(用您的服务名称替换 YourService)添加到其他 using 语句列表中。

现在我们准备好访问我们的服务了。在构造函数的正上方添加一个名为 IService1 patientSvc 的类变量,并将其设置为 null。就像在我们的宿主应用程序中一样,我们必须使用我们的 ABC 来为我们的服务设置一个终结点。在 Form1_Load 事件处理程序中,添加以下代码:

EndpointAddress address = new EndpointAddress
            (new Uri("net.tcp://:2202/PatientService"));
NetTcpBinding binding = new NetTcpBinding();
ChannelFactory<IService1> factory = 
            new ChannelFactory<IService1>(binding, address);
patientSvc = factory.CreateChannel();

这段代码以我们的 PatientService 类的形式创建了一个新的 IService1 对象,同样使用了服务契约的 ABC。微软将 ChannelFactory 定义为... 一个工厂,用于创建不同类型的通道,客户端使用这些通道向各种配置的服务终结点发送消息。所以现在我们有了一个 PatientService (IService1) 对象,这就是我们目前需要知道的全部内容。

将以下代码放入按钮的 click 处理程序中:

private void button1_Click(object sender, EventArgs e)
{
    Patient patient = patientSvc.GetPatient(Convert.ToInt32(textBox1.Text));

    if (patient != null)
    {
        textBox2.Text = patient.FirstName;
        textBox3.Text = patient.LastName;
    }
}

private void button2_Click(object sender, EventArgs e)
{
    Patient patient = new Patient();
    patient.FirstName = textBox2.Text;
    patient.LastName = textBox3.Text;

    patientSvc.SetPatient(Convert.ToInt32(textBox1.Text), patient);
}

当点击 button1 时,它会获取在 textBox1 中输入的索引,将其转换为 Int32,并将其传递给我们 PatientService 中的 GetPatient 方法。如果索引有效,返回的是一个功能齐全的 Patient 对象(否则为 null)。这与我们在服务的 DataContract 类中定义的 Patient 对象是同一个。然后我们用名和姓填充另外两个文本框。

当点击 button2 时,我们创建一个新的 Patient 对象,用我们文本框中的值填充它的两个属性,然后将我们 PatientService 中指定索引处的 Patient 设置为这个新的 Patient

结论

就是这样!我们现在有了一个可以工作的自托管 WCF 服务和客户端。右键单击解决方案并选择“设置启动项目”,然后选择“多个启动项目”。将两个项目的“操作”都设置为“启动”,然后单击“确定”。现在调试解决方案,服务宿主和客户端都将运行。在尝试单击客户端上的任何按钮之前,请确保先单击“启动服务”按钮。

历史

  • 2007年5月14日:上传 1.0 版本
  • 2007年5月17日:修正了文章中客户端 ChannelFactory 设置的错误
© . All rights reserved.