手写一个简版 asp.net core
动手写一个简版 asp.net core
Intro
之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp.net core 的理解 ,最近自己写了一个 MiniAspNetCore ,写篇文章总结一下。
HttpContext
HttpContext 可能是最为常用的一个类了,HttpContext 是请求上下文,包含了所有的请求信息以及响应信息,以及一些自定义的用于在不同中间件中传输数据的信息
来看一下 HttpContext 的定义:
public class HttpContext
{
public IServiceProvider RequestServices { get; set; }
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
public IFeatureCollection Features { get; set; }
public HttpContext(IFeatureCollection featureCollection)
{
Features = featureCollection;
Request = new HttpRequest(featureCollection);
Response = new HttpResponse(featureCollection);
}
}
HttpRequest 即为请求信息对象,包含了所有请求相关的信息,
HttpResponse 为响应信息对象,包含了请求对应的响应信息
RequestServices 为 asp.net core 里的RequestServices,代表当前请求的服务提供者,可以使用它来获取具体的服务实例
Features 为 asp.net core 里引入的对象,可以用来在不同中间件中传递信息和用来解耦合
,下面我们就来看下 HttpRequest 和 HttpResponse 是怎么实现的
HttpRequest:
public class HttpRequest
{
private readonly IRequestFeature _requestFeature;
public HttpRequest(IFeatureCollection featureCollection)
{
_requestFeature = featureCollection.Get<IRequestFeature>();
}
public Uri Url => _requestFeature.Url;
public NameValueCollection Headers => _requestFeature.Headers;
public string Method => _requestFeature.Method;
public string Host => _requestFeature.Url.Host;
public Stream Body => _requestFeature.Body;
}
HttpResponse:
public class HttpResponse
{
private readonly IResponseFeature _responseFeature;
public HttpResponse(IFeatureCollection featureCollection)
{
_responseFeature = featureCollection.Get<IResponseFeature>();
}
public bool ResponseStarted => _responseFeature.Body.Length > 0;
public int StatusCode
{
get => _responseFeature.StatusCode;
set => _responseFeature.StatusCode = value;
}
public async Task WriteAsync(byte[] responseBytes)
{
if (_responseFeature.StatusCode <= 0)
{
_responseFeature.StatusCode = 200;
}
if (responseBytes != null && responseBytes.Length > 0)
{
await _responseFeature.Body.WriteAsync(responseBytes);
}
}
}
Features
上面我们提到我们可以使用 Features 在不同中间件中传递信息和解耦合
由上面 HttpRequest/HttpResponse 的代码我们可以看出来,HttpRequest 和 HttpResponse 其实就是在 IRequestFeature 和 IResponseFeature 的基础上封装了一层,真正的核心其实是 IRequestFeature/IResponseFeature ,而这里使用接口就很好的实现了解耦,可以根据不同的 WebServer 使用不同的 RequestFeature/ResponseFeature,来看下 IRequestFeature/IResponseFeature 的实现
public interface IRequestFeature
{
Uri Url { get; }
string Method { get; }
NameValueCollection Headers { get; }
Stream Body { get; }
}
public interface IResponseFeature
{
public int StatusCode { get; set; }
NameValueCollection Headers { get; set; }
public Stream Body { get; }
}
这里的实现和 asp.net core 的实际的实现方式应该不同,asp.net core 里 Headers 同一个 Header 允许有多个值,asp.net core 里是 StringValues 来实现的,这里简单处理了,使用了一个
NameValueCollection对象
上面提到的 Features 是一个 IFeatureCollection 对象,相当于是一系列的 Feature 对象组成的,来看下 FeatureCollection 的定义:
public interface IFeatureCollection : IDictionary<Type, object> { }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection
{
}
这里 IFeatureCollection 直接实现 IDictionary<Type, object> ,通过一个字典 Feature 类型为 Key,Feature 对象为 Value 的字典来保存
为了方便使用,可以定义两个扩展方法来方便的Get/Set
public static class FeatureExtensions
{
public static IFeatureCollection Set<TFeature>(this IFeatureCollection featureCollection, TFeature feature)
{
featureCollection[typeof(TFeature)] = feature;
return featureCollection;
}
public static TFeature Get<TFeature>(this IFeatureCollection featureCollection)
{
var featureType = typeof(TFeature);
return featureCollection.ContainsKey(featureType) ? (TFeature)featureCollection[featureType] : default(TFeature);
}
}
Web服务器

上面我们已经提到了 Web 服务器通过 IRequestFeature/IResponseFeature 来实现不同 web 服务器和应用程序的解耦,web 服务器只需要提供自己的 RequestFeature/ResponseFeature 即可
为了抽象不同的 Web 服务器,我们需要定义一个 IServer 的抽象接口,定义如下:
public interface IServer
{
Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default);
}
IServer 定义了一个 StartAsync 方法,用来启动 Web服务器,
StartAsync 方法有两个参数,一个是 requestHandler,是一个用来处理请求的委托,另一个是取消令牌用来停止 web 服务器
示例使用了 HttpListener 来实现了一个简单 Web 服务器,HttpListenerServer 定义如下:
public class HttpListenerServer : IServer
{
private readonly HttpListener _listener;
private readonly IServiceProvider _serviceProvider;
public HttpListenerServer(IServiceProvider serviceProvider, IConfiguration configuration)
{
_listener = new HttpListener();
var urls = configuration.GetAppSetting("ASPNETCORE_URLS")?.Split(';');
if (urls != null && urls.Length > 0)
{
foreach (var url in urls
.Where(u => u.IsNotNullOrEmpty())
.Select(u => u.Trim())
.Distinct()
)
{
// Prefixes must end in a forward slash ("/")
// https://stackoverflow.com/questions/26157475/use-of-httplistener
_listener.Prefixes.Add(url.EndsWith("/") ? url : $"{url}/");
}
}
else
{
_listener.Prefixes.Add("http://localhost:5100/");
}
_serviceProvider = serviceProvider;
}
public async Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default)
{
_listener.Start();
if (_listener.IsListening)
{
Console.WriteLine("the server is listening on ");
Console.WriteLine(_listener.Prefixes.StringJoin(","));
}
while (!cancellationToken.IsCancellationRequested)
{
var listenerContext = await _listener.GetContextAsync();
var featureCollection = new FeatureCollection();
featureCollection.Set(listenerContext.GetRequestFeature());
featureCollection.Set(listenerContext.GetResponseFeature());
using (var scope = _serviceProvider.CreateScope())
{
var httpContext = new HttpContext(featureCollection)
{
RequestServices = scope.ServiceProvider,
};
await requestHandler(httpContext);
}
listenerContext.Response.Close();
}
_listener.Stop();
}
}
HttpListenerServer 实现的 RequestFeature/ResponseFeatue
public class HttpListenerRequestFeature : IRequestFeature
{
private readonly HttpListenerRequest _request;
public HttpListenerRequestFeature(HttpListenerContext listenerContext)
{
_request = listenerContext.Request;
}
public Uri Url => _request.Url;
public string Method => _request.HttpMethod;
public NameValueCollection Headers => _request.Headers;
public Stream Body => _request.InputStream;
}
public class HttpListenerResponseFeature : IResponseFeature
{
private readonly HttpListenerResponse _response;
public HttpListenerResponseFeature(HttpListenerContext httpListenerContext)
{
_response = httpListenerContext.Response;
}
public int StatusCode { get => _response.StatusCode; set => _response.StatusCode = value; }
public NameValueCollection Headers
{
get => _response.Headers;
set
{
_response.Headers = new WebHeaderCollection();
foreach (var key in value.AllKeys)
_response.Headers.Add(key, value[key]);
}
}
public Stream Body => _response.OutputStream;
}
为了方便使用,为 HttpListenerContext 定义了两个扩展方法,就是上面 HttpListenerServer 中的 GetRequestFeature/GetResponseFeature:
public static class HttpListenerContextExtensions
{
public static IRequestFeature GetRequestFeature(this HttpListenerContext context)
{
return new HttpListenerRequestFeature(context);
}
public static IResponseFeature GetResponseFeature(this HttpListenerContext context)
{
return new HttpListenerResponseFeature(context);
}
}
RequestDelegate
在上面的 IServer 定义里有一个 requestHandler 的 对象,在 asp.net core 里是一个名称为 RequestDelegate 的对象,而用来构建这个委托的在 asp.net core 里是 IApplicationBuilder,这些在蒋老师和 Edison 的文章和代码里都可以看到,这里我们只是简单介绍下,我在 MiniAspNetCore 的示例中没有使用这些对象,而是使用了自己抽象的 PipelineBuilder 和原始委托实现的
asp.net core 里 RequestDelegate 定义:
public delegate Task RequestDelegate(HttpContext context);
其实和我们上面定义用的 Func<HttpContext, Task> 是等价的
IApplicationBuilder 定义:
/// <summary>
/// Defines a class that provides the mechanisms to configure an application's request pipeline.
/// </summary>
public interface IApplicationBuilder
{
/// <summary>
/// Gets or sets the <see cref="T:System.IServiceProvider" /> that provides access to the application's service container.
/// </summary>
IServiceProvider ApplicationServices { get; set; }
/// <summary>
/// Gets the set of HTTP features the application's server provides.
/// </summary>
IFeatureCollection ServerFeatures { get; }
/// <summary>
/// Gets a key/value collection that can be used to share data between middleware.
/// </summary>
IDictionary<string, object> Properties { get; }
/// <summary>
/// Adds a middleware delegate to the application's request pipeline.
/// </summary>
/// <param name="middleware">The middleware delegate.</param>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
/// <summary>
/// Creates a new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> that shares the <see cref="P:Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties" /> of this
/// <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.
/// </summary>
/// <returns>The new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
IApplicationBuilder New();
/// <summary>
/// Builds the delegate used by this application to process HTTP requests.
/// </summary>
/// <returns>The request handling delegate.</returns>
RequestDelegate Build();
}
我们这里没有定义 IApplicationBuilder,使用了简化抽象的 IAsyncPipelineBuilder,定义如下:
public interface IAsyncPipelineBuilder<TContext>
{
IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware);
Func<TContext, Task> Build();
IAsyncPipelineBuilder<TContext> New();
}
对于 asp.net core 的中间件来说 ,上面的 TContext 就是 HttpContext,替换之后也就是下面这样的:
public interface IAsyncPipelineBuilder<HttpContext>
{
IAsyncPipelineBuilder<HttpContext> Use(Func<Func<HttpContext, Task>, Func<HttpContext, Task>> middleware);
Func<HttpContext, Task> Build();
IAsyncPipelineBuilder<HttpContext> New();
}
是不是和 IApplicationBuilder 很像,如果不像可以进一步把 Func<HttpContext, Task> 使用 RequestDelegate 替换
public interface IAsyncPipelineBuilder<HttpContext>
{
IAsyncPipelineBuilder<HttpContext> Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
IAsyncPipelineBuilder<HttpContext> New();
}
最后再将接口名称替换一下:
public interface IApplicationBuilder1
{
IApplicationBuilder1 Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
IApplicationBuilder1 New();
}
至此,就完全可以看出来了,这 IAsyncPipelineBuilder<HttpContext> 就是一个简版的 IApplicationBuilder
IAsyncPipelineBuilder 和 IApplicationBuilder 的作用是将注册的多个中间件构建成一个请求处理的委托

中间件处理流程:

更多关于 PipelineBuilder 构建中间件的信息可以查看 让 .NET 轻松构建中间件模式代码 了解更多
WebHost
通过除了 Web 服务器之外,还有一个 Web Host 的概念,可以简单的这样理解,一个 Web 服务器上可以有多个 Web Host,就像 IIS/nginx (Web Server) 可以 host 多个站点
可以说 WebHost 离我们的应用更近,所以我们还需要 IHost 来托管应用
public interface IHost
{
Task RunAsync(CancellationToken cancellationToken = default);
}
WebHost 定义:
public class WebHost : IHost
{
private readonly Func<HttpContext, Task> _requestDelegate;
private readonly IServer _server;
public WebHost(IServiceProvider serviceProvider, Func<HttpContext, Task> requestDelegate)
{
_requestDelegate = requestDelegate;
_server = serviceProvider.GetRequiredService<IServer>();
}
public async Task RunAsync(CancellationToken cancellationToken = default)
{
await _server.StartAsync(_requestDelegate, cancellationToken).ConfigureAwait(false);
}
}
为了方便的构建 Host对象,引入了 HostBuilder 来方便的构建一个 Host,定义如下:
public interface IHostBuilder
{
IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction);
IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction);
IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction);
IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction);
IHost Build();
}
WebHostBuilder:
public class WebHostBuilder : IHostBuilder
{
private readonly IConfigurationBuilder _configurationBuilder = new ConfigurationBuilder();
private readonly IServiceCollection _serviceCollection = new ServiceCollection();
private Action<IConfiguration, IServiceProvider> _initAction = null;
private readonly IAsyncPipelineBuilder<HttpContext> _requestPipeline = PipelineBuilder.CreateAsync<HttpContext>(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
public IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction)
{
configAction?.Invoke(_configurationBuilder);
return this;
}
public IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction)
{
if (null != configureAction)
{
var configuration = _configurationBuilder.Build();
configureAction.Invoke(configuration, _serviceCollection);
}
return this;
}
public IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction)
{
if (null != configureAction)
{
var configuration = _configurationBuilder.Build();
configureAction.Invoke(configuration, _requestPipeline);
}
return this;
}
public IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction)
{
if (null != initAction)
{
_initAction = initAction;
}
return this;
}
public IHost Build()
{
var configuration = _configurationBuilder.Build();
_serviceCollection.AddSingleton<IConfiguration>(configuration);
var serviceProvider = _serviceCollection.BuildServiceProvider();
_initAction?.Invoke(configuration, serviceProvider);
return new WebHost(serviceProvider, _requestPipeline.Build());
}
public static WebHostBuilder CreateDefault(string[] args)
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder
.ConfigureConfiguration(builder => builder.AddJsonFile("appsettings.json", true, true))
.UseHttpListenerServer()
;
return webHostBuilder;
}
}
这里的示例我在
IHostBuilder里增加了一个Initialize的方法来做一些初始化的操作,我觉得有些数据初始化配置初始化等操作应该在这里操作,而不应该在Startup的Configure方法里处理,这样Configure方法可以更纯粹一些,只配置 asp.net core 的请求管道,这纯属个人意见,没有对错之分这里 Host 的实现和 asp.net core 的实现不同,有需要的可以深究源码,在 asp.net core 2.x 的版本里是有一个
IWebHost的,在 asp.net core 3.x 以及 .net 5 里是没有IWebHost的取而代之的是通用主机IHost, 通过实现了一个IHostedService来实现WebHost的
Run
运行示例代码:
public class Program
{
private static readonly CancellationTokenSource Cts = new CancellationTokenSource();
public static async Task Main(string[] args)
{
Console.CancelKeyPress += OnExit;
var host = WebHostBuilder.CreateDefault(args)
.ConfigureServices((configuration, services) =>
{
})
.ConfigureApplication((configuration, app) =>
{
app.When(context => context.Request.Url.PathAndQuery.StartsWith("/favicon.ico"), pipeline => { });
app.When(context => context.Request.Url.PathAndQuery.Contains("test"),
p => { p.Run(context => context.Response.WriteAsync("test")); });
app
.Use(async (context, next) =>
{
await context.Response.WriteLineAsync($"middleware1, requestPath:{context.Request.Url.AbsolutePath}");
await next();
})
.Use(async (context, next) =>
{
await context.Response.WriteLineAsync($"middleware2, requestPath:{context.Request.Url.AbsolutePath}");
await next();
})
.Use(async (context, next) =>
{
await context.Response.WriteLineAsync($"middleware3, requestPath:{context.Request.Url.AbsolutePath}");
await next();
})
;
app.Run(context => context.Response.WriteAsync("Hello Mini Asp.Net Core"));
})
.Initialize((configuration, services) =>
{
})
.Build();
await host.RunAsync(Cts.Token);
}
private static void OnExit(object sender, EventArgs e)
{
Console.WriteLine("exiting ...");
Cts.Cancel();
}
}
在示例项目目录下执行 dotnet run,并访问 http://localhost:5100/:

仔细观察浏览器 console 或 network 的话,会发现还有一个请求,浏览器会默认请求 /favicon.ico 获取网站的图标

因为我们针对这个请求没有任何中间件的处理,所以直接返回了 404
在访问 /test,可以看到和刚才的输出完全不同,因为这个请求走了另外一个分支,相当于 asp.net core 里 Map/MapWhen 的效果,另外 Run 代表里中间件的中断,不会执行后续的中间件

More
上面的实现只是我在尝试写一个简版的 asp.net core 框架时的实现,和 asp.net core 的实现并不完全一样,如果需要请参考源码,上面的实现仅供参考,上面实现的源码可以在 Github 上获取 https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore
asp.net core 源码:https://github.com/dotnet/aspnetcore
Reference
- https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
- https://www.cnblogs.com/artech/p/mini-asp-net-core-3x.html
- https://www.cnblogs.com/edisonchou/p/aspnet_core_mini_implemention_introduction.html
- https://www.cnblogs.com/weihanli/p/12700006.html
- https://www.cnblogs.com/weihanli/p/12709603.html
- https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore
手写一个简版 asp.net core的更多相关文章
- 极简版ASP.NET Core学习路径及教程
绝承认这是一个七天速成教程,即使有这个效果,我也不愿意接受这个名字.嗯. 这个路径分为两块: 实践入门 理论延伸 有了ASP.NET以及C#的知识以及项目经验,我们几乎可以不再需要了解任何新的知识就开 ...
- 来,我们手写一个简易版的mock.js吧(模拟fetch && Ajax请求)
预期的mock的使用方式 首先我们从使用的角度出发,思考编码过程 M1. 通过配置文件配置url和response M2. 自动检测环境为开发环境时启动Mock.js M3. mock代码能直接覆盖g ...
- 手写一个简单版的SpringMVC
一 写在前面 这是自己实现一个简单的具有SpringMVC功能的小Demo,主要实现效果是; 自己定义的实现效果是通过浏览器地址传一个name参数,打印“my name is”+name参数.不使用S ...
- 手写一个简易版Tomcat
前言 Tomcat Write MyTomcat Tomcat是非常流行的Web Server,它还是一个满足Servlet规范的容器.那么想一想,Tomcat和我们的Web应用是什么关系? 从感性上 ...
- 手写一个虚拟DOM库,彻底让你理解diff算法
所谓虚拟DOM就是用js对象来描述真实DOM,它相对于原生DOM更加轻量,因为真正的DOM对象附带有非常多的属性,另外配合虚拟DOM的diff算法,能以最少的操作来更新DOM,除此之外,也能让Vue和 ...
- 一个Mini的ASP.NET Core框架的实现
一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...
- 手写一个webpack,看看AST怎么用
本文开始我会围绕webpack和babel写一系列的工程化文章,这两个工具我虽然天天用,但是对他们的原理理解的其实不是很深入,写这些文章的过程其实也是我深入学习的过程.由于webpack和babel的 ...
- 教你如何使用Java手写一个基于链表的队列
在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...
- 动手写一个简单版的谷歌TPU-矩阵乘法和卷积
谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...
随机推荐
- Shodan使用简述
申明 本文只做相关介绍,使用者应当严格自律,承诺遵守法律法规 Shodan,一款互联网下的可怕搜索引擎.它的可怕之处在于Shodan可以搜索各种在线的网络设备.比如:摄像头.路由器.打印机.服 ...
- Vue Cli 3 搭建单页应用项目刷新 404 问题 解决方案(以Apache为例)
vue 项目 版本 Vue Cli 3.3 官方文档 https://router.vuejs.org/zh/guide/essentials/history-mode.html 因为本项目部署在 A ...
- Netty随记之ChannelInboundHandlerAdapter、SimpleChannelInboundHandler
ChannelInboundHandlerAdapter ChannelInboundHandlerAdapter是ChannelInboundHandler的一个简单实现,默认情况下不会做任何处理, ...
- #Week1 Introduction
一.What is Machine Learning 课程里主要给了两个供参考的定义: By Arthur Samuel: Field of study that gives computers th ...
- C++编程入门题目--No.2
题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高 于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可可提 成7.5%:20万到4 ...
- TestNG测试用例重跑详解及实践优化
测试用例运行稳定性是自动化质量的一个重要指标,在运行中需要尽可能的剔除非bug造成的测试用例执行失败,对于失败用例进行重跑是常用策略之一.一种重跑策略是所有用例运行结束后对失败用例重跑,另一种重跑策略 ...
- Java——多线程之线程间通信
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
- 最小点覆盖(König定理)
König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数.如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有 ...
- Vue实现靠边悬浮球(PC端)
我想把退出登录的按钮做成一个悬浮球的样子,带动画的那种. 实现是这个样子: 手边没有球形图.随便找一个,功能这里演示的为单机悬浮球注销登录 嗯,具体代码: <div :class="[ ...
- Spring Cloud学习 之 Spring Cloud Ribbon(执行流程源码分析)
Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 文章目录 分析: 总结: 分析: 在上篇文章中,我们着重分析了RestTempla ...