http://www.cnblogs.com/Hedonister/
二是AJAX,.net本身就自带,如果细节化,建议用jqueryajax,也很简单。
好早就想分析下AjaxPro的代码实现机制了,一直苦于没时间,现在嘛总算有那么丁点了,开篇了,慢慢分析……
以一个最简单的例子开始:
点击一个客户端button,触发一个javascript函数,执行一个只有一个string参数的服务端方法,返回一个处理过的string,处理方法是将传入的string变成“Hi”+string +“!”;够简单了,为了是不希望罗嗦的代码影响简单的分析;
所有代码如下:
<%
@ Page Language="C#" AutoEventWireup="true" CodeFile="Test.aspx.cs" Inherits="Test" %>
<!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>
<script type="text/javascript">
function doTest()
{
AJAXDemo.Examples.Test.TestMethod.GetTest("AjaxPro",doTest_callback);
}
function doTest_callback(res)
{
alert(res.value);
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input id="Button1" type="button" onclick="doTest()" value="测试"/></div>
</form>
</body>
</html>
Test.aspx.cs
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Utility.RegisterTypeForAjax(typeof(AJAXDemo.Examples.Test.TestMethod));
}
}
AJAXDemo.Examples.Test
using System;
using AjaxPro;
namespace AJAXDemo.Examples.Test
{
public class TestMethod
{
public TestMethod()
{}
[AjaxMethod]
public string GetTest(string testText)
{
return "Hi," + testText + "!";
}
}
}
1.首先我们看AjaxPro在页面上给我们生成了什么?
Test[1]
<!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><title>
无标题页
</title>
<script type="text/javascript">
function doTest()
{
AJAXDemo.Examples.Test.TestMethod.GetTest("AjaxPro",doTest_callback);
}
function doTest_callback(res)
{
alert(res.value);
}
</script>
</head>
<body>
<form name="form1" method="post" action="Test.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGRFekXifzWDNb+qFWPbJumdlZh/dQ==" />
</div>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/prototype.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/core.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/converter.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/AJAXDemo.Examples.Test.TestMethod,App_Code.un7rskvh.ashx"></script>
<div>
<input id="Button1" type="button" onclick="doTest()" value="测试"/></div>
</form>
</body>
</html>
一定要注意这几行
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/prototype.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/core.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/converter.ashx"></script>
<script type="text/javascript" src="/AJAXDemo.2/ajaxpro/AJAXDemo.Examples.Test.TestMethod,App_Code.urx4hqkg.ashx"></script>
通过使用http://localhost:3578/AJAXDemo.2/ajaxpro/prototype.ashx和http://localhost:3578/AJAXDemo.2/ajaxpro/core.ashx不难发现,其中前面两个是源代码中带的两个js文件(core.js和prototype.js)转化出来的,基本内容也跟原来的文件一样,而converter.ashx和AJAXDemo.Examples.Test.TestMethod,App_Code.urx4hqkg.ashx里面有什么呢?看下面:
AJAXDemo.Examples.Test.TestMethod,App_Code.urx4hqkg.ashx
addNamespace("AJAXDemo.Examples.Test");
AJAXDemo.Examples.Test.TestMethod_class = Class.create();
AJAXDemo.Examples.Test.TestMethod_class.prototype = (new AjaxPro.AjaxClass()).extend(
{
GetTest: function(testText)
{
return this.invoke("GetTest",
{"testText":testText}, this.GetTest.getArguments().slice(1));
},
initialize: function()
{
this.url = '/AJAXDemo.2/ajaxpro/AJAXDemo.Examples.Test.TestMethod,App_Code.un7rskvh.ashx';
}
});
AJAXDemo.Examples.Test.TestMethod = new AJAXDemo.Examples.Test.TestMethod_class();
converter.ashx
addNamespace("Ajax.Web");
Ajax.Web.NameValueCollection = function()
{
this.__type = "System.Collections.Specialized.NameValueCollection";
this.add = function(key, value)
{
if(this[key] == null)
{
this[key] = value;
}
}
this.getKeys = function()
{
var keys = [];
for(key in this)
if(typeof this[key] != "function")
keys.push(key);
return keys;
}
this.getValue = function(key)
{
return this[key];
}
this.toJSON = function()
{
var o = this;
o.toJSON = null;
delete o.toJSON;
return AjaxPro.toJSON(o);
}
}
addNamespace("Ajax.Web");
Ajax.Web.DataTable = function(columns, rows)
{
this.__type = "System.Data.DataTable, System.Data";
this.Columns = new Array();
this.Rows = new Array();
this.addColumn = function(name, type)
{
var c = new Object();
c.Name = name;
c.__type = type;
this.Columns.push(c);
}
this.toJSON = function()
{
var dt = new Object();
dt.Columns = [];
for(var i=0; i<this.Columns.length; i++)
dt.Columns.push([this.Columns[i].Name, this.Columns[i].__type]);
dt.Rows = [];
for(var i=0; i<this.Rows.length; i++)
{
var row = [];
for(var j=0; j<this.Columns.length; j++)
row.push(this.Rows[i][this.Columns[j].Name]);
dt.Rows.push(row);
}
return AjaxPro.toJSON(dt);
}
this.addRow = function(row)
{
this.Rows.push(row);
}
if(columns != null)
{
for(var i=0; i<columns.length; i++)
{
this.addColumn(columns[i][0], columns[i][1]);
}
}
if(rows != null)
{
for(var i=0; i<rows.length; i++)
{
var row = new Object();
for(var c=0; c<this.Columns.length && c<rows[i].length; c++)
{
row[this.Columns[c].Name] = rows[i][c];
}
this.addRow(row);
}
}
}
addNamespace("Ajax.Web");
Ajax.Web.DataSet = function(tables)
{
this.__type = "System.Data.DataSet, System.Data";
this.Tables = new Array();
this.addTable = function(table)
{
this.Tables.push(table);
}
if(tables != null)
{
for(var i=0; i<tables.length; i++)
{
this.addTable(tables[i]);
}
}
}
function Person(id)
{
this.FirstName = "";
this.FamilyName = "";
this.Age = 0;
this.ID = id;
this.__type = 'AJAXDemo.Examples.Classes.Person, App_Code.un7rskvh, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null';
}
Person.prototype.get_FullName = function()
{
return this.FirstName + " " + this.FamilyName;
}
Person.prototype.toJSON = function()
{
var o = new Object();
o.firstName = this.FirstName;
o.familyName = this.FamilyName;
o.age = this.Age;
o.id = this.ID;
return AjaxPro.toJSON(o);
}
Person.prototype.save = function()
{
return Person.save(this);
}
Person.save = function(p)
{
var ps = new PersonSaver();
return ps.savePerson(p); // synchronous call
}
var PersonSaver = Class.create();
PersonSaver.prototype = (new AjaxPro.Request()).extend(
{
savePerson: function(p)
{
return this.invoke("SavePerson",
{"p":p}).value;
},
initialize: function()
{
this.url = "ajaxpro/AJAXDemo.Examples.Classes.Person, App_Code.un7rskvh, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null.ashx";
}
})
正因为是有了上面四个ashx文件我们的
function doTest()
{
AJAXDemo.Examples.Test.TestMethod.GetTest("AjaxPro",doTest_callback);
}
才得以异步执行,这些ashx文件又是怎么生成到页面上的,那得归功于web.config的相关配置和下面这句代码:
Utility.RegisterTypeForAjax(typeof(AJAXDemo.Examples.Test.TestMethod));
至于Utility.RegisterTypeForAjax方法产生的一序列动作我将在后文中继续说明,有兴趣的可以自己跟踪下这些代码的执行。
[未完待续]
上篇 说到AjaxPro为我们生成了四个ashx文件来达到异步调用的目的,这次呢,就说说这些ashx文件是怎么生成的;
核心部分是其web.config配置了httpHandlers 元素
<httpHandlers>
<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>
</httpHandlers>
作用是:对ajaxpro目录下的*.ashx文件的POST,GET请求交由程序集AjaxPro下的AjaxPro.AjaxHandlerFactory类来处理;
ps:关于httpHandlers的说明可以参看下面几个连接
1.httpHandlers 元素(ASP.NET 设置架构)
2.MicrosoftASP.NET 快速入门教程
3.ASP.NET中的Http Handles
先看看实现IHttpHandlerFactory接口的AjaxHandlerFactory类,只实现了GetHandler方法
其中对core,prototype的处理没什么可说,只是简单的通过EmbeddedJavaScriptHandler的ProcessRequest中将js写入.ashx文件,而converter比较复杂留代下次再做详细分析;这里就重点看看AJAXDemo.Examples.Test.TestMethod,App_Code.urx4hqkg.ashx是怎么来的了;
我跟踪了下代码的运行,在页面加载的时候自定义 HttpHandler 启用 HTTP Web 请求的处理ProcessRequest,也就是进入AjaxHandlerFactory类GetHandler方法的如下入口
default:
if(Utility.Settings.UrlNamespaceMappings.Contains(filename))
t = Type.GetType(Utility.Settings.UrlNamespaceMappings[filename].ToString());
if(t == null)
t = Type.GetType(filename);
return new TypeJavaScriptHandler(t);
TypeJavaScriptHandler也实现了IHttpHandler接口,其ProcessRequest方法如下:
public void ProcessRequest(HttpContext context)
{
// The request was not a request to invoke a server-side method.
// Now, we will render the Javascript that will be used on the
// client to run as a proxy or wrapper for the methods marked
// with the AjaxMethodAttribute.
if(context.Trace.IsEnabled) context.Trace.Write(Constant.AjaxID, "Render class proxy Javascript");
System.Reflection.MethodInfo[] mi = type.GetMethods();
// Check wether the javascript is already rendered and cached in the
// current context.
string etag = context.Request.Headers["If-None-Match"];
string modSince = context.Request.Headers["If-Modified-Since"];
string path = type.FullName + "," + type.Assembly.FullName.Split(',')[0];
if(Utility.Settings != null && Utility.Settings.UrlNamespaceMappings.ContainsValue(path))
{
foreach(string key in Utility.Settings.UrlNamespaceMappings.Keys)
{
if(Utility.Settings.UrlNamespaceMappings[key].ToString() == path)
{
path = key;
break;
}
}
}
if(context.Cache[path] != null)
{
CacheInfo ci = (CacheInfo)context.Cache[path];
if(etag != null)
{
if(etag == ci.ETag) // TODO: null check
{
context.Response.StatusCode = 304;
return;
}
}
if(modSince != null)
{
try
{
DateTime modSinced = Convert.ToDateTime(modSince.ToString()).ToUniversalTime();
if(DateTime.Compare(modSinced, ci.LastModified.ToUniversalTime()) >= 0)
{
context.Response.StatusCode = 304;
return;
}
}
catch(Exception)
{
if(context.Trace.IsEnabled) context.Trace.Write(Constant.AjaxID, "The header value for If-Modified-Since = " + modSince + " could not be converted to a System.DateTime.");
}
}
}
etag = type.AssemblyQualifiedName; // + "_" + type.Assembly. DateTime.Now.ToString(System.Globalization.DateTimeFormatInfo.CurrentInfo.SortableDateTimePattern);
etag = MD5Helper.GetHash(System.Text.Encoding.Default.GetBytes(etag));
DateTime now = DateTime.Now;
DateTime lastMod = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); // .ToUniversalTime();
context.Response.AddHeader("Content-Type", "application/x-javascript");
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
context.Response.Cache.SetETag(etag);
context.Response.Cache.SetLastModified(lastMod);
// Ok, we do not have the javascript rendered, yet.
// Build the javascript source and save it to the current
// Application context.
System.Text.StringBuilder sb = new System.Text.StringBuilder();
AjaxNamespaceAttribute[] cma = (AjaxNamespaceAttribute[])type.GetCustomAttributes(typeof(AjaxNamespaceAttribute), true);
string clientNS = type.FullName;
if(cma.Length > 0 && cma[0].ClientNamespace != null)
{
if(cma[0].ClientNamespace.IndexOf(".") > 0)
sb.Append("addNamespace(\"" + cma[0].ClientNamespace + "\");\r\n");
clientNS = cma[0].ClientNamespace;
}
else
{
sb.Append("addNamespace(\"" + (type.FullName.IndexOf(".") > 0 ? type.FullName.Substring(0, type.FullName.LastIndexOf(".")) : type.FullName) + "\");\r\n");
}
sb.Append(clientNS);
sb.Append("_class = Class.create();\r\n");
sb.Append(clientNS);
sb.Append("_class.prototype = (new AjaxPro.AjaxClass()).extend({\r\n");
System.Reflection.MethodInfo method;
for(int y=0; y<mi.Length; y++)
{
method = mi[y];
if(!method.IsPublic)
continue;
AjaxNamespaceAttribute[] cmam = (AjaxNamespaceAttribute[])method.GetCustomAttributes(typeof(AjaxNamespaceAttribute), true);
AjaxMethodAttribute[] ma = (AjaxMethodAttribute[])method.GetCustomAttributes(typeof(AjaxMethodAttribute), true);
if(ma.Length == 0)
continue;
System.Reflection.ParameterInfo[] pi = method.GetParameters();
// Render the function header
sb.Append("\t");
if(cmam.Length == 0)
sb.Append(method.Name);
else
sb.Append(cmam[0].ClientNamespace);
sb.Append(": function(");
// Render all parameters
for(int i=0; i<pi.Length; i++)
{
sb.Append(pi[i].Name);
if(i<pi.Length -1)
sb.Append(", ");
}
sb.Append(") {\r\n");
// Create the XMLHttpRequest object
sb.Append("\t\treturn this.invoke(\"" + method.Name + "\", {"); // must be the original method name
for(int i=0; i<pi.Length; i++)
{
sb.Append("\"");
sb.Append(pi[i].Name);
sb.Append("\":");
sb.Append(pi[i].Name);
if(i<pi.Length -1)
sb.Append(", ");
}
sb.Append("}, this.");
if(cmam.Length == 0)
sb.Append(method.Name);
else
sb.Append(cmam[0].ClientNamespace);
sb.Append(".getArguments().slice(");
sb.Append(pi.Length.ToString());
sb.Append("));\r\n\t},\r\n");
}
string url = context.Request.ApplicationPath + (context.Request.ApplicationPath.EndsWith("/") ? "" : "/") + Utility.HandlerPath + "/" + (context.Session != null && context.Session.IsCookieless ? "(" + context.Session.SessionID + ")/" : "") + path + Utility.HandlerExtension;
sb.Append("\tinitialize: function() {\r\n\t\tthis.url = '" + url + "';\r\n\t}\r\n");
sb.Append("});\r\n");
sb.Append(clientNS);
sb.Append(" = new ");
sb.Append(clientNS);
sb.Append("_class();\r\n");
// save the javascript in current Application context to
// speed up the next requests.
// TODO: was knen wir hier machen??
// System.Web.Caching.CacheDependency fileDepend = new System.Web.Caching.CacheDependency(type.Assembly.Location);
context.Cache.Add(path, new CacheInfo(etag, lastMod), null,
System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal, null);
context.Response.Write(sb.ToString());
context.Response.Write("\r\n");
if(context.Trace.IsEnabled) context.Trace.Write(Constant.AjaxID, "End ProcessRequest");
}
代码很明了,只是注释少了点,可能是作者认为没什么难理解的吧,感兴趣的可以到http://www.ajaxpro.info/找源代码跟踪一下,具体的运行过程,可能就更明白是怎么回事了,下篇该来说说这些ashx是用来干什么的了!