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

在现代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
其他阅读

vscode Material Design Theme

Material Design Theme 是由猪头少年(scung-cn)开发的一套基于 Material Design 设计语言的 Visual Studio Code 主题插件,可以在扩展市场上直接下载安装。

查看原文

浅析web前端中的MVC模式

MVC是常见的软件架构设计模式,它通过分离关注点改进代码的组织方式。区别于软件设计模式,只是为了解决问题总结出的抽象方法,一种架构模式种往往会用到多种设计模式。

查看原文

Linux中查看,添加,修改,删除用户和用户组

将用户分组是Linux系统中对用户进行管理及控制访问权限的一种手段。某个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不同的组。当一个用户同时是多个组中的成员时,登录时所属的为默认组,而其他组称为附加组。本文将会介绍在 Linux 中查看,添加,修改,删除用户和用户组,注意:权限管理非常重要,可能一不小心就导致系统无法登录,请谨慎操作

查看原文

使用C#接入DeepSeek API实现自己的AI助手

过年期间DeepSeek非常火爆,这段时间用着官方的客户端访问,总是会提示“服务器繁忙,请稍后再试。”,本文介绍怎么通过接入DeepSeek的API实现自己的客户端。

查看原文

Js使用原型链对对象进行扩展

在C#的扩展方法中,我们了解到了一种不需要修改源对象定义即可为对象添加新的行为的方法,在JavaScript中,我们通过原型链也可以实现类似的效果,为对象添加新的行为。需要一定的Js原型链基础。

查看原文