前言
在asp.net core web api项目中,默认提供了很多的中间件,比如访问静态文件中间件UseStaticFiles
,跨域配置中间件UseCors
,路由中间件UseRouting
,身份验证中间件UseAuthentication
。
那么如何添加一些自定义的中间件呢。
需求
现在有一个需求,我们的所有接口中都有一个
TimeSpan
参数,传入的是当前时间的时间戳,正常需要对时间戳进行加密,然后在加一个统一的验证方法,只正常处理2分钟以内的请求,超时的请求不在处理,直接返回错误代码,这样可以一定程度上保护我们的业务数据。
这时我们就可以添加一个自定义的中间件,对所有过来的请求先进行时间戳校验处理,处理通过的再返回到业务逻辑正常处理,时间戳校验不通过的则直接返回错误码。
实现
接下来看实现。
为了演示,我还是新建一个空的asp.net core web api项目。然后调用WeatherForecastController
下的Get
方法来做测试。
然后添加一个类,为了简单点,这个类就一个TimeSpan
参数。
public class BaseRequest{public string TimeSpan { get; set; }}
为了方便的使用中间件,我们希望可以直接在Program下的Main函数里直接调用。类似这样。
public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();var app = builder.Build();//这里是自定义添加的中间件app.UseRequestCheckMiddleware();app.UseAuthorization();app.MapControllers();app.Run();}
Startup里添加原理一样。
所以首先我需要添加一个ApplicationBuilder
的扩展方法。这样才能调用方法一样用.
出来。
添加一个ApplicationBuilderExtension
类。
public static class ApplicationBuilderExtension
{public static IApplicationBuilder UseRequestCheckMiddleware(this IApplicationBuilder builder){return builder.UseMiddleware<RequestCheckMiddleware>();}}
在这个类里通过builder.UseMiddleware
传入一个实现类,就可以实现中间件添加的效果了,如果想添加多个自定义的中间件,可以继续添加新的Use
方法。
接下来重点就是RequestCheckMiddleware
的实现。
public class RequestCheckMiddleware
{private readonly RequestDelegate _next;public RequestCheckMiddleware(RequestDelegate next){_next= next;}public async Task InvokeAsync(HttpContext context){HttpRequest request = context.Request;//缓存下来允许多次读取request.EnableBuffering();var reader = new StreamReader(request.Body, Encoding.UTF8);string data = await reader.ReadToEndAsync();// 重置流的位置以便后续中间件可以读取 request.Body.Position = 0;try{var inputJson = JsonSerializer.Deserialize<BaseRequest>(data);// 假设 BaseRequest.TimeSpan 是一个 long 类型的 UNIX 时间戳 if (string.IsNullOrEmpty(inputJson.TimeSpan)){await HandleError(context, 500, "时间戳为空!");return;}var requestTime = UnixTimeStampToDateTime(Convert.ToInt64(inputJson.TimeSpan));if (DateTime.Now - requestTime > TimeSpan.FromMinutes(2)){await HandleError(context, 429, "超时请求!");return;}await _next(context);}catch (Exception ex){await HandleError(context, 400, $"处理请求失败: {ex.Message}");}}private async Task HandleError(HttpContext context, int statusCode, string message){context.Response.StatusCode = statusCode;var result = new { code = statusCode, message = message, result = new object() };await context.Response.WriteAsync(JsonSerializer.Serialize(result));}private DateTime UnixTimeStampToDateTime(long unixTimeStamp){// UNIX 时间戳转换为 DateTime DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);dateTime = dateTime.AddSeconds(unixTimeStamp).ToLocalTime();return dateTime;}
}
这里有几点可以解释一下。
1、这里的主函数名必须是Invoke
或者InvokeAsync
,且入参是HttpContext
。表示这是在请求管道中对请求进行处理的中间件。
2、这里需要定义RequestDelegate
的委托,因为需要在当前逻辑处理完成后,还需要把请求传递到下一步。
3、request.Body
默认只能被读取一次,为了传递到下一步依然有原模原样的请求参数,所以需要先对请求进行缓存处理。读取完成之后,需要把流的位置重置到开始。方便后面可以再次读取请求内容。
然后在Program里添加对应中间件就行了。
//这里是自定义添加的中间件app.UseRequestCheckMiddleware();
验证
最后来演示一下效果。
首先传递一个2分钟内正常的时间戳。
请求可以正常返回。
接着等一会吧,等时间戳过期。
结语
Study hard and make progress every day.
欢迎关注下方微信公众号,一起学习,一起娱乐,一起进步,点击卡片可以查看公众号二维码哟。