管道技术——中间件的灵魂
在现代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;
}
}
}