ASP.NET Core学习总结

Posted xsddxz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASP.NET Core学习总结相关的知识,希望对你有一定的参考价值。

  经过那么长时间的学习,终于想给自己这段时间的学习工作做个总结了。记得刚开始学习的时候,什么资料都没有,光就啃文档。不过,值得庆幸的是,自己总算还有一些Web开发的基础。至少ASP.NET的WebForm和MVC那一套还是有所了解的,虽然也不是很精通。说起来,那时候对整个网络应用的整体流程以及什么HTTP协议都不是很了解。终归是在微软爸爸的庇护下艰难的成长。

1、概念

  概念这种东西,感觉还是太过于学术化。也就是时间长了,慢慢就能理解的一些经常用到的词而已。对于大多数人来说,我们几乎每天都会浏览网页。也许,我们对于网络应用的基本认识,就是从这里开始的。可惜,很多人的认识仍然停留在打开浏览器看网页上。以至于,对于网页是怎么来的,怎么呈现的毫无概念。

  网络应用是一种分布式系统,通常由客户端和服务端组成。通过HTTP协议进行通信,是一种请求/应答模式。浏览器通常作为客户端,而我们开发的Web应用,通常作为服务端。

2、原理

 技术分享图片

  上面这张图来源于微软的官方文档,它简单直观的描述了我们将要开发的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都实现了IRouter接口。第一部分的代码的意图在于将这两个Handler添加到服务容器。而第二部分代码的意图在于将它们从服务容器中取出,传递给路由中间件。

? 那么,问题在于,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;
        }

  我们来解释一下,关键在于_actionSelector。它会根据routeContext挑选出candidates(候选者),这是第一步。这一步只是筛选出了所有URL匹配的Action,而第二步则需要继续考虑路由约束的问题。如果第二步还是有多个符合条件的Action,则会引发异常。第三步我们看到,利用一个lambda表达式生成RequestDelegate的委托,即一个Handler(前面我们曾看到在路由中间件中调用)。在这个委托中我们需要关注的是invoker,它是一个IActionInvoker类型的变量。它的默认实现通常是ControllerActionInvoker,我们稍后会深入讨论这个类的内部实现。我们看到,invoker是由工厂函数根据actionContext生成的,最终调用了InvokeAsync方法。也就是说,至此为止,我们还是无法得知我们所编写的Action代码是怎样执行的。而为了知道这一点,我们只能继续深入。

  我们看到,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学习总结

学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位

Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章

ASP.NET Core 行军记 -----拔营启程

.Net Core 学习 - ASP.NET Core 概念学习

net core体系-web应用程序-4asp.net core2.0 项目实战-2项目说明和源码下载