简单的示例:WCF 服务






4.65/5 (27投票s)
2010 年 11 月 1 日
11分钟阅读

486112

51390
使用 VS2010 编写、配置和使用 WCF 服务
引言
本文讨论了使用 Visual Studio 2010 编写、配置和使用 Windows Communication Foundation (WCF) 服务的最简单方法。这将有助于更好地理解 WCF 服务,它与 ASP.NET Web 服务略有不同。
背景
我们将使用 VS2010 探讨如何编写和使用一个简单的服务。
场景:书店服务,用于获取图书信息。
阅读完本文,您将了解
- 如何构建 WCF 服务
- 如何在 Windows Forms 中使用 WCF 服务
- 如何将自定义对象绑定到
DataGridView
- 如何发布 WCF 服务时进行配置
- 如何使用 LINQ 将
XElement
转换为自定义对象
此外,您可能还对 DataContract 已存在时为何要使用 MessageContract? 感兴趣;这是我之前写的一篇文章。顺便说一句,我们将在本示例中使用两者。
遵循的步骤
让我们创建一个 WCF 服务库项目。Visual Studio 2010 会自动生成一个默认服务,称为 Service1
。我们暂时忽略这个现有的服务。我们将创建一个单独的服务来返回客户端请求的书籍列表。
请注意,为了保持简单,我们将使用 XML 文件作为我们的数据存储;该 XML 文件来自 MSDN。
XML 包含以下列:作者、标题、类型、价格、出版日期、描述和图书 ID。
图书 ID
(一个 string
类型)将用作标识图书的主键。
我们将添加一个图书接口,该接口将定义该服务提供的图书服务。因此,我们希望提供一项服务,该服务返回基于用户条件找到的书籍列表。
在 Store
命名空间下添加一个名为 IBookService
的新项(接口)。添加指令 using System.ServiceModel;
。
用 [ServiceContract]
属性装饰该接口。
在此示例范围内,我们希望具备以下功能
- 所有书籍列表
- 过滤功能;根据
ID
、Title
、Genre
或Author
返回一本或多本书籍
请注意,在此示例中,我们将同时研究 C# v4.0 中提供的 .NET 默认/可选参数功能,以实现上述方法。
该接口将包含方法。
让我们为 IBookService
接口定义操作
namespace Store
{
[ServiceContract]
interface IBookService
{
[OperationContract]
List GetAllBooks();//Get all books; returns list of books
[OperationContract]
List GetBookByID(string BookID);//Gets a(single) book by ID
[OperationContract]
List Filter(string Author, string Genre, string Title); //Returns list of
//books by specified filter
}
}
让我们添加一个 Book
类型,并定义客户端所需的图书属性。目前,它包含 XML 数据中的所有属性。
右键单击 Book
返回类型,然后选择 生成类
(Generate Class)为 Book
。这将生成一个 Book
类型的类。请注意,您也可以在此处编写属性,VS 会自动将这些属性添加到类中。
如果选择生成新类型,将显示以下窗口,并提供有关类的选项。它的访问修饰符、种类(class
、struct
等)以及是创建新文件还是在当前文件中存根代码。我们将选择一个单独的文件。
右键单击 Book
返回类型,然后选择 转到定义
(Goto Definition)。
添加 ServiceModel
的指令,以及 using System.Runtime.Serialization;
。并在 Book
类上添加 DataContract
属性;它看起来会是这样
在图 3 中,请注意 ID 是 string
类型。理想情况下,当用作主键时,ID
应该是整数类型,因为整数键比 string
键工作得更快。我们使用 string
类型主键的原因是我们有 XML 数据存储中的 string
数据。
让我们用 DataContract
属性装饰 Book
类。
数据协定是服务与客户端之间的正式协议,它抽象地描述了要交换的数据。也就是说,为了通信,客户端和服务不必共享相同的类型,只需共享相同的数据协定。数据协定精确地定义了每个参数或返回类型将被序列化(转换为 XML)以进行交换的数据。
Windows Communication Foundation (WCF) 默认使用名为数据协定序列化器的序列化引擎来序列化和反序列化数据(将其转换为 XML 或从 XML 转换)。所有 .NET Framework 的基本类型,如整数和 string
,以及某些被视为基本类型的类型,如 DateTime
和 XmlElement
,都可以直接序列化,无需其他准备,并被认为具有默认的数据协定。
让我们添加所需的类型 ID, Title, Author, Description, Genre, Price, and Publish Date
,并用 [DataMember]
属性标记所有成员。
现在,我们将添加一个实现 IBookService
接口的 BookService
类;
图书服务应包含定义。
请注意,如果您计划使用数据库(而不是本示例中的 XML),则可以使用 **Enterprise Library, Data Application Block** 进行数据事务;您需要添加对 Data.dll 文件的引用,该文件通常可在 DRIVE:\Program Files\Microsoft Enterprise Library 4.1 - October 2008\Bin 中找到。
现在让我们添加方法的实现。首先,有一个小的 GetDb()
方法,它从给定的 XML 加载数据并将其放入 XDocument
对象中。
然后,因为我们对 book 节点感兴趣,所以我们选择所有的书。
select new Book()
创建一个新对象,并将图书属性中的数据复制到我们定义的图书对象属性中。因此,当图书结构“}
”完成时,我们的图书对象就可以准备好插入到 List
对象中了。
使用 XDocument
和 LINQ 实现这两个方法,回答了如何使用 LINQ 将 XElement
转换为自定义对象的问题。
public List GetAllBooks()
{
XDocument db = GetDb();
List lstBooks
=
(from book in db.Descendants("book")
select new Book()
{
ID = book.Attribute("id").Value //Get attribute from XML and
//set into the Book object attribute.
,
Author = book.Element("author").Value
,
Genre = book.Element("genre").Value
,
Price = Convert.ToDecimal(book.Element("price").Value)
,
Description = book.Element("description").Value
,
PublishDate = Convert.ToDateTime(book.Element("publish_date").Value)
,
Title = book.Element("title").Value
}).ToList(); //Cast it into the list
return lstBooks;
}
以上是获取数据存储中所有图书的方法。现在,让我们为 GetBookByID()
添加定义。该方法与获取所有图书的方法相同,只是 where
子句不同。请注意,在这种情况下,将只有一本书,因此列表将只包含一项。
public List GetBookByID(string BookID)
{
XDocument db = GetDb();
//Howto: Convert XElements to Custom Object
List lstBooks =
(from book in db.Descendants("book")
where book.Attribute("id").Value.Equals(BookID)
select new Book() //Instantiate a new object
{
ID = book.Attribute("id").Value
,
Author = book.Element("author").Value
,
Genre = book.Element("genre").Value
,
Price = Convert.ToDecimal(book.Element("price").Value)
,
Description = book.Element("description").Value
,
PublishDate = Convert.ToDateTime(book.Element("publish_date").Value)
,
Title = book.Element("title").Value
}).ToList();
return lstBooks;
}
上面的代码使用 LINQ 获取给定 ID
的图书。
配置和部署
在 app.config 文件的 system.serviceModel/services
标签下添加服务定义。
system.serviceModel/services
标签包含构建服务和客户端应用程序所需的类、枚举和接口,这些应用程序可用于构建广泛分布的应用程序。
<service name="Store.BookService">
<endpoint binding="basicHttpBinding" contract="Store.IBookService"></endpoint>
</service>
basicHttpBinding
表示服务可以用来配置和公开能够与 ASMX 类型 Web 服务和客户端以及符合 WS-I Basic Profile 1.1 [^] 的其他服务进行通信的终结点。Contract
是我们公开的接口名称。
请注意,WCF 服务需要一个应用程序主机才能运行并可供客户端访问。
我们在这里有几个选项,例如
- 创建自定义主机应用程序
- 构建 Windows 服务应用程序
- 使用 IIS
在我们的案例中,我们将使用 IIS
来简单地发布服务。
右键单击项目并选择 发布
,将在 IIS 中生成以下目录结构。
仅供参考,如果您使用的是 VS2010 之前的版本来配置 WCF 服务,则手动过程如下
- 确保您在 \bin 文件夹中构建了二进制文件,而不是在 \debug 或 \release 文件夹中。
- 添加服务定义文件,一个扩展名为 .svc 的文件。
- 添加新项,选择文本文件模板;将文件重命名为 BookService.Svc。它将包含服务定义。
幸运的是,Visual Studio 2010 为我们完成了这项工作。
我们还需要告诉 IIS 我们的服务将使用 .NET Framework 4.0 版本,以便它不使用其默认的 .NET Framework。
图 6 显示了如何更改 IIS 将为我们的应用程序使用的框架。
发布到 IIS
为了能够被外界访问,我们需要允许访问。您可以在 IE 中打开 URL 查看它是否正常工作。例如,在我的情况下,它位于 WCF 文件夹下:https:///WCF/Store.BookService.svc。
请注意上面图片中高亮的文本。这需要在配置文件中添加一个服务行为,Visual Studio 2010 会自动为我们生成。
您需要将 httpGetEnabled
属性设置为 true
,以便发布您的服务元数据。这是一个布尔值,指定是否允许通过 HTTP
/Get
请求检索服务元数据。默认值为 false
。
要将服务保存并发布到 IIS,请单击 保存
、发布
。
现在您可以在 Internet Explorer 中再次打开 URL,您应该能够看到您的服务的元数据。
快速查看 wsdl
的方法是,在地址栏中输入 ?wsdl
以查看 wsdl,例如:https:///WCF/Store.BookService.svc?wsdl
。
如何使用 WCF 服务?
我们将创建一个小型基于窗体的客户端应用程序,它将显示一些过滤选项,并提供一个搜索按钮,该按钮根据用户提供的过滤条件向服务请求书籍。
添加一个 Windows Forms
项目并设计窗体。
为了能够使用该服务,我们需要为其添加一个引用。因此,当您尝试添加服务引用时,IDE 会发现系统上的所有服务。或者,您可以提供您拥有的服务的路径。
请注意,Web 服务本质上是 public
类型的。尽管 WCF 添加了服务、消息和数据级别的协定,但服务本身是 public
的。
因此,右键单击 WCF 客户端项目并添加服务引用。在您的客户端应用程序中,添加服务引用。
将以下内容作为服务引用 URI 添加:https:///WCF/Store.BookService.svc?wsdl
。我将把引用重命名为 SvcBookstore
。
由于此时我们已经添加了服务引用,因此可以通过在窗体类中添加 using
指令,然后声明服务对象来访问它;这与添加/声明其他 .NET 对象的方式完全相同。让我们在窗体中声明服务对象。并添加 using
指令。
重要提示:如果您正在开发真实的 WCF 服务,现在想对其进行测试。而在使用演示客户端应用程序进行测试时,您的服务抛出您完全不知道原因的异常。在这种情况下,开发人员“自然”希望能够“单步调试 (F11)
”服务代码以自行查看。这将非常有帮助。因此,如果出现这种情况,您可以随时返回服务配置文件,在 behavior 元素中添加一个 serviceDebug
。
<servicedebug includeexceptiondetailinfaults="True" />
当设置为 true
时,serviceDebug
允许客户端应用程序在故障中接收异常详细信息以进行调试。部署前务必将其设置为 false
,以避免泄露异常信息。
因此,如果您想在此客户端端获取与服务相关的异常,请在服务中添加一个标签。因为,此时,您可能想单步调试它。
客户端代码
因此,让我们添加最终代码,该代码收集用户指定的过滤器,并调用服务。检索数据后,您可以简单地将对象数组分配给 .DataSource
属性以显示在 Grid
上。以下是客户端的输出。
private void button1_Click(object sender, EventArgs e)
{
//Get the combo choice, if there is any.
string strGenre = cbxGenre.SelectedIndex > -1 ?
cbxGenre.SelectedItem.ToString() : string.Empty;
//Declare the books array; though the actual return type is List,
//it actually gets casted into
//Book[] array.
Book[] lstBooks = null;
//Discard other filters, if user has entered a book id
if (!string.IsNullOrEmpty(txtID.Text))
{
lstBooks = bookService.GetBookByID(txtID.Text);
}
else
{
//Lets get books by filter.
lstBooks = bookService.Filter(Author: txtAuthor.Text,
Title: TxtTitle.Text, Genre: strGenre);
}
//Set datasource, custom object.
dataGridView1.DataSource = lstBooks;
}
您是否注意到上面代码中的 bookService.Filter(Author: txtAuthor.Text, Title: TxtTitle.Text, Genre: strGenre);
行?
这就是命名参数。命名参数使您不必记住或查找被调用方法参数列表中的参数顺序。每个参数的参数都可以通过参数名称指定。命名参数可以跟在位置参数后面。
例如,如果此时,对于此客户端应用程序,用户仅提供作者进行查找,您可以仅将作者提供给此方法。例如:bookService.Filter(Author: "Paulo Coelho");
请注意,您必须更新服务的 .Filter()
方法并提供默认参数;例如,如下所示
public List<book> Filter(string Author = "N/A", string Genre = "N/A",
string Title = "N/A")
关注点
我注意到几点是,WCF 服务与 ASP.NET 服务大不相同。文件扩展名也不同。ASP.NET 使用 .asmx,WCF 使用 .svc。另外,在您完成以上所有操作并部署了应用程序后,您可能会想,为什么我的服务在第一次加载时需要很长时间。好吧,为什么我的服务第一次访问时很慢? 可能有助于您。
尽情享用!
历史
- 2010 年 11 月 1 日:初始发布