Osheep

时光不回头,当下最重要。

asp.net拦截器

拦截器又称过滤器。

asp.net mvc本身是自带3种拦截器:Action拦截器、Result拦截器、Exception拦截器。 应用中常见的拦截器有日志拦截器(Action拦截器)和异常处理拦截器(Exception拦截器)。

java里spring mvc也常用拦截器来做些非干预业务逻辑的事,诸如实现HandlerInterceptor接口。

拦截器要解决的问题:
1.代码复用。拦截器可被复用
2.职责单一。比如厨师只负责炒菜,不管前期的洗菜、后续的送菜工作。菜变质了也是直接喊一声就有人来处理。

asp.net的拦截器怎么实现呢?
旧瓶装新酒,asp.net的拦截器需要通过IHttpModule接口来实现。

这两天重构支付中心代码,将设置线程名和IP白名单这2个功能做成拦截器。

如下是线程名Filter的代码:

/// <summary>
/// 设置当前工作线程的名称。供用来统一标识记录的日志
/// </summary>
public class ThreadNameFilter : IHttpModule
{
    LogHelperUtil logHelper = new LogHelperUtil(typeof(ThreadNameFilter).Name);

    public void Dispose()
    {
        //throw new NotImplementedException();
    }

    public void Init(HttpApplication context)
    {
        //NewMethod(context);请求在此上下文中不可用

        context.BeginRequest += context_BeginRequest;
    }

    /// <summary>
    /// 设置当前工作线程的name
    /// </summary>
    /// <param name="sender"></param>
    private void SetThreadName(object sender)
    {

        if (null != Thread.CurrentThread.Name)
        {
            return;
        }

        HttpApplication application = (HttpApplication)sender;
        HttpRequest request2 = application.Context.Request;
        HttpResponse response = application.Context.Response;
        string url = request2.Url.LocalPath;
        url = url.Trim('/');

        // * 根据请求url得到一个nameFlag
        string nameFlag;
        if (url.IndexOf(".ashx", StringComparison.OrdinalIgnoreCase) > 0)
        {
            var arr = url.Split('/');
            string ashxName = arr.FirstOrDefault(str => str.IndexOf(".ashx", StringComparison.OrdinalIgnoreCase) > 0);
            nameFlag = ashxName.Substring(0, ashxName.IndexOf('.'));
            if (nameFlag == "AgentPayQuery")
            {
                nameFlag = "QueryAgentPay";
            }
        }
        else
        {
            nameFlag = url.Replace('/', '_').Replace('.', '_');
        }

        // * 设置当前工作线程的name
        Thread.CurrentThread.Name = string.Format("[{0}_T{1:HHmmssfff}_{2}]", nameFlag, DateTime.Now, Guid.NewGuid().ToString().Replace("-", "").Substring(0, 5).ToUpper());
        logHelper.Write("线程名已设置为:{0} url:{1}", Thread.CurrentThread.Name, url);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        SetThreadName(sender);
    }

}

接下来,web.config配置此module:

可在<system.web>节点下的<httpModules>里配置,也可在<system.webServer>节点下的<modules>里配置。 这取决于应用程序池的托管管道模式。经典模式用前者,集成模式用后者。

本地vs2013里的iisexpress默认是集成模式。所以,本地vs2013调试程序要在<system.webServer>里配置module。

<system.webServer>
    <modules> <!--runAllManagedModulesForAllRequests="true"-->
      <add name="threadNameFilter" type="PaymentPlatform.Filters.ThreadNameFilter" preCondition="managedHandler" />
      <add name="ipValidationInterceptor" type="PaymentPlatform.Filters.IPValidationInterceptor" preCondition="managedHandler"/> <!--只对托管资源起作用-->
    </modules> 
    <handlers>
      。。。。。。
    </handlers>
</system.webServer>

这样,一个拦截器的开发就完成了。

在后续的测试时,出现了一些波折。
本地在启动vs2013执行iisexpress站点应用程序时,发现明明在ThreadNameFilter 里设置了线程名,但观察在后续ashx里记录的日志里,并没有获取到那个线程名,whatever in Debug or in Release。这让我想到之前写的一篇博客《巧用CurrentThread.Name来统一标识日志记录(续)》,在ashx文件的默认构造器里设置的线程名,在其ProcessRequest方法的处理逻辑里也是获取不到的。

经多次鼓捣,才发现,把站点程序发布到IIS7上之后,无论apppool的托管模式是集成(目前,集成模式是主流)还是经典,在ThreadNameFilter 里设置的线程名可以被后续ashx里获取到!
同样,细心的观察了一下,《巧用CurrentThread.Name来统一标识日志记录(续)》里提到的问题,发布到IIS7后也不存在,即在ashx文件的默认构造器里设置的线程名,在其ProcessRequest方法的处理逻辑里可以获取到!

下图是本地vs2013里访问接口所记录的日志:

《asp.net拦截器》

本地vs调试

下图是发布到iis7后访问接口所记录的日志:

《asp.net拦截器》

发布到iis后
点赞