加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

动手写一个简版 asp.net core

发布时间:2020-12-16 09:03:07 所属栏目:asp.Net 来源:网络整理
导读:动手写一个简版 asp.net core Intro 之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp.net core 的理解 ,最近自己写了一个 MiniAspNetCore ,写篇文章总结一下。 HttpContext Http

动手写一个简版 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 里引入的对象,可以用来在不同中间件中传递信息和用来解耦合

,下面我们就来看下 HttpRequestHttpResponse 是怎么实现的

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 的代码我们可以看出来,HttpRequestHttpResponse 其实就是在 IRequestFeatureIResponseFeature 的基础上封装了一层,真正的核心其实是 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,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,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

IAsyncPipelineBuilderIApplicationBuilder 的作用是将注册的多个中间件构建成一个请求处理的委托

中间件处理流程:

更多关于 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,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 的方法来做一些初始化的操作,我觉得有些数据初始化配置初始化等操作应该在这里操作,而不应该在 StartupConfigure 方法里处理,这样 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,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/:

仔细观察浏览器 consolenetwork 的话,会发现还有一个请求,浏览器会默认请求 /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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读