ASP.NET Core学习总结
Posted xsddxz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASP.NET Core学习总结相关的知识,希望对你有一定的参考价值。
1、概念
概念这种东西,感觉还是太过于学术化。也就是时间长了,慢慢就能理解的一些经常用到的词而已。对于大多数人来说,我们几乎每天都会浏览网页。也许,我们对于网络应用的基本认识,就是从这里开始的。可惜,很多人的认识仍然停留在打开浏览器看网页上。以至于,对于网页是怎么来的,怎么呈现的毫无概念。
网络应用是一种分布式系统,通常由客户端和服务端组成。通过HTTP协议进行通信,是一种请求/应答模式。浏览器通常作为客户端,而我们开发的Web应用,通常作为服务端。
上面这张图来源于微软的官方文档,它简单直观的描述了我们将要开发的Web应用的基本原理。首先,ASP.NET Core application代表了我们的整个Web应用,它通过HTTP协议与外部进行通信。而在我们程序的内部,首先就是由ASP.NET Core的框架所支配的。Kestrel是一个可以监听和响应请求的底层服务,它会把接收到的HTTP报文封装成HttpContext传递给我们的应用程序代码。同时,把应用程序处理好的响应转换为响应报文返回给客户端。
? 现在,让我们深入应用程序代码的内部。应用程序管道,本质上是由一个委托链构成的。这个名为RequestDelegate的委托有两个参数,第一个是httpContext,第二个是next,类型也是RequestDeletage,指向下一个委托。
由上图我们看到,请求和响应实质上是由一系列中间件处理共同处理的。而事实上,这些中间件最终会编译为一个委托链(所有Middleware类按照约定都应该包含一个Invoke方法和构造函数,构造函数中包含了next,Invoke中包含了httpContext)。总结来说,当请求进来以后,首先会执行第一个委托。而第一个委托的内部可以选择是否调用下一个委托。如同上图所示,如果第一中间件,实质上会变成一个委托,不调用next()。那么,请求便在该中间件短路了,即请求不再向下传递,而是直接返回响应了。
接下来,我们来介绍ASP.NET Core框架的核心部分,即MVC。对于每一个请求来说,应该都会有一个对应的URL。而我们的程序通常也会有一个对应的处理方法,即我们的控制器动作。现在,框架所解决的第一个问题即是,如何根据URL映射到对应的处理方法,即路由机制。
路由机制是由Microsoft.AspNetCore.Routing实现的。它最核心的部分是RouterMiddleware中的那段代码。
public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router);//_router是通过依赖注入,从服务容器中获得的 //这一步是最重要的,它会根据RouteContext寻找一个合适的Handler //也就是说,整个路由匹配在于这一步是如何实现的 await _router.RouteAsync(context); if (context.Handler == null) { _logger.RequestDidNotMatchRoutes(); await _next.Invoke(httpContext); } else { httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() { RouteData = context.RouteData, }; await context.Handler(context.HttpContext); } }
上面这个_router是一个IRouter接口类型的变量。实质上,当我们在注册MVC服务的时候,已经添加了实现类。如下所示:
// // Route Handlers // services.TryAddSingleton<MvcRouteHandler>(); // Only one per app services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(), }; configureRoutes(routes); routes.Routes.Insert(0,AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); return app.UseRouter(routes.Build());
? 那么,问题在于,MvcRouteHandler和MvcAttributeRouteHandler是如何实现的?首先,我们来看看MvcRouteHandler内部是如何实现的。
public Task RouteAsync(RouteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } //海选 var candidates = _actionSelector.SelectCandidates(context); if (candidates == null || candidates.Count == 0) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } //精选 var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); if (actionDescriptor == null) { _logger.NoActionsMatched(context.RouteData.Values); return Task.CompletedTask; } //使用lambda表达式编译成RequestDelegate context.Handler = (c) => { var routeData = c.GetRouteData(); var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); if (_actionContextAccessor != null) { _actionContextAccessor.ActionContext = actionContext; } var invoker = _actionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { throw new InvalidOperationException( Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( actionDescriptor.DisplayName)); } //这里才是核心处理部分 return invoker.InvokeAsync(); }; return Task.CompletedTask; }
我们看到,invoker是由_actionInvokerFactory
创建的。而_actionInvokerFactory
是IActionInvokerFactory类型,从服务容器中获取。我们来查找它是怎样注入到容器中的。
// // Action Invoker // // The IActionInvokerFactory is cachable services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>(); services.TryAddEnumerable( ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
可以看到,它注入了一个默认实现,ActionInvokerFactory。它的内部是这样的:
public IActionInvoker CreateInvoker(ActionContext actionContext) { var context = new ActionInvokerProviderContext(actionContext); foreach (var provider in _actionInvokerProviders) { provider.OnProvidersExecuting(context); } for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--) { _actionInvokerProviders[i].OnProvidersExecuted(context); } return context.Result; }
事实上,ActionInvokerFactory并没有直接处理,而是交给了IActionInokerProvider。而在上面我们看到它的默认实现是ControllerActionInvokerProvider。
public void OnProvidersExecuting(ActionInvokerProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) { var controllerContext = new ControllerContext(context.ActionContext); // PERF: These are rarely going to be changed, so let‘s go copy-on-write. controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories); controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; //缓存策略 var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext); var invoker = new ControllerActionInvoker( _logger, _diagnosticSource, controllerContext, cacheResult.cacheEntry, cacheResult.filters); context.Result = invoker; } }
我们最终发现,invoker来源于这里,其中还做了缓存策略。现在,是时候揭开这个ControllerActionInvoker的神秘面纱了。
以上是关于ASP.NET Core学习总结的主要内容,如果未能解决你的问题,请参考以下文章
学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位
Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章