筛选器类型
授权筛选器
授权过滤器是过滤器管道的第一个被执行的过滤器,用于系统授权。一般不会编写自定义的授权过滤器,而是配置授权策略或编写自定义授权策略。简单举个例子。
cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterStudy01.Filter
{
/// <summary>
/// 授权过滤器
/// builder.Services.AddMvc(options =>{options.Filters.Add(new WjAuthorizationlFilter());});
/// [TypeFilter(typeof(WjAuthorizationlFilter))]可以加在类或者控制器上
/// 不登陆的情况下访问/Admin/Index
/// </summary>
public class WjAuthorizationlFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// 需要排除具有AllowAnymons 这个标签的控制器
// 过滤掉带有AllowAnonymousFilter
if (HasAllowAnonymous(context))
{
return;
}
// 获取当前用户的信息
var user = context.HttpContext.User;
// 自定义的授权检查逻辑
if (user == null || user?.Identity?.IsAuthenticated != true)
{
// 如果检查不通过,设置 Result 属性为一个 IActionResult 对象,可以阻止请求进一步被处理
context.Result = new ForbidResult();
}
}
// 判断是否含有IAllowAnonymous
private bool HasAllowAnonymous(AuthorizationFilterContext context)
{
if (context.Filters.Any(filter => filter is IAllowAnonymousFilter))
{
return true;
}
// 终节点:里面包含了路由方法的所有元素信息(特性等信息)
var endpoint = context.HttpContext.GetEndpoint();
return endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null;
}
}
}
https://zhuanlan.zhihu.com/p/677748480
https://blog.csdn.net/qq_41942413/article/details/135163599
https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/simple?view=aspnetcore-9.0
IP过滤器,不过这个可以放到Action过滤器中,看需求,如果全部限制可以放授权筛选器,也可以简单的做个ip黑名单和白名单
cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterStudy01.Filter
{
/// <summary>
/// 实现ip过滤器
/// </summary>
public class WjlIpAuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var allowIps = new List<string>()
{
"127.0.0.1"
};
var requestIp = context?.HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? "";
if (!allowIps.Contains(requestIp))
{
var result = new
{
Success = false,
Msg = "非法请求"
};
if (context != null)
{
context.Result = new JsonResult(result);
}
}
}
}
}
资源筛选器
资源过滤器,在授权过滤器执行后执行,该过滤器包含"之前"和"之后"两个行为,包裹了模型绑定、操作过滤器、Action执行、异常过滤器、结果过滤器以及结果执行。
缓存结果提高网站的响应速度,缓存后,就可以直接从内存中直接取数据,而无需在执行方法。
cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterStudy01.Filter
{
/// <summary>
/// 资源过滤器实现缓存
/// IAsyncResourceFilter
/// 如果继承Attribute 使用方式如下[WjlResouerceFilter]
/// 如何不继承 builder.Services.AddMvc(options =>{options.Filters.Add(new WjlResouerceFilter());});
/// </summary>
public class WjlResouerceFilterAttribute : Attribute, IResourceFilter
{
private static readonly Dictionary<string, IActionResult?> dic = new Dictionary<string, IActionResult?>();
/// <summary>
/// 方法执行之后
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuted(ResourceExecutedContext context)
{
var path = context.HttpContext.Request.Path;
dic[path] = context?.Result;
}
/// <summary>
/// 方法执行之前
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuting(ResourceExecutingContext context)
{
var path = context.HttpContext.Request.Path;
if (dic.ContainsKey(path))
{
context.Result = dic[path];
}
}
}
}
操作筛选器
操作过滤器,在模型绑定后执行,该过滤器同样包含"之前"和"之后"两个行为,包裹了Action的执行(不包含Controller的创建)。如果Action执行过程中或后续操作过滤器中抛出异常,首先捕获到异常的是操作过滤器的OnActionExecuted,而不是异常过滤器。
案例:接口访问日志记录,完整的日志记录方便跟踪分析问题以及攻击
cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Primitives;
using System.Diagnostics;
using System.Dynamic;
using System.Text;
using System.Text.Json;
namespace FilterStudy01.Filter
{
/// <summary>
/// 操作过滤器
/// builder.Services.AddMvc(options =>{options.Filters.Add(new WjlAsyncActionFilter());});
/// </summary>
public class WjlAsyncActionFilter : IAsyncActionFilter
{
/// <summary>
/// 记录请求日志,方便跟踪以及预警
/// 需要开启Buffer
/// app.Use(next => new RequestDelegate(async context => {context.Request.EnableBuffering();await next(context);}));
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
dynamic model = new ExpandoObject();
var httpContext = context.HttpContext;
var name = context.ActionDescriptor.DisplayName;
var actionName = context.ActionDescriptor.RouteValues["action"] ?? ""; //action名字
var controllerName = context.ActionDescriptor.RouteValues["controller"] ?? ""; //controller名字
var queryStrings = httpContext.Request.QueryString; //GET请求的后缀
var fromString = new StringBuilder();
if (httpContext.Request.HasFormContentType)
{
var forms = httpContext.Request?.Form; //Form表单请求
if (forms != null)
{
foreach (var item in forms)
{
fromString.Append($"{item.Key}={item.Value}");
}
}
}
var ipAddress = httpContext?.Connection?.RemoteIpAddress?.ToString();
string body = "";
StringValues authHeader = "";
if (httpContext != null && httpContext.Request != null)
{
if (httpContext.Request.Body != null)
{
httpContext.Request.Body.Position = 0;
//读取请求体
var sr = new System.IO.StreamReader(httpContext.Request.Body);
body = await sr.ReadToEndAsync(); //请求体内容
httpContext.Request.Body.Position = 0;
}
httpContext.Request.Headers.TryGetValue("Authorization", out authHeader);
}
//赋值
model.Headers = authHeader;
model.RequestBody = body;
model.IPAddress = ipAddress;
model.Result = "";
model.FormString = fromString.ToString();
model.QueryString = queryStrings;
model.Action = actionName;
model.ActionClassName = name;
model.Controller = controllerName;
var stopWatch = Stopwatch.StartNew();
stopWatch.Reset();
await next();
stopWatch.Stop();
var customerTime = Math.Round(stopWatch?.Elapsed.TotalMilliseconds ?? 0, 2);
//ObjectResult、JsonResult、ViewResult、LocalRedirectResult
//RedirectResult、RedirectToActionResult、BadRequestResult、BadRequestObjectResult
//OkResult OkObjectResult NoContentResult NotFoundResult ForbiddenResult
//ChallengeResult StatusCodeResult ObjectResult FileResult(FileContentResult、FilePathResult、FileStreamResult、VirtualFileResult)
//ContentResult EmptyResult ActionResult(基类不能直接用)
//上面是全部的类型按照需要自己处理
var fileresult = context.Result as FileResult;
if (fileresult == null)
{
var result = context.Result as ObjectResult;
var resValue = result?.Value;
if (result != null && resValue != null)
{
model.Result = JsonSerializer.Serialize(resValue);
}
}
else
{
model.Result = "文件下载";
}
Console.WriteLine(JsonSerializer.Serialize(model));
}
}
}
异常筛选器
监听全局异常并统一格式返回
cs
using FilterStudy01.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Text;
namespace FilterStudy01.Filter
{
/// <summary>
/// 使用
/// builder.Services.AddMvc(options =>{options.Filters.Add(new WjlExceptionFilter());});
/// 并不是所有的异常都捕获,比如mvc中razor页面报错不能捕获
/// 可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常
/// 其他异常不会捕获,可以使用中间件
/// </summary>
public class WjlExceptionFilter : IAsyncExceptionFilter
{
/// <summary>
/// 获取异常的详细信息
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private string GetExceptionDetails(Exception ex)
{
if (ex == null)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
sb.Append("异常消息: ");
sb.Append(ex.Message);
sb.Append("\n");
sb.Append("堆栈跟踪: ");
sb.Append(ex.StackTrace);
// 递归获取内部异常的详细信息
if (ex.InnerException != null)
{
sb.Append(GetExceptionDetails(ex.InnerException));
}
return sb.ToString();
}
/// <summary>
/// 出现异常时触发
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
// 如果异常没有被处理则进行处理
if (context.ExceptionHandled == false)
{
var httpContext = context.HttpContext;
//action名字
var actionName = context.ActionDescriptor.RouteValues["action"] ?? "";
//controller名字
var controllerName =
context.ActionDescriptor.RouteValues["controller"] ?? "";
var path = httpContext.Request.Path;
//这里要替换成日志
Console.WriteLine(GetExceptionDetails(context.Exception));
CommonResult commonResult = new CommonResult();
commonResult.ResultNo = 1;
commonResult.ResultData = "server error";
context.Result = new JsonResult(commonResult);
}
// 设置为true,表示异常已经被处理了
context.ExceptionHandled = true;
}
}
}
using FilterStudy01.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Text;
namespace FilterStudy01.Filter
{
/// <summary>
/// 局部使用
/// [WjlExceptionFilter]
/// </summary>
public class WjlExceptionFilterAttribute : ExceptionFilterAttribute
{
/// <summary>
/// 获取异常的详细信息
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private string GetExceptionDetails(Exception ex)
{
if (ex == null)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
sb.Append("异常消息: ");
sb.Append(ex.Message);
sb.Append("\n");
sb.Append("堆栈跟踪: ");
sb.Append(ex.StackTrace);
// 递归获取内部异常的详细信息
if (ex.InnerException != null)
{
sb.Append(GetExceptionDetails(ex.InnerException));
}
return sb.ToString();
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
// 如果异常没有被处理则进行处理
if (context.ExceptionHandled == false)
{
var httpContext = context.HttpContext;
//action名字
var actionName = context.ActionDescriptor.RouteValues["action"] ?? "";
//controller名字
var controllerName =
context.ActionDescriptor.RouteValues["controller"] ?? "";
var path = httpContext.Request.Path;
//这里要替换成日志
Console.WriteLine(GetExceptionDetails(context.Exception));
CommonResult commonResult = new CommonResult();
commonResult.ResultNo = 1;
commonResult.ResultData = "服务器开小差了";
context.Result = new JsonResult(commonResult);
}
// 设置为true,表示异常已经被处理了
context.ExceptionHandled = true;
}
}
}
结果筛选器
对返回的结果封装,统一结果返回
cs
using FilterStudy01.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterStudy01.Filter
{
/// <summary>
/// 全局使用
/// </summary>
public class WjlResultFilter : IResultFilter
{
/*
使用
builder.Services.AddMvc(options =>
{
options.Filters.Add(new WjlResultFilter());
});
*/
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
//ObjectResult、JsonResult、ViewResult、LocalRedirectResult
//RedirectResult、RedirectToActionResult、BadRequestResult、BadRequestObjectResult
//OkResult OkObjectResult NoContentResult NotFoundResult ForbiddenResult
//ChallengeResult StatusCodeResult ObjectResult FileResult(FileContentResult、FilePathResult、FileStreamResult、VirtualFileResult)
//ContentResult EmptyResult ActionResult(基类不能直接用)
var jsonResult = context.Result as JsonResult;
if (jsonResult != null && jsonResult.Value != null)
{
//1、只能拦截JsonResult
CommonResult commonResult = new CommonResult();
commonResult.ResultNo = 0;
commonResult.ResultData = jsonResult.Value;
jsonResult.Value = commonResult;
}
}
}
}
using FilterStudy01.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FilterStudy01.Filter
{
/// <summary>
/// 局部使用
/// </summary>
public class WjlResultFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var jsonResult = context.Result as JsonResult;
if (jsonResult != null && jsonResult.Value != null)
{
//1、只能拦截JsonResult
CommonResult commonResult = new CommonResult();
commonResult.ResultNo = 0;
commonResult.ResultData = jsonResult.Value;
jsonResult.Value = commonResult;
}
}
}
}
筛选器接口的同步和异步版本任意实现一个,而不是同时实现 。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。
大佬总结的图示
参考
授权过滤器
https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-8.0
https://blog.csdn.net/sD7O95O/article/details/119223675
Razor页面筛选器
异常过滤器理解
错误处理
过滤器应用