虽然基于Web Service 的SOAP和POST并不总是加载数据的理想机制,但它们仍然适合用于对方法的调用,特别是对复杂数据的输入或修改。使用WCF时,并不真正需要作出一区分——同样的WCF方法能够像简单XML终结点或SOAP终结点一样发布。服务层将完全由AJAX界面分开,既不需要改变服务中的代码,也不需要对AJAX或者任何其他客户端技术指定任何的约束。事实上,可以选择在一些替代的传输机制上发布WCF应用。例如MSMQ、TCP/IP,甚至SMTP。
注意:
Wiki是为了便于在Web上进行沟通而开发的一种记事本类应用程序,其设计宗旨是能够高效、简便地使用。下例中同时建立一个知识基础应用来学习使用Wiki技术。更多 关于Wiki的信息参见 http://en.wikipedia.org/wiki/Wiki
建立简单的Web Service来记录应用状态的消息。首先,定义一个名叫WikilData的数据类,这个类是WCF服务的起始点——它定义了服务返回的消息。可以使用DataContract和DataMember属性来定义数据结构,在WikiData类中,使用DataContract属性来定义元素的命名空间,使用DataMember属性来定义XML序列元素(WikiData类的定义参见下面代码)。定义这个协议后,就能够编写任何针对这种数据格式的客户端应用。
[DataContract(Name = "WikiData",
Namespace = "ServiceOrientedAjax.Examples")]
public class WikiData
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Body { get; set; }
}
输出的格式如下:
<WikiData xmlns="ServiceOrientedAjax.Examples" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Body>Hello WCF World!</Body>
<Title i:nil="true" />
</WikiData>
在定义了数据协议之后,下一步是实现服务。可以是用VS模板或者通过定义标有ServiceContract属性的类来创建服务。ServiceContract属性将这个类标记为WCF的服务实现。每一个作为Web Service终结点发布的方法都必须使用OperationContract属性进行标识。OperationContract属性与ASP.NET Web Service中使用的WebMethod相似,定义了一个通过WCF终结点发布的方法。
由于定义具有DataContract属性的数据类WikiData,因此能够简单使用WikiData作为Get请求的返回类型以及Set操作的输入参数。下列程序定义了一个简单WCF服务,此服务使用上例中的wikiData数据协定。
[ServiceContract(Namespace = "ServiceOrientedAjax.Examples")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
[OperationContract]
public void SetWiki(WikiData wiki)
{
HttpContext.Current.Application[wiki.Title] = wiki.Body;
}
[OperationContract]
[WebGet]
public WikiData GetWiki(string title)
{
WikiData wiki = new WikiData
{
Title=title,
Body=(string)HttpContext.Current.Application[title] ?? "Hello WCF World!"
};
return wiki;
}
}
在定义了WCF服务之后,需要在ASP.NET应用中通过服务文件(SVC文件)和web.config定义终结点。SVC文件时ASP.NET中用来激活WCF服务终结点的简单文本文件,该文件中应当包含一下对 Service1 WCF终结点的引用:
<%@ ServiceHost Service="WcfServiceLibrary1.Service1" %>
除了在SVC文件中定义服务终结点之外,还必须在web.config中定义终结点和行为。这些行为可以影响多个终结点,并且能够改变终端访问WCF终结点的方式。在这个例子中,定义了POX终结点以及包括enableWebScript在内的JSON-enable终结点。这些增加的行为可以通过behaviorConfiguration元素来启用。
为了启用ASP.NET AJAX来为服务建立JavaScript代理类,可以指定能够映射到多个WCF终结点的enableWebScript行为。下面的例子定义了名为JsonBehavior的行为配置项(JsonBehavior 是随意选择的命名),其中包含着enableWebScript行为:
<behavior name="JsonBehavior">
<enableWebScript />
</behavior>
enableWebScript行为使用JavaScript对象符号来定义数据,将默认响应从XML格式转换JSON格式。在手工处理数据并且不能使用XSLT转换时,这种格式比较易于处理。由于WCF从实现中抽象出终结点行为,因此可以在web.config内定义XML终结点和JSON终结点而不会导致服务的任何改变。非常重要的一点是:这种映射通过配置而不是服务来开发完成。
为了定义一个负载的提供JSON数据的终结点,可以使用web.config将终结点映射到行为配置上。例如将一个附加的终结点(这是一个具备与Service.svc终结点同样实现的JSON-enable终结点)映射到Service.svc/json上。要定义这个终结点,需要使用一个子终结点元素来创建服务元素,终结点元素为服务定义了地址、行为配置、绑定以及协定。终结点的地址与激活WCF服务的.svc文件相关,behaviorConfiguration定义了终结点的附加行为。接下来的例子为Service1定义了服务中金额的,同时含有一个映射到/json、使用JsonBehavior配置的附加终结点。
<services>
<service name="WcfServiceLibrary1.Service1" behaviorConfiguration="Global">
<endpoint address="" behaviorConfiguration="PoxBehavior"
binding="webHttpBinding" contract="WcfServiceLibrary1.Service1" />
<endpoint address="json" behaviorConfiguration="JsonBehavior"
binding="webHttpBinding" contract="WcfServiceLibrary1.Service1" />
</service>
</services>
当通过具有enableClientScipt行为的终结点访问服务时,上面的数据协定会输出如下的JSON数据类:
{"wiki":{"Title":"default","Body":"Hello WCF World!"}}
这个JSON格式数据流包含着与数据协定定义的XML输出相同的内容。由于在脚本中,JSON流比较容易使用,因此如果你不使用XSLT转换器,Microsoft将选择它作为脚本激活的Web Service的默认行为。
由于已经具备了在web.config中为/json终结点定义的enableWebScript行为,ASP.NET AJAX运行环境会为服务生产一个JavaScript代理,这个代理能够调用WCF服务并获取相应。为了手工检查JavaScript代理,要使用WCF终结点并在URL后面加上/jsdebug开工选项,例如: http://localhost/ajaxfundamentals/simpleservice.svc/json/jsdebug 。代理中会含有通过ASP.NET AJAX JavaScript框架调用Web Service的正确的JavaScript语法细节,包括用来调用和索引数据的序列化对象,此时,你所需要了解的就是直接调用的实例方法。对于每一个Web Service操作,在方法的参数中应当添加 onSuccess 、onFailed 以及 userContext。由于所有的网络调用都是以异步方式的,因此必须为响应传递回调句柄。尽管能够将任意对象作为用户上下文进行传递,但通常userContext对象是原样传递给公共代理方法,包括GetWiki方法和SetWiki方法。实际的代理中包含更多的代码,但通常你需要调用的方法是对于公共服务方法的JavaScript函数。
SetWiki:function(wiki,succeededCallback, failedCallback, userContext) {
/// <param name="wiki" type="ServiceOrientedAjax.Examples.WikiData">WcfServiceLibrary1.WikiData</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'SetWiki',false,{wiki:wiki},succeededCallback,failedCallback,userContext); },
GetWiki:function(title,succeededCallback, failedCallback, userContext) {
/// <param name="title" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'GetWiki',true,{title:title},succeededCallback,failedCallback,userContext); }
在引用ASP.NET AJAX生成的JavaScript代理类后,才能在AJAX应用中使用这段代码,这就需要在Service节点中使用ScriptManager控件,定义对Service.svc的引用。为了增加ServiceReference,需要把带有Path参数的ServiceReference加到WCF终结点中,如下所示:
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Service.svc/json" /></Services>
</asp:ScriptManager>
下面是整体的程序清单:
HTML界面:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Service.svc/json" /></Services>
</asp:ScriptManager>
<div id="MainContent" οnclick="editMessage">
Loading....
</div>
<br />
<span style="border:solid 1px silver;" id="editButton" οnclick="editMessage();">
Edit
</span>
<div id="EditorRegion" style="display:none;">
<textarea id="TheEditor" rows="10" cols="10">
</textarea>
<br />
<span style="border:solid 1px silver;" οnclick="save();">
Save
</span>
</div>
</form>
<script language="javascript" type="text/javascript">
var wikiName = 'default';
function OnAjaxLoad() {
var userContext = new Object();
ServiceOrientedAjax.Examples.Service1.GetWiki(wikiName, getMessageSuccess, onMessageFailure, userContext);
}
function editMessage() {
$get('EditorRegion').style.display = '';
$get('editButton').style.display = 'none';
$get('MainContent').style.display = 'none';
$get('TheEditor').value = $get('MainContent').innerHTML;
$get('TheEditor').enabled = true;
}
function getMessageSuccess(wikiData, userContext) {
var EditorRegion = $get('EditorRegion');
EditorRegion.style.display = 'none';
var content = $get('MainContent');
content.innerHTML = wikiData.Body;
content.style.display = '';
$get('editButton').style.display = '';
}
function save() {
var msg = $get('TheEditor').value;
var userContext = new Object();
userContext.msg = msg;
userContext.title = wikiName;
var wiki = new Object();
wiki.Title = wikiName;
wiki.Body = msg;
ServiceOrientedAjax.Examples.Service1.SetWiki(wiki, onSaveSuccess, onMessageFailure, wiki);
$get('TheEditor').enabled = false;
}
function onSaveSuccess(response, wiki) {
getMessageSuccess(wiki, null);
}
function onMessageFailure(ex, userContext) {
var EditorRegion = $get('EditorRegion');
EditorRegion.style.display = 'none';
var content = $get('MainContent');
content.innerHTML = ex.get_message();
content.style.display = '';
}
Sys.Application.add_load(OnAjaxLoad);
</script>
</body>
</html>
WCF服务类:
using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web;
using System.Runtime.Serialization;
namespace WcfServiceLibrary1
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.
[ServiceContract(Namespace = "ServiceOrientedAjax.Examples")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
[OperationContract]
public void SetWiki(WikiData wiki)
{
HttpContext.Current.Application[wiki.Title] = wiki.Body;
}
[OperationContract]
[WebGet]
public WikiData GetWiki(string title)
{
WikiData wiki = new WikiData
{
Title=title,
Body=(string)HttpContext.Current.Application[title] ?? "Hello WCF World!"
};
return wiki;
}
}
[DataContract(Name = "WikiData",
Namespace = "ServiceOrientedAjax.Examples")]
public class WikiData
{
[DataMember]
public string Title { get; set; }
[DataMember]
public string Body { get; set; }
}
}
Web.config 配置:
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="PoxBehavior">
<webHttp />
</behavior>
<behavior name="JsonBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Global">
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<!-- -->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service name="WcfServiceLibrary1.Service1" behaviorConfiguration="Global">
<endpoint address="" behaviorConfiguration="PoxBehavior"
binding="webHttpBinding" contract="WcfServiceLibrary1.Service1" />
<endpoint address="json" behaviorConfiguration="JsonBehavior"
binding="webHttpBinding" contract="WcfServiceLibrary1.Service1" />
</service>
</services>
</system.serviceModel>