有一个需求,需要在读取request中的参数,记录日志,然后返回缓存内容。为啥不用url?因为这个参数比较复杂,比如json字符串,无法在url中传递。
首先写一个ResourceFilter
using Castle.Core.Resource;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using TournamentAuthCenter.Utility.ApiResult;
using TournamentAuthCenter.Utility.HttpHandle;
using TournamentAuthCenter.ViewModel.Request;
namespace TestResourseFilter.Utility.CacheHandler
{
/// <summary>
/// 同步版
/// </summary>
public class ClentAccessResourceAsyncFilter : Attribute, IAsyncResourceFilter
{
/// <summary>
/// 缓存的区域--定义的保存数据的区域
/// </summary>
private readonly IMemoryCache _memoryCache;
private ILogger<ClentAccessResourceAsyncFilter> _logger;
public ClentAccessResourceAsyncFilter(
IMemoryCache memoryCache,
ILogger<ClentAccessResourceAsyncFilter> logger)
{
_memoryCache = memoryCache;
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
//string key = context.HttpContext.Request.Path;
string key = string.Empty;
string body = string.Empty;
HttpRequest request = context.HttpContext.Request;
// 重点在这里
context.HttpContext.Request.EnableBuffering();
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
using (var ms = new MemoryStream())
{
request.Body.CopyTo(ms);
var b = ms.ToArray();
body = Encoding.UTF8.GetString(b);
}
// ClientPostModel Post Action的
入参模型
ClientPostModel postModel = JsonConvert.DeserializeObject<ClientPostModel>(body);
if (postModel.ClientName is not null)
{
key = $"{context.HttpContext.Request.Path}/{postModel.ClientName}";//请求的路径
_logger.LogInformation($"ClientAccess:key:{key},deviceinfo:{postModel.ClientDeviceInfo}");
}
else
{
throw new Exception("parameter is null");
}
//在这里就应该判断缓存
var data = _memoryCache.Get(key);
if (data != null)
{
context.Result = (IActionResult)data;
}
else{
// 重新开启流的可读性,并将指针置0
context.HttpContext.Request.EnableBuffering();
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
// 离开Filter继续向下
ResourceExecutedContext resourceExecutedContext = await next.Invoke(); //执行这句就是继续往后执行控制器构造函数+Action方法,已经执行了控制器的构造函数和Action方法--在这里就应该保存缓存
// 获得结果返回之前,将结果存入缓存
// ResultModel 统一返回值模型
ResultModel _result = (ResultModel)resourceExecutedContext.Result;
// 如果返回值正确,就将其存入缓存
if (_result is not null && _result.ResCode == 200)
{
_memoryCache.Set(key, _result, new TimeSpan(2, 0, 0));
}
}
await Task.CompletedTask;
}
}
}
然后在program.cs中配置使用
// 配置同步读取流数据
builder.Services.Configure<KestrelServerOptions>(x => x.AllowSynchronousIO = true)
.Configure<IISServerOptions>(x => x.AllowSynchronousIO = true);
//允许body重用
app.Use((context,next)=>
{
context.Request.EnableBuffering();
return next(context);
});
然后将Filter标记在Action即可。
注意重点是每次需要重读时,要加以下两句代码
context.HttpContext.Request.EnableBuffering();
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
并且注意,需要将body拷贝到MemoryStream中操作
using (var ms = new MemoryStream())
{
request.Body.CopyTo(ms);
var b = ms.ToArray();
body = Encoding.UTF8.GetString(b);
}