CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
.NET的管理处理模型,这一篇就够了!不对是这个系列就够了!(一)
0
.NetMvc
架构设计
小笨蛋
发布于:2021年09月09日
更新于:2021年09月09日
111
#custom-toc-container
本章将和大家分享ASP.NET中的管道处理模型。 所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(IHttpModule), 最后有个IHttpHandler完成请求的处理,这个过程就是管道处理模型。 还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。 下面我们将结合部门源码(通过ILSpy反编译得到)进行讲解: 首先我们先来看下 请求到程序响应 的示例图: ![](/uploads/images/20210909/210618-1eb345e3c9c944cbbd4e3e2ad9a562bd.png) 从图中可以看出Http请求需要经过一系列的步骤才会进入到我们的ASP.NET入口`System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr)。` 接下来我们就从请求进入ASP.NET入口开始讲解: 我们通过反编译工具ILSpy找到ASP.NET的入口`System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr):` ![](/uploads/images/20210909/210652-987817600ce7444fb60d547906325a5b.png) ```csharp // System.Web.HttpRuntime [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)] public static void ProcessRequest(HttpWorkerRequest wr) { if (wr == null) { throw new ArgumentNullException("wr"); } if (HttpRuntime.UseIntegratedPipeline) { throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[] { "HttpRuntime.ProcessRequest" })); } HttpRuntime.ProcessRequestNoDemand(wr); } ``` 接着我们沿 `HttpRuntime.ProcessRequestNoDemand(wr)` 一直往里找: 会找到`System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)`方法,如下所示: ```csharp // System.Web.HttpRuntime private void ProcessRequestInternal(HttpWorkerRequest wr) { Interlocked.Increment(ref this._activeRequestCount); if (this._disposingHttpRuntime) { try { wr.SendStatus(503, "Server Too Busy"); wr.SendKnownResponseHeader(12, "text/html; charset=utf-8"); byte[] bytes = Encoding.ASCII.GetBytes("Server Too Busy"); wr.SendResponseFromMemory(bytes, bytes.Length); wr.FlushResponse(true); wr.EndOfRequest(); } finally { Interlocked.Decrement(ref this._activeRequestCount); } return; } HttpContext httpContext; try { httpContext = new HttpContext(wr, false); } catch { try { wr.SendStatus(400, "Bad Request"); wr.SendKnownResponseHeader(12, "text/html; charset=utf-8"); byte[] bytes2 = Encoding.ASCII.GetBytes("Bad Request"); wr.SendResponseFromMemory(bytes2, bytes2.Length); wr.FlushResponse(true); wr.EndOfRequest(); return; } finally { Interlocked.Decrement(ref this._activeRequestCount); } } wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext); HostingEnvironment.IncrementBusyCount(); try { try { this.EnsureFirstRequestInit(httpContext); } catch { if (!httpContext.Request.IsDebuggingRequest) { throw; } } httpContext.Response.InitResponseWriter(); IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext); if (applicationInstance == null) { throw new HttpException(SR.GetString("Unable_create_app_object")); } if (EtwTrace.IsTraceEnabled(5, 1)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start"); } if (applicationInstance is IHttpAsyncHandler) { IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance; httpContext.AsyncAppHandler = httpAsyncHandler; httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext); } else { applicationInstance.ProcessRequest(httpContext); this.FinishRequest(httpContext.WorkerRequest, httpContext, null); } } catch (Exception e) { httpContext.Response.InitResponseWriter(); this.FinishRequest(wr, httpContext, e); } } ``` 从源码可以看出首先它是使用`HttpWorkerRequest`打包出一个`HttpContext`,然后再使用`HttpContext`创建一个`IHttpHandler`实例,最后用这个`IHttpHandle`r实例来处理请求。 接下来我们沿着 `HttpApplicationFactory.GetApplicationInstance(httpContext)` 往里找: ```csharp // System.Web.HttpApplicationFactory internal static IHttpHandler GetApplicationInstance(HttpContext context) { if (HttpApplicationFactory._customApplication != null) { return HttpApplicationFactory._customApplication; } if (context.Request.IsDebuggingRequest) { return new HttpDebugHandler(); } HttpApplicationFactory._theApplicationFactory.EnsureInited(); HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context); return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context); } ``` 其中 `HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context)` 这句话就是用来启动我们的网站完成项目初始化的,它会去调用我们的`Global.asax`里面的`Application_Start`方法。 我们继续往 `HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context)` 里面找: ```csharp // System.Web.HttpApplicationFactory private HttpApplication GetNormalApplicationInstance(HttpContext context) { HttpApplication httpApplication = null; if (!this._freeList.TryTake(out httpApplication)) { httpApplication = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType); using (new ApplicationImpersonationContext()) { httpApplication.InitInternal(context, this._state, this._eventHandlerMethods); } } if (AppSettings.UseTaskFriendlySynchronizationContext) { httpApplication.ApplicationInstanceConsumersCounter = new CountdownTask(1); Task arg_8A_0 = httpApplication.ApplicationInstanceConsumersCounter.Task; Action
arg_8A_1; if ((arg_8A_1 = HttpApplicationFactory.<>c.<>9__34_0) == null) { arg_8A_1 = (HttpApplicationFactory.<>c.<>9__34_0 = new Action
(HttpApplicationFactory.<>c.<>9.
b__34_0)); } arg_8A_0.ContinueWith(arg_8A_1, httpApplication, TaskContinuationOptions.ExecuteSynchronously); } return httpApplication; } ``` 可以看到该方法就是为了得到一个HttpApplication的实例,但是它并不是简单的创建HttpApplication的实例,HttpApplication有可能是重用的(对象池--Stack--会重用)。 我们点击HttpApplication进去看下: ![](/uploads/images/20210909/210920-6b7a1e5efd904d7b9d63492827ee0058.png) 可以看到它是实现 `IHttpHandler`和`IHttpAsyncHandler` 接口的。 到这里我们大概知道,任何一个Http请求一定是有一个IHttpHandler来处理的,任何一个Http请求就是一个HttpApplication对象来处理。 我们知道处理请求的过程一般包括固定步骤,例如:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端等, 与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计? 这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件, 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下。 `HttpApplication`里面定义了一系列的事件,最终会按一定的顺序去执行这些事件,我们可以通过反编译工具来看下这些事件的执行顺序。 通过反编译工具找到`System.Web.HttpApplication.ProcessEventSubscriptions`方法(处理事件订阅的方法): ```csharp // System.Web.HttpApplication private void ProcessEventSubscriptions(out RequestNotification requestNotifications, out RequestNotification postRequestNotifications) { requestNotifications = (RequestNotification)0; postRequestNotifications = (RequestNotification)0; if (this.HasEventSubscription(HttpApplication.EventBeginRequest)) { requestNotifications |= RequestNotification.BeginRequest; } if (this.HasEventSubscription(HttpApplication.EventAuthenticateRequest)) { requestNotifications |= RequestNotification.AuthenticateRequest; } if (this.HasEventSubscription(HttpApplication.EventPostAuthenticateRequest)) { postRequestNotifications |= RequestNotification.AuthenticateRequest; } if (this.HasEventSubscription(HttpApplication.EventAuthorizeRequest)) { requestNotifications |= RequestNotification.AuthorizeRequest; } if (this.HasEventSubscription(HttpApplication.EventPostAuthorizeRequest)) { postRequestNotifications |= RequestNotification.AuthorizeRequest; } if (this.HasEventSubscription(HttpApplication.EventResolveRequestCache)) { requestNotifications |= RequestNotification.ResolveRequestCache; } if (this.HasEventSubscription(HttpApplication.EventPostResolveRequestCache)) { postRequestNotifications |= RequestNotification.ResolveRequestCache; } if (this.HasEventSubscription(HttpApplication.EventMapRequestHandler)) { requestNotifications |= RequestNotification.MapRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventPostMapRequestHandler)) { postRequestNotifications |= RequestNotification.MapRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventAcquireRequestState)) { requestNotifications |= RequestNotification.AcquireRequestState; } if (this.HasEventSubscription(HttpApplication.EventPostAcquireRequestState)) { postRequestNotifications |= RequestNotification.AcquireRequestState; } if (this.HasEventSubscription(HttpApplication.EventPreRequestHandlerExecute)) { requestNotifications |= RequestNotification.PreExecuteRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventPostRequestHandlerExecute)) { postRequestNotifications |= RequestNotification.ExecuteRequestHandler; } if (this.HasEventSubscription(HttpApplication.EventReleaseRequestState)) { requestNotifications |= RequestNotification.ReleaseRequestState; } if (this.HasEventSubscription(HttpApplication.EventPostReleaseRequestState)) { postRequestNotifications |= RequestNotification.ReleaseRequestState; } if (this.HasEventSubscription(HttpApplication.EventUpdateRequestCache)) { requestNotifications |= RequestNotification.UpdateRequestCache; } if (this.HasEventSubscription(HttpApplication.EventPostUpdateRequestCache)) { postRequestNotifications |= RequestNotification.UpdateRequestCache; } if (this.HasEventSubscription(HttpApplication.EventLogRequest)) { requestNotifications |= RequestNotification.LogRequest; } if (this.HasEventSubscription(HttpApplication.EventPostLogRequest)) { postRequestNotifications |= RequestNotification.LogRequest; } if (this.HasEventSubscription(HttpApplication.EventEndRequest)) { requestNotifications |= RequestNotification.EndRequest; } if (this.HasEventSubscription(HttpApplication.EventPreSendRequestHeaders)) { requestNotifications |= RequestNotification.SendResponse; } if (this.HasEventSubscription(HttpApplication.EventPreSendRequestContent)) { requestNotifications |= RequestNotification.SendResponse; } } ``` 通过上面的源码,我们就能很清楚的看出各个事件的执行顺序了。 下面我们可以通过一张图来更直观的了解这些事件的执行顺序,如下所示: ![](/uploads/images/20210909/211023-51534d11a6bb410b89bad47374fc4337.png) 而对`HttpApplication`里面的事件进行动作注册的,就叫`IHttpModule`,下面我们就来看下如何实现一个自定义HttpModule: 首先我们先来看下Demo的目录结构: ![图片alt](/uploads/images/20210909/211119-e7289f22e2e2498684072a43a5aa867d.png ''图片title'') 本Demo的Web项目为ASP.NET Web 应用程序(目标框架为.NET Framework 4.5) MVC项目。 其中Home控制器: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace AspNetPipeline.Controllers { ///
/// 1 Http请求处理流程 /// 2 HttpApplication的事件 /// 3 HttpModule /// 4 Global事件 /// /// Runtime--运行时 /// Context--上下文 /// 任何一个Http请求一定是有一个IHttpHandler来处理的 ashx aspx.cs MvcHttpHandler /// 任何一个Http请求就是一个HttpApplication对象来处理 /// 然后处理过程固定包含:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端 /// 与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计? /// 这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件, /// 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下 /// /// 对HttpApplication里面的事件进行动作注册的,就叫IHttpModule /// 自定义一个HttpModule--配置文件注册--然后任何一个请求都会执行Init里面注册给Application事件的动作 /// 学习完HttpModule,我们可以做点什么有用的扩展? /// 1 日志-性能监控-后台统计数据 /// 2 权限 /// 3 缓存 /// 4 页面加点东西 /// 5 请求过滤--黑名单 /// 6 MVC--就是一个Module扩展 /// /// 不适合的(不是全部请求的,就不太适合用module,因为有性能损耗) /// 1 跳转到不同界面--也不适合 /// 2 防盗链--针对一类的后缀来处理,而不是全部请求--判断--再防盗链 /// /// HttpModule里面发布一个事件CustomHttpModuleHandler,在Global.asax增加一个动作, /// MyCustomHttpModule_CustomHttpModuleHandler(配置文件module名称_module里面事件名称),请求响应时,该事件会执行 /// /// HttpModule是对HttpApplication的事件注册动作,而Global则是对HttpModule里面的事件注册动作 /// /// /// C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config /// .NetFramework安装路径,是一个全局的配置,是当前电脑上任何一个网站的默认配置,不要去修改它 /// /// /// 1 HttpHandler及扩展,自定义后缀,图片防盗链等 /// 2 RoutingModule,IRouteHandler、IHttpHandler /// 3 MVC扩展Route,扩展HttpHandle /// /// 配置文件指定映射关系:后缀名与处理程序的关系(IHttpHandler---IHttpHandlerFactory) /// Http任何一个请求一定是由某一个具体的Handler来处理的,不管是成功还是失败 /// 以前写aspx,感觉请求访问的是物理地址,其实不然,请求的处理是框架设置的 /// /// 所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(HttpModule), /// 最后有个HttpHandler完成请求的处理,这个过程就是管道处理模型。 /// 还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。 /// /// 自定义Handler处理,就是可以处理各种后缀请求,可以加入自己的逻辑 /// 如果没有--请求都到某个页面--传参数---返回图片 /// 防盗链---加水印---伪静态---RSS--robot--trace.axd /// /// MVC里面不是controller action?其实是由 MvcHandler来处理请求,期间完成对action调用的 /// 网站启动时---对RouteCollection进行配置 /// 把正则规则和RouteHandler(提供HttpHandler)绑定,放入RouteCollection, /// 请求来临时---用RouteCollection进行匹配 /// 所谓MVC框架,其实就是在Asp.Net管道上扩展的,在PostResolveCache事件扩展了UrlRoutingModule, /// 会在任何请求进来后,先进行路由匹配,如果匹配上了,就指定HttpHandler;没有匹配就还是走原始流程 /// /// 扩展自己的Route,写入RouteCollection,可以自定义规则完成路由 /// 扩展HttpHandle,就可以为所欲为,跳出MVC框架 ///
public class HomeController : Controller { public ActionResult Index() { return View(); } } } ``` 对应的 /Home/Index 视图: ```csharp @{ ViewBag.Title = "Home Page"; }
This is Home/Index View
``` 未进行HttpModule注册前我们先来访问下 /Home/Index ,运行结果如下所示: ![图片alt](/uploads/images/20210909/211204-3a844bd85172408ea96f5e1bf690575e.png ''图片title'') 下面我们自定义一个`HttpModule`如下所示: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { ///
/// 自定义HttpModule ///
public class CustomHttpModule : IHttpModule { public event EventHandler CustomHttpModuleHandler; public void Dispose() { Console.WriteLine("This is CustomHttpModule.Dispose"); } ///
/// 注册动作context ///
///
public void Init(HttpApplication context) { context.BeginRequest += (s, e) => { this.CustomHttpModuleHandler?.Invoke(context, null); }; //为每一个事件,都注册了一个动作,向客户端输出信息 context.AcquireRequestState += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AcquireRequestState ")); context.AuthenticateRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AuthenticateRequest ")); context.AuthorizeRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "AuthorizeRequest ")); context.BeginRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "BeginRequest ")); context.Disposed += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "Disposed ")); context.EndRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "EndRequest ")); context.Error += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "Error ")); context.LogRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "LogRequest ")); context.MapRequestHandler += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "MapRequestHandler ")); context.PostAcquireRequestState += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAcquireRequestState ")); context.PostAuthenticateRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAuthenticateRequest ")); context.PostAuthorizeRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostAuthorizeRequest ")); context.PostLogRequest += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostLogRequest ")); context.PostMapRequestHandler += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostMapRequestHandler ")); context.PostReleaseRequestState += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostReleaseRequestState ")); context.PostRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostRequestHandlerExecute ")); context.PostResolveRequestCache += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostResolveRequestCache ")); context.PostUpdateRequestCache += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PostUpdateRequestCache ")); context.PreRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreRequestHandlerExecute ")); context.PreSendRequestContent += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreSendRequestContent ")); context.PreSendRequestHeaders += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "PreSendRequestHeaders ")); context.ReleaseRequestState += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "ReleaseRequestState ")); context.RequestCompleted += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "RequestCompleted ")); context.ResolveRequestCache += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "ResolveRequestCache ")); context.UpdateRequestCache += (s, e) => context.Response.Write(string.Format("
来自CustomHttpModule 的处理,{0}请求到达 {1}
", DateTime.Now.ToString(), "UpdateRequestCache ")); } } } ``` 可以在IHttpModule.Init方法内部为HttpApplication事件注册动作。 然后我们需要在Web.config里面配置下这个HttpModule节点,如下所示: ```csharp
``` ![图片alt](/uploads/images/20210909/211255-7a45063af59249a6abf87dbfdc28924e.png ''图片title'') 其中type值为【类的完整名称 + 英文逗号 + 项目名称】。 此处,我们还在`CustomHttpModule`里面定义了一个`CustomHttpModuleHandler`事件,那么我们要在哪里给这个事件注册动作呢? 可以在`Global.asax`里面为`CustomHttpModuleHandler`事件绑定动作,如下: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace AspNetPipeline { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } ///
/// 为HttpModule里面的事件注册动作 /// 配置文件module名称_module里面事件名称 ///
///
///
protected void MyCustomHttpModule_CustomHttpModuleHandler(object sender, EventArgs e) { HttpContext.Current.Response.Write("
This is MvcApplication/MyCustomHttpModule_CustomHttpModuleHandler
"); } } } ``` 最后我们再来访问下 /Home/Index,看下运行结果: ![](/uploads/images/20210909/211349-a4a1cad89ad34f519f7dbe3976e12342.png) 其中“`This is Home/Index View`”这句话就是由某一个具体的`IHttpHandler`处理器对象来处理的。 PS: 1、对`HttpApplication`里面的事件进行动作注册的,就叫IHttpModule。 2、自定义一个`HttpModule`--配置文件注册--然后任何一个请求都会执行Init里面注册给`HttpApplication`事件的动作。 3、`HttpModule`里面发布一个事件`CustomHttpModuleHandler`,在`Global.asax`增加一个动作, `MyCustomHttpModule_CustomHttpModuleHandler`(配置文件module名称_module里面事件名称),请求响应时,该事件会被执行。 4、`HttpModule`是对`HttpApplication`里面的事件注册动作,而`Global`则是对`HttpModule`里面的事件注册动作。 介绍到这里,我们知道Http的任何一个请求最终一定是由某一个具体的HttpHandler来处理的,不管是成功还是失败。 竟然如此,那我们能不能自定义一个HttpHandler来处理一些特殊的请求呢?答案:可以的。 例如:我们想要实现某个特定后缀(如.log后缀)的所有请求都指派给我们自定义的HttpHandler来处理,那这个要如何实现呢? 下面我们就带大家来实现这一想法,先来看下示例所涉及到的代码的目录结构: ![图片alt](/uploads/images/20210909/211526-01a2d65a9e6d4142896e7aa1ceb77e6d.png ''图片title'') 自定义HttpHandler: ```csharp using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { ///
/// 自定义HttpHandler /// /// 我们可以从请求级出发,避开默认机制,动态响应 .log(自定义)后缀的请求 ///
public class CustomHttpHandler : IHttpHandler { public bool IsReusable => true; ///
/// 处理请求 ///
///
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/html"; context.Response.WriteFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web.config")); } } } ``` 在Web.config里面进行配置说明: ```csharp
``` ![图片alt](/uploads/images/20210909/211613-65458f6931ac4957b69c971e8fb3898d.png ''图片title'') 此时我们去访问一下 /log.log 会发现报错了,如下所示: ![图片alt](/uploads/images/20210909/211639-21af3d629460405d8fcde79e68d0f800.png ''图片title'') 这是因为此时它被MVC的路由匹配了,所以无法找到资源。 我们需要到MVC路由配置那边把它忽略掉: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace AspNetPipeline { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { //忽略路由 正则表达式 {resource}表示变量 a.axd/xxxx resource=a pathInfo=xxxx //.axd是历史原因,最开始都是WebForm,请求都是.aspx后缀,IIS根据后缀转发请求; //MVC出现了,没有后缀,IIS6以及更早版本,打了个补丁,把MVC的请求加上个.axd的后缀,然后这种都转发到网站 //新版本的IIS已经不需要了,遇到了就直接忽略,还是走原始流程 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //该行框架自带的 //.log后缀的请求忽略掉,不走MVC流程,而是用我们自定义的CustomHttpHandler处理器来处理 routes.IgnoreRoute("{resource}.log/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } } ``` 最后我们再来访问下 /log.log 运行结果如下所示: ![图片alt](/uploads/images/20210909/211716-4f8ca446cac149bc9d3a04becd31e22c.png ''图片title'') 可以发现此时访问正常了,我们右键查看网页源代码,会发现输出了我们想要的东西,如下所示: ![](/uploads/images/20210909/211736-afbe37be860045c786b66e1eacaa1b74.png) 之前我们在访问 .aspx 页面时可能有个错觉,感觉就是访问物理路径,然而从上面这个例子可以看出这是不对的。 它应该是由我们的配置文件来指定映射关系:后缀名与处理程序的关系(`IHttpHandler---IHttpHandlerFactory`) 。 自定义`HttpHandler`处理,就是可以处理各种后缀请求,可以加入自己的逻辑。 为了加深印象,下面我们就再举个防盗链的例子: 防盗链HttpHandler: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace AspNetPipeline.Pipeline { ///
/// 防盗链HttpHandler ///
public class ImageHttpHandler : IHttpHandler { public bool IsReusable => true; public void ProcessRequest(HttpContext context) { // 如果UrlReferrer为空,大部分都是爬虫,则显示一张默认的禁止盗链的图片 if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/Content/Image/Forbidden.jpg"); } else { // 如果UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片 if (context.Request.UrlReferrer.Host.Contains("localhost")) { // 获取文件服务器端物理路径 string fileName = context.Server.MapPath(context.Request.FilePath); context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(fileName); } else { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/Content/Image/Forbidden.jpg"); } } } } } ``` 在Web.config里面进行配置: ```csharp
``` ![图片alt](/uploads/images/20210909/211842-e9808b65e01943a38a39b3394e46a277.png ''图片title'') PS:此处需要将自定义的CustomHttpModule这个配置节点给注释掉,因为在CustomHttpModule中注册的动作有向客户端输出字符串,这会导致图片输出异常。 图片存放路径如下所示: ![图片alt](/uploads/images/20210909/211908-d106edf487ee41288de16f87f6b9a84e.png ''图片title'') 访问 /content/image/scenery.jpg 运行结果如下所示: ![图片alt](/uploads/images/20210909/211934-56335b2320f14d8fa6ba6e679a61f673.png ''图片title'') 可以发现此时返回的并不是我们访问的真实图片,而是防止盗链的图片。
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024