asp.net

位置:IT落伍者 >> asp.net >> 浏览文章

ASP.NET 3.5核心编程之AJAX Web Service的调用


发布日期:2020年01月29日
 
ASP.NET 3.5核心编程之AJAX Web Service的调用
AJAX模型基于两个层次客户端应用程序层和服务器应用程序层在这种模型下客户端层向服务器层发送请求而服务器层向客户端层返回响应服务器端点通过URL标识并通过源(feed)(通常为JSON[JavaScript Object Notation]数据流)向客户端暴露数据服务器层只是一个接收调用并将其转发给应用程序业务逻辑层的外观

下图描绘了整个模型

为使ASPNET AJAX页面能够调用远程服务该服务必须满足几点要求其中最关键的一点与端点和底层平台的位置有关支持AJAX的服务必须位于调用者所处的域中这意味着该服务必须是ASPNET XML Web服务(asmx端点)必须以ASPNET应用程序的形式寄放于同一Web服务器的某个IIS应用程序中

总的来说对于ASPNET AJAX应用服务种定义服务器层服务的方式

带有asmx端点的ASPNET XML Web服务

带有svc端点的WCF服务

带有aspx端点的页面方法这些方法定义在与主调页面相同的页面中

服务(Service)这个词往往被误用在AJAX中服务指的是隶属于应用程序的代码(位于应用程序的域中)用于向客户端暴露相应的功能从根本上讲AJAX应用程序使用的服务一般不通过简单对象访问协议(SOAP)进行通信(而是使用JSON)不必是面向服务架构(SOA)中自治的服务它们与自身所处平台和域绑定因此不能称这里的服务为WS*Web服务和SOA服务

REST服务

针对AJAX应用程序的服务围绕着暴露给Web客户端数据和资源二者可通过HTTP获取要求客户端通过URL(也可以有HTTP标头)来访问数据和命令操作客户端与服务的交互是通过GETPOSTPUT和DELETE这样的动作来完成换言之URL用于描述所要获取的资源HTTP动作用于描述对资源执行的操作这类交互过程中交换的数据由简单的格式表示甚至可以用联合格式(如RSS和ATOM)表示

具有这些特性的服务为具象状态传输(Representational State TransferREST)

数据的序列化

AJAX调用包含作为参数传给被调服务方法的数据及作为输出返回的数据这些数据是如何序列化的?

通信双方都能理解的序列化格式为JavaScript对象表示法(JSON)JSON是一种基于文本的格式专门用于在不同层次间传递对象的状态JavaScript支持JSON可通过JavaScript的eval函数将JSON兼容的字符串转换为JavaScript对象然而如果JavaScript字符串代表自定义对象的状态那么开发者应确保具有相应类的定义

ASPNET AJAX网络堆栈要负责为每个远程传递的对象创建JSON字符串在服务器端通过专门的格式化程序类接收数据并通过NET反射来填充与之匹配的托管类在返回时NET托管类会被序列化为JSON字符串并发送给客户端脚本管理器会确保引用这些JSON字符串的类(Web服务代理类)存在于客户端

下面给出一个描述对象状态的JSON格式的示例

{ID:ALFKI Company:Alfred Futterkiste}

这个字符串说明该对象有两个属性ID和Company存储的是以字符串的形式序列化的值如果某个属性被赋予一个非基本类型的值(如自定义对象)那么该值会以递归方式序列化为JSON[

JSON与XML

相比XMLJSON更精炼更适合JavaScript语言

应用程序特定的Web服务

在默认情况下ASPNET Web服务收发的是SOAP数据包(而不是JSON数据包)通过Web服务描述语言(Web Services Description LanguageWSDL)文档来暴露其协定AJAX应用程序上下文中的ASPNET XML Web服务是如何工作的呢?

可以通过ASPNET AJAX应用程序的nfig文件来修改接收asmx请求的HTTP处理程序将这些调用重定向给能够理解JSON流的HTTP处理程序这意味着ASPNET XML Web服务可以是一种双重的服务即可接受和处理SOAP请求也可针对JSON请求在配置层我们可以禁用SOAP支持并隐藏用于对外公开该服务功能的WSDL文件

如果要使用支持JSON的ASPNET Web服务则需要删除XML因为在调用ASPNET Web服务时我们不处理SOAP和XML针对AJAX应用程序的ASPNET Web服务不采用SOAP消息

远程编程接口的定义

协定(contract)用于定义服务器端端点暴露给调用者的内容如果希望以ASPNET Web服务的形式实现则不严格要求存在实际的协定但如果ASPNET 中的WCF服务那么协定就必须存在总而言之以接口形式设计的公共API会使代码更整洁在实现该接口的类创建完毕后有关服务器API接口的工作就结束了这样我们就可以发布这个远程API并使ASPNET AJAX运行库来管理来自客户端的调用

对于ASPNET Web服务我们通过纯粹的接口来定义协定使该接口包含与服务器API有关的方法和属性下面给出一个简单的服务

using System;public interface ITimeService{

DateTime GetTime()

string GetTimeFormat(string format)}

这两个方法构成了可以在客户端调用的服务器API

实现已约定的接口

ASPNET Web服务通常通过派生自基类WebService的NET类来实现

using SystemWebServices;public class TimeService : WebService ITimeService{

…}

注意没有必要一定要从基类WebService派生这个基类主要用于直接访问一些常用的ASPNET对象(如Application和Session)如果不需要直接访问这些ASPNET内部的对象即使不从WebService类派生也能创建ASPNET Web服务这种情况下我们可通过HttpContext对象来间接地使用ASPNET内部的对象

协定的发布

从本质来说发布给定的服务器协定就是生成一个嵌在页面中的脚本能够调用的JavaScript代理类如果服务器API通过Web服务实现我们要向ASPNET AJAX页面的脚本管理器注册该Web服务此外我们还要在nfig文件中添加一个特殊的asmx请求HTTP处理程序

Web服务的远程调用

Web服务提供了服务器端代码的宿主环境以便在响应客户端的操作时进行调用服务中的Web方法指向应用程序特定的代码

AJAX Web服务的创建

为ASPNET AJAX应用程序定制的Web服务比其他ASPNET Web服务要小ASPNET AJAX Web服务与传统的ASPNET XML Web服务间存在两方面的差异

首先若使用ASPNET AJAX Web服务那么为满足特定应用程序的需要我们要设计ASPNET AJAX Web服务的协定而不是配置公共服务的行为目标应用程序就是Web服务的宿主其次我们必须使用一个新的特性(attribute)来声明这种Web服务的类而在常规的ASPNET XML Web服务中这是不允许的

最终的效果是ASPNET AJAX Web服务可能有两套公共接口一套是基于JSON对接口由宿主ASPNET AJAX应用程序使用另一套是基于SOAP的接口暴露给客户端任何平台都能访问该服务的URL

ScriptService特性(attribute)

为创建ASPNET AJAX Web服务第一步是要建立标准的ASPNET Web服务项目随后导入SystemWebScriptServices命名空间

using SystemWebScriptServices;namespace CoreWebService{

[WebService(Namespace=)]

[WebServiceBinding(ConformsTo=WsiProfilesBasicProfile_)]

[ScriptService]

public class TimeService : SystemWebServicesWebService ITimeService

{

}}

ScriptService特性是使ASPNET XML Web服务与ASPNET AJAX Web服务间产生差异的关键该特性指出该服务旨在接受来自基于JavaScript客户端代理的调用

阻塞SOAP客户端

一旦创建AJAX Web服务便可以ASMX资源的形式发布它默认情况下它会有公共的URL能够由AJAX客户端调用同时也能被SOAP客户端和工具发现和使用但我们可禁用SOAP客户端和工具为此只需在nfig文件中添加以下设置

<webSevices>

<protocols>

<clear />

</protocols></webServices>

这段简单的设置能禁用ASPNET Web服务定义的所有协议(包括SOAP)使该服务只能响应JSON请求

注意如果添加这些设置则不能够通过浏览器的地址栏来调用Web服务以便进行简单地测试类似地我们也不能为URL添加?wsdl后缀来调用WSDL

Web服务方法的定义

客户端页面能够调用Web服务类中带有WebMethod特性的公共方法在默认情况下这些方法要通过HTTP动作POST来调用以JSON对象的形式返回其值我们可通过一个可选特性ScriptMethod来更改单个方法的默认设置

ScriptMethod特性带有个属性见下表

由于涉及安全性和性能问题因谨慎使用ScriptMethod特性下面的代码使用了该特性但未修改默认设置

[WebMethod][ScriptMethod]public DateTime GetTime(){

…}

WebMethod特性是必选的而ScriptMethod特性是可选的

AJAX Web服务的注册

为在客户端发起对ASPNET Web服务的调用我们只需要XMLHttpRequest目标Web服务的URL和JSON流的管理功能为方便起见所有功能都包装在映射到远程编程接口的JavaScript代理类中该代理类会由ASPNET AJAX框架自动生成并注入到客户端

为使内建的引擎生成所需的JavaScript代理和辅助类我们应在需要AJAX Web服务的页面中向脚本管理器控件注册该Web服务

<asp:ScriptManager ID=ScriptManager runat=server>

<Services>

<asp:ServiceReference Path=~/WebServices/TimeServiceasmx />

</Services></asp:ScriptManager>

对于每个要绑定到页面的Web服务我们添加一个ServiceReference标签将Path属性设为对应asmx资源的URL对于每个服务引用都会在客户端自动生成一个额外的<script>块该脚本的URL指向一个系统HTTP处理程序在内部调用以下URL:

~/WebServices/TimeServiceasmx/js

追加到Web服务URL的/js后缀指示ASPNET AJAX运行库为指定的Web服务生成JavaScript代理类如果页面处于调试模板该后缀会被改为/jsdebug

默认情况下JavaScript代理通过<script>标签连接到页面这样就需要单独下载通过将ServiceReference对象InlineScript属性设置为true我们还可以将任何所需的脚本并入当前页面如果启用浏览器缓存且多个Web页面使用相同的服务引用那么默认值false更合适在这种情况下不论多少页面需要这个代理类都只需执行一次请求将InlineScript属性设为true会降低网络请求数但会多占用一定的带宽

如果以编程方式注册AJAX Web服务我们使用类似以下的代码

ServiceReference service = new ServiceReference()servicePath = ~/WebServices/TimeServiceasmx;ScriptManagerServicesAdd(service)

不论采用哪种方式为调用Web服务我们只需通过JavaScript代理类发起调用即可

使用ASPNET应用程序来承托AJAX Web服务

为启用ASPNET AJAX应用程序中的Web服务调用我们需要在nfig文件中添加以下内容以注册一个特殊的asmx请求HTTP处理程序

<httpHandlers>

<remove verb=* path=*asmx />

<add verb=* path=*asmx

type=SystemWebScriptServicesScriptHandlerFactory />

…</httpHandlers>

该设置已包含在VS为支持AJAX的Web项目而创建的nfig文件中

处理程序工厂(SystemWebScriptServicesScriptHandlerFactory类)会选择负责处理给定类型请求的HTTP处理程序且能通过Web服务调用中的脚本来识别JSON调用基于JSON的请求由特殊的HTTP处理程序处理而常规的SOAP调用会穿越ASPNET管道

AJAX Web服务的调用

被引用的ASPNET AJAX Web服务暴露给JavaScript代码的类名与服务器类名相同代理类采用单例模式暴露了外界调用的静态方法无需实例化

JavaScript代理类

以上述的timeserviceasmx生成的JavaScript代理类为例让我们看看它的代码

TyperegisterNamespace(CoreWebServicesCoreWebServicesTimeService = function(){

CoreWebServicesTimeServiceinitializeBase(this)

this_timeout = ;

this_userContext = null;

this_succceeded = null;

this_failed = null;}CoreWebServicesTimeServiceprototype ={

//调用GetTime方法

GetTime : function(succeededCallback failedCallback userContext)

{

//invoke参数分别为

//Web Service URL路径

//Web Service方法名称

//

//传入方法的参数数组

//执行成功回调函数

//执行失败回调函数

//调用上下文对象

return this_invoke(CoreWebServicesTimeServiceget_path()

GetTime

false

{}

succeededCallback

failedCallback

userContext)

}

GetTimeFormat : function(timeFormat succeededCallback failedCallback userContext)

{

return this_invoke(CoreWebServicesTimeServiceget_path()

GetTimeAsFormat

false

{format:timeFormat}

succeededCallback

failedCallback

userContext)

}}//注册CoreWebServicesTimeService类该类继承于SysNetWebServiceProxyCoreWebServicesTimeServiceregisterClass(CoreWebServicesTimeService

SysNetWebServiceProxy)//创建一个JavaScript代理类实例CoreWebServiceTimeService_staticInstance = new CoreWebServicesTimeService()

在JavaScript中调用WebService方法其实是通过最后创建的JavaScript代理类实现的

CoreWebServiceTimeServiceGetTime = function(onSuccess onFailed userContext){

CoreWebServiceTimeService_staticInstanceGetTime(onSuccess onFailed userContext)}CoreWebServiceTimeServiceGetTimeFormat = function(onSuccess onFailed userContext){

CoreWebServiceTimeService_staticInstanceGetTimeFormat(onSuccess onFailed userContext)}

在这个代理类的定义中带有几个公共属性

path属性用于定义Web服务的URL我们可以编程方式更改该属性值以便将代理重定向到其他URL

远程调用的执行

下面是将JavaScript代理与客户端按钮点击关联的典型方法

<input type=button value=Get Time onclick=getTime() />

按钮最好是客户端按钮但也可以是服务器端Button对象生成的提交按钮只要将OnClientClick属性设置为false的JavaScript代码即可这会避免它执行默认的回发操作

<asp:Button ID=Button runat=server Text=Button OnClientClick=getTime() return false; />

getTime函数用于采集必要的输入数据并调用代理类中的静态方法如果希望为回调或用户上下文对象赋予默认值那么最好在pageLoad函数中进行因为pageLoad函数会在客户端页面ASPNET AJAX成功初始化后调用该函数比浏览器的onload事件更可靠示例代码如下

<script language=javascript type=text/javascript>

function pageLoad()

{

//设置默认的调用失败回调函数

CoreWebServicesTimeServiceset_defaultFailedCallback(methodFailed)

}

function getTime()

{

CoreWebServicesTimeServiceGetTimeFormat(ddd dd MMMM yyyy [hh:mm:ss] methodComplete)

}

function methodComplete(results context methodName)

{

$get(LabelinnerHTML = results;

}

function methodFailed(errorInfo context methodName)

{

$get(LabelinnerHTML = StringFormat(Execution of method {} failed because of the following:\r\n{}

methodName errorInfoget_message())

}</script>

由于Web服务调用是以异步方式处理的因而我们需要回调来处理调用成功和失败这两种情况这两个回调的签名类似

function method(results context methodName)

下表对各参数做了简要说明

错误处理

failed回调会在服务器上的远程方法执行期间发生异常时被调用在这种情况下HTTP响应会包含HTTP错误码(内部错误)

在客户端服务器异常通过JavaScript中的Error对象暴露该对象会基于从服务器端获得的消息和堆栈跟蹤而动态创建Error对象会通过results参数暴露给failed回调我们可通过Error对象的message和stackTrace属性来分别读取收到的消息和堆栈跟蹤

如果我们未指派默认的错误回调函数ASPNET AJAX会调用自己的默认回调函数该回调函数会弹出一个带有服务器异常消息的消息框

为用户提供反馈

虽然UpdatePanel中提供了异步调用的反馈机制(如UpdateProgress控件)但对于传统的远程方法调用我们只能自行编写代码实现对用户的反馈

我们可以在远程方法调用执行前显示等待消息GIF动画或其他内容

function takeaWhile(){

//显示等待消息

$get(FeedbackinnerHTML = Please wait …;

CoreWebServicesMySampleServiceVeryLengthyTask(methodCompletedWithFeedback methodFailedWithFeedback)}

在completed回调中我们首先重置用户界面然后再进行其他操作

function methodCompletedWithFeedback(results context methodName){

$get(FeedbackinnerHTML = ;

…}

注意在发生错误时我们也要清除用户界面

超时处理

如果发起对asmx Web服务的客户端调用则是对asmx的直接调用对于该请求ASPNET运行库中只有同步处理程序也就是说不论客户端如何检测当前调用是否正在进行ASPNET线程都会被完全阻塞直到AJAX方法执行完毕为此我们可以设置超时时间

CoreWebServicesMySampleServiceset_timeout(

timeout属性是全局的作用于代理类的所有方法

如果请求超时我们便不会从服务器收到响应客户端只能单方面的撤销执行

               

上一篇:ASP.NET基于TCP的网络通信程序演示

下一篇:自定义ASP.NET CheckBox的设计思路