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

在现代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