为了账号安全,请及时绑定邮箱和手机立即绑定

我的Ajax服务端框架

标签:
架构

注意:本文所介绍的框架已有新版本,点击后面链接即可阅读。【写自己的ASP.NET MVC框架】

返回到目录:晒晒我的Ajax服务端框架

我的Ajax服务端框架 - 安全问题

通过前面章节的示例代码,您会发现一个问题:那就是在JS中可以调用所有的C#的方法(理论上是可以调用任何一个程序集中的所有Public类的所有方法)。如果您认为这样做,有安全问题,那么可以订阅事件 OnAjaxCall 来过滤请求。FishWebLib提供的Handler或者Module都有这个事件,您可以统一处理。可参考以下代码:

// Ajax调用的安全检查事件。FishWebLib.Ajax.AjaxMethodV2Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV2Handler_OnAjaxCall);
/// <summary>/// Ajax调用检查/// </summary>/// <param name="e"></param>static void AjaxMethodV2Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e){    // ##################################################################################################    // 在这里可以做一些在Ajax调用时的安全检查。    // ##################################################################################################        // 如果经过您的检查逻辑,不允许一个调用请求,可以做如下处理:    //e.IsAllowed = false;    //e.DenyMessage = "请求的资源不允许访问。";    // 在本示例中,就不处理了。因为在另一个地方,我仍然有机会处理。}

AjaxCallEventArgs的定义请见后文。

AjaxMethodV1Handler的安全检查示例

// Ajax调用的安全检查事件。FishWebLib.Ajax.AjaxMethodV1Handler.OnAjaxCall += new FishWebLib.Ajax.AjaxCallCheckHandler(AjaxMethodV1Handler_OnAjaxCall);
static void AjaxMethodV1Handler_OnAjaxCall(FishWebLib.Ajax.AjaxCallEventArgs e){    string fileName = System.IO.Path.GetFileNameWithoutExtension(e.context.Request.PhysicalPath);    // 在这里,我将只检查要调用的类名是不是以Ajax开头,如果不是,则不允许调用。    if( fileName.StartsWith("Ajax", StringComparison.OrdinalIgnoreCase) == false ) {        e.IsAllowed = false;        e.DenyMessage = "不允许调用指定的类方法。";    }    // 要调用的方法名中URL的查询字符串中,也可以检查到。这里就不检查了。}

UserControlHandler 和 PageMethodModule 也有 OnAjaxCall 事件,可以按上面的方式来类似处理。

您也可以定义一个统一的安全检查方法,只要符合下面的委托定义即可:

namespace FishWebLib.Ajax{    /// <summary>    /// AJAX调用发生时的委托类型    /// </summary>    /// <param name="e">AjaxCallEventArgs类型的事件参数</param>    public delegate void AjaxCallCheckHandler(AjaxCallEventArgs e);    /// <summary>    /// 发生AJAX调用时的事件参数    /// </summary>    public sealed class AjaxCallEventArgs : System.EventArgs    {        /// <summary>        /// 本次请求的HttpContext实例        /// </summary>        public HttpContext context;        /// <summary>        /// 调用类型        /// </summary>        public AjaxCallType AjaxCallType;        /// <summary>        /// 调用是否允许        /// </summary>        public bool IsAllowed = true;        /// <summary>        /// 当设置IsAllowed=false时,可为本成员设置一个用于表示禁止访问的消息。        /// </summary>        public string DenyMessage;        /// <summary>        /// 构造方法        /// </summary>        /// <param name="cxt">HttpContext对象</param>        /// <param name="type">Ajax调用类型</param>        public AjaxCallEventArgs(HttpContext cxt, AjaxCallType type)        {            this.context = cxt;            this.AjaxCallType = type;        }    }    /// <summary>    /// AJAX调用类型    /// </summary>    public enum AjaxCallType    {        /// <summary>        /// 调用C#方法,由AjaxMethodV1Handler引发        /// </summary>        AjaxMethodV1,        /// <summary>        /// 调用用户控件,由UserControlHandler引发        /// </summary>        UserControl,        /// <summary>        /// 调用页面方法,由PageMethodModule引发        /// </summary>        PageMethod,        /// <summary>        /// 调用C#方法,由AjaxMethodV2Handler引发        /// </summary>        AjaxMethodV2    }}


如果上面的处理方式仍不能满足要求,那么请创建自己的ashx处理器,实现您自定义的过滤检查,然后调用FishWebLib.Ajax.MethodExecutor中的以下方法:

public static void ProcessRequest(HttpContext context, Type type, string method)


我的Ajax服务端框架 - 初始化设置

请参考以下代码:(在演示程序的AppHelper.cs中可以找到)

AjaxMethodV2Handler的初始化设置

/// <summary>/// 设置AjaxMethodV2Handler查找类型时的工作方式。/// </summary>static void SetAjaxClassSearchMode(){    // 这里先说明一下:    // 当AjaxMethodV2Handler被Asp.net调用时,需要知道要调用哪个类型的哪个方法。    // 在AjaxMethodV2Handler的默认实现中,调用了AjaxClassSearchHelper.Parse,    //    这个方法分析URL,并根据指定的类型查找模式,去查找指定的类型,并获取一个方法名称。    // ##################################################################################################    // 这里,我们有二种选择:    // ##################################################################################################    // 1. 自己实现一个 ParseTypeMethodPairFromRequest 的委托并赋值给AjaxMethodV2Handler.ParseFunc,这样做有二个好处:    //     a. 可以实现自己认为更方便的URL,比如URL:/Classname/MethodName.ext    //     b. 可以检查指定的类型是否允许被Ajax调用。(###安全检查###)    // 2. 保持默认的设置,但需要简单的2个配置AjaxClassSearchHelper。    //FishWebLib.Ajax.AjaxClassSearchHelper.Placeholder = ;    // 这个参数这里就不设置了,保持默认值。    FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] {         // 建议将全部供Ajax调用的类,放在一个命名空间下,这样也可以保证在客户端的JS不至于可以调用所有的类型        // 还可以像Asp.net MVC那样,为所有允许Ajax调用的类,取一个后缀,或者前缀也是可行的。        typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}")    };    // #############################################################################################3    // 在本网站中,我们选择第一种方法,但为了方便,我将仍借助于第二种方法来简化实现。    FishWebLib.Ajax.AjaxMethodV2Handler.ParseFunc = MyParseTypeMethodPairFromRequest;}/// <summary>/// 根据当前请求获取要调用的类型及方法名/// </summary>/// <param name="context"></param>/// <returns></returns>static FishWebLib.Ajax.TypeMethodPair MyParseTypeMethodPairFromRequest(HttpContext context){    // 使用FishWebLib提供的方法,简化实现。    FishWebLib.Ajax.TypeMethodPair result = FishWebLib.Ajax.AjaxClassSearchHelper.Parse(context);    if( result != null ) {        // 在这里,我还可以检查将要调用的类型和方法是否是允许的。        // 这里的规则很简单:如果不是Ajax开头的类型,将不允许访问        if( result.Type.Name.StartsWith("Ajax") == false ) {            // 转向另一个方法的调用,或者返回 null 也是表示禁止访问。            result = new FishWebLib.Ajax.TypeMethodPair(typeof(AppHelper), "DenyAjaxAccess");        }    }    return result;}/// <summary>/// 禁止Ajax访问时要调用的方法/// </summary>/// <returns></returns>public static string DenyAjaxAccess(){    return "请求的资源不允许访问。";}


AjaxMethodV1Handler的初始化设置

// 注意:下面的配置指定AjaxMethodV1Handler查找类型的程序集范围。FishWebLib.Ajax.AjaxMethodV1Handler.AjaxAssemblyName = typeof(AjaxTestClass).Assembly.ToString();


统一的异常处理

// 设置Ajax 调用时的异常事件,处理异常。FishWebLib.Ajax.AjaxExceptionHelper.OnAjaxInvokeException += 			new FishWebLib.Ajax.AjaxExceptionAction(AjaxExceptionHelper_OnAjaxInvokeException);
/// <summary>/// Ajax异常处理/// </summary>/// <param name="e"></param>static void AjaxExceptionHelper_OnAjaxInvokeException(FishWebLib.Ajax.AjaxExceptionEventArgs e){    // 指示异常已经过处理    e.ExceptionHandled = true;    // 异常的处理方式也很简单:把异常写入到响应流,并保存异常。    e.context.Response.Write(e.Exception.GetBaseException().Message);    SafeLogException(e.Exception);}


我的Ajax服务端框架 - 实现原理

本文将分别介绍FishWebLib提供的三个Handler及一个Module的实现原理。

1. AjaxMethodV1Handler

AjaxMethodV1Handler的主要实现代码如下:

public void ProcessRequest(HttpContext context){    if( string.IsNullOrEmpty(AjaxAssemblyName) ) {        AjaxCallChecker.WriteSimpleMessage(context, SR.AjaxAssemblyNameIsNull);        return;    }    if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV1) == false )         return;        string className = System.IO.Path.GetFileNameWithoutExtension(context.Request.PhysicalPath);    Type type = TypeManager.GetTypeByName(string.Concat(className, ", ", AjaxAssemblyName));    if( type == null ) {        AjaxExceptionHelper.ProcessException(context, new Exception(string.Format(SR.TypeNotFound, className)));        return;    }    MethodExecutor.ProcessRequest(context, type);}

从以上代码可以看出,处理器非常简单:根据要请求的文件名,去掉扩展名,当成类名,然后与参数AjaxAssemblyName合并,得到一个类名的完全限定形式,最后获取要调用类的具体类型,然后把请求交给MethodExecutor.ProcessRequest()来处理,在那里将会从URL的查询字符串中读取参数method,就可以得到要调用的方法名。有了类型与方法名后,就可以唯一确定一个方法了,最后只需要去调用就可以了。

至于如何调用方法,如何给方法的参数赋值,最后如何处理返回值给客户端,就属于框架本身的事情了。
所有的这一切,对于客户端来说,更是透明的。这些透明的实现也就是框架的意义了。



2. AjaxMethodV2Handler

AjaxMethodV2Handler的主要实现代码如下:

private static ParseTypeMethodPairFromRequest s_ParseFunc = AjaxClassSearchHelper.Parse;public void ProcessRequest(HttpContext context){    if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.AjaxMethodV2) == false )        return;    TypeMethodPair pair = s_ParseFunc(context);    if( pair == null ) {        AjaxExceptionHelper.ProcessException(context, new Exception(SR.InvalidRequest));        return;    }        MethodExecutor.ProcessRequest(context, pair.Type, pair.Method);}

如果比较AjaxMethodV1Handler的实现,可以发现,AjaxMethodV2Handler更简单。最终也是把请求交给MethodExecutor.ProcessRequest()来处理。其实,这二个处理器与PageMethodModule的实现是比较类似的:获取一个类型和一个方法名,扔给MethodExecutor就完事了。

只是AjaxMethodV2Handler把“获取类型和方法名”的过程交给委托的实现来处理了。
默认的实现使用了:AjaxClassSearchHelper.Parse ,它能拆分这种形式的URL: class.method.xx
AjaxClassSearchHelper定义了二个数据成员:

/// <summary>/// 在URL中用于分隔类名和方法名的特殊字符,默认值:'.'/// </summary>public static char Placeholder = '.';/// <summary>/// 用于搜索类类型的搜索模式字符串数组。搜索模式通常是一个类型的完全限定字符串中将类名改成{0}/// </summary>public static string[] ClassNameSearchPattern = null;

可以这样设置ClassNameSearchPattern:

FishWebLib.Ajax.AjaxClassSearchHelper.ClassNameSearchPattern = new string[] {     typeof(MyLab.AjaxService.AjaxOrder).AssemblyQualifiedName.Replace("AjaxOrder", "{0}")};



3. UserControlHandler

UserControlHandler的主要实现代码如下:

public void ProcessRequest(HttpContext context){    if( AjaxCallChecker.RaiseCheckEvent(context, OnAjaxCall, AjaxCallType.UserControl) == false )        return;    string filePath = context.Request.AppRelativeCurrentExecutionFilePath;    // 这里不检查指定的用户控件是否存在,如果不存在Asp.net会告诉调用方的。        UcExecutor.ProcessRequest(context, filePath, true);}

从代码可以看出:请求最后是由UcExecutor来处理的。
所以,也可以不使用这个处理器,而是将请求交给C#方法来处理,获取数据后,再去调用UcExecutor.ProcessRequest(),这种做法是符合MVC的设计思想的。



4. PageMethodModule

PageMethodModule的实现与前二个处理器类似,不一样的地方在于它是以Module的形式存在的。
为了能够调用MethodExecutor.ProcessRequest(),它也需要知道一个类型和一个方法名。有了请求页面地址,就可以知道当前在请求哪个页面,自然也就能获取一个类型了,方法名可以通过从FORM中获取,PageMethodModule会尝试读取FORM中键名为"AjaxPageMethod"对应的值,如果找到,后面的事情就如前面所说的那样处理了。
当然,为了性能,PageMethodModule只会处理POST请求。如果没有从FROM找到方法名,也会忽略本次请求。


返回到目录:晒晒我的Ajax服务端框架

点击此处进入示例展示及下载页面

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消