51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

ASP.NET WebForm也可以这样用Ajax(程序猿,你知道不)

对于asp.net WebForm项目,进行Ajax操作大概有三种方式:web服务(.asmx文件) , 一般处理程序(.ashx)和 一些Ajax控件。
对于.net提供的ajax控件,暂且不说,只说另外两种方式,都需要引入额外的代码文件对Ajax进行操作(asmx和ashx,且web服务还要引入一个cs文件与之对应),假如要对Example.aspx这个页面添加一些自定义的Ajax操作,并且这些Ajax操作并不会在别的页面上用到,如此不得不引入额外的代码文件完成这个操作,假如这个Ajax操作很简单,只需要一个简单的函数就可以执行,那岂不是很麻烦的过程吗?如此一来,随着项目中 Ajax操作的增多,ashx和asmx文件都会随着时间的增长而增长,项目的维护也会随之加倍。为什么我们不能把Ajax操作的后台代码直接放在 Example.aspx对应的Example.aspx.cs文件里 ? 如果能这样做,以上两种烦恼都会迎刃而解,不是吗?
于是,按照以上思路,实现了如下Ajax框架。先来看这个框架的实现机制:
ASP.NET WebForm也可以这样用Ajax(程序猿,你知道不)_https://www.tiejiang.org_ASP.Net_第1张
上图是自己画的一个缩减版IIS接收到一个aspx请求的HttpApplication管线和asp.net Page在执行ProcessRequest()方法中的页面生命周期的部分事件。Page本身是继承自IHttpHandler接口,IHttpHandler提供了一个重要的约束方法ProcessRequest,该方法是对接收到的信息(HttpContext)进行具体的处理同样,一般处理程序和web服务也实现了自己的IHttpHandler,并以此提供一些Ajax服务。具体的操作过程请自行查找MSDN。
原理是在页面生命周期开始的第一个事件PreInit进行一些处理,一旦发现劫持到的请求是一个ajax请求,那么利用C#的反射来调用aspx.cs中定义的方法,执行完方法之后,调用Response.End()方法,调用这个方法会直接跳到管线的EndRequest事件,从而结束请求,这样就无需走一些没有必要的页面生命周期的步骤,从而完成一个Ajax操作。如果发现是一个正常的操作,那么就走正常流程。
下面以一个简单例子说明该Ajax框架的使用:

  1. 添加一个解决方案
  2. 新建一个 Default.aspx 页面
  3. 在Default.aspx.cs页面中创建一个被调用的测试方法:
    1 public List<string> TestAjaxMethod(int a, string b, float c)
    2 {
    3 return new List<string> { a.ToString(), b, c.ToString() };
    4 }
  4. 在Default.aspx中写一个Ajax请求
    5 PowerAjax.AsyncAjax('TestAjaxMethod', [1, 2, "333","sss"], function (SucceessResponse) {
    6 // 成功后的代码
    7 });
    PowerAjax.js是用Jquery专门为这个框架封装的一个及其简单的JS类库,这个类库中有两个主要的方法:PowerAjax.AsyncAjax和PowerAjax.SyncAjax,一个提供同步操作,一个提供异步操作,参数有三个:
    第一个参数是即将操作在aspx.cs的Ajax方法的名字(用名字反射方法)。
    第二个参数是一个以数组形式组成参数列表数据。
    第三个参数是操作成功之后执行执行的回调方法,与c#中的委托一个道理。
    以下为这个简单JS库的代码:
    8 var PowerAjax = function () { }
    9 PowerAjax.__Private = function () { }
    10
    11 // 进行异步操作
    12 PowerAjax.AsyncAjax = function (methodName, paramArray, success) {
    13 PowerAjax.__Private.Ajax(methodName, paramArray, success, true);
    14 }
    15
    16 // 进行的是同步操作
    17 PowerAjax.SyncAjax = function (methodName, paramArray, success) {
    18 PowerAjax.__Private.Ajax(methodName, paramArray, success, false);
    19 }
    20
    21 PowerAjax.__Private.Ajax = function (methodName, paramArray, success, isAsync) {
    22 var data = {};
    23 switch (paramArray.length) {
    24 case 0:
    25 data = { 'isAjaxRequest': true, 'MethodName': methodName };
    26 break;
    27 case 1:
    28 data = { 'isAjaxRequest': true, 'MethodName': methodName, "param0": paramArray[0] };
    29 break;
    30 case 2:
    31 data = { 'isAjaxRequest': true, 'MethodName': methodName, "param0": paramArray[0], "param1": paramArray[1] };
    32 break;
    33 case 3:
    34 data = { 'isAjaxRequest': true, 'MethodName': methodName, "param0": paramArray[0], "param1": paramArray[1], "param2": paramArray[2] };
    35 break;
    36 case 4:
    37 data = { 'isAjaxRequest': true, 'MethodName': methodName, "param0": paramArray[0], "param1": paramArray[1], "param2": paramArray[2], "param3": paramArray[3] };
    38 break;
    39 case 5:
    40 data = { 'isAjaxRequest': true, 'MethodName': methodName, "param0": paramArray[0], "param1": paramArray[1], "param2": paramArray[2], "param3": paramArray[3], "param4": paramArray[4] };
    41 break;
    42 }
    43
    44 var url = document.location.href;
    45 $.ajax({
    46 type: "post",
    47 url: url,
    48 data: data,
    49 async: isAsync,
    50 datatype: "json",
    51 contentType: "application/x-www-form-urlencoded; charset=UTF-8",
    52 success: function (response) {
    53 success(response);
    54 },
    55 error: function (response) {
    56 if (response.status == 500) {
    57 var errorMessage = response.responseText;
    58 var errorTitle = errorMessage.substring(errorMessage.indexOf("<title>") + 7, errorMessage.indexOf("</title>"))
    59 throw new Error("服务器内部错误:" + errorTitle);
    60 }
    61 }
    62 });
    63 }
  5. 更改Default.aspx.cs的继承页面为AjaxBasePage
    64 public partial class _Default : AjaxBasePage
  6. 主要基类:AjaxBasePage类
    如下代码:
    65 public class AjaxBasePage : System.Web.UI.Page
    66 {
    67 /// <summary>
    68 /// 是否是一个ajax请求。
    69 /// </summary>
    70 public bool IsAjaxRequest { get; private set; }
    71
    72 /// <summary>
    73 /// 如果是Ajax请求,劫持页面生命周期的PreInit的事件,直接返回Response
    74 /// </summary>
    75 protected override void OnPreInit(EventArgs e)
    76 {
    77 AjaxRequest ajaxRequest = AjaxRequest.GetInstance(Request.Form);
    78 this.IsAjaxRequest = ajaxRequest.IsAjaxRequest;
    79
    80 if (this.IsAjaxRequest)
    81 {
    82 AjaxApplication ajaxApplication = new AjaxApplication(this, ajaxRequest);
    83 ajaxApplication.EndRequest();
    84 }
    85 else
    86 {
    87 // 如果不是Ajax请求,继续执行页面生命周期的剩余事件
    88 base.OnPreInit(e);
    89 }
    90 }
    91 }
    该类重写了PreInit方法,判断请求是否是一个Ajax请求。通过AjaxRequest类接收并处理接收到的请求,提取出一些有效的数据,比如说是否是一个Ajax请求,方法的名字,参数列表(AjaxParameter类)。
    至于AjaxParameter类,内部用的数据结构其实是一个字典,并使用索引来提供对数据的方便访问,提供一个Count属性,方便获取参数的个数。 如下代码
    92 public class AjaxParameter
    93 {
    94 private IDictionary<int, string> m_DictionaryParamsData = new Dictionary<int, string>();
    95
    96 /// <summary>
    97 /// 返回参数的个数。
    98 /// </summary>
    99 public int Count
    100 {
    101 get
    102 {
    103 return this.m_DictionaryParamsData.Count;
    104 }
    105 }
    106
    107 /// <summary>
    108 /// 索引具体参数的值。
    109 /// </summary>
    110 /// <param name="index"></param>
    111 /// <returns></returns>
    112 public string this[int index]
    113 {
    114 get
    115 {
    116 if (index >= 5 || index < 0)
    117 {
    118 throw new NotSupportedException("请求方法的参数的个数限制为:0-5");
    119 }
    120 return this.m_DictionaryParamsData[index];
    121 }
    122 }
    123
    124 public AjaxParameter(IDictionary<int, string> paramsData)
    125 {
    126 this.m_DictionaryParamsData = paramsData;
    127 }
    128 }
    AjaxRequest类的设计思路其实是模仿HttpContext设计,HttpContext能够从基础的http请求报文分析出以后处理将要用到的数据(response,request,session,cookie等等)数据,而AjaxRequest通过分析Ajax的Post请求的数据域 Data分析出各种以后会用到的数据。如下是该类的代码:
    129 public class AjaxRequest
    130 {
    131 private Dictionary<int, string> m_DictionaryParamsData = new Dictionary<int, string>();
    132 private AjaxParameter m_AjaxParameter;
    133 private int m_Count = 0;
    134
    135 #region 属性
    136 /// <summary>
    137 /// 是否是一个Ajax请求。
    138 /// </summary>
    139 public bool IsAjaxRequest { get; private set; }
    140
    141 /// <summary>
    142 /// 请求的方法名字。
    143 /// </summary>
    144 public string MethodName { get; private set; }
    145
    146 /// <summary>
    147 /// 参数数据。
    148 /// </summary>
    149 public AjaxParameter Parameters
    150 {
    151 get { return this.m_AjaxParameter; }
    152 }
    153 #endregion
    154
    155 #region 构造函数
    156 private AjaxRequest(NameValueCollection nameValueCollection)
    157 {
    158 this.IsAjaxRequest = nameValueCollection["isAjaxRequest"] == "true";
    159 if (this.IsAjaxRequest)
    160 {
    161 this.MethodName = nameValueCollection["MethodName"];
    162
    163 foreach (string value in nameValueCollection)
    164 {
    165 string formKey = string.Format("param{0}", this.m_Count);
    166 if (nameValueCollection[formKey] != null)
    167 {
    168 this.m_DictionaryParamsData.Add(this.m_Count, nameValueCollection[formKey]);
    169 this.m_Count++;
    170 }
    171 }
    172 m_AjaxParameter = new AjaxParameter(this.m_DictionaryParamsData);
    173 }
    174 }
    175
    176 #endregion
    177
    178 #region 实例方法
    179 public static AjaxRequest GetInstance(NameValueCollection nameValueCollection)
    180 {
    181 return new AjaxRequest(nameValueCollection);
    182 }
    183 #endregion
    184
    185 #region ToString
    186 public override string ToString()
    187 {
    188 return this.MethodName;
    189 }
    190 #endregion
    191 }
    通过分析AjaxRequest的属性IsAjaxRequest可判断是否为Ajax请求,若该请求为一个Ajax请求,那么创建一个AjaxApplication实例,在创建AjaxApplication实例的过程中会利用当前页面和AjaxRequest提供的数据进行实际方法的调用(反射),该类是执行Ajax方法的核心类,其中会判断是否读取的到的方法是一个有效的方法,并判断从JS中AjaxApplication传入的参数类型的有效性,目前只提供对以下13中参数提供支持,如下:
    192 (String、Boolean、Int32、Int64、UInt32、UInt64、Single、Double、Decimal、DateTime、DateTimeOffset、TimeSpan、Guid)
    如果方法中出现非以上类型,将会抛出异常。为了方便Ajax的调试,在JS前段类库中我会对异常进行处理,并抛出Error,Error信息有效的截取了继承自Exception的抛出信息,至于如何获 得更加详细的JS调试信息,以后JS库中可能会做提供更加详细的调用信息,毕竟框架是在改进中进行的。如下是AjaxApplication类的具体代码:
    193 public class AjaxApplication
    194 {
    195 private AjaxBasePage m_AjaxBasePage;
    196 private object m_ResponseData;
    197
    198 public AjaxApplication(AjaxBasePage ajaxBasePage, AjaxRequest ajaxRequest)
    199 {
    200 this.m_AjaxBasePage = ajaxBasePage;
    201 Type ajaxBasePageType = ajaxBasePage.GetType();
    202 MethodInfo methodInfo = ajaxBasePageType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)
    203 .FirstOrDefault(item => item.Name == ajaxRequest.MethodName);
    204 object[] parameterData = this.GetParameterData(ajaxRequest, methodInfo);
    205
    206 if (methodInfo.IsStatic)
    207 {
    208 this.m_ResponseData = methodInfo.Invoke(null, parameterData);
    209 }
    210 else
    211 {
    212 this.m_ResponseData = methodInfo.Invoke(ajaxBasePage, parameterData);
    213 }
    214 }
    215
    216 /// <summary>
    217 /// 获取参数数据。
    218 /// </summary>
    219 private object[] GetParameterData(AjaxRequest ajaxRequest, MethodInfo methodInfo)
    220 {
    221 if (methodInfo != null)
    222 {
    223 ParameterInfo[] parameterInfos = methodInfo.GetParameters();
    224
    225 if (parameterInfos.Length > 5)
    226 {
    227 throw new NotSupportedException("最多支持5个参数");
    228 }
    229
    230 if (parameterInfos.Length > ajaxRequest.Parameters.Count)
    231 {
    232 throw new ArgumentException("缺少参数!");
    233 }
    234
    235 List<object> parameterData = new List<object>(parameterInfos.Length);
    236 for (int i = 0; i < parameterInfos.Length; i++)
    237 {
    238 ParameterInfo parameterInfo = parameterInfos[i];
    239 string paramValue = ajaxRequest.Parameters[i];
    240
    241 try
    242 {
    243 parameterData.Add(ParseAjaxParameter(paramValue, parameterInfo));
    244 }
    245 catch (FormatException)
    246 {
    247 string format = string.Format("传入静态方法 {0} 的第 {1} 个(从0开始计数)参数类型不匹配,应该为 {2} 类型 请检查!", methodInfo.Name, i, parameterInfo.ParameterType.Name);
    248 throw new FormatException(format);
    249 }
    250 }
    251 return parameterData.ToArray();
    252 }
    253 else
    254 {
    255 throw new InvalidOperationException("没有发现此方法,请检查该方法签名(方法必须为public)");
    256 }
    257 }
    258
    259 /// <summary>
    260 /// 类型转换。支持 String、Boolean、Int32、Int64、UInt32、UInt64、Single、Double、Decimal、DateTime、DateTimeOffset、TimeSpan、Guid
    261 /// </summary>
    262 private object ParseAjaxParameter(string ajaxParameterValue, ParameterInfo parameterInfo)
    263 {
    264 object obj;
    265 if (parameterInfo.ParameterType == typeof(String))
    266 {
    267 obj = ajaxParameterValue;
    268 }
    269 else if (parameterInfo.ParameterType == typeof(Boolean))
    270 {
    271 obj = bool.Parse(ajaxParameterValue);
    272 }
    273 else if (parameterInfo.ParameterType == typeof(Int32))
    274 {
    275 obj = Int32.Parse(ajaxParameterValue);
    276 }
    277 else if (parameterInfo.ParameterType == typeof(UInt32))
    278 {
    279 obj = UInt32.Parse(ajaxParameterValue);
    280 }
    281 else if (parameterInfo.ParameterType == typeof(UInt64))
    282 {
    283 obj = UInt64.Parse(ajaxParameterValue);
    284 }
    285 else if (parameterInfo.ParameterType == typeof(Single))
    286 {
    287 obj = Single.Parse(ajaxParameterValue);
    288 }
    289 else if (parameterInfo.ParameterType == typeof(Double))
    290 {
    291 obj = Double.Parse(ajaxParameterValue);
    292 }
    293 else if (parameterInfo.ParameterType == typeof(Decimal))
    294 {
    295 obj = Decimal.Parse(ajaxParameterValue);
    296 }
    297 else if (parameterInfo.ParameterType == typeof(DateTime))
    298 {
    299 obj = DateTime.Parse(ajaxParameterValue);
    300 }
    301 else if (parameterInfo.ParameterType == typeof(DateTimeOffset))
    302 {
    303 obj = DateTimeOffset.Parse(ajaxParameterValue);
    304 }
    305 else if (parameterInfo.ParameterType == typeof(TimeSpan))
    306 {
    307 obj = TimeSpan.Parse(ajaxParameterValue);
    308 }
    309 else if (parameterInfo.ParameterType == typeof(Guid))
    310 {
    311 obj = Guid.Parse(ajaxParameterValue);
    312 }
    313 else
    314 {
    315 throw new NotSupportedException("方法参数类型不支持!");
    316 }
    317 return obj;
    318 }
    319
    320 /// <summary>
    321 /// 结束页面生命周期,同时直接执行应用程序生命周期的EndRequest事件。
    322 /// </summary>
    323 public void EndRequest()
    324 {
    325 HttpResponse response = this.m_AjaxBasePage.Page.Response;
    326 response.ContentType = "application/json";
    327 response.Clear();
    328 JavaScriptSerializer jsonSerializer2 = new JavaScriptSerializer();
    329 response.Write(jsonSerializer2.Serialize(new JsonResponse { IsSuccess = true, Message = "处理成功", ResponseData = this.m_ResponseData }));
    330 response.End();
    331 }
    332 }
    当初始化了一个AjaxApplication实例后, 可以调用该实例的EndRequest()方法,来结束Ajax请求。该方法内部最后调用Response.End()方法来结束页面生命周期和大部分管线事件。
    并用JsonResponse类来封装返回数据。
    333 public class JsonResponse
    334 {
    335 public bool IsSuccess { get; set; }
    336 public string Message { get; set; }
    337 public object ResponseData { get; set; }
    338 }
    该类最后一个参数即承载了调用方法的返回值,为一个Object类型,也就是说,框架可以支持任何类型的返回值,当然该类型可以被序列化。
  7. 回过头来再看Ajax请求,针对返回值可以这样用:
    339 PowerAjax.AsyncAjax('TestAjaxMethod', [1, 2, "333", "sss"], function (SucceessResponse) {
    340 if (SucceessResponse.IsSuceess) {
    341 alert("恭喜,该ajax方法调用成功!");
    342 Process(SucceessResponse.ResponseData); // 处理返回的数据,这里可能需要你自己实现了,因为我无法判断你要返回的是什么东西,这是个Object
    343 } else {
    344 alert("这是操作错误奥,不是内部异常,内部异常的抛出会我内部会处理的!");
    345 alert("错误信息:" + SucceessResponse.Message);
    346 }
    347 });
    以上是试水的东西,希望各位大牛指正。

赞(0)
未经允许不得转载:工具盒子 » ASP.NET WebForm也可以这样用Ajax(程序猿,你知道不)