写一个简版 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 在不同中间件中传递信息和解耦合

由上面 HttpRequestHttpResponse 的代码我们可以看出来, HttpRequest 和 HttpResponse 其实就是在 IRequestFeature 和 IResponseFeature 的基础上封装了一层,真正的核心其实是 IRequestFeatureIResponseFeature ,而这里使用接口就很好的实现了解耦,可以根据不同的 WebServer 使用不同的 RequestFeatureResponseFeature,来看下 IRequestFeatureIResponseFeature 的实现

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 服务器通过 IRequestFeatureIResponseFeature 来实现不同 web 服务器和应用程序的解耦,web 服务器只需要提供自己的 RequestFeatureResponseFeature 即可

为了抽象不同的 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 实现的 RequestFeatureResponseFeatue

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 中的 GetRequestFeatureGetResponseFeature

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 里 MapMapWhen 的效果,另外 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

  • 让 .NET 轻松构建中间件模式代码

  • 让 .NET 轻松构建中间件模式代码(二)

  • https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++11]独占的智能指针unique_ptr的删除器

unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的&#xff0c;unique_ptr 指定删除器的时候需要确定删除器的类型&#xff0c;所以不能像 shared_ptr 那样直接指定删除器&#xff0c;举例说明&#xff1a; 代码如下: #include <iostream> #include <memory…

如何在Windows上使用Git创建一个可执行脚本?

长话短说&#xff0c;今天介绍如何在windows上使用Git上创建一个可执行的shell脚本。“首先我们要知道windows上Git默认添加的文件权限是:-rw-r--r--(对应权限值是644)&#xff0c;而通常创建的shell脚本都希望天然可执行&#xff0c;故有必要在Windows上使用Git管理shell脚本时…

[C++11]共享智能指针shared_ptr指定删除器

当智能指针管理的内存对应的引用计数变为 0 的时候&#xff0c;这块内存就会被智能指针析构掉了。另外&#xff0c;我们在初始化智能指针的时候也可以自己指定删除动作&#xff0c;这个删除操作对应的函数被称之为删除器&#xff0c;这个删除器函数本质是一个回调函数&#xff…

基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁

在开始本篇正文之前&#xff0c;解决一个 疯疯过 指出的错误&#xff0c;再次感谢指正。步骤如下&#xff1a;删掉.Domain.Shared层中的项目引用&#xff0c;添加nuget依赖包Volo.Abp.Identity.Domain.Shared&#xff0c;可以使用命令&#xff1a;Install-Package Volo.Abp.Ide…

[C++11]独占的智能指针unique_ptr的初始化和使用

std::unique_ptr 是一个独占型的智能指针&#xff0c;它不允许其他的智能指针共享其内部的指针&#xff0c;可以通过它的构造函数初始化一个独占智能指针对象&#xff0c;但是不允许通过赋值将一个 unique_ptr 赋值给另一个 unique_ptr。std::unique_ptr 不允许复制&#xff0c…

Abp v2.8.0发布 路线图

ABP框架和ABP商业版v2.8已经发布.这篇文章将涵盖这些发布中的新增内容和项目的中期路线图.ABP框架2.8有哪些新增内容?你可在GitHub的发行说明中看到所有的变更.这篇博客只包括重要的一些功能/变更.SignalR集成包我们已经发布了一个新的包用来集成SignalR到基于ABP框架应用程序…

贵州大学计算机专业的导师是谁,贵州大学计算机科学与信息学院导师介绍:王以松...

贵州大学计算机科学与信息学院导师介绍&#xff1a;王以松王以松&#xff0c;男&#xff0c;副教授&#xff0c;硕士研究生导师。主要研究方向&#xff1a;人工智能(知识表示与推理、逻辑程序设计)&#xff0c;语义网络等。 Em作者佚名次阅读2012-01-04王以松&#xff0c;男&am…

BitArray虽好,但请不要滥用,又一次线上内存暴增排查

一&#xff1a;背景1. 讲故事前天写了一篇大内存排查在园子里挺火&#xff0c;这是做自媒体最开心的事拉&#xff0c;干脆再来一篇满足大家胃口&#xff0c;上个月我写了一篇博客提到过使用bitmap对原来的List<CustomerID>进行高强度压缩&#xff0c;将原来的List内存压缩…

[翻译]用于.NET Core的Windows窗体设计器发布

本文由微信公众号《开发者精选资讯》翻译首发&#xff0c;转载请注明来源今天我们很高兴地宣布&#xff0c;.NET Core 项目的 Windows 窗体设计器现在可以在 Visual Studio 2019 16.6 版中作为预览使用&#xff01;我们在 Visual Studio 16.7 预览版 1 中也提供了更新的设计器版…

【视频回放与课件】零基础入门AI开发

今天上午&#xff0c;受广州图书馆邀请&#xff0c;在第一讲《零代码上手人工智能》的基础上&#xff0c;以《零基础入门AI开发》为主题&#xff0c;分四步解锁人工智能学习的概念与开发工具&#xff0c;让您在一小时内轻松掌握人工智能开发要领。本次课程内容主要包括&#xf…

Redis背后的故事

导语Redis已成为世界上最受欢迎的数据库之一&#xff0c;但当初正是因为Sanfilippo对数据库“缺乏经验”&#xff0c;使他敢于打破“良好”数据库工程的各种神圣规则&#xff0c;创建了Redis。正文如果Redis之父萨尔瓦多桑菲利波普&#xff08;Salvatore Sanfilippo&#xff09…

C++实现AOE网中的关键路径算法(邻接表存储)

代码如下: #include <iostream> #include <stack> #include <string> using namespace std; const int N 10010; using vnodeType int;typedef struct Node {int adj;int tw;//弧的时间权值Node *next; }Node;typedef struct Vnode {vnodeType v;//存储图…

Minimal Square CodeForces - 1360A(简单思维和图形判断)

题意&#xff1a; 给你两个大小一样的&#xff0c;边长为a&#xff0c;b的矩形将其放入一个正方形里&#xff0c;问怎样放可以使正方形面积最小&#xff08;要求正方形边和矩形边平行&#xff09; 题目&#xff1a; Find the minimum area of a square land on which you ca…

基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API

上一篇文章再次把Swagger的使用进行了讲解&#xff0c;完成了对Swagger的分组、描述和开启小绿锁以进行身份的认证授权&#xff0c;那么本篇就来说说身份认证授权。开始之前先搞清楚几个概念&#xff0c;请注意认证与授权是不同的意思&#xff0c;简单理解&#xff1a;认证&…