200行代码实现Mini ASP.NET Core

2023-11-04

前言

在学习ASP.NET Core源码过程中,偶然看见蒋金楠老师的ASP.NET Core框架揭秘,不到200行代码实现了ASP.NET Core Mini框架,针对框架本质进行了讲解,受益匪浅,本文结合ASP.NET Core Mini框架讲述ASP.NET Core核心。

微软官网关于ASP.NET Core的概念“ASP.NET Core是一个开源和跨平台的框架,用于构建基于Web的现代互联网连接应用程序,例如Web应用程序,IoT应用程序和移动后端。 ASP.NET Core应用程序可以在.NET Core或完整的.NET Framework上运行。 它的架构旨在为部署到云或在本地运行的应用程序提供优化的开发框架。 它由模块化组件组成,开销最小,因此您可以在构建解决方案时保持灵活性。 您可以在Windows,Mac和Linux上跨平台开发和运行ASP.NET核心应用程序”。可以从定义上看出ASP.NET Core框架具有跨平台、部署灵活、模块化等特点。


ASP.NET Core框架揭秘

ASP.NET Core Mini是200行代码实现的迷你版ASP.NET Core框架,有三大特点“简单”,“真实模拟”,“可执行”来让我们更加容易理解ASP.NET Core。

代码结构:

下图是项目运行页面输出的结果:

本文从以下五个角度讲述:

  • Program: 项目入口
  • Middleware:中间件
  • HttpContext:Http相关
  • WebHost:WebHost
  • Server:Server相关

 Program

 using System.Threading.Tasks;
 using App.Server;
 using App.WebHost;

 namespace App
 {
     public static class Program
     {
         public static async Task Main(string[] args)
         {
             await CreateWebHostBuilder()
                 .Build()
                 .Run();
         }
 
         private static IWebHostBuilder CreateWebHostBuilder()
         {
             return new WebHostBuilder()
                 .UseHttpListener()
                 .Configure(app => app
                     .Use(Middleware.Middleware.FooMiddleware)
                     .Use(Middleware.Middleware.BarMiddleware)
                     .Use(Middleware.Middleware.BazMiddleware));
         }
     }
 }

可以看到项目的入口是Main方法,它只做了三件事,构造WebHostBuilder,然后Build方法构造WebHost,最后Run方法启动WebHost。我们可以简单的理解WebHostBuilder作用就是为了构造WebHost,他是WebHost的构造器,而WebHost是我们Web应用的宿主。

再看CreateWebHostBuilder的方法具体干了什么。首先创建了WebHostBuilder,然后UseHttpListener配置Server(比如ASP.NET Core中的Kestrel或IIS等等),一般包括地址和端口等,最后注册一系列的中间件。

从Program可以看出整个App运行起来的流程,如下图所示:

 Middleware 

在看HttpContext之前,我们先来看ASP.NET Core 的Http处理管道是什么样子,上图是官方给出的管道处理图,当我们的服务器接收到一个Http请求,管道进行处理,处理后再进行返回,可以看到,我们的Http请求经过多层中间件处理最后返回。

 using System.Threading.Tasks;
 
 namespace App.Middleware
 {
     public delegate Task RequestDelegate(HttpContext.HttpContext context);
 }

首先来看RequestDelegate.cs,定义了一个参数类型是HttpContext,返回结果是Task的委托。

为什么会定义这个委托,可以想到一个Http请求会经过多层中间件处理,那么多层中间件处理可以想像成一个HttpHandler,他的参数就是HttpContext,返回结果是Task的委托。

 using App.HttpContext;
 
 namespace App.Middleware
 {
     public static class Middleware
     {
         public static RequestDelegate FooMiddleware(RequestDelegate next)
             => async context =>
             {
                 await context.Response.WriteAsync("Foo=>");
                 await next(context);
             };
 
         public static RequestDelegate BarMiddleware(RequestDelegate next)
             => async context =>
             {
                 await context.Response.WriteAsync("Bar=>");
                 await next(context);
             };
 
         public static RequestDelegate BazMiddleware(RequestDelegate next)
             => context => context.Response.WriteAsync("Baz");
     }
 }

Middleware中定义了三个简单的中间件,可以看到,中间件其实就是委托,将HttpContext一层一层进行处理。

Http请求进入管道,第一个中间件处理完,把自身作为结果传输到下一个中间件进行处理,那么参数是RequestDelegate,返回值是RequestDelegate的委托就是中间件,所以中间件其实就是Func<RequestDelegate,RequestDelegate>,简单来说,中间件就是RequestDelegate的加工工厂。

 HttpContext

从Middleware了解到,HttpContext是RequestDelegate的参数,是每一个Middleware处理数据的来源。

我们可以这么理解,HttpContext就是我们整个Http请求中的共享资源,所以的中间件共享它,而每个中间件就是对它进行加工处理。

 using System;
 using System.Collections.Specialized;
 using System.IO;
 using System.Text;
 using System.Threading.Tasks;
 
 namespace App.HttpContext
 {
     public class HttpContext
     {
         public HttpRequest Request { get; }
         public HttpResponse Response { get; }
 
         public HttpContext(IFeatureCollection features)
         {
             Request = new HttpRequest(features);
             Response = new HttpResponse(features);
         }
     }
 
     public class HttpRequest
     {
         private readonly IHttpRequestFeature _feature;
         
         public Uri Url => _feature.Url;
         
         public NameValueCollection Headers => _feature.Headers;
         
         public Stream Body => _feature.Body;
         
         public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>();
     }
 
     public class HttpResponse
     {
         private readonly IHttpResponseFeature _feature;
         public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>();
         
         public NameValueCollection Headers => _feature.Headers;
         public Stream Body => _feature.Body;
 
         public int StatusCode
         {
             get => _feature.StatusCode;
             set => _feature.StatusCode = value;
         }
     }
 
     public static partial class Extensions
     {
         public static Task WriteAsync(this HttpResponse response, string contents)
         {
             var buffer = Encoding.UTF8.GetBytes(contents);
             return response.Body.WriteAsync(buffer, 0, buffer.Length);
         }
     }
 }

代码结构可以看出request和reponse构成httpcontext,也反映出httpcontext的职责:Http请求的上下文。

但是,不同的Server和单一的HttpContext之间需要如何适配呢?因为我们可以注册多样的Server,可以是IIS也可以是Kestrel还可以是这里的HttpListenerServer。

所以我们需要定义统一的request和response接口,来适配不同的Server。如下图的IHttpRequestFeature和IHttpResponseFeature。

 using System;
 using System.Collections.Specialized;
 using System.IO;
 
 namespace App.HttpContext
 {
     public interface IHttpRequestFeature
     {
         Uri Url { get; }
         
         NameValueCollection Headers { get; }
         
         Stream Body { get; }
     }
 
     public interface IHttpResponseFeature
     {
         int StatusCode { get; set; }
         
         NameValueCollection Headers { get; }
         
         Stream Body { get; }
     }
 }

在HttpListenerFeature.cs中实现request和response的接口,实现了适配不同的server。

 using System;
 using System.Collections.Specialized;
 using System.IO;
 using System.Net;
 
 namespace App.HttpContext
 {
     public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
     {
         private readonly HttpListenerContext _context;
 
         public HttpListenerFeature(HttpListenerContext context) => _context = context;
 
         Uri IHttpRequestFeature.Url => _context.Request.Url;
 
         NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;
 
         NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;
 
         Stream IHttpRequestFeature.Body => _context.Request.InputStream;
 
         Stream IHttpResponseFeature.Body => _context.Response.OutputStream;
 
         int IHttpResponseFeature.StatusCode
         {
             get => _context.Response.StatusCode;
             set => _context.Response.StatusCode = value;
         }
     }
 }

至于FeatureCollection.cs,它的作用就是将从httpListenerContext中获取的Http信息存储在FeatureCollection的Dictionary里,更加方便的对HttpRequestFeature和HttpResponseFeature进行操作。

扩展方法Get和Set的作用是方便操作FeatureCollection。

 using System;
 using System.Collections.Generic;
 
 namespace App.HttpContext
 {
     public interface IFeatureCollection : IDictionary<Type, object>
     {
     }
 
     public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection
     {
     }
 
     public static partial class Extensions
     {
         public static T Get<T>(this IFeatureCollection features) =>
             features.TryGetValue(typeof(T), out var value) ? (T) value : default(T);
 
         public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature)
         {
             features[typeof(T)] = feature;
             return features;
         }
     }
 }

 WebHost

using System;
 using System.Collections.Generic;
 using App.Server;
 
 namespace App.WebHost
 {
     public interface IWebHostBuilder
     {
         IWebHostBuilder UseServer(IServer server);
         
         IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
         
         IWebHost Build();
     }
 
     public class WebHostBuilder : IWebHostBuilder
     {
         private IServer _server;
         private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>();
 
         public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
         {
             _configures.Add(configure);
             return this;
         }
 
         public IWebHostBuilder UseServer(IServer server)
         {
             _server = server;
             return this;
         }
 
         public IWebHost Build()
         {
             var builder = new ApplicationBuilder();
             foreach (var configure in _configures)
             {
                 configure(builder);
             }
 
             return new WebHost(_server, builder.Build());
         }
     }
 }

WebHost是我们App的宿主,通过WebHostBuild构造,代码里定义了三个方法,

  • UseServer: 配置server
  • Configure: 注册中间件
  • Build: 构造WebHost
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using App.Middleware;
 
 namespace App.WebHost
 {
     public interface IApplicationBuilder
     {
         IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
 
         RequestDelegate Build();
     }
 
     public class ApplicationBuilder : IApplicationBuilder
     {
         private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares =
             new List<Func<RequestDelegate, RequestDelegate>>();
 
         public RequestDelegate Build()
         {
             _middlewares.Reverse();
             return httpContext =>
             {
                 RequestDelegate next = _ =>
                 {
                     _.Response.StatusCode = 404;
                     return Task.CompletedTask;
                 };
 
                 foreach (var middleware in _middlewares)
                 {
                     next = middleware(next);
                 }
 
                 return next(httpContext);
             };
         }
 
         public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
         {
             _middlewares.Add(middleware);
             return this;
         }
     }
 }

ApplicationBuilder做了什么,Use方法我们把自定义的中间件放进集合里,而build方法就是构建webhost。首先把中间键集合顺序倒置,然后构造一个StatusCode为404的中间件,其次遍历中间件集合,最后返回构造好的管道。

如果中间件集合为空,我们返回Http 404错误。

至于为什么要Reverse(),是因为我们注册中间件的顺序与我们需要执行的顺序相反。

using System.Threading.Tasks;
 using App.Middleware;
 using App.Server;
 
 namespace App.WebHost
 {
     public interface IWebHost
     {
         Task Run();
     }
 
     public class WebHost : IWebHost
     {
         private readonly IServer _server;
         private readonly RequestDelegate _handler;
 
         public WebHost(IServer server, RequestDelegate handler)
         {
             _server = server;
             _handler = handler;
         }
 
         public Task Run() => _server.RunAsync(_handler);
     }
 }

WebHost只做了一件事,将我们构造的中间件管道处理器在指定Server运行起来。

 Server

我们自定义一个服务器,IServer定义统一接口,HttpListenerServer实现我们自定义的Server

using System;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
 using App.HttpContext;
 using App.Middleware;
 using App.WebHost;
 
 namespace App.Server
 {
     public class HttpListenerServer : IServer
     {
         private readonly HttpListener _httpListener;
         private readonly string[] _urls;
 
         public HttpListenerServer(params string[] urls)
         {
             _httpListener = new HttpListener();
             _urls = urls.Any() ? urls : new[] {"http://localhost:5000/"};
         }
 
         public async Task RunAsync(RequestDelegate handler)
         {
             Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));
             
             if (!_httpListener.IsListening)
             {
                 _httpListener.Start();
             }
 
             Console.WriteLine("Server started and is listening on: {0}", string.Join(';', _urls));
 
             while (true)
             {
                  var listenerContext = await _httpListener.GetContextAsync();
                 var feature = new HttpListenerFeature(listenerContext);
                 var features = new FeatureCollection()
                     .Set<IHttpRequestFeature>(feature)
                     .Set<IHttpResponseFeature>(feature);
                 var httpContext = new HttpContext.HttpContext(features);
                 
                 await handler(httpContext);
                 
                 listenerContext.Response.Close();
             }
         }
     }
 
     public static class Extensions
     {
         public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder, params string[] urls)
             => builder.UseServer(new HttpListenerServer(urls));
     }
 }

使用UseHttpListener扩展方法,指定监听地址,默认为“http://localhost:5000/”。

RunAsync方法是我们WebHost的Run方法,循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。


总结 

看完这篇文章应该对ASP.NET Core有一定对理解,核心就是中间件管道。不过ASP.NET Core源码远远不止这些,每个模块的实现较复杂,还有其他必不可少的模块(依赖注入、日志系统、异常处理等),需要更加深入的学习。我也会记录我的学习记录,最后来一张完整的Http请求管道图。

 

 

参考资料 :200行代码,7个对象——让你了解ASP.NET Core框架对本质

代码地址: GitHub

转载于:https://www.cnblogs.com/xiandnc/p/11480735.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

200行代码实现Mini ASP.NET Core 的相关文章

  • Mybatis从头到尾(一)--MyBatis简介及项目搭建

    一 简介 MyBatis的前身是iBATIS 是ClintonBegin在2001年发起的一个开源项目 最初侧重于密码软件的开发 后来发展成为一款基于Java的持久层框架 2004年 Clinton将iBATIS的名字和源码捐赠给了Apac
  • labelme 5.0.1版本指南(二)labelme快捷键详解与修改

    如何自定义labelme的快捷键 非常简单也很实用 吐槽一下Ctrl J创建 Ctrl N编辑 对我的键盘很不友好 路径文件名是这个是这个 home mac labelmerc 打开以后可以看到各种快捷键 shortcuts close C
  • 深度学习06-pytorch从入门到精通

    文章目录 概述 环境准备 安装cuda和cudnn 安装pytorch 基础 张量 定义 numpy转换 数学函数 随机数 计算函数 矩阵处理函数 自动梯度 案例 计算图 torchvision模块 Transforms DataSet D
  • 鲜为人知的编程真相(转载)

    当程序员的经历让我知道了一些关于软件编程的事情 下面的这些事情可能会让朋友们对软件开发感到惊讶 一个程序员用在写程序上的时间大概占他的工作时间的10 20 大部分的程序员每天大约能写出10 12行的能进入最终的产品的代码 不管他的技术水平有
  • 数据量超过亿级别,MySQL大表迁移该如何做?

    MySQL 作为当前应用最广泛的开源关系型数据库之一 具有高性能 稳定性和易用性等特性 是许多网站 应用和商业产品的主要数据存储 在一些场景中 如果出现单表行数上亿的情况 就可能需要开发和 DBA 对大表进行优化 分表 归档或扩容操作 而在

随机推荐

  • std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题

    在std shared ptr被引入之前 C 标准库中实现的用于管理资源的智能指针只有std auto ptr一个而已 std auto ptr的作用非常有限 因为它存在被管理资源的所有权转移问题 这导致多个std auto ptr类型的局
  • 一文解决linux下mysql FEDERATED 存储引擎的配置和使用(笔者亲测有效)

    C C 气象数据中心实战工业级项目系列文章目录 第五章 解决linux下mysql FEDERATED 存储引擎的配置和使用 文章目录 C C 气象数据中心实战工业级项目系列文章目录 一 linux下mysql FEDERATED 存储引擎
  • 华为OD机试 - 文件目录大小(Python)

    题目描述 一个文件目录的数据格式为 目录id 本目录中文件大小 子目录id列表 其中目录id全局唯一 取值范围 1 200 本目录中文件大小范围 1 1000 子目录id列表个数 0 10 例如 1 20 2 3 表示目录1中文件总大小是2
  • ubuntu 下 opencv的安装以及配置(亲测有效)

    当在Ubuntu上安装OpenCV时 可以按照以下详细步骤进行操作 1 更新apt包列表 sudo apt update 这将更新系统的包列表 确保可以获取到最新的软件包信息 2 安装所需依赖项 sudo apt install build
  • Vue命名规范

    JS文件命名 一般采用的是小驼峰命名法 如 pieChartHelp 第一个单词小写 其他单词首字母大写 Components 文件命名 一般采用的是大驼峰命名法 如PieChart 所有单词的首字母大写 常量命名 一般全部大写 每个单词使
  • 目标检测YOLO实战应用案例100讲-无监督领域自适应目标检测方法研究与应用

    目录 无监督领域自适应目标检测方法研究 领域自适应目标检测 目标检测相关技术介绍
  • 数学建模之主成分分析(matlab算法)

    主成分分析是一种降维算法 它能将多个指标转换为少数几个主成分 这些主成分是原始变量的线性组合 且彼此之间互不相关 其能反映出原始数据的大部分信息 一般来说 当研究的问题涉及到多变量且变量之间存在很强的相关性时 我们可以考虑使用主成分分析的方
  • 非对称加密算法

    文章目录 概述 DH Diffie Hellman 秘钥交换算法 RSA 基于因子分解 ElGamal 基于离散对数 ECC Elliptical Curve Cryptography 椭圆曲线加密 概述 对称加密算法是因为秘钥的对称而由来
  • alpha shapes提取平面点云边界点

    1 原理介绍 由Edelsbrunner H提出的alpha shapes算法是一种简单 有效的快速提取边界点算法 其克服了点云边界点形状影响的缺点 可快速准确提取边界点 其原理如下 如下图所示 对于任意形状的平面点云 若一个半径为a的圆
  • 从零到一不一样的TOC商城项目:Cloud-Alibaba+DDD,私活利器开源

    刚果商城 不一样的商城系统 刚果商城是个从零到一的商城项目 包含商城核心业务和基础架构两大模块 参照商城系统原型 推出用户 消息 商品 订单 优惠券 支付 网关 购物车等业务模块 通过商城系统中复杂场景 给出对应解决方案 使用 DDD 模型
  • 瑞吉外卖项目1 + 源码

    目录 一 瑞吉外卖项目介绍 1 1 项目介绍 1 2 技术点 1 3 功能架构 项目中所用到的全部功能 1 4 角色 不同角色所对应的不同权限 二 开发环境搭建 2 1 创建项目对应的数据库 两种方式 2 1 1 图形界面创建库形式 2 1
  • 大二第二周总结

    问题 想到了之前追的辩论赛 主题是 被误解是表达者的宿命 反方认为被误解不是表达者的宿命 由于表达者表意含混造成误解的可能性是人力可控的 表达者可在真诚沟通的基础之上 根据对方反应不断调整语言 或者采用表情和肢体等表达方式 对于暂时未理解表
  • 当用了万恶的crontab -r命令后……

    明天计划上个新的应用 需要写脚本获取服务器状态并写入数据库 下午写完脚本 准备先放cron里测试一下 登进服务器 想敲crontab e加一条计划 结果手一滑就敲成了crontab r 然后就悲剧了 因为发现没有备份 没办法 想办法恢复吧
  • redis2txt-获取redis数据并存储到txt文件

    txt文件导入redis 单线程 多线程 list类型 redis cli h ip LRANGE key 0 999999 while read item do echo item gt gt tmp listdata txt done
  • 超全面的语音交互知识总结:从原理、场景到趋势

    1 什么是语音交互 语音交互 VUI 指的是人类与设备通过自然语音进行信息的传递 一次完整的语音交互需要经历ASR NLP Skill TTS的流程 1 ASR 用于将声学语音进行分析 并得到对应的文字或拼音信息 语音识别系统一般分训练和解
  • where not exists 避免重复插入SQL语句

    项目场景 避免重复插入SQL语句 insert into TABLE2 select from TABLE1 where not exists select 1 from TABLE2 where TABLE2 id TABLE1 id a
  • Ubuntu下chgrp的用法

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 每天一个linux命令链接 http www cnblogs com peida archive 2012 12 03 2799003 html 实例1 改变文件的群组属性
  • Elasticsearch中 match、match_phrase、query_string和term的区别

    一 text字段和keyword字段的区别 以下给出一个例子 首先建立一个索引和类型 引入一个keywork的字段 PUT my index mappings products properties name type keyword 然后
  • 华为OD机试 Java 实现【计算日期到天数转换】【牛客练习题】

    一 题目描述 根据输入的日期 计算是这一年的第几天 保证年份为4位数且日期合法 二 输入描述 输入一行 每行空格分割 分别是年 月 日 三 输出描述 输出是这一年的第几天 四 Java算法源码 public static void main
  • 200行代码实现Mini ASP.NET Core

    前言 在学习ASP NET Core源码过程中 偶然看见蒋金楠老师的ASP NET Core框架揭秘 不到200行代码实现了ASP NET Core Mini框架 针对框架本质进行了讲解 受益匪浅 本文结合ASP NET Core Mini