基于.Net 写我自己的Ajax后台框架AjaxFramework
小小目录:
现在Ajax在web应用方面已经用的稀巴烂了,如果你做一个网站没有用到ajax都不好意思拿出手,虽然面对ajax的潮流下,在.net开放方向,微软已经做了很多工作了,比如推出了ajax的服务器端控件,但是他不够灵活,用的人多吗?不多!
在.Net环境下与ajax配合的几种情况(主要是针对webform,因为.net mvc你只要写action就可以将方法给发布出来,实现ajax的调用):
1.用ajax访问aspx页面:直接在page_load中输出html代码并且End掉多余标签的输出,但是他还要走生命周期,一个好好的aspx页面只用这个功能怪怪的,当然还可以直接在aspx.cs上面的方法上加上[webMethod]特性来标记ajax访问,但是必须该方法为静态,用起来也不是很爽。
2.用ajax访问ashx一般处理程序:这个是在.net的ajax开发中一种常见明确推荐的方式,他输出页面少,不冗余代码,调用灵活,但是他通常一个ashx页面只提供一个功能,但是市面上有可以通过switch方法的参数或者用委托或者反射的方法来实现一个ashx页面提供多个ajax访问的功能,但是没发现这样也挺乱的吗,我之前一直用这个,慢慢的 慢慢的开始讨厌这种写好写法!
3.用ajax直接调用webservice服务:这样的好处是在一个普通的webserive类中可以使用多个对外ajax访问点的发布,用起来很灵活,但是你一般的小网站为了ajax的访问还是部署webservice还是比较烦的吧!
4.用ajax去调用wcf:微软推出了wcf,并且现在公认为他是一种可以替代webservice的新产品,并且他还支持奖wcf部署在web服务器上,以url形式发布,当然了,这样就可以用ajax来访问调用,但是同上,小网站小应用还要去弄wcf,也是挺烦的一件事情!
不知道大家发现没有,第一二中方法其实终于还是访问了业务类里面的方法,是否感觉ajax调ashx,aspx有些多余?还有第三四中方法其实可以看做业务类里面的方法,但是需要发布才可以使用,挺烦!那有没有可能让ajax直接去访问cs类里面的方法呢?
我就开始查资料,看看网上别人的一些做法和看法,之前在.net方面有个叫做ajaxpro的dll可以实现cs发布成ajax,但是感觉使用起来还是有点复杂,就自己琢磨着自己能否开发一个.net的ajax后台框架,在普通cs类的方法上面稍加修饰,不改变方法原有功能,但是能将方法发布出来?
经过几点的奋战,终于有了一个初步可以用的版本,先贴上设计的类图:
看了这个图是不是看上出还是比较繁琐?那接下来再来看下这个框架运行的泳道图应该就比较明了:
他其实就分让相应的url进入指定的handlerfactory里面,这个factory去指定调用相应的handler(你可以看做ashx文件),然后去解析地址栏的url,得到具体的方法,再去动态执行,最后输出结果即可,大致流程就是这样,现在我来说一些细节:
url的一些解析问题:本次设计的url例如:http://domain.com/classname/method.ajax?param...,从url里面可以解析得到此次请求的class类名以及具体的method方法名,当然所属的程序集是需要配置的好的,这样就可以通过反射具体得到方法
如何得到方法:由于得到的类名,方法名都是字符串,我们知道可以使用.Net里面的反射功能来实现通过字符串去调用相应的方法,但是反射消耗的性能还是很大的,所以我做了缓存,在一个方法成功请求之后我就将该方法缓存起来,以便下一次的时候不需要再反射直接调用缓存.
1 #region 获取该方法 2 3 if (_idictMethod.Keys.Contains(this.DictKey)) 4 { 5 #region 存在缓存中 直接从缓存中取得 6 //如果该方法被访问过 存在该字典中 7 customMethodInfo = _idictMethod[this.DictKey]; 8 //更新执行时间 9 customMethodInfo.LastUpdateTime = DateTime.Now; 10 //访问次数加1 11 customMethodInfo.Count++; 12 //将调用后的信息反写进去 13 _idictMethod[this.DictKey] = customMethodInfo; 14 #endregion 15 } 16 else { 17 #region 如果缓存中不存在 将会反射重新获取方法信息 并且记录缓存 18 customMethodInfo = this.GetMethodBaseInfo(); 19 if (customMethodInfo == null) 20 { 21 throw new MethodNotFoundOrInvalidException(string.Format("没有找到方法{0}", this._methodPathInfo.MethodName)); 22 } 23 else 24 { 25 #region 初始化方法的一些信息 26 //特性列表 27 customMethodInfo.AttrList = ReflectionHelper.GetAttributes<ValidateAttr>(customMethodInfo.Method); 28 //参数列表 29 customMethodInfo.ParamterInfos = customMethodInfo.Method.GetParameters(); 30 //返回值类型 31 32 customMethodInfo.RetureType = customMethodInfo.Method.ReturnType; 33 //方法最后的更新时间 34 customMethodInfo.LastUpdateTime = DateTime.Now; 35 //方法的执行次数 36 customMethodInfo.Count = 1; 37 #endregion 38 39 #region 加了双重锁 防止死锁掉 将该方法加入缓存 40 //通过了特性的检测 41 if (!_idictMethod.Keys.Contains(this.DictKey)) 42 { 43 lock (obj) 44 { 45 //防止在锁的时候 其他用户已经添加了键值 46 if (!_idictMethod.Keys.Contains(this.DictKey)) 47 { 48 //将 此方法的信息记录到静态字典中 以便下次从内存中调用 49 _idictMethod.Add(this.DictKey, customMethodInfo); 50 } 51 } 52 } 53 #endregion 54 55 } 56 #endregion 57 } 58 #endregion
并且为了尽量少反射,减少用户请求时间,在第一次程序运行的时候,我就将可能为标记ajax的方法给预缓存了起来(用静态构造方法可以实现该功能,该构造方法仅仅在第一次使用该类的时候运行,并且只运行一次)
1 #region 初始化缓存 2 /// <summary> 3 /// 初始化缓存 4 /// </summary> 5 private static void InitCache() 6 { 7 ICollection assemblies = BuildManager.GetReferencedAssemblies(); 8 9 foreach (Assembly assembly in assemblies) 10 { 11 12 if (UrlConfig.ASSEMBLY.Equals(assembly)) 13 { 14 //如果在指定的ajax的业务的程序集中 15 16 //添加到程序集的缓存中 17 if (!_idictAssemby.Keys.Contains(GetAssemblyName(assembly))) 18 { 19 _idictAssemby.Add(GetAssemblyName(assembly), assembly); 20 } 21 22 try 23 { 24 foreach (Type t in assembly.GetExportedTypes()) 25 { 26 Type[] allInterface = t.GetInterfaces(); 27 foreach (Type interfaceName in allInterface) 28 { 29 if ("IAjax".Equals(interfaceName.Name)) 30 { 31 // 该类有IAjax的接口 则默认添加进缓存 添加到类的缓存中 32 if (!_idictClass.Keys.Contains(t.FullName)) 33 { 34 _idictClass.Add(t.FullName, t); 35 } 36 37 } 38 } 39 } 40 } 41 catch { } 42 } 43 44 45 } 46 47 //针对方法添加缓存 48 foreach (string className in _idictClass.Keys) 49 { 50 foreach (MethodInfo methodInfo in _idictClass[className].GetMethods(_bindingAttr)) 51 { 52 try 53 { 54 List<ValidateAttr> attrList = ReflectionHelper.GetAttributes<ValidateAttr>(methodInfo); 55 56 //有标志的WebMethodAttr属性 添加进方法的缓存 57 WebMethodAttr webMethodAttr = attrList.Find(x => x is WebMethodAttr) as WebMethodAttr; 58 if (webMethodAttr == null) 59 { 60 //没有该特性 61 continue; 62 } 63 CustomMethodInfo customMethonfInfo = new CustomMethodInfo() 64 { 65 AttrList = attrList, 66 Count = 0, 67 LastUpdateTime = DateTime.Now, 68 Method = methodInfo, 69 RetureType = methodInfo.ReturnType, 70 ParamterInfos = methodInfo.GetParameters(), 71 Instance = Activator.CreateInstance(_idictClass[className]), 72 Assembly = _idictClass[className].Assembly 73 74 }; 75 //添加进方法的缓存里面去 76 if (!_idictMethod.Keys.Contains(className+"."+methodInfo.Name)) 77 { 78 _idictMethod.Add(className + "." + methodInfo.Name, customMethonfInfo); 79 } 80 81 }catch{} 82 83 84 } 85 } 86 } 87 88 /// <summary> 89 /// 得到程序集的名称 不带版本信息的 90 /// </summary> 91 /// <param name="assembly"></param> 92 /// <returns></returns> 93 private static string GetAssemblyName(Assembly assembly) 94 { 95 return assembly.FullName.Split(',')[0]; 96 } 97 #endregion
这样就可以尽可能得降低反射因为消耗的性能而带来产生的效率问题!
本框架在方法特性上作了扩展,以标记特性的方式可以对ajax方法做一些操作,比如参数的验证,缓存的输出等,并且该特性都是很容易扩展的,只要继承ValidateAttr类即可实现接口的扩展,为以后比如用户权限认证,访问量限制等需要提供很好的解决方案
1 #region 检查方法的特性 (未完善) 2 /// <summary> 3 /// 检查方法的特性 (未完善) 4 /// 现在主要是请求权限的验证 参数的验证 5 /// </summary> 6 /// <param name="methodInfo"></param> 7 /// <param name="errMsg">验证失败时带出的消息</param> 8 /// <returns></returns> 9 private bool CheckAttribute(List<ValidateAttr> attrList) 10 { 11 bool ret = true; 12 if (attrList.Count > 0) 13 { 14 foreach (ValidateAttr attr in attrList) 15 { 16 attr.CurrentHttpRequest = this._httpRequestDescription; 17 #region 判断此特性是否能通过验证 18 if (!attr.IsValidate()) 19 { 20 //特性的验证规则失败 21 ret = false; 22 break; 23 } 24 #endregion 25 } 26 } 27 else 28 { 29 throw new MethodNotFoundOrInvalidException("此方法并不是网络方法,无法直接访问"); 30 } 31 32 return ret; 33 } 34 #endregion
在不可或缺的参数验证特性上面简单的使用了策略模式,可以低耦合的实现其他一些参数类型扩展
最后在页面输出方面,使用了一个简单的输出帮助以,以AjaxResult结果寄存类和json的一个序列化类就行输出
/// <summary> /// /// </summary> /// <param name="returnValue">返回值</param> /// <param name="returnType">返回类型</param> /// <returns>返回字符串</returns> public static string GetResponseString(object returnValue,Type returnType) { string ret = string.Empty; try { if (returnType.IsSampleType()) { //返回的是简单类型 AjaxResult ajaxResult = new AjaxResult() { Flag = "1", Data = Convert.ToString(returnValue) }; ret = ajaxResult.ToString(); } else { ret = JsonConvert.SerializeObject(returnValue); } } catch (Exception ex) { //遇到异常 AjaxResult errResult = new AjaxResult() { Flag = "0", ErrorMsg = ex.Message }; ret = errResult.ToString(); } return ret; } /// <summary> /// 返回错误 /// </summary> /// <param name="errorMessge"></param> /// <returns></returns> public static string ResponseError(string errorMessge) { //返回错误 AjaxResult errResult = new AjaxResult() { Flag = "0", ErrorMsg = errorMessge }; return errResult.ToString(); }
3、框架如何使用
本框架暂时命名为AjaxFramework,所以你在使用本框架时只需要引用该dll,然后在webconfig里面针对不同的IIS版本配置好相应的HttpHandler
<httpHandlers> <!--针对IIS6 再这里配置handler 以支持ajax后缀的扩展--> <add verb="*" path="*.ajax" validate="true" type="AjaxFramework.AjaxHandlerFactory,AjaxFramework" /> </httpHandlers> <handlers> <!--针对IIS7 再这里配置handler 以支持ajax后缀的扩展--> <add name="ajaxhandler" verb="*" preCondition="integratedMode" path="*.ajax" type="AjaxFramework.AjaxHandlerFactory,AjaxFramework" /> </handlers>
这里的.ajax后缀根据你的需求修改,比如你可以修改成.json,以后以相应的后缀访问就可以了
再配置好你需要公布的命名空间
<appSettings> <!--在此处配置ajax后台框架所需映射的项目--> <add key="AjaxFramewok" value="TestBLL"/> </appSettings>
至此,配置已经完成,是不是还算简单!
用法就如下了
这标明TestBLL空间里面的Data类里面的Add方法被发布了出来,是不是很简单,只需要添加相应的特性即可完成发布网络方法的操作
然后你可以用http://domain.com/data/add.ajax?a=4&b=6.787的url形式就行访问了
4、 框架使用效果图
先来看一下上面一个章节里面那个发布方法的效果图:
我们来看下用Get方式去访问Post标记方法的情况
/// <summary> /// 这个方法只有Post请求才可以 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Post)] public string Get_Pat() { return "pat"; }
我们来看下普通类型返回的情况
/// <summary> /// 返回普通的字符串 会加上一个json的外壳 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Get)] [OutputCacheAttr(20)] public string Get_Pat2() { return "pat" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); }
但是如果返回的是DataTable,会怎么样呢?
/// <summary> /// 返回DataTable的数据 /// </summary> /// <returns></returns> [WebMethodAttr(RequestType.Get)] public DataTable Get_Data() { DataTable dt = new DataTable("dt"); dt.Columns.Add("id"); dt.Columns.Add("name"); DataRow row = dt.NewRow(); row["id"] = 1; row["name"] = "tom"; dt.Rows.Add(row); DataRow row2 = dt.NewRow(); row2["id"] = 2; row2["name"] = "peter"; dt.Rows.Add(row2); return dt; }
那么如果方法的参数是实体类型,我们该如何传呢?
/// <summary> /// 这个方法是用来测试传实体的 /// </summary> /// <param name="user"></param> /// <returns></returns> [WebMethodAttr(RequestType.Get)] public User Insert_User(User user) { return user; }
估计方法+配图,大家应该看的很明白了吧!
在这里提一下,由于现在ajax的请求返回类型基本都是json格式,所以本框架暂时只支持json格式!
5、框架的优缺点
先来说优点吧:
1:你以后在使用ajax的时候不需要再加各种ashx,aspx页面,加上相应的特性即可将你业务层里面的方法给发布出来
2:利用特性扩展这一特点可以很方便的满足你其他的要求
3:运用了各种方式的缓存,效率应该不会将到哪里去
3:整体框架还算简单,运行思路明了,相对wcf,webservice这些复杂的框架,以后出错可以手动调试源码
估计缺点也有很多:
1:由于是通过反射来运行方法的,某些时候效率可能会不怎么高
2:在参数动态赋值模块对于其他的一些参数可能会有问题
3:发布方法无法重载
4:只是刚刚写出来,没有投入到实际项目中,可用性还有待商榷
这个框架可以用哪里?
一般的小OA,ERP,个人博客站,小的CMS站,普通企业站个人觉得这个框架还是可以应付的,如果需要在其他类型的网站上使用你就自己看着办吧!
6、源码下载
我厚着脸皮的发到了Github上面去,点我去Github下载AjaxFramework(json类库在lib文件夹中,可能需要重新引用一遍),希望有兴趣的同学可以加入进来一些开发哦,有问题留言吧,很期待您的指导!