经过那么长时间的学习,终于想给自己这段时间的学习工作做个总结了。记得刚开始学习的时候,什么资料都没有,光就啃文档。不过,值得庆幸的是,自己总算还有一些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(,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 == )
{
_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 - ; i >= ; 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学习总结(1)的更多相关文章

  1. ASP.NET Core学习系列

    .NET Core ASP.NET Core ASP.NET Core学习之一 入门简介 ASP.NET Core学习之二 菜鸟踩坑 ASP.NET Core学习之三 NLog日志 ASP.NET C ...

  2. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  3. ASP.NET Core学习指导

    ASP.NET Core 学习指导 "工欲善其事必先利其器".我们在做事情之前,总应该做好充分的准备,熟悉自己的工具.就像玩游戏有一些最低配置一样,学习一个新的框架,也需要有一些基 ...

  4. Asp.Net Core学习笔记:入门篇

    Asp.Net Core 学习 基于.Net Core 2.2版本的学习笔记. 常识 像Django那样自动检查代码更新,自动重载服务器(太方便了) dotnet watch run 托管设置 设置项 ...

  5. ASP.NET Core学习零散记录

    赶着潮流听着歌,学着.net玩着Core 竹子学Core,目前主要看老A(http://www.cnblogs.com/artech/)和tom大叔的博客(http://www.cnblogs.com ...

  6. ASP.NET Core学习之三 NLog日志

    上一篇简单介绍了日志的使用方法,也仅仅是用来做下学习,更何况只能在console输出. NLog已是日志库的一员大佬,使用也简单方便,本文介绍的环境是居于.NET CORE 2.0 ,目前的版本也只有 ...

  7. ASP.NET Core学习之一 入门简介

    一.入门简介 在学习之前,要先了解ASP.NET Core是什么?为什么?很多人学习新技术功利心很重,恨不得立马就学会了. 其实,那样做很不好,马马虎虎,联系过程中又花费非常多的时间去解决所遇到的“问 ...

  8. Asp.net Core学习笔记

    之前记在github上的,现在搬运过来 变化还是很大的,感觉和Nodejs有点类似,比如中间件的使用 ,努力学习ing... 优点 不依赖IIS 开源和跨平台 中间件支持 性能优化 无所不在的依赖注入 ...

  9. 2019年ASP.NET Core学习路线

    - [先决条件] + C# + Entity Framework + ASP.NET Core + SQL 基础知识 - [通用开发技能] + 学习 GIT, 在 GitHub 中创建开源项目 + 掌 ...

随机推荐

  1. python scikit-learn 安装中的各种事宜

    由于兴趣,想安装scikit,但是安装时提示pip版本低,让更新,但是他给的更新命令用了之后并不能更新成功(我是指我的) 网上的各种命令都试过了,弄了大半天还是不行,后来我把SCIKIT换成(whl- ...

  2. collections系列之Counter

    collections模块中有一个叫做Counter的类,该类的作用就是计数器,Counter是对dict的加工,所有Counter继承了dict的方法 1.创建一个Counter,需要import ...

  3. sign和token设计

    签名设计 对于敏感的api接口,需使用https协议 https是在http超文本传输协议加入SSL层,它在网络间通信是加密的,所以需要加密证书. https协议需要ca证书,一般需要交费. 签名的设 ...

  4. Zabbix监控PostgreSQL

    目录 Zabbix监控PostgreSQL 1. 安装libzbxpgsql 2. 配置zabbix配置文件zabbix_agentd.conf 3. 创建监控用户 4. 导入监控模板 5. 主机链接 ...

  5. 使用Spring+Junit4进行测试

    前言 单元测试是一个程序员必备的技能,我在这里就不多说了,直接就写相应的代码吧. 单元测试基础类 import org.junit.runner.RunWith; import org.springf ...

  6. inux中Vi不能高亮显示行号的解决办法

    适用版本:CentOS,RedHat,UBUNTU,Fedora解决办法如下: 在UBUNTU中vim的配置文件存放在/etc/vim目录中,配置文件名为vimrc 在Fedora中vim的配置文件存 ...

  7. 洛谷 P2986 [USACO10MAR]伟大的奶牛聚集(树形动规)

    题目描述 Bessie is planning the annual Great Cow Gathering for cows all across the country and, of cours ...

  8. wpf 进度条 下拉

    <Window x:Class="WpfApplication1.MainWindow"        xmlns="http://schemas.microsof ...

  9. 常用C字符串函数

    static void str_repalce(char *src,char *from,char *to) {     char *p,*q;     int lenFrom;     int le ...

  10. Basic4android v3.80 beta 发布

    增加了条件编译,共享模块,部分支持jar 文件直接访问.还有其他一些更新. I'm happy to release B4A v3.80 BETA. This version includes sev ...