WCF Service示例
两个月前,写了篇随笔《XML Web Service示例》,当时是基于.NET 2.0实现的,后来有位网友说现在都用WCF了,于是初步学习了一下WCF,将这个示例在.NET 3.5下用WCF实现了一下,现在再写篇随笔,以记录实现的过程。
服务示例的目的没变,还是根据客户程序的请求返回一幅图像。不过这次改在Windows 7 64位家庭高级版和IIS 7.5上实现,开发环境仍是VS2008。
1. 创建WCF Service Library
打开VS2008,选择File/New/Project菜单项,在打开的New Project对话框中,依次选择Visual C# -> WCF -> WCF Service Library,然后输入项目名称(Name),存放位置(Location)和解决方案名称(Solution Name),点击“OK”生成项目。此例中我们用AnnotationWCFService作为项目和解决方案的名称(见下图)。
2. 用右键菜单中的“Rename”将缺省生成的IService1.cs和Service1.cs改为IImageService.cs和ImageService.cs(见下图)。在改名过程中,系统会询问是否也同时修改代码中相应的名字,选择“yes”。另外,编辑App.config,将所有的Service1替换为ImageService。
3. 为WCF Service编码
修改IImageService.cs的代码如下:
using System.ServiceModel;
namespace AnnotationWCFService
{
[ServiceContract]
public interface IImageService
{
[OperationContract]
byte[] GetImage(string imageFileName);
}
}
修改ImageService.cs的代码如下:
using System.IO;
using System.ServiceModel;
using System.Web.Services.Protocols;
namespace AnnotationWCFService
{
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
public class ImageService : IImageService
{
public byte[] GetImage(string imageFileName)
{
byte[] imageArray = GetBinaryFile(imageFileName);
if (imageArray.Length < 2)
{
throw new SoapException("Could not open image on server.", SoapException.ServerFaultCode);
}
else
{
return imageArray;
}
}
private byte[] GetBinaryFile(string fileName)
{
string fullPathFileName = System.AppDomain.CurrentDomain.BaseDirectory + fileName;
if (File.Exists(fullPathFileName))
{
try
{
FileStream fileStream = File.OpenRead(fullPathFileName);
return ConvertStreamToByteBuffer(fileStream);
}
catch
{
return new byte[0];
}
}
else
{
return new byte[0];
}
}
public byte[] ConvertStreamToByteBuffer(Stream imageStream)
{
int imageByte;
MemoryStream tempStream = new MemoryStream();
while ((imageByte = imageStream.ReadByte()) != -1)
{
tempStream.WriteByte((byte)imageByte);
}
return tempStream.ToArray();
}
}
}
这里需要注意一下:如果获取文件失败,程序抛出一个SoapException。但是,缺省地,WCF不会暴露异常的细节给客户端,所以客户端要想能获得异常的细节,需要在此再做两件事。
首先,将App.config的serviceDebug节点的参数includeExceptionDetailInFaults设置改为True,如下:
<serviceDebug includeExceptionDetailInFaults="True" />
其次,如上面代码,在class ImageService声明前加ServiceBehavior特性,即:
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
4. 增加svc文件
在工程所在路径下(本例为D:\Solutions\AnnotationWCFService\AnnotationWCFService)生成一个ImageService.svc文件,目的是让WCF服务能够寄宿在IIS上。ImageService.svc文件内容如下(与XML Web Service的ASMX文件格式相似):
<%@ ServiceHost Service="AnnotationWCFService.ImageService" %>
实际上,服务实现代码可以直接内嵌在svc文件中。但本例中没有这么做,这样,WCF就会到svc所在目录的bin子目录下查找程序集来运行服务。但是工程的输出目录缺省为bin\Debug\,所以需要修改一下,否则系统将找不到服务。在Solution Explorer中,右键点击工程AnnotationWCFService,选“Properties”,在属性页中点“Build”标签,将Output Path改为bin\,如下图:
5. 在IIS中添加应用程序
打开“Internet信息服务(IIS)管理器”,右键点击Default Web Site,在弹出的菜单上选择“添加应用程序”,在打开的对话框中输入别名和物理路径(见下图),此例中我们输入AnnotationWCFService以及ImageService.svc所在物理路径。
此时,如果通过浏览器访问http://localhost/AnnotationWCFService/ImageService.svc,会出现以下内容,说明服务创建成功。但还不够,我们还需要在客户程序中成功调用这个服务。
6. 在客户程序中为WCF Service创建代理
在VS2008中,打开一个Windows应用程序解决方案(.sln),此例中我们打开一个叫做AnnotationApp的解决方案。在要调用WCF Service的项目上(比如此例中我们选择用DataLib)点击右键,选择Add Service Reference菜单项。”),在弹出的Add Service Reference对话框中,输入我们要调用的WCF Service的地址,此例中我们输入:
http://localhost/AnnotationWCFService/ImageService.svc
然后点击“Go”,ImageService就会显示在下面的Services框里,在Namespace编辑框输入服务引用的命名空间名,为了避免再用ImageService这个名字,这里我们输入WCFImageService(见下图),然后点击“OK”来添加服务引用。
这会在Solution Explorer中增加一个Service Reference(见下图)。
添加的引用是Image Service的代理代码,其中包括一个ImageServiceClient的类,派生于System.ServiceModel.ClientBase<DataLib.WCFImageService.IImageService>,并实现DataLib.WCFImageService.IImageService接口。这样在客户代码中就可以像调用自己的Assembly里的方法一样调用ImageServiceClient的GetImage方法。
7. 客户程序调用WCF Service
在客户程序中需要调取图像的地方增加如下代码,之后使用Bitmap对象可以将图像显示出来:
ImageServiceClient client = null;
Bitmap bitmap = null;
try
{
client = new ImageServiceClient();
byte[] imageBuffer = client.GetImage(imageFileName);
MemoryStream memoryStream = new MemoryStream(imageBuffer);
bitmap = new Bitmap(memoryStream);
}
catch (FaultException e)
{
Console.WriteLine("Fault exception - {0}", e.Message);
}
catch (InvalidOperationException e)
{
Console.WriteLine("Invalid operation exception - {0}", e.Message);
}
catch (CommunicationException e)
{
Console.WriteLine("Communication exception - {0}", e.Message);
}
catch (Exception e)
{
Console.WriteLine("Unexpected exception - {0}", e.Message);
}
finally
{
if (client != null)
{
client.Close();
}
}
这时候会捕获InvalidOperationException,异常的消息是:
在 ServiceModel 客户端配置部分中,找不到引用协定“WCFImageService.IImageService”的默认终结点元素。这可能是因为未找到应用程序的配置文件,或者是因为客户端元素中找不到与此协定匹配的终结点元素。
造成这个问题的原因是当添加服务引用时,WCF只是更新了DataLib工程的App.config文件,作为客户端程序的AnnotationApp工程的App.config并没有更新,所以程序无法获得服务的配置信息。消除这个问题需要将DataLib的App.config文件中system.serviceModel节点及其所有子节点复制到AnnotationApp的App.config的Configuration节点中。
之后仍会捕获CommunicationException,异常的消息是:
反序列化操作“GetImage”的响应消息的正文时出现错误。读取 XML 数据时,超出最大数组长度配额(16384)。通过更改在创建 XML 读取器时所使用的 XmlDictionaryReaderQuotas 对象的 MaxArrayLength 属性,可增加此配额。 第 1 行,位置为 29931。
造成这个问题的原因是WCF生成配置文件时,最大数据长度缺省设置为16384,因为我们需要获取的图像文件大于这个值。消除这个问题需要将App.config中的readerQuotas节点的maxArrayLength参数从16384改为合适的值,这里我们改成65536。
如果服务端读取图像文件失败抛出异常,在客户端会捕获FaultException。所以代码中还catch了FaultException。
8. 运行客户程序来测试WCF Service调用
编译运行客户程序并运行,如果一切正常,WCF Service被成功调用并返回所调用的图像(见下图)。
本文参考了Steve Resnick等编著的《Essential WCF》(WCF核心技术)。