管道技术——中间件的灵魂

在现代Web开发中,中间件技术使用越来越广泛,本文带大家了解中间件的基础,同时也是中间件的灵魂所在,管道技术。在C#中,依赖于委托,我们可以很容易就实现一个中间件管道。所以在阅读本文前,请确保你已经学会了什么是委托,包括但不限于Delegate,Action,Func。除此之外,本文还会使用到反射相关知识,请确保你已经学会了什么是反射。

入门介绍

阅读MSDN文档中,关于自定义中间件这一部分。里面提到使用类来实现的时候,类大概样子如下

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // 写入自定义内容
             ....
            // 走向管道下一中间件
            await _next(context);
        }
    }
}

观察如上代码可以得出

  • 中间件类有一个构造器
  • 中间件类有一个Invoke或者InvokeAsync的方法,参数为HttpContext
  • 有一个RequestDelegate表示管道下一任务

编写自定义管道

下面简单实现一个管道,传入参数为一个int类型参数,效果为每当参数经过管道一个节点,就会 +1,然后打印到控制台。

中间件节点类

依照着AspNetCore的例子编写两个节点如下,每个中间件类具备上述三点内容。明明委托的类型和传入的参数就不一样啊!

AppMiddleware

using System;

namespace Middleware
{
    public class AppMiddleware
    {
        private readonly Action<int> _next;

        public AppMiddleware(Action<int> next)
        {
            _next = next;
        }

        public void Invoke(int value)
        {
            Console.WriteLine($"App Start : {value}");
            value += 1;
            _next(value);
            Console.WriteLine("App End");
        }
    }
}

TestMiddleware

using System;

namespace Middleware
{
    public class TestMiddleware
    {
        private readonly Action<int> _next;

        public TestMiddleware(Action<int> next)
        {
            _next = next;
        }

        public void Invoke(int value)
        {
            Console.WriteLine($"Test Start : {value}");
            value += 1;
            _next(value);
            Console.WriteLine("Test End");
        }
    }
}

管道构建类

MiddlewareBuilder类里同样有一个Action<int>的委托,这和中间件类中的一样。在程序开始时,会根据_types中注册过的中间件类来形成执行管道,管道的执行顺序,按照执行AddMiddleware方法的顺序。管道的起点是第一个注册的中间件类,终点则是自身的_action

using System;
using System.Collections.Generic;
using System.Linq;

namespace Middleware
{
    public class MiddlewareBuilder
    {
        private Action<int> _action;
        private List<Type> _types = new List<Type>();

        public MiddlewareBuilder AddAction(Action<int> action)
        {
            _action = action;
            return this;
        }

        public MiddlewareBuilder AddMiddleware<TMiddleware>() where TMiddleware : class
        {
            _types.Add(typeof(TMiddleware));
            return this;
        }

        public Action<int> Build()
        {
            return Create(0);

            Action<int> Create(int index)
            {
                if (index < _types.Count - 1)
                {
                    var child = Create(index + 1);
                    var pipe = Activator.CreateInstance(_types[index], child);
                    if (pipe.GetType().GetMethods().FirstOrDefault(x => x.Name == "Invoke") is not null)
                    {
                        return pipe.GetType().GetMethod("Invoke").CreateDelegate<Action<int>>(pipe);
                    }
                }
                else
                {
                    var final = Activator.CreateInstance(_types[index], _action);
                    if (final.GetType().GetMethods().FirstOrDefault(x => x.Name == "Invoke") is not null)
                    {
                        //反射出Invoke方法生成委托
                        return final.GetType().GetMethod("Invoke").CreateDelegate<Action<int>>(final);
                    }
                }
                return null;
            }
        }
    }
}

启动

Program中,首先实例化出MiddlewareBuilder,然后依次先添加终点执行委托,再依次添加中间件(严格按照执行顺序),最后调用即可

using System;

namespace Middleware
{
    class Program
    {
        static void Main(string[] args)
        {
            MiddlewareBuilder builder = new();

            builder.AddAction(x => Console.WriteLine($"Exec {x}"));
            builder.AddApp();
            builder.AddTest();

            builder.Build().Invoke(1);
        }
    }
}

// 控制台输入
App Start : 1
Test Start : 2
Exec 3
Test End
App End

扩展方法

鉴于可能有小伙伴没了解到扩展方法,可能要问了,你不是定义了一个泛型方法AddMiddleware<TMiddleware>(),怎么到了下面写的就不一样了,可以去查看一下扩展方法

namespace Middleware
{
    public static class MiddlewareExtension
    {
        public static MiddlewareBuilder AddApp(this MiddlewareBuilder builder)
        {
            builder.AddMiddleware<AppMiddleware>();
            return builder;
        }

        public static MiddlewareBuilder AddTest(this MiddlewareBuilder builder)
        {
            builder.AddMiddleware<TestMiddleware>();
            return builder;
        }
    }
}

扩展

MiddlewareBuilder中,使用反射生成对象后,又使用反射将该对象的Invoke方法生成委托进行嵌套,两次反射加上判断需要消耗大量的性能。解决方案可以使用一个基类或者接口来进行强约束。在我印象中,曾经的Asp.Net Core编写中间件时,就是需要继承一个对应接口(或是类?),不知为何改成现在这样,也有可能是我记错了吧。

基类约束

BaseMiddleware抽象类,其中一个只允许子类访问的委托_next,一个用于重写的抽象方法Inoke

using System;

namespace Middleware
{
    public abstract class BaseMiddleware
    {
        protected Action<int> _next;

        public AppMiddleware(Action<int> next)
        {
            _next = next;
        }

        public abstract void Invoke(int value)
    }
}

子类只需要继承并实现方法即可。

public class AppMiddleware : BaseMiddleware
{
    public AppMiddleware(Action<int> next) : base(next) { }

    public override void Invoke(int value)
    {
        Console.WriteLine($"App Start : {value}");
        value += 1;
        _next(value);
        Console.WriteLine("App End");
    }
}

在构建管道时,使用基类型强转只需要反射一次即可得到Invoke委托

public class MiddlewareBuilder
{
    ......
    public MiddlewareBuilder AddMiddleware<TMiddleware>() where TMiddleware : BaseMiddleware
    {
        _types.Add(typeof(TMiddleware));
        return this;
    }

    public Action<int> Build()
    {
        return Create(0);

        Action<int> Create(int index)
        {
            if (index < _types.Count - 1)
            {
                var child = Create(index + 1);
                var pipe = (BaseMiddleware)Activator.CreateInstance(_types[index], child);
                return pipe.Invoke;
            }
            else
            {
                var final = (BaseMiddleware)Activator.CreateInstance(_types[index], _action);
                return final.Invoke;
            }
            return null;
        }
    }
}
发布时间:2021-05-24
其他阅读

在ASP.NET Core中使用过滤器

ASP.NET Core是一个高性能的Web开发框架,过滤器(Filter)在ASP.NET Core中被用于路由方法之上,可以在路由方法执行前后完成一些额外的操作。本文将会介绍在ASP.NET Core中使用 IActionFilter 实现过滤器并读取路由方法中的参数。

查看原文

asp.net core实现一个反向代理

本文将向你展示如何在C#和ASP.NET Core中实现一个反向代理功能。

查看原文

HTTP方法

HTTP报文中包含了方法,指的是客户端希望服务器对资源执行的动作,是一个单独的词,比如GET,POST,OPTIONS等,本文将会介绍主流的几种方法。

查看原文

命令行打包.net项目

.net 日常开发中,我们接触最多的就是 Visual Studio ,它是微软为了 .net 平台专门打造的 IDE (集成开发环境),为整个 .net 平台开发带来了无与伦比的图形化体验,但是有时候,我们也会遇到需要通过命令行来生成 .net 项目的情况,本文会介绍几种命令行打包的姿势。

查看原文

网页上通过超链接直接打开PC应用

有时候我们会发现有些网页可以直接打开本地应用,比如在百度网盘网页版下载文件时,会自动打开本地的百度网盘软件。Visual Studio Code打开浏览器认证后也会转到本地引用,Unity官网打开本地的Unity Hub应用进行Unity的下载和更新等。

查看原文