【AjaxPro实现机制浅析二】*.ashx文件是怎么来的?
上篇 说到AjaxPro为我们生成了四个ashx文件来达到异步调用的目的,这次呢,就说说这些ashx文件是怎么生成的;
核心部分是其web.config配置了httpHandlers 元素
作用是:对ajaxpro目录下的*.ashx文件的POST,GET请求交由程序集AjaxPro下的AjaxPro.AjaxHandlerFactory类来处理;
ps:关于httpHandlers的说明可以参看下面几个连接
1.httpHandlers 元素(ASP.NET 设置架构)
2.Microsoft ASP.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方法的如下入口
TypeJavaScriptHandler也实现了IHttpHandler接口,其ProcessRequest方法如下:
代码很明了,只是注释少了点,可能是作者认为没什么难理解的吧,感兴趣的可以到http://www.ajaxpro.info/找源代码跟踪一下,具体的运行过程,可能就更明白是怎么回事了,下篇该来说说这些ashx是用来干什么的了!
核心部分是其web.config配置了httpHandlers 元素
<httpHandlers>
<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>
</httpHandlers>
<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>
</httpHandlers>
ps:关于httpHandlers的说明可以参看下面几个连接
1.httpHandlers 元素(ASP.NET 设置架构)
2.Microsoft ASP.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);
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");
}
{
// 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是用来干什么的了!