ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

开篇:上一篇我们了解了在WebForm模式下一个Page页面的生命周期,它经历了初始化Init、加载Load以及呈现Render三个重要阶段,其中构造了页面控件树,并对页面控件树进行了大量的递归操作,最后将与模板结合生成的HTML返回给了浏览器。那么,在ASP.NET MVC模式下,一个页面的生命周期又经历了哪些步凑呢?别急,本篇漫漫道来!

(1)Part 1:前奏

(2)Part 2:核心

(3)Part 3:管道

(4)Part 4:WebForm页面生命周期

(5)Part 5:ASP.NET MVC请求处理流程

一、开放的ASP.NET MVC代码

  2009年,Microsoft推出了ASP.NET MVC,也将ASP.NET MVC项目作为开源项目推送到了开源社区中,至今时间也过去快6年了,ASP.NET MVC已经到了5.0的版本阶段了。我们看到ASP.NET MVC从一个不完整的小孩长成一个日渐成熟的巨人,我们可以从开源社区找到ASP.NET MVC的源码,相比之前我们需要Reflector进行反编译查看,这次则轻松得多。

  这里我们选择ASP.NET MVC 4的源码作为分析对象,我已经将其上传到了网盘中,你可以通过下面这个地址进行下载:

  传送门:http://pan.baidu.com/s/1bnF8ZPt

  下载完成后,打开ASP.NET MVC 4的源代码,你会看到如下解决方案:这里我们主要关注System.Web.Mvc这个类库项目

二、从MvcHandler.ProcessRequest开始

  从Part 3中我们知道了在请求处理管道中的第7个事件生成了MvcHandler,在第11和第12个事件之间调用了MvcHandler的ProcessRequest方法开始了ASP.NET MVC的处理响应之旅。那么,我们就从MvcHandler的ProcessRequest方法开始查看,一个ASP.NET MVC页面是如何加载出来一个HTML页的!

(1)Controller的激活

  ①借助HttpConetxtWrapper封装HttpContext

    protected virtual void ProcessRequest(HttpContext httpContext){HttpContextBase httpContextBase = new HttpContextWrapper(httpContext);ProcessRequest(httpContextBase);}

  可以看出,这里通过了一个基于包装器(又称装饰者)模式实现的一个HttpContextWrapper类对HttpContext进行了一个封装,并调用重载的另一个ProcessRequest方法进行继续处理。

PS:有关ASP.NET MVC中HttpContext, HttpContextBase, HttpContextWrapper三者之间的联系请参考:http://blog.csdn.net/sundacheng1989/article/details/10551091

  ②控制器工厂根据URL创建控制器

复制代码

        protected internal virtual void ProcessRequest(HttpContextBase httpContext){IController controller;IControllerFactory factory;ProcessRequestInit(httpContext, out controller, out factory);try{controller.Execute(RequestContext);}finally{factory.ReleaseController(controller);}}

复制代码

  可以看出,这里通过调用ProcessRequestInit方法将上下文对象传入进行处理,然后返回生成的控制器实例以及控制器工厂。因此,我们转入ProcessRequestInit方法看看:

复制代码

        private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory){......string controllerName = RequestContext.RouteData.GetRequiredString("controller");factory = ControllerBuilder.GetControllerFactory();controller = factory.CreateController(RequestContext, controllerName);......}

复制代码

  在这个方法中,首先根据RouteData路由数据取得要请求的Controller名称,然后取得ControllerFactory(控制器工厂)对象,通过ControllerFactory来创建指定名称的控制器,最后将控制器作为out参数传递出去。

  ③调用控制器的Execute方法进入Action

  具体实现了IController接口的Controller对象通过调用Excute方法开始执行具体的Action,那么Action究竟又是怎样被触发的呢?

    public interface IController{void Execute(RequestContext requestContext);}

(2)Action的触发

  ①从ControllerBase的Excute方法开始 

复制代码

    public abstract class ControllerBase : IController{protected virtual void Execute(RequestContext requestContext){if (requestContext == null){throw new ArgumentNullException("requestContext");}if (requestContext.HttpContext == null){throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");}VerifyExecuteCalledOnce();Initialize(requestContext);using (ScopeStorage.CreateTransientScope()){ExecuteCore();}}// 抽象方法-让Controller去具体实现protected abstract void ExecuteCore();}

复制代码

  首先,Controller并没有实现IController接口,而是Controller的基类ControllerBase实现了IController接口;然后,ControllerBase中定义了一个抽象方法ExcuteCore,让其子类去具体执行,这里主要是让Controller类对象执行这个方法。

  ②根据URL获取Action名称并准备触发Action

复制代码

    public abstract class Controller : ControllerBase, IActionFilter, IAuthenticationFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter, IAsyncController, IAsyncManagerContainer{protected override void ExecuteCore(){PossiblyLoadTempData();try{string actionName = GetActionName(RouteData);if (!ActionInvoker.InvokeAction(ControllerContext, actionName)){HandleUnknownAction(actionName);}}finally{PossiblySaveTempData();}}}

复制代码

  首先,通过路由数据获取Action名称,例如请求URL为:http://xxx.com/Home/Index,这里获取的Action名称即为Index。然后,通过ActionInvoker.InvokeAction去执行具体的Action。那么问题来了,这个ActionInvoker又是啥东东?我们先看看这个接口的定义:

    public interface IActionInvoker{bool InvokeAction(ControllerContext controllerContext, string actionName);}

  通过查阅资料,我们发现原来是一个叫做ControllerActionInvoker的类实现了IActionInvoker接口,那么我们就去看看这个ControllerActionInvoker类吧。

  ③获取Controller与Action的描述信息和过滤器信息

复制代码

    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName){......ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);if (actionDescriptor != null){FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);......}   ......}

复制代码

  看到这里,也许会有人问什么是描述信息?那么看到我们在开发中经常给Controller或者Action添加的Attribute信息也许就不会感到陌生了:例如我们给某个名为Index的Action添加了[HttpPost]或者[HttpGet]特性,在请求时需要通过HTTP报文请求方式来区分这两个Action。

  那么,什么又是过滤器信息?首先,过滤器涉及到一个叫做AOP(面向切面编程)的概念,我们可以通过前面的请求处理管道进行理解,虽然我们的ASP.NET页面请求处理部分只是其中一小部分,但是在这部分执行之前还经历了许多事件,在这之后又经历了许多事件,而这些事件都是可以自定义逻辑的,它们都可以叫做过滤器。ASP.NET MVC默认为我们提供了四种类型的过滤器(Filter),如下图所示:

Filters

PS:对过滤器不熟悉的朋友可以看看我的另一篇对ASP.NET MVC基础知识中的过滤器(Filter)的介绍:http://www.cnblogs.com/edisonchou/p/3932640.html

  ④获取参数信息并开始真正执行Action:Filter->Action->Filter

复制代码

    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName){......IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);......}

复制代码

  通过上面所获取的各种描述信息与过滤器信息找到Action并获取所需的参数,然后调用InvokeActionMethodWithFilters方法执行Action。因此,再转到InvokeActionMethodWithFilters方法看看:

复制代码

        protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters){ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);Func<ActionExecutedContext> continuation = () =>new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */){Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)};Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,(next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));return thunk();}

复制代码

  在这个方法中,首先将上下文对象、描述信息、参数信息传入InvokeActionMethod方法中,得到了一个Result对象。这个Result对象又是什么?转到定义一看,原来不就是我们在开发中经常返回的ActionResult类型吗?

    public ActionResult Result{get { return _result ?? EmptyResult.Instance; }set { _result = value; }}

  那么,在InvokeActionMethod方法中又是如何返回Result的呢?再次转到定义看看:

复制代码

        protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters){object returnValue = actionDescriptor.Execute(controllerContext, parameters);ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);return result;}

复制代码

  在这个方法中,首先执行了指定的Action,然后获得了一个returnValue返回值,通过传入返回值创建具体类型的ActionResult作为方法的返回值。这里需要注意的是,ActionResult是一个抽象类,像什么JsonResult、EmptyResult、ViewResult等都是其子类,而这里的CreateActionResult就是要创建其具体子类的实例并返回。

  现在将目光返回到InvokeActionMethodWithFilters方法中,看到代码最后声明了一个委托thunk,它是过滤器结合经过反转之后再合并之前声明的委托continuation之后的一个新委托(它所持有的委托链顺序会协调一致),目的是为了完成AOP的效果,比如首先要执行Action执行之前的过滤器,才能执行Action方法。

  ⑤ActionResult闪亮登场:Filter->Result

复制代码

    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName){......InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, challengeContext.Result ?? postActionContext.Result);......}

复制代码

  现在回到InvokeAction这个主方法中,刚刚执行完Action之后将结果都保存在了postActionContext中的Result中,现在继续执行过滤器(比如:可以对刚刚的Action结果进行一些处理),目的也是为了完成AOP的效果,比如执行完Action之后,必须要执行Action结束后的过滤器业务逻辑方法。那么,这里又是进行了什么操作呢?转到InvokeActionResultWithFilters方法中去看看:

复制代码

    private ResultExecutedContext InvokeActionResultFilterRecursive(IList<IResultFilter> filters, int filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult){......if (filterIndex > filters.Count - 1){InvokeActionResult(controllerContext, actionResult);return new ResultExecutedContext(controllerContext, actionResult, canceled: false, exception: null);}  IResultFilter filter = filters[filterIndex];filter.OnResultExecuting(preContext);  ......int nextFilterIndex = filterIndex + 1;postContext = InvokeActionResultFilterRecursive(filters, nextFilterIndex, preContext, controllerContext, actionResult);......}

复制代码

  首先,判断过滤器执行的序号是否已经到了最后,如果不是,则继续递归执行本方法调用过滤器(这里对应的过滤器是OnResultExecuting事件,即在Result被生成时之前进行触发)。如果到了最后,则开始生成最终的ActionResult。看看这个InvokeActionResult方法,它是一个虚方法。

        protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult){actionResult.ExecuteResult(controllerContext);}

(3)View的呈现

  我们知道ActionResult是一个抽象类,那么这个InvokeActionResult应该是由其之类来实现。于是,我们找到ViewResult,但是其并未直接继承于ActionResult,再找到其父类ViewResultBase,它则继承了ActionResult。于是,我们来查看它的ExecuteResult方法:

  ①约定大于配置的缘故

复制代码

        public override void ExecuteResult(ControllerContext context){......if (String.IsNullOrEmpty(ViewName)){ViewName = context.RouteData.GetRequiredString("action");}......}

复制代码

  我们在日常开发中,总是被告知约定大于配置,View中的名字必须与Controller中Action的名字一致。在这了,我们知道了原因,可以看出,这里就是国通URL来取得ViewName然后去查找View的。

  ②找到ViewEngine视图引擎并获取ViewEngineResult

  首先,我们了解一下什么是ViewEngine视图引擎:我们在ASP.NET MVC开发中一般会有两个选择,一个是aspx视图引擎,另一个是ASP.NET MVC 3.0推出的Razor视图引擎。Razor视图引擎在减少代码冗余、增强代码可读性和Visual Studio智能感知方面,都有着突出的优势。因此,Razor一经推出就深受广大ASP.Net开发者的喜爱。

复制代码

    public override void ExecuteResult(ControllerContext context){......ViewEngineResult result = null;if (View == null){result = FindView(context);View = result.View;}......      }

复制代码

   这里通过FindView方法获取到具体的View对象,而FindView又是ViewResultBase的一个抽象方法。这时,我们需要到ViewResult中去看看这个FindView方法。

复制代码

        protected override ViewEngineResult FindView(ControllerContext context){ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);if (result.View != null){return result;}......}    

复制代码

  这里通过在ViewEngineCollection视图引擎集合中调用FindView方法返回一个ViewEngineResult对象,而View则作为属性存在于这个ViewEngineResult对象之中。

  ③加载ViewData/TempData等数据生成ViewContext

复制代码

        protected override ViewEngineResult FindView(ControllerContext context){......TextWriter writer = context.HttpContext.Response.Output;ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);......}    

复制代码

  这里开始加载ViewData、TempData等数据生成ViewContext,可以在ViewContext的构造函数中看到如下代码:

复制代码

        public ViewContext(ControllerContext controllerContext, IView view, ViewDataDictionary viewData, TempDataDictionary tempData, TextWriter writer): base(controllerContext){if (controllerContext == null){throw new ArgumentNullException("controllerContext");}if (view == null){throw new ArgumentNullException("view");}if (viewData == null){throw new ArgumentNullException("viewData");}if (tempData == null){throw new ArgumentNullException("tempData");}if (writer == null){throw new ArgumentNullException("writer");}View = view;ViewData = viewData;Writer = writer;TempData = tempData;}

复制代码

  现在知道我们在Action方法中定义的那些ViewData或者TempData是在哪里被存入上下文了吧?

  ④开始Render:HTML页面的呈现

复制代码

        protected override ViewEngineResult FindView(ControllerContext context){......View.Render(viewContext, writer);......}    

复制代码

  ViewContext上下文对象已生成好,TextWriter已经拿到,现在就开始对View进行正式的呈现了,也就是返回给浏览器端请求的HTML。由于这里View对象是一个实现了IView接口的类对象,于是我们找到RazorView,但是它并未直接实现IView接口,于是我们找到它的父类BuildManagerCompiledView 

复制代码

    public abstract class BuildManagerCompiledView : IView{public virtual void Render(ViewContext viewContext, TextWriter writer){if (viewContext == null){throw new ArgumentNullException("viewContext");}object instance = null;Type type = BuildManager.GetCompiledType(ViewPath);if (type != null){instance = ViewPageActivator.Create(_controllerContext, type);}if (instance == null){throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,MvcResources.CshtmlView_ViewCouldNotBeCreated,ViewPath));}RenderView(viewContext, writer, instance);}}

复制代码

  首先,通过ViewPath获取View的类型(Type),这里也是通过BuildManger来完成的,每个cshtml都会被asp.net编译成一个类。然后,通过反射生成了View的具体实例。最后,通过RendView方法进行下一步的呈现工作。RenderView是一个抽象方法,具体实现是在RazorView类或WebFormView类中。

复制代码

        protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance){if (writer == null){throw new ArgumentNullException("writer");}WebViewPage webViewPage = instance as WebViewPage;if (webViewPage == null){throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,MvcResources.CshtmlView_WrongViewBase,ViewPath));}webViewPage.OverridenLayoutPath = LayoutPath;webViewPage.VirtualPath = ViewPath;webViewPage.ViewContext = viewContext;webViewPage.ViewData = viewContext.ViewData;webViewPage.InitHelpers();if (VirtualPathFactory != null){webViewPage.VirtualPathFactory = VirtualPathFactory;}if (DisplayModeProvider != null){webViewPage.DisplayModeProvider = DisplayModeProvider;}WebPageRenderingBase startPage = null;if (RunViewStartPages){startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);}webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);}

复制代码

  在此方法中,首先将传递过来的实例转换成了一个WebViewPage类的实例,然后将ViewContext、ViewData等数据赋给WebViewPage实例作为属性,以便在View中获取。然后,如果有开始页则先执行开始页。最后,将HttpContext、Page与Model对象封装为一个WebPageContext对象传入ExecutePageHierarchy方法中进行执行页面的渲染。

  首先,我们从字面上来看,Hierarchy代表层次,那么方法名的意思大概是:根据层次执行页面。那么,什么是页面的层次?

  在执行ExecutePageHierachy这个方法来渲染View时,这个方法里面要完成相当多的工作,主要是ViewStart的执行,和Layout的执行。这里的困难之处在于对于有Layout的页面来说,Layout的内容是先输出的,然后是RenderBody内的内容,最后还是Layout的内容。如果仅仅是这样的话,只要初始化一个TextWriter,按部就班的往里面写东西就可以了,但是实际上,Layout并不能首先执行,而应该是View的代码先执行,这样的话View就有可能进行必要的初始化,供Layout使用。例如我们有如下的一个View:

@{ViewBag.Title = "Code in View";Layout = "_LayoutPage1.cshtml";
}

  这个Layout的内容如下:

复制代码

@{ Layout = "~/Views/Shared/_Layout.cshtml";ViewBag.ToView = "Data from Layout";
}
<div>Data In View: @ViewBag.Title
</div>
<div>@RenderBody();    
</div>

复制代码

  这样可以在页面显示Code in View字样。 但是反过来,如果试图在View中显示在Layout里面的"Data from Layout" 则是行不通的,什么也不会被显示。所以RenderBody是先于Layout中其他代码执行的,这种Layout的结构称为 Page Hierachy

  在这样的代码执行顺序下,还要实现文本输出的顺序,因此asp.net mvc这里的实现中就使用了栈,这个栈是OutputStack,里面压入了TextWriter。注意到这只是一个页面的处理过程,一个页面之中还会有Partial View 和 Action等,这些的处理方式都是一样的,因此还需要一个栈来记录处理到了哪个(子)页面,因此还有一个栈,称之为TemplateStack,里面压入的是PageContext,PageContext维护了view的必要信息,比如Model之类的,当然也包括上面提到的OutputStack。有了上面的基本信息,下面看代码,先看入口点:

复制代码

        public void ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) {PushContext(pageContext, writer);if (startPage != null) {if (startPage != this) {var startPageContext = Util.CreateNestedPageContext<object>(parentContext: pageContext, pageData: null, model: null, isLayoutPage: false);startPageContext.Page = startPage;startPage.PageContext = startPageContext;}startPage.ExecutePageHierarchy();}else {ExecutePageHierarchy();}PopContext();}

复制代码

  这个方法中,第一步首先将pageContext入栈:PushContext

复制代码

        public void PushContext(WebPageContext pageContext, TextWriter writer){_currentWriter = writer;PageContext = pageContext;pageContext.Page = this;InitializePage();// Create a temporary writer_tempWriter = new StringWriter(CultureInfo.InvariantCulture);// Render the page into itOutputStack.Push(_tempWriter);SectionWritersStack.Push(new Dictionary<string, SectionWriter>(StringComparer.OrdinalIgnoreCase));// If the body is defined in the ViewData, remove it and store it on the instance// so that it won't affect rendering of partial pages when they call VerifyRenderedBodyOrSectionsif (PageContext.BodyAction != null){_body = PageContext.BodyAction;PageContext.BodyAction = null;}}

复制代码

  第二步判断是否存在ViewStart文件,如果有,就执行startPage.ExecutePageHierachy()。如果不存在,则直接执行ExecutePageHierachy()

复制代码

    public override void ExecutePageHierarchy(){......TemplateStack.Push(Context, this);try{// Execute the developer-written code of the WebPageExecute();}finally{TemplateStack.Pop(Context);}        }    

复制代码

  这个方法就是将context压栈,然后执行相应的view的代码,然后出栈。有了这些出入栈的操作,可以保证View的代码,也就是Execute的时候的writer是正确的。Execute中的方法除去PartialView,Action之类的,最终调用的是WebPageBase中的WriteLiteral方法:

        public override void WriteLiteral(object value){Output.Write(value);}

  这里的Output属性是:

复制代码

        public TextWriter Output{get { return OutputStack.Peek(); }}

复制代码

  在调用了Excute方法后,页面上的HTML内容基本输出完毕,至此View就渲染完毕了

  第三步,pageContext出栈,主要是栈中的元素的清理工作。

三、一图胜千言,总体上概览

参考资料

致谢:本文参阅了大量园友的相关文章,向以下文章作者表示感谢!

(1)Darren Ji,《ASP.NET MVC请求处理管道声明周期的19个关键环节》:http://www.cnblogs.com/darrenji/p/3795661.html

(2)初心不可忘,《综述:ASP.NET MVC请求处理管道》:http://www.cnblogs.com/luguobin/archive/2013/03/15/2962458.html

(3)学而不思则罔,《ASP.NET Routing与MVC之二:请求如何激活Controller与Action》:http://www.cnblogs.com/acejason/p/3886968.html

(4)王承伟,《ASP.NET MVC请求原理与源码分析》:http://bbs.itheima.com/thread-134340-1-1.html

(5)Ivony,《通过源代码研究ASP.NET MVC中的Conroller和View》:http://www.cnblogs.com/Ivony/archive/2010/11/13/aspnet-mvc-by-source-1.html

(6)痞子一毛,《ASP.NET MVC请求处理图解》:http://www.cnblogs.com/piziyimao/archive/2013/02/27/2935969.html

(7)蒋金楠,《ASP.NET MVC中的View是如何被呈现出来的》:http://www.cnblogs.com/artech/archive/2012/08/22/view-engine-01.html

(8)yinzixin,《深入ASP.NET MVC之七:ActionResult的执行》:http://www.cnblogs.com/yinzixin/archive/2012/12/05/2799459.html (一篇好文,值得阅读)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438044.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

REVERSE-PRACTICE-CTFSHOW-6

REVERSE-PRACTICE-CTFSHOW-6真的是签到批量生产的伪劣产品来一个派森snake真的是签到 附件是一个压缩包&#xff0c;解压需要密码&#xff0c;但是题目没有给到解压密码 实际上是压缩包伪加密&#xff0c;将如图橙色处原本的字节0x09改为0x00&#xff0c;保存后即可解压出文件…

REVERSE-PRACTICE-CTFSHOW-7

REVERSE-PRACTICE-CTFSHOW-7签层饼Tea_tube_poteasy贪吃蛇的秘密签层饼 32位exe&#xff0c;ida打开 main函数中&#xff0c;输入两个大于0的数字&#xff0c;如果flag_num为0&#xff0c;则将两个输入作为flag的一部分&#xff0c;打印flag 选中input_1按x查找交叉引用&…

HttpModule与HttpHandler详解(转)

ASP.NET对请求处理的过程&#xff1a; 当请求一个*.aspx文件的时候&#xff0c;这个请求会被inetinfo.exe进程截获&#xff0c;它判断文件的后缀&#xff08;aspx&#xff09;之后&#xff0c;将这个请求转交给 ASPNET_ISAPI.dll&#xff0c;ASPNET_ISAPI.dll会通过http管道&am…

Windows 系统中 Python下 Pygame 的安装

Pygame是跨平台Python模块&#xff0c;专为电子游戏设计&#xff0c;可用于管理图形、动画乃至声音&#xff0c;建立在SDL基础上&#xff0c;允许实时电子游戏研发而无需被低级语言&#xff08;如机器语言和汇编语言&#xff09;束缚&#xff0c;通过使用Pygame来处理在屏幕上绘…

REVERSE-PRACTICE-CTFSHOW-8

REVERSE-PRACTICE-CTFSHOW-8Matara OkinaanniuwarmupeMatara Okina apk文件&#xff0c;jadx-gui打开 MainActivity什么都没有&#xff0c;来到FlagActivity scheme&#xff0c;host这些看不懂 第44行有个ans和bytes的比较&#xff0c;ans已知&#xff0c;bytes是从某个地方获…

Html控件和Web控件(转)

作为一名ASP.NET的初学者&#xff0c;了解并且区别一些混淆概念是很必须的&#xff0c;今天这篇博文 就是主要向大家介绍一下Html控件和Web控件。在ASP.net中&#xff0c;用户界面控件主要就是 Html控件和Web控件&#xff0c;在前台页面上看&#xff0c;两种控件非常相似&…

VMware Pro 14 安装 Ubuntu 18.04 详细教程

目录● 安装步骤1.下载安装 VMware Workstation Pro 142.下载 Ubuntu 18.04 系统3.在 VMware 中创建虚拟机4.在虚拟机上安装 Ubuntu 系统● 安装过程中可能会出现的一些问题 1.在虚拟机上安装 Ubuntu 系统的过程中卡死不动 2.Ubuntu 不能全屏显示● 安装步骤 1.下载安装 VMware…

Web服务器控件和HTML控件的区别与联系

我们知道&#xff0c;在ASP.NET中&#xff0c;使用了两类控件&#xff0c;一类是HTML控件&#xff0c;还有一类是WEB控件。 WEB控件我们已经很熟悉&#xff0c;因为它是ASP.NET提供&#xff0c;一般关于ASP.NET的教程文章都会仔细介绍这些控件的详细应用技巧&#xff1b; 而H…

Hexo 博客优化之博客美化系列(持续更新)

2022-01-25 更新&#xff1a;博客新地址&#xff1a;https://www.itbob.cn/&#xff0c;文章距上次编辑时间较远&#xff0c;部分内容可能已经过时&#xff01; 本文将讲述一些 Hexo 博客的美化&#xff0c;本文以作者 luuman 的 spfk 主题和作者 xaoxuu 的 Material X 主题为例…

一台电脑部署两个或多个 Hexo 博客(一台电脑使用两个或多个 GitHub 账号)

由于个人原因需要在一台电脑上部署两个Hexo博客&#xff0c;本来以为挺简单&#xff0c;没想到问题重重&#xff0c;首先是一个GitHub账号只能搭建一个Hexo博客&#xff0c;因此就需要使用其他GitHub账号&#xff1b;其次是一台电脑绑定两个GitHub账号&#xff0c;则需要两对公…

element.style内联样式修改

最近在修改我个人博客的一个样式时发现&#xff0c;无论我如何修改style里面的值&#xff0c;页面上的样式都不会被修改&#xff0c;F12检查一下发现个element.style&#xff0c;但是却找不到这个值在哪里&#xff0c;查资料后才知道element.style为内联样式&#xff0c;基本很…

免费CDN:jsDelivr+Github 使用方法

CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。CDN是构建在网络之上的内容分发网络&#xff0c;依靠部署在各地的边缘服务器&#xff0c;通过中心平台的负载均衡、内容分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c;降低网络拥塞&…

EasyUI学习总结(一)——EasyUI入门

一、EasyUI下载 EasyUI官方下载地址&#xff1a;http://www.jeasyui.com/download/index.php&#xff0c;目前最新的版本是&#xff1a;jQuery EasyUI 1.4.1 下载完成之后&#xff0c;得到压缩包&#xff0c;解压后&#xff0c;得到一个【jquery-easyui-1.4.1】文件夹&#xff…

PWN-PRACTICE-CTFSHOW-1

PWN-PRACTICE-CTFSHOW-1PWN签到题pwn02pwn03pwn04PWN签到题 nc连上去就会打印flag pwn02 栈溢出&#xff0c;覆盖返回地址为后门函数stack起始地址即可 # -*- coding:utf-8 -*- from pwn import * #ioprocess("./pwn1") ioremote("pwn.challenge.ctf.show&…

利用QQ邮箱设置个性域名邮箱,并在网易邮箱大师客户端添加域名邮箱

平常我们使用的邮箱都是xxxqq.com、xxx126.com、xxx163.com、xxxgmail.com之类的&#xff0c;这些邮箱会暴露你的QQ号、电话号等信息&#xff0c;最重要的是后缀都和别人一样&#xff0c;没有个性&#xff0c;也许你看见过xxxliruihao.com、xxxwangxiaoxiao.com之类的带有个性后…

MVC和WebForm区别

WebForm的理解 1、 WebForm概念 ASP.NETWebform提供了一个类似于Winform的事件响应GUI模型&#xff08;event-drivenGUI&#xff09;&#xff0c;隐藏了HTTP、HTML、JavaScript等细节&#xff0c;将用户界面构建成一个服务器端的树结构控件&#xff08;Control&#xff09;&a…

VMware Pro 15 安装 Deepin15.9 国产操作系统详细教程

Deepin是由武汉深之度科技有限公司开发的Linux发行版&#xff0c;个人认为其界面设计非常美观&#xff0c;而且作为国产操作系统&#xff0c;值得我们去体验和支持&#xff01; 文章目录1.下载安装 VMware Workstation Pro 152.下载 Deepin15.9 系统3.在 VMware 中创建虚拟机4.…

Asp.Net WebForm生命周期的详解

一&#xff0e;http://Asp.Net页面生命周期的概念 当我们在浏览器地址栏中输入网址&#xff0c;回车查看页面时&#xff0c;这时会向服务器端IIS&#xff09;发送一个request请求&#xff0c;服务器就会判断发送过来的请求页面&#xff0c;当完全识别 TTP页面处理程序类后&…

Python PEP8 代码规范常见问题及解决方法

之前一直用 Python IDLE 写代码&#xff0c;最近换成 PyCharm 写代码总是会出现波浪号&#xff0c;这才了解到 Python 的 PEP8 代码规范&#xff0c;所以将常见的 PEP8 代码规范问题和解决方法记录一下&#xff0c;学习一下&#xff0c;遇到了再持续更新&#xff0c;养成良好的…

Eclipse 通过 JDBC 连接 SQL Server

文章目录1.配置 SQL Server 20122.开启 Telnet 服务3.测试1433端口是否打开4.下载JDBC5.Eclipse 连接 SQL Server6.测试连接本文用到的软件版本以及相关环境&#xff1a; Eclipse Photon Release (4.8.0) JDK-10.0.2 SQL Server 2012 1.配置 SQL Server 2012 打开 SQL Server …