代码改变世界

[翻译]让WCF使用自定义商业对象(Using Custom Business Objects with Windows Communica)

2008-08-29 12:04  G yc {Son of VB.NET}  阅读(768)  评论(0编辑  收藏  举报

 

原文链接:http://msdn.microsoft.com/zh-cn/vbasic/bb960413.aspx

作者:Rob Windsor  Visual Basic MVP, ObjectSharp Consulting

 

 

在我之前的文章《Windows Communication Foundation 入门》中,我描述如何创建简单的WCF服务。服务事例演示了入户是使用简单类型,像数字和字符串,但在真实的应用程序中大多使用更复杂的数据类型,像是Customers Invoices。在这篇文章中,我将介绍如何在WCF中使用这些自定义的业务对象(类型)。 

In my previous article, Getting Started with Windows Communication Foundation, I described the basics of creating and consuming a simple WCF service. The services demonstrated used simple types like numbers and strings, but most real-world applications use more complex data like customers and invoices. In this article I’ll demonstrate how to work with these custom business objects in WCF.  

事例代码在这里下载。 要运行事例代码,你需安装要Visual Studio 2005.NET Framework 3.0, Visual Studio 2005 extensions for .NET Framework 3.0 (WCF and WPF)(或者安装VS2008 

Download the sample code here. To run the sample code, you'll need Visual Studio 2005, the .NET Framework 3.0, and the Visual Studio 2005 extensions for .NET Framework 3.0 (WCF and WPF) installed. 

开始 

Setting Up 

我的演示的解决方案里包含4个项目。 

I’ll start with a solution that contains four projects.  

clip_image002 

Business 是一个类库项目,包含自定义名称为Person的业务类。Person有几个属性(IdName,和BirthDate)和一个ToString 方法。 

Business is a class library project with a custom business class named Person. Person has a few simple properties (Id, Name 

, and BirthDate) and a ToString method. 

 

Code

 

Services 是一个类库项目,包含一个名为PersonService的服务类。它暴露有3个操作:获取单个Person对象,获取一组Person对象,和更新一个Person对象。因为服务使用内存存储数据(像字典),所以实例模式将设置成Single。这意味着一个服务对象将处理所有的过来的请求,并且这些请求将访问和修改字典中的数据。 

Services is a class library project with a service class named PersonService. It exposes three operations: one to get a single Person object, one to get a group of Person objects, and one to update a Person object. Because the service uses an in-memory data store (i.e., the Dictionary), the instancing mode has been set to Single. This means that a single service object will handle all incoming requests, and those requests will access or modify the data inside the Dictionary. 

 

 

Code

 

Hosts 是一个控制台程序,它是我们服务的宿主。配置了一个使用了http://localhost:8081/PersonService地址和basicHttpBinding 的端点。 

Hosts is a console application that will be our service host. An endpoint has been configured to use an address of “http://localhost:8081/PersonService” and the basicHttpBinding. 

 

 

Code

 

Config

 

最后, Client 是一Windows Forms 项目,将用来浏览也编辑数据。用户界面的元素已经添加到窗体,但还没有实现事件处理。 

Finally, Client is a Windows Forms project that will be used to browse and edit data. The user interface elements have been added to the form, but none of the event handlers have been implemented. 

clip_image004 

数据契约 

Data Contracts 

一旦你准备好和编译后,右键单击Hosts项目,并选择 调试->启动新的实例。你将收到一个像错误,它看起来像这样样子: 

Once you have the solution set up and have a clean build, right-click the Hosts project and choose Debug > Start new instance. You should receive an error that looks like this: 

clip_image006 

不像ASMX Web ServicesWCF不允许自动序列化对象。所有你接收参数或方法的返回值得类型都必须是可序列化的。虽然你可以通过实现IXmlSerializable接口,或者应用Serializable attribute 到你的类上,来要完成这个,不过这里推荐使用新的DataContact DataMember attributes System.Runtime.Serialization 命名空间。 

Unlike ASMX Web Services, WCF does not allow automatic serialization of objects. All types you receive as parameters or return from methods must be serializable. While this can be done by having your type implement the IXmlSerializable interface, or by applying the Serializable attribute to your class, the preferred method is to use the new DataContact and DataMember attributes from the System.Runtime.Serialization namespace. 

为了解决InvalidDataContractException异常,我们需要添加这些attributes到我们自定义的业务对象上。右键单击Business项目并选择引用,选择System.Runtime.Serialization,然后单击确定。 

To address the InvalidDataContractException, we will add these attributes to our custom business object. Right-click the Business project and select Add reference, select System.Runtime.Serialization, and click OK. 

clip_image008 

Person类的源代码文件的最顶端添加导入System.Runtime.Serialization命名空间的声明。然后将DataContract attribute添加到Person类的声明上和将DataMember attribute 添加到每个属性上。 

Add an Imports statement for System.Runtime.Serialization to the top of the source code file for the Person class. Then add the DataContract attribute to the Person class declaration and the DataMember attribute to each of the properties. 

 

 

Code

 

经过这些修改,我们的服务应该可以使用了。右键单击Hosts项目,选择 调试 ->启动新的实例。这次所有的东西应该正确的工作了:控制台窗口应该显示出来并且应该看到指示服务已经启动的消息。 

With these changes, our service should be ready to go. Right-click the Hosts project and select Debug > Start new instance. This time everything should work correctly: the console window should appear and there should be an indication that the service has started.  

让服务宿主继续运行,右键单击Client项目并选择添加服务引用。输入http://localhost:8081/PersonService到服务URI中并输入PersonServiceProxy 到服务引用名字中并点击确定。一旦将服务引用添加到客户端,就可以关闭服务宿主的控制台窗口了。 

With the service host still running, right-click the Client project and select Add service reference. Enter “http://localhost:8081/PersonService” for the Service URI and PersonServiceProxy for the Service reference name, and then click OK. Once the service reference has been added to the client, close the service host console window. 

代理类 

Proxy Class 

添加服务对话框做了2件事。一是添加客户端终结点到项目或网站的配置文件中。二是建立服务代理类。要查看代理类,选择Client项目并单击解决方案浏览器工具栏中的显示所有文件按钮。 

 

The Add Service Reference dialog does two things. It adds a client endpoint to the configuration for the project or web site, and it creates a service proxy class. To see the proxy class, select the Client project and click the Show All Files button in the Solution Explorer’s toolbar. 

clip_image010 

双击PersonServiceProxy.vb 文件,在文本编辑器中打开。在这个部分,我们将说说Preson类。代理类不仅仅包含调用服务的代码,也包含客户端表示业务对象类。服务端发送Person数据将储存在这里。另外,请注意这个类仅用来表示数据,没有函数(方法)。原来类中的方法将不会包含在这里。 

Double-click PersonServiceProxy.vb to open it in the text editor. The part we are interested in is the Person class. Not only does the proxy contain the code we need to call our service, it also has a client-side representation of our business class. When the data for a Person is sent from the service, it will be stored here. Also note that this class only represents the data, not the functionality. None of the methods from the original class will be included here. 

 

 

Code

 

 

使用服务 

Using the Service 

现在我们有了代理类,我们可以向客户端添加逻辑了。我们将添加ListBox填充代码,在窗体载入的时候。为窗体的Load事件和ListBoxSelectedIndexChanged事件添加事件处理。Leave these empty for now.  

Now that we have the proxy, we can add the logic to our client application. We will start by adding code to populate the list box when the form loads. Add event handlers for the form’s Load event and the list box's SelectedIndexChanged event. Leave these empty for now.  

就如下面的代码,添加一个私有的子过程LoadListBox.在这个方法中我们建立一个服务实例并将调用GetPeople方法的返回值绑定到ListBox上。一旦ListBox填充了数据,我们将激活SelectedIndexChanged事件处理。为了执行这个方法,还需要在在窗体的Load事件中调用LoadListBox 

Just below this code, add the declaration for a private subroutine called LoadListBox. In this method we want to create an instance of our service proxy and then data bind the results of calling GetPeople to the list box. Once the list box is populated, we want to activate the event handler for the SelectedIndexChanged event. With this method complete, have the form’s Load event call LoadListBox. 

 

 

Code

 

现在,当用户选择ListBox中的项目时,我们希望显示关联的Person对象数据在下面的区域中。在SelectedIndexChanged 事件处理中,建立服务代理实例并调用GetPerson方法,将ListBoxSelectedValue 作为参数传入。我们需要一个变量来储存结果,所以我们声明了一个私有字段(如类级别的)叫做_person并使用它。然后将Person对象的属性值复制到文本框中。 

Now, when the user selects an item in the list box, we want to show the data for the associated Person object in the data entry area below. In the SelectedIndexChanged event hander, create an instance of the service proxy and call GetPerson, passing in the SelectedValue from the list box (cast to an integer) as a parameter. We’ll need a variable to store the result, so declare a private field (i.e., a class-level variable) called _person and use it. Then copy the values of the properties from the Person object into the text boxes. 

 

Code

 

最后,在Save按钮的单击(Click)事件中,我们希望反转这个过程。将文本框里的值复制到_person 字段的属性中,建立服务代理实例,并调用UpdatePerson 发送更给到服务。最后,在调用LoadListBox方法来重新加载服务上的数据(你更改的)。 

Finally, in the Save button’s Click event, we want to reverse this process. Copy the values of the text boxes into the _person field’s properties, create an instance of the service proxy, and then call UpdatePerson to send the changes back to the service. The last line should be a call to LoadListBox to reload the data (with your changes) from the service. 

 

Code

完成后,你应该就能够测试了。我们需要启动Clientservice host 来运行程序,所以右键单击解决方案并选择属性。选择多启动项目并设置Hosts项目先于Client项目启动。 

With this done, you should now be able to test. We are going to need both the client and the service host running to use the application, so right-click the solution and select Properties. Select Multiple startup projects and then set the Hosts project to start first followed by the Client project.  

clip_image012 

现在你应该能够浏览People列表,编辑数据并保存到服务。 

You should now be able browse through the list of people (Hobbits actually), edit their data and save it back to the service. 

只读属性 

Read-Only Properties 

你可能已经注意到了我们程序中的小问题,在测试的时候,我们可以编辑Person对象的ID属性。这可不是什么好事,由于我们的服务使用属性来识别Person对象。如果你编辑ID,并提交更改,结果可能被忽略或者更糟,你可能更改错误的Person对象的数据。 

You may have noticed a small problem with our application while testing it: we can edit the Id property of the Person object. This is not a good idea, since we are using the property to identify the Person object in the service. If you edit the Id, your changes may be ignored or, worse yet, you may change the data for the wrong person. 

要解决这个问题,定位到Person类并设置ID属性为只读。 

To address this problem, go to the Person class and make the Id property read-only. 

 

Code

 

清理解决方案,并右键单击Hosts项目,选择调试->启动新的实例。 

Get a clean build on the solution, and then right-click the Hosts project and select Debug > Start new instance. 

clip_image014 

噢~~!它没有工作。就像ASMX Web Services,只有可以读写的实体才可以被序列话。有几种方式可以解决这个问题,我将描述其中的一种。 

Arggghhh!! This won’t work. Just like ASMX Web Services, only entities that can be read from and written to can be serialized. There are several possible solutions for this issue. I will describe one of them. 

解决方法 

One Solution 

自从设置只读属性后,将不能序列化,我们将更改Person类,让字段包含data contract 来替换原来的属性。开打Person类并移除DataMember attribute 3个属性中,并切添加它到3个字段声明上(如 _id _name,和_birthDate 

Since read-only properties cannot be serialized, we are going to change the Person class so that the fields are included in the data contract instead of the properties. Open the Person class and remove the DataMember attribute from the three properties, and then add it to the declarations of the three fields (i.e. _id, _name, and _birthDate). 

 

 

Code

 

既然我们更改了Person对象的序列化的样子,所以客户端的服务代理需要修改。你应该能够通过右键单击PersonServiceProxy.map 并选择更新服务引用来完成这个。不过我这建立是有问题的,代替的,右键单击服务引用文件夹,并选择删除。对App.config文件也做用羊的事。 

Since we’ve changed how the Person object will look when it is serialized, the service proxy on the client needs to be modified. While you should be able to right-click PersonServiceProxy.map and select Update service reference to do this, I have found doing so problematic. Instead, right-click the Service References folder and select Delete. Do the same for the app.config file. 

清理Hosts项目,并右键单击选择调试>启动新的实例。当服务Host运行的时候,右键单击并选择调试->穷调试。当服务宿主运行后,右键单击Client项目,并选择添加服务引用。在服务引用中输入服务地的URL http://localhost:8081/PersonService,并将PersonServiceProxy 添加到服务引用名著中,之后单击确定,一旦将服务引用添加到Client,就可以关闭服务宿主的控制台窗口了。 

Get a clean build of the Hosts project, and then right-click and select Debug > Start new instance. With the service host running, right-click the client project and select Add service reference. Enter “http://localhost:8081/PersonService” for the Service URI and PersonServiceProxy for the Service reference name, and then click OK. Once the service reference has been added to the client, close the service host console window. 

现在还没有解决我们的问题。客户端版本的Person_ID属性还是可读写的。我们需要使用真正的Person对象代替客户端代理生成的版本。右键单击Client项目并选择添加引用,然后选择项目选项卡,选择Business,单击确定。 

This hasn’t really solved our problem. The _id property of the client-side version of Person is still read/write. What we need to do to address this issue is to use the real Person object on the client side instead of the version in the proxy. Right-click the Client project and select Add reference, then from the Projects tab, select Business, and click OK. 

接下来将是冒险的部分,我们需要手动修改客户端代理类,即使在违背在代理类顶端注释中的警告。这个问题是因为,如果我们重新生成代理类,我们所作的所有修改将会丢失。所以,你需要记住在每次重新生成代理类的时候再次修改代码。值得欣慰的是,在VS2008添加服务引用对话框中将解决这个问题。 

Here comes the dodgy part. We need to manually edit the client-side proxy—even though the comment at the top of the proxy warns you against doing so. This is a problem because, if we need to regenerate our proxy, all of the changes we are about to make will be blown away. So, you will need to remember to make the edits every time you regenerate the proxy. If it’s any comfort, the Add Service Reference dialog in Visual Studio 2008 will address this issue. 

在代码编辑器中打开 PersonServiceProxy.vb 并移除Person类。然后替换所有出现的PersonServiceProxy.Person Business.Person。现在, 在代码编辑器中打开Form1.vb并替换PersonServiceProxy.Person Business.Person。最后,在Save按钮单击事件中,移除设置ID属性的代码。由于ID不能更改,你也可以将显示文本框设置成只读。 

Open PersonServiceProxy.vb in the code editor and remove the Person class from it. Then replace PersonServiceProxy.Person with Business.Person everywhere it appears. Now open Form1.vb in the code editor and replace PersonServiceProxy.Person with Business.Person everywhere it appears. Finally, in the Save button’s Click event hander, remove the line of code that sets the Id property. Since the Id cannot be changed, you may also want to make the text box that displays it read-only. 

 

 

Code

 

你现在应该能够运行和测试完成的程序了。 

You should now be able to run and test the completed application. 

在客户端使用原Person类型的另一个好处是你可以使用它的方法。为了演示这个,我们将添加一些代码来调用Person类重写的ToString方法。添加按钮到客户端窗体上并双击他添加事件处理。事件处理的代码将PeopleListBoxSelectItem转换成Person对象并在消息框中显示调用ToString方法的结果。 

Another benefit of using the real Person type on the client is that you can now make use of its methods. To demonstrate this, we will add some code to call the ToString method, which the Person class has overridden. Add the button to the client form and then double-click it to add an event handler. In the event handler add the code to cast the SelectItem from the PeopleListBox to a Person and then show the results of calling ToString in a message box. 

 

 

Code

 

当你单击按钮后,你看到的结果像如下样子: 

When you click the button, you should see results that look like this: 

clip_image016 

销毁 

Dispose() 

有更多关于数据序列化的主题,在这个文章中只讲解了基本的和主要的问题。最常见的挑战是处理只读数据,我已经向你展示处理这种情况的技术。武装你的知识,你现在能够暴露和使用自定义业务对象的服务。 

While there is more to the subject of data serialization, this article covers the fundamentals and the core issues. The most common challenge is dealing with read-only data, and I’ve shown you a technique to deal with this situation. Armed with this knowledge, you should now be able to build services that expose and consume custom business objects.