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






4.91/5 (25投票s)
2007 年 5 月 14 日
7分钟阅读

205086

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
类(数据契约)的 Get
和 Set
方法。
[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 个按钮。
|
最终成品应如下所示: |
现在双击窗体的空白部分为窗体添加一个 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
设置的错误