ASP.NET Ajax 学习(二)客户端部分
Asp.net Ajax客户端编程时,与服务端进行交互的方式无非有以下三种:
1. 客户端使用webservice代理与服务端进行交互,也是ajax客户端编程时使用最频繁的一种通信方式。
2. 客户端使用页面方法代理与服务端进行交互,这种方式使用不多,原因是服务端的页面方法被限制在只有页面的方法,在客户端才能够通过代理访问到,而用户控件的方法,则在客户端无法访问。
3. 客户端直接使用框架的webRequest组件与服务端进行通信,从而绕过webservice代理和页面方法代理。这种通信方式实现起来较为复杂,和直接不使用框架而使用xmlhttprequest来进行异步通信差不多,因此使用的也不是很多,只有在某些特殊情况下才会用到,但是这种方法需要掌握,因为任何和服务端通信的方式,都是使用底层的webrequest来进行的,他是webservice代理和页面方法代理的基础。
关于异步通信层客户端部分框架图中的profile proxy和Authentication proxy则是一类支持组件,他们主要是用来支持在客户端可以直接使用异步的方式来访问服务端的用户个性化服务和身份验证服务。
下面来一一的介绍下这三种的交互方式:
一.客户端使用webservice 代理与服务端通信
1.调用webservice的基本步骤
在asp.net ajax中异步调用服务端webservice,我们需要如下步骤:
- 为服务端webService类或需要暴露给客户端的Webservice方法添加[ScriptService]属性,该属性在System.Web.Script.Service命名空间内。
- 为webservice中要暴露给客户端的方法添加[WebMethod]属性。
- 在页面的ScriptManager控件中添加对该webService的引用。
- 在客户端使用如下的JS语法调用webService:
[NameSpace].[ClassName].[MethodName](param1,param2,…,onSucceed,onFaild,userContext),后面三个参数是可选的。userContext可以使json对象或者普通类型的数据。
onSucceed和onFaild的签名为onSucceed(result,userContext,MethodName),后面俩个参数是可选的,其中userContext是调用webservice方法时传递的用户上下文参数,MethodName即为调用的webservice名称,result用来接收webservice方法的返回值。
- 为客户端异步调用指定成功和失败的回调函数。
在asp.net ajax客户端中,我们还可以为我们的webservice代理类设置默认的onSucceed,onFaild和userContext参数,分别如下形式:
[NameSpace].[ClassName].set_defaultSucceededCallback(onSucceed);
[NameSpace].[ClassName].set_defaultFailedCallback(onFailed);
[NameSpace].[ClassName].set_defaultUserContext(“default contex”);
然后在客户端实现这些默认的onSucceed,onFailed方法即可。
在具体调用某个webservice方法是,如果重新设定了成功,失败的回调函数和新的用户上下文,则这些新的设定将会覆盖掉之前的默认设置。
2.调用webservice时的异常的处理
在调用webservice时,webservice服务端可能会抛出异常,或者出现了调用
webservice方法超时(即超出设定的时间,服务端webservice方法还没有返回值)。
在asp.net ajax客户端中,对于调用webservice方法过程中出现的异常,在调用webservice方法时指定的错误回调函数中进行处理,即onFailed函数中来进行处理,该函数有一个error参数,它的签名如下:
Function onFailed(error,userContext,methodName)
{
//函数体
}
error参数是Sys.Net.WebServiceError类型的参数,表示异常对象,它封装了异步请求服务器时可能发生的异常,它提供了若干只读的属性,提供了对异常信息的详细描述。如下:
exceptionType 获取服务器端异常的具体类型
message 获取详细的异常描述信息
statusCode 获取造成异常的http响应的状态吗
stackTrace 获取服务器端异常的栈跟踪信息
timeOut 获取一个布尔值,表示异常是否是由于网络连接超时造成的
根据asp.net ajax 客户端组件的命名规范,访问属性均要在属性名称前加上get_或set_前缀,如火的异常的message属性值,如error.get_message();
对于webservice的超时设置,只能在webservice类级别上进行设置,不能在具体的webservice方法上进行超时设置,如在客户端脚本中,需要如下进行设置超时值 :
[NameSpace].[ClassName].set_timeOut(200),表示客户端在调用该webservice时,如果超过2s,客户端没有收到服务端的返回值,就视为超时了,此时,会在客户端调用已经设置好的错误回调函数onFailed,在该函数中,使用error.get_timeOut()即可获得表示是否出现了超时的布尔值。
3.使用Http Get方式调用webservice
在asp.net ajax 的异步通信层中,对Webservice进行异步调用时,默认采取的是http post的方式传递参数给webservice方法,我们也可以指定使用http get的方式传递参数给webservice方法,采用这种方式后,方法参数会被序列化成Json字符串,经过url编码后,放到URL后面,送回给服务器处理。
采用http get方式传递参数给webservice时,服务端需要做的就是在该webservice方法上使用[UseHttpGet=true]属性。
4.调用webservice时.Net 类型和Js类型的自动转换
在asp.net ajax中,.net类型和Js类型可以相互转换,但这只限于基本类型,如整型,浮点型,字符串,枚举类型,日期时间类型等,在使用枚举类型时,在客户端如下使用:
[Namespace].[EnumName].[EnumKey]
对于自定义的复杂数据类型,我们需要做如下的一些工作才可以:
- 为webservice类或方法添加[ScriptService]属性
- 为webservice方法添加[WebMethod]属性
- 为webservice类添加若干个[GenerateScriptType(typeof([typeName]))]属性,[typeName]表示该复杂类型或其嵌套的复杂类型的名称
- 该复杂类型必须有一个无参数的公有构造函数
- 该复杂类型的公有属性应该提供获取方法和设置方法,除以下几种情况:
一. 该复杂类型的公有属性应用了[System.Web.Script.Serialization.ScriptIgnore]属性,因为应用了该属性后,在生成客户端js时,会忽略掉该属性。
二. 在客户端传入的时候不会设置该属性的值,那么该属性可以没有设置方法
三. 该服务端对象只是用来单向输出json字符串,那么其属性可以没有设置方法。
- 在页面的ScriptManager控件中添加对该webservice引用。
经过上述的步骤以后,服务端的复杂类型就可以生成相应的客户端js类型了,但是还需要注意以下的几点:
- 服务端会将没有应用[System.web.script.Serialization.ScriptIgnore]的公有属性或公有字段映射到客户端javascript类型中。
- 不会讲该复杂类型的私有字段映射到客户端javascript类型中。
- 不会将该复杂类型的方法映射到客户端javascript类型中。
当服务端复杂类型映射到客户端得javascript类型后,客户端就可以直接向在服务端一样使用该类型的变量了。如在调用webservice方法成功的回调函数中:
function onSucceed(result)
{//我们假设服务端返回的是一个Employee对象,它有Name,ID,SexCode属性等
Var tempObject=result;
Var.Name;
……….
}
另外,我们还可以再客户端直接创建该复杂类型的对象,如:
Var myObj=new [NameSpace].Employee();
myObj.Name=”Brown”;
myObj.ID=”00001”;
myObj.SexCode=”男”;
在客户端调用webservice方法时,可以将该对象myObj作为参数传递给webservice方法。
对于服务端的泛型集合类型,则ajax框架会自动将List<基本类型>转换为js的数组Array,而如果泛型中的类型为复杂类型,则需要在webservice方法或者webservice类上使用GenerateScriptType(typeof([TypeName]))属性。也即是说,如果webservice方法中需要 List<基本类型/复杂类型>的参数,那么可以在客户端构造一个Array 对象,然后将该Array对象传递给服务端的webservice方法;对于服务端返回给客户端的List<基本类型/复杂类型>的结果,在客户端可以将该结果当做Array来进行处理。如:
客户端传递Array类型参数给服务端方法:
Var employeeList=new Array();
For(var i=0;i<10;i++)
{
Var em=new Employee();
em.Name=”Brown”;
employeeList.push(em);
}
[NameSpace].[ClassName].[Method](employeeList);
客户端接收服务端返回的泛型结果:
function onSucceed(result)
{
for(var i=0;i<result.length;i++)
{
$get(“employee”+i+“Name”).innerHtml=Result[i].Name;
…..
}
}
对于Dictionary<Tkey,Tvalue>类型,其中Tkey必须是string类型,也会自动转换成JS 的相应类型,若Tkey或Tvalue为复杂类型,仍需要为webservice类添加[GenerateScriptType(typeof([TypeName]))]属性,我们在客户端js中要获取服务端返回的该Dictionary< Tkey,Tvalue >得值,我们可以使用如下:
for( var key in result)
{
Var em=result[key];
……..
}
对于服务端返回的数组类型或者需要数组类型的参数时,客户端同样会自动转换为相应的数组类型,只是如果数组类型若是复杂类型,则需要在服务端的webservice类上使用GenerateScriptType(typeof([TypeName]))属性,如下使用:
客户端传递数组给服务端webservice方法:
Var intArray=[1,2,3,4,5];
[NameSpace][ClassName][MethodName](intArray);
如果是复杂类型,则
Var emArray=new Array();
For(var i=0;i<10;i++)
{
Var em=new Employee();
em.Name=”Brown”;
emArray.push(em);
}
[NameSpace][ClassName][MethodName]( emArray);
服务端返回给客户端的数组类型,我们同样使用数组来接收:
Function onSucceed(result)
{
For(var i=0;i<result.length;i++)
{
$get(“employee”+i+“Name”).innerHtml=Result[i].Name;
}
}
对于DataTable和DataSet类型的数据,第一种方法是,我们可以自定义JavaScriptConverter来实现这两种类型的客户端/服务端自动转换。第二种方法是使用asp.net ajax futures ctp中自带的datatable和dataset 相关的javascriptConverter组件,首先确保安装了asp.net ajax futures ctp 部分,然后将web.config配置文件中的关于<jsonSerialization><conberters>节中的内容取消注释即可。转换后,客户端收到的结果就可以当做datatable/dataset一样的使用,如
Function onSucceed(result)
{//我们假设该result返回的是DataTable
Var idColName=result.columns[0].name;
…..
}
如果返回的dataset,那么就将result当做是一个dataset,可以根据表名,先获取DataTable,然后再进行其他操作。
5.以XML方式序列数据
Asp.net ajax 异步通信层在传递数据时默认采用JSON序列化方式,也可采用XML方式序列化,一般来将,若webservice方法返回值类型为XmlDocument或XmlElement时,应以XML方式进行序列化,值要在该Webservice方法上添加[ScriptMethod(ResponseFormat=ResponseFormat.Xml)],对于webservice方法返回的复杂类型或者普通类型的数据,也可以采用xml序列化的方式,但是,之前在复杂类型的某个属性中添加[System.web.Script.Serialization.Script.Ignore]属性后,客户端生成的对象会忽略该属性,该特性只适用于默认的Json序列化方式,若希望在xml序列化时也忽略该属性,需要在该属性上添加[System.Xml.Serialization.XmlIgnore]属性。
另外一个需要注意的是,如果Webservice方法返回的是一个字符串,而且该字符串本身就是一个xml片段,如“<person><name>Brown</name><sexcode>man</sexcode></person>”时,如果采用xml序列化方式,则客户端会将<person><name>等解释成为节点,如果您确实就是想在客户端将<person><name>等就当做普遍的字符串,而不被解释成节点,那么你需要做的就是在该webservice方法上的[ScriptMethod(ResponseFormat=ResponseFormat.Xml)]
属性修改为[ScriptMethod(ResponseFormat=ResponseFormat.Xml,XmlSerializeString=true)]即可。
二.客户端使用页面方法与服务端通信
Asp.net ajax中js 要异步调用定义在asp.net页面中的方法是,需要:
- 该方法只能定义在aspx.cs中,而且该方法必须声明为public.
- 该方法声明为类方法,即static方法
- 为该方法添加[WebMethod]属性。
- 将页面中的ScriptManager控件的EnabledPageMethods属性设置为true.
- 在客户端使用如下js语法调用该页面方法:
PageMethods.[MethodName](param1,param2,…,onSucceed,onFailed);
- 为客户端异步调用实现指定的成功回调函数和失败回调函数,在回调函数中接收返回值,并进一步处理。
三. 客户端页面生存周期
- 维护客户端应用程序的Application对象
Application 对象是asp.net ajax 客户端应用程序的最高调节者和管理者,只要页面
中包括了ScriptManager控件,客户端应用程序初始化完成后,asp.net ajax 客户端框架会自动创建一个运行于浏览器的Application对象。
Application 对象提供3个客户端事件:init, load 和unload事件。
Init事件:在客户端应用程序中所有js脚本文件加载完成之后触发,在该事件中,可以创建一些自定义的组件,一旦组件创建完毕,客户端应用程序在随后的页面生存周期中访问到该组件,并使用其提供的各种功能。Init事件只有在ajax客户端应用程序在页面第一次加载时触发,后续的由updatePanel所引发的局部更新将不会触发该事件。
我们使用如下语法为init事件添加/移除事件处理函数:
Sys.Application.add_init(onInit);
Sys.Application.remove_init(onInit);
Function onInit(){…..}
Load事件:在客户端应用程序中组件创建完成之后触发,我们一般讲自定义的客户端程序代码放在该事件的处理函数中执行。该事件在客户端应用程序的第一次加载,以及后续的updatepanel所引发的局部更新结束时,load事件均会被触发。
我们使用如下语法为load事件添加/移除事件处理函数:
Sys.Application.add_load(onLoad);
Sys.Application.remove_load(onLoad);
Function onLoad(sender,args){…..}
onLoad函数中sender为当前的Application对象,args参数有两个只读属性:components,表示自上次load事件以来,客户端应用程序中新创建的组件的集合;isPartialLoad,表示本次load事件是否由updatePanel所引发的页面局部更新引发。
Ajax客户端为application对象提供了一个默认的load事件处理函数,完整签名为:
Function pageLoad(sender,args){…….}
如果程序中还是用了Sys.Application.add_load(onLoad)方法为load事件添加了另外一个处理函数,那么捷径方法pageLoad将在onLoad方法之后执行。
Unload事件:客户端应用程序卸载时触发,该事件中可以进行一些析构相关的处理工作,或者弹出个用户调查表之类的。客户端也为Application 对象提供了一个默认的unload事件处理函数。
Function pageUnload(){………}
Application 对象还有一个notifyScriptLoaded()方法,用来告诉客户端应用程序该脚本已经加载完毕,因此我们在以.js文件方式引用脚本时,在最后,最好加上一句
if(typeof(Sys) !== "undefined")Sys.Application.notifyScriptLoaded();其实该方法是为了兼容Safari浏览器的,如果在IE或其他浏览器中,不用该方法也没有关系。
Application对象queueScriptReference()方法可以让我们在application对象的load事件处理函数中延迟加载一些非必须启动时加载的脚本。
四. 为updatepanel而设计的PageRequestManager对象
当页面上使用了updatePanel控件后,scriptManager的EnablePartialRendering=true.默认情况下,该属性值也为true,此时,scriptManager会将MicrosoftAjaxWebForms.js发送到客户端,该脚本文件中定义了 PageRequestManager对象,利用它可以对updatePanel异步更新的每一个步骤进行精细的控制。也即是说pageRequestManager对象只在通过服务器端updatepanel控件进行页面异步回送和局部更新时才有用,其他通过webservice方法等和服务端通信时,pageRequestmanager对象不参与。
在ajax客户端,我们无需创建Sys.WebForms.PageRequestManager类的实例,我们只需要如下即可得到该类的实例:
Var prm=Sys.WebForms.PageRequestManager.getInstance();
通过该该方法获得该类的实例时,最早是在Application对象的init事件中。
- initializaRequest事件:该事件将在异步回送请求初始化时被触发,在该处理函数中,我们可以取消本次异步回送请求,该事件的参数为Sys.WebForms.InitializeRequestEventArgs类型,它有三个属性:cancel—获取或设置一个布尔值,表示十分取消本次异步回送;postBackelement—获取触发本次异步回送的源Dom元素;request—获取表示即将发出的http请求的Sys.Net.WebRequest对象。
- beginRequest事件将在向服务器发送异步回送请求开始之前被触发。在该处理函数中,我们可以设置即将发出的http请求的首部信息,或是显示一段动画等提示信息,用来明确告知用户当前应用程序的状态。该事件参数的两个属性为:postBackElement,request.意义和initializaRequest的事件参数意义一致。
- pageLoading事件将在客户端收到服务器端异步回送响应,且客户端页面尚未进行任何更新时被触发。在该事件的处理函数中,我们可以修改将要对页面中UpdatePanel 进行的操作。该处理函数的参数类型为Sys.WebForms.PageLoadingEventArgs,它的属性有:panelsDeleting—获取一个html<div/>元素的集合,表示此次异步回送导致的将被删除的各个区域的内容。panelsUpdating--获取一个html<div/>元素的集合,表示此次异步回送导致的将被更新的各个区域的内容。dataItems—获取一个JsON对象,表示服务器端ScriptManager在此次异步回送过程中使用RegisterDataItem()方法捎带至客户端的数据。
- pageLoaded事件:将在客户端收到服务器端异步回送响应或是传统整页回送响应,且客户端页面已经完成了对updatePanel的更新时被触发。在异步回送中,可以创建或更新区域,而对于传统的整页回送,则只能够创建区域。该事件处理函数中,我们可以访问到本次更新对页面中updatepanel进行的操作。参数属性有:panelCreated--获取一个html<div/>元素的集合,表示此次异步回送导致的新创建的各个区域的内容。panelsUpdated--获取一个html<div/>元素的集合,表示此次异步回送导致的被更新的各个区域的内容。dataItems—如上。
- endRequest事件:将在客户端的一次异步更新过程完成时被触发,在事件的处理函数中,我们可以访问到本次更新中发生的一些异常信息,并根据异常进行处理,或给用户提示。参数属性有:error—获取本次异步回送中发生的异常对象。errorHandled—获取或设定一个布尔值,表示本次异步回送中发生的异常是否经过了处理,如果未处理,则以默认的方式显示给用户,如果经过了处理,则不会执行默认行为。Reponse—获取一个表示本次回送中http响应的webRequestExecutor对象。dataItems--如上。
为PageRequestManager对象的任何事件的添加处理函数的方法都如下:
Var prm=Sys.WebForms.PageRequestManager.getInstance();
Prm.add_initializeRequest(onInitializeRequest);
Function onInitializeRequest(sender,args){…….}
对于在PRM 的事件中取消异步请求的方法,如果是在initializeRequest事件中,则只要在该事件处理函数中,将args参数的cancel属性设为true即可。若要在其他事件中或者任何其他按钮的触发事件中(如自定义一个cancel按钮,在该按钮的单击事件中)取消已经发送出去的异步回送,可以使用PRM 的abortPostBack()方法。同时PRM 还有一个isInAsyncPostBack只读属性,用来表示当前是否处在异步回送过程中,获得该属性值可以如下:
Var isInAsyncPostBack=prm.get_isInAsyncPostBack();
一次典型的页面操作过程中触发的客户端事件顺序图如下:(包括Application对象的三个事件,和pageRequestManager对象的五个事件)
客户端 服务端
Application_init()
Application_load()
下面开始的是一次异步回送过程
PRM_initializeRequest()
PRM_beginRequest() 转向服务端处理
…..
Init
Load
Render
Unload
PRM_pageLoading() 服务端返回客户端
PRM_pageLoaded
Application_load()
PRM_endRequest()
至此一次异步回送过程结束
Application_unload()
在异步回送发生时程序若已经在执行某次异步回送的话,那么最晚发生的异步回送将占有最高的优先级,其他异步回送将被取消。