鞍汉 发表于 2025-8-28 14:07:59

c#.net的学习(二)

ASP.CORE的学习(二)

Filter的使用

简单来说,过滤器的作用和意义在于提供一种机制,用于在请求处理管道的特定阶段执行代码,从而优雅地处理那些“横切关注点” (Cross-Cutting Concerns)。
常见的过滤器的执行顺序
请求 -> [授权] -> [资源] -> [操作] -> Action方法 -> [操作] -> [结果] -> [资源] -> 响应
异常过滤器像一个安全网,在上述任何一个环节(授权之后)出错时都会被触发。
ResoureFilter


[*]接口:IResourceFilter和IAsyncResourceFilter
[*]执行时机:在授权之后,但在模型绑定之前运行。它会"环绕"后续大部分管道,所以在Action执行后还会再次执行。
[*]核心作用:非常适合用于缓存或任何需要在管道早期"短路"以避免执行Action的场景。
[*]常见用途:

[*]实现输出缓存:在请求到达时,检查缓存中是否有结果。如果命中,则直接返回缓存内容,后续的模型绑定、Action的执行等全部跳过,性能极高。
[*]请求预处理:在模型绑定前对请求做一些全局性的处理。
[*]向响应中添加全局Header:在管道末尾,向所有响应中添加一个公共响应头(如X-Version)

[*]一句话总结:"缓存和资源的管家",能在第一时间决定是否需要干活,并负责善后。
同步的例子:
由于是演示,没有使用内置缓存
实现同步的Filter的代码CustomResourceFilterAttribute.cs
public class CustomResourceFilterAttribute : Attribute, IResourceFilter
{
    private static Dictionary<string, Object> CacheDictionary = new Dictionary<string, Object>();

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
      string key = context.HttpContext.Request.Path.ToString().ToLower();
      Console.WriteLine($"CustomResourceFilter: OnResourceExecuting - Checking cache for key: {key}");
      if (CacheDictionary.TryGetValue(key, out var cachedValue))
      {
            Console.WriteLine("CustomResourceFilter: Cache hit - Returning cached response.");
            context.Result = (Microsoft.AspNetCore.Mvc.IActionResult)cachedValue;
      }
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
      string key = context.HttpContext.Request.Path.ToString().ToLower();
      if (!CacheDictionary.ContainsKey(key))
      {
            Console.WriteLine($"CustomResourceFilter: OnResourceExecuted - Caching response for key: {key}");
            CacheDictionary = context.Result;
      }
    }
}在控制器的代码
")]


public class FilterController : ControllerBase
{
      
      public IActionResult Index()
      {
          // 直接返回一个简单的字符串响应
          // 在 Action 中打印日志,用于判断 Action 是否被执行
          Console.WriteLine(">>> Action Executed: Generating new server time...");

          // 返回一个每次都会变化的内容
          var result = new
          {
            message = "This is a fresh response from the server1.",
            serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
          };

          return Ok(result);
      }
}这样在用户访问的时候就会返回固定的时间戳。
异步版本
public class CustomResourceAsyncFilterAttribute:Attribute,IAsyncResourceFilter
{
   private static ConcurrentDictionary<string, Object> CacheDictionary = new ConcurrentDictionary<string, Object>();
   public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
   {
         string key = context.HttpContext.Request.Path.ToString().ToLower();
         Console.WriteLine($"AsyncCustomResourceFilter: OnResourceExecuting - Checking cache for key: {key}");

         // 1. 在 Action 执行前,检查缓存
         if (CacheDictionary.TryGetValue(key, out var cachedValue))
         {
             Console.WriteLine("AsyncCustomResourceFilter: Cache hit - Returning cached response.");
             // 缓存命中,直接设置结果并短路,不调用 next()
             context.Result = (IActionResult)cachedValue;
             return; // 直接返回,后续代码不执行
         }

         // 2. 缓存未命中,调用 next() 来执行后续的管道(包括 Action)
         // next() 返回一个 ResourceExecutedContext,包含了执行完成后的结果
         ResourceExecutedContext executedContext = await next();

         // 3. 在 Action 执行后,将结果存入缓存
         // 确保请求成功且结果不为空
         if (executedContext.Result != null)
         {
             Console.WriteLine($"AsyncCustomResourceFilter: OnResourceExecuted - Caching response for key: {key}");
             CacheDictionary = executedContext.Result;
         }
   }

}控制器的代码

public async Task<IActionResult> ResourceAsyncFilter()
{
   // 直接返回一个简单的字符串响应
   // 在 Action 中打印日志,用于判断 Action 是否被执行
   Console.WriteLine(">>> Async Action Executed: Generating new server time...");

   // 模拟一个异步操作,例如数据库查询或调用外部 API
   await Task.Delay(100); // 暂停 100 毫秒

   var result = new
   {
         message = "This is a fresh ASYNC response from the server.",
         serverTime = DateTime.Now.ToString("o")
   };

   return Ok(result);
}操作过滤器


[*]接口:IActionFilter/IAsyncFilter
[*]执行的时机:在模型绑定完成之后,紧接着Action方法执行的前后运行。
[*]核心的作用:这是最常用的过滤器,因为它能访问到已经绑定和验证好的模型数据(ActionExecutingContext.ActionArguments),并且能操纵Action的执行结果。
[*]常见的用途:

[*]模型状态验证:检查ModelState.IsValid,如果模型数据无效,则提前返回400 Bad Request.
[*]记录Action的执行日志,记录Action的执行时间传入传出的参数等。
[*]修改Action参数:在Action执行前,对参数进行一些修改或增强。
[*]修改Action结果:在Action执行后,对返回的IActionResult进行修改。

[*]一句话总结:Action的方法贴身助理,负责处理与Action执行直接相关的杂务。
同步版的代码:
public void OnActionExecuting(ActionExecutingContext context)
{
    context.HttpContext.Items["start"]=Stopwatch.StartNew();
    Console.WriteLine("CustomActionFilter: OnActionExecuting - Action is about to execute.");
    // 可以在这里添加自定义逻辑,比如记录日志、修改请求数据等
}
public void OnActionExecuted(ActionExecutedContext context)
{
    Console.WriteLine("CustomActionFilter: OnActionExecuted - Action has executed.");
    // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
   var stopwatch = context.HttpContext.Items["start"] as Stopwatch;
    if (stopwatch != null)
    {
      stopwatch.Stop();
      Console.WriteLine($"Action executed in {stopwatch.ElapsedMilliseconds} ms");
    }
    else
    {
      Console.WriteLine("CustomActionFilter: Stopwatch not found in HttpContext.");
    }
}控制器的代码:

public IActionResult ActionFilter()
{
      // 直接返回一个简单的字符串响应
      // 在 Action 中打印日志,用于判断 Action 是否被执行
      Console.WriteLine(">>> Action Executed: Generating new server time...");
      // 返回一个每次都会变化的内容
      var result = new
      {
          message = "This is a response from the server with Action Filter.",
          serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
      };
      return Ok(result);
}会显示执行Action前后的时间。
异步版的代码:
public class CustomAsyncActionFilterAttribute : Attribute, IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
      var sw = Stopwatch.StartNew();
      var executed = await next();
      sw.Stop();
      Console.WriteLine($"Async action {context.ActionDescriptor.DisplayName} took {sw.ElapsedMilliseconds}ms" );
}
}控制器的代码:

public async Task<IActionResult> ActionAsyncFilter()
{
   // 直接返回一个简单的字符串响应
   // 在 Action 中打印日志,用于判断 Action 是否被执行
   Console.WriteLine(">>> Async Action Executed: Generating new server time...");
   // 模拟一个异步操作,例如数据库查询或调用外部 API
   await Task.Delay(100); // 暂停 100 毫秒
   var result = new
   {
         message = "This is a fresh ASYNC response from the server with Action Filter.",
         serverTime = DateTime.Now.ToString("o")
   };
   return Ok(result);
}匿名Filter函数

系统支持的AllowAnoymous只能在后续的授权的Filter中去使用,对于我们自己扩展过的Filter,allowAnonymous不能直接使用。
支持Anonymous的匿名---在扩展Filter的时候,也要实现AllowAnonymou匿名。

[*]扩展一个CustomAllowAnonymousAttribute
[*]CustomAllowAnonymousAttribute专门用来做匿名的
[*]升级扩展的Filter,让Filter来支持CustomAllowAnonymousAttribute匿名。
[*]在扩展的Filter的内部进行判断,是否标记了这个特性,否则自定义的Filter不生效。
ExceptionFilter


[*]接口:IExceptionFilter/IAsyncExceptionFilter
[*]执行时机:当控制器创建、模型绑定、过滤器或Action方法执行过程中抛出未被处理的异常时被触发。
[*]核心作用:提供一个集中的地方来捕获和处理应用程序中的异常,避免将原始的错误堆栈信息暴露给客户端。
[*]常见用途:

[*]全局异常处理:捕获所有未处理的异常。
[*]记录异常日志:将异常信息详细地记录到日志系统中。
[*]返回统一的错误响应:向客户端返回一个标准化的、友好的错误信息(例如,{"error":An internal server error occurred.}),而不是一个HTML错误页面或堆栈跟踪。

[*]一句话总结:"安全网和事故报告员",专门处理意外情况。
同步版代码:
filter的代码:
public class CustomExceptionFilterAttribute:Attribute,IExceptionFilter
{
   public void OnException(ExceptionContext context)
   {
         Console.WriteLine($"CustomExceptionFilter: An exception occurred in action {context.ActionDescriptor.DisplayName}.");
         Console.WriteLine($"Exception Message: {context.Exception.Message}");
         context.ExceptionHandled = true; // 标记异常已处理,防止进一步传播

   }

}控制器的代码

public IActionResult ExceptionFilter()
{
   Console.WriteLine(">>> Action Executed: About to throw an exception...");
   // 故意抛出一个异常,触发异常过滤器
   throw new InvalidOperationException("This is a test exception thrown from the action.");
}异步版代码
Filter代码:
public class CustomAsyncExceptionAttribute:Attribute, IAsyncExceptionFilter
{
    public async Task OnExceptionAsync(ExceptionContext context)
    {
      await Task.Run(() =>
      {
            Console.WriteLine($"CustomAsyncExceptionFilter: An exception occurred in action {context.ActionDescriptor.DisplayName}.");
            Console.WriteLine($"Exception Message: {context.Exception.Message}");
            context.ExceptionHandled = true; // 标记异常已处理,防止进一步传播
      });
    }
}控制器的代码

public async Task<IActionResult> AsyncExceptionFilter()
{
   Console.WriteLine(">>> Async Action Executed: About to throw an exception...");
   // 故意抛出一个异常,触发异常过滤器
   await Task.Run(() =>
   {
         throw new InvalidOperationException("This is a test exception thrown from the async action.");
   });
   return Ok(); // 这行代码实际上不会被执行到
}   ResultFilter


[*]接口: IResultFilter / IAsyncResultFilter
[*]执行时机: 仅当 Action 方法成功执行并返回一个 ActionResult 之后,在结果被写入响应体的前后运行。如果 Action 抛出异常或被其他过滤器短路,它不会执行。
[*]核心作用: 对 Action 的执行结果进行最后的加工和处理。
[*]常见用途:

[*]统一响应格式包装: 将所有成功的 API 响应数据包装在一个标准的结构中,例如 { "success": true, "data": ... }。
[*]修改响应头: 根据 Action 的结果动态地添加或修改响应头。
[*]格式化响应数据: 例如,对所有返回的 DateTime 类型进行统一的格式化。
[*]一句话总结: “响应的化妆师”,负责在结果展示给客户前进行最后的包装和美化。
同步版代码:
filter的代码:

public class CustomResultFilterAttribute:Attribute, IResultFilter

{
      public void OnResultExecuting(ResultExecutingContext context)
      {
          Console.WriteLine("CustomResultFilter: OnResultExecuting - Result is about to be executed.");
          // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
      }
      public void OnResultExecuted(ResultExecutedContext context)
      {
          Console.WriteLine("CustomResultFilter: OnResultExecuted - Result has been executed.");
          // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
      }
}控制器的代码

public IActionResult ResultFilter()
{
    Console.WriteLine(
      ">>> Action Executed: Generating new server time..."
    );
    var result = new
    {
      message = "This is a response from the server with Result Filter.",
      serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
    };
    return Ok(result);
}异步版的代码
Filter的代码
public class CustomAsyncResultFilterAttribute: Attribute, IAsyncResultFilter

{
    public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
    {
      Console.WriteLine("CustomAsyncResultFilter: OnResultExecutionAsync - Before result execution.");
      // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
      // 调用下一个过滤器或结果执行
      await next();
      Console.WriteLine("CustomAsyncResultFilter: OnResultExecutionAsync - After result execution.");
      // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
    }
}控制器的代码

public async Task<IActionResult> AsyncResultFilter()
{
   Console.WriteLine(">>> Async Action Executed: Generating new server time...");
   await Task.Delay(100); // 暂停 100 毫秒
   var result = new
   {
         message = "This is a fresh ASYNC response from the server with Result Filter.",
         serverTime = DateTime.Now.ToString("o")
   };
   return Ok(result);
}IAlwaysRunResultFilter

IAlwaysRunResultFilter (和它的异步版本 IAsyncAlwaysRunResultFilter) 是一个非常有用的 Result Filter 接口,它的核心作用正如其名:一个保证总是会运行的 Result Filter。
“无论管道是否被前置的 Filter(如 Authorization, Resource, Action Filter)短路,只要一个 IActionResult 准备被执行,我这个 Filter 就必须运行。”
它忽略了短路信号,确保对最终要发往客户端的任何 Result 对象都能进行处理。
Authorization Filter


[*]接口: IAuthorizationFilter / IAsyncAuthorizationFilter
[*]执行时机: 最早运行。在请求管道中位于所有其他过滤器之前。
[*]核心作用: 决定当前用户是否有权限访问请求的资源。它的职责非常单一和明确:要么通过,要么拒绝。
[*]常见用途:

[*]身份验证检查: 确认用户是否已登录。
[*]角色或策略检查: 检查用户是否属于某个角色(如 "Admin")或满足某个策略(如 "Over18")。
[*]API 密钥验证: 检查请求头中是否包含有效的 API 密钥。
[*]IP 白名单/黑名单: 检查请求的来源 IP 是否被允许。
[*]一句话总结: “看门人”,决定你是否能进门。
创建需要验证的Model类

public class UserModel
{
   public int Id { get; set; }
   
   
   public string Username { get; set; }
   
   
   
   public string Password { get; set; }
   // 为了授权填加的字段
   //
   publicstring Role { get; set; }

   publicstringDepartment { get; set; }

   publicDateTime BirthDate { get; set; }

   publicboolIsEmailVerified { get; set; }

   publicstring Email { get; set; }
}填加数字验证码的服务
接口代码 ICaptchaService.cs
public interface ICaptchaService
{
      (string text, byte[] imageBytes) GenerateCaptcha();
}实现数字验证的代码:
要安装SixLabors.ImageSharp和SixLabors.ImageSharp.Drawing的nuget包
public class CaptchaService:ICaptchaService
{
    private const int ImageWidth = 150;
    private const int ImageHeight = 50;

    public (string text, byte[] imageBytes) GenerateCaptcha()
    {
      string captchaText = GenerateRandomText(4);
      using (var image = new Image<Rgba32>(ImageWidth, ImageHeight))
      {
            // Draw the captcha text on the image
            var font = SystemFonts.CreateFont("Arial", 32, FontStyle.Bold);

            image.Mutate(ctx => ctx.Fill(Color.White));

            var random = new Random();
            for (int i = 0; i < 10; i++)
            {
                var startPoint = new PointF(random.Next(0, ImageWidth), random.Next(0, ImageHeight));
                var endPoint = new PointF(random.Next(0, ImageWidth), random.Next(0, ImageHeight));
                var color = Color.FromRgb((byte)random.Next(150, 256), (byte)random.Next(150, 256), (byte)random.Next(150, 256));
                image.Mutate(ctx => ctx.DrawLine(color, 1, startPoint, endPoint));
            }
            for (int i = 0; i < captchaText.Length; i++)
            {
                char character = captchaText;
                var location = new PointF(10 + i * 35, 5);
                var color = Color.FromRgb((byte)random.Next(0, 150), (byte)random.Next(0, 150), (byte)random.Next(0, 150));

                image.Mutate(ctx => ctx.DrawText(character.ToString(), font, color, location));
            }
            // 5. 将图片保存到内存流
            using (var ms = new MemoryStream())
            {
                image.SaveAsPng(ms);
                return (captchaText, ms.ToArray());
            }

      }

    }

    private string GenerateRandomText(int length)
    {
      const string chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
      var random = new Random();
      var sb = new StringBuilder();
      for (int i = 0; i < length; i++)
      {
            sb.Append(chars);
      }
      return sb.ToString();
    }
}视图的代码
@* filepath: Views/Login/Login.cshtml *@
@model UserModel
@{
    ViewData["Title"] = "登录";
}

<h1>@ViewData["Title"]</h1>


   
      <form asp-action="Login" method="post">
            @* 用于显示摘要错误,如“用户名或密码无效” *@
            

            
                <label asp-for="Username" ></label>
                <input asp-for="Username"autocomplete="username" />
               
            

            
                <label asp-for="Password" ></label>
                <input asp-for="Password"autocomplete="current-password" />
               
            

            @* --- 新增字段开始 --- *@

            
                <label asp-for="Email" ></label>
                <input asp-for="Email"autocomplete="email" />
               
            

            
                <label asp-for="Role" ></label>
                <input asp-for="Role"/>
               
            

            
                <label asp-for="Department" ></label>
                <input asp-for="Department"/>
               
            

            
                <label asp-for="BirthDate" ></label>
                @* Tag Helper 会自动为 DateTime 生成 type="date" *@
                <input asp-for="BirthDate"/>
               
            

            
                @* 对于布尔值,使用 form-check 样式 *@
                <inputasp-for="IsEmailVerified" />
                <labelasp-for="IsEmailVerified"></label>
               
            

            @* --- 新增字段结束 --- *@

            
                <label for="captchaCode" >验证码</label>
               
                  @*
                        关键点 1:
                        使用手动的 name="captchaCode" 和 id="captchaCode"。
                        name="captchaCode" 必须与 Controller Action 的参数名匹配。
                  *@
                  <input name="captchaCode" id="captchaCode"   autocomplete="off" />
                  
                        @*
                            关键点 2:
                            使用 <img> 标签,其 src 指向返回图片流的 Action。
                        *@
                        <img id="captcha-image" src="https://www.cnblogs.com/@Url.Action("GetCaptchaImage", "Login")"
                           alt="Captcha"title="点击刷新验证码" />
                  
               
                @*
                  关键点 3:
                  这个验证标签可以保留,它会显示 ModelState 中键为 "CaptchaCode" 的错误。
                *@
                @Html.ValidationMessage("CaptchaCode", "", new { @class = "text-danger" })
            

            
                <input type="submit" value="登录"/>
            
      </form>
   


@section Scripts {
    @{
      await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }

   
}填加退出的视图代码
@* 只在用户登录后显示登出按钮 *@
@if (User.Identity.IsAuthenticated)
{
    <form asp-controller="Login" asp-action="Logout" method="post" >
      <button type="submit" >登出</button>
    </form>
}控制器的代码
LOGIN的控制器
public class LoginController : Controller
{
      private readonly ILogger<LoginController> _logger;
      private readonly ICaptchaService _captchaService;
      private const string CaptchaSessionKey = "CaptchaCode";

      public LoginController(ILogger<LoginController> logger, ICaptchaService captchaService)
      {
          _logger = logger;
          _captchaService = captchaService;
      }

      
      public IActionResult Login()
      {
          return View();
      }

      
      public IActionResult GetCaptchaImage()
      {
          var (text, imageBytes) = _captchaService.GenerateCaptcha();
          HttpContext.Session.SetString(CaptchaSessionKey, text);
          _logger.LogInformation("Generated Captcha Image with text: {Captcha}", text);
          return File(imageBytes, "image/png");
      }

      
      
      public async Task<IActionResult> Login(UserModel model, string captchaCode)
      {
          if (string.IsNullOrEmpty(captchaCode))
          {
            ModelState.AddModelError("CaptchaCode", "验证码为必填项。");
          }

          if (!ModelState.IsValid)
          {
            return View(model);
          }

          var sessionCaptcha = HttpContext.Session.GetString(CaptchaSessionKey);
          if (sessionCaptcha == null || !sessionCaptcha.Equals(captchaCode, StringComparison.OrdinalIgnoreCase))
          {
            ModelState.AddModelError("CaptchaCode", "验证码不正确。");
            return View(model);
          }

          if (model.Username.Equals("admin", StringComparison.OrdinalIgnoreCase) && model.Password == "123456")
          {
            var claims = new List<Claim>
            {
                  new Claim(ClaimTypes.NameIdentifier, model.Id.ToString()),
                  new Claim(ClaimTypes.Name, model.Username),
                  new Claim(ClaimTypes.Role, model.Role),
                  new Claim("Department", model.Department),
                  new Claim("BirthDate", model.BirthDate.ToString("yyyy-MM-dd")),
                  new Claim("IsEmailVerified", model.IsEmailVerified.ToString()),
                  new Claim(ClaimTypes.Email, model.Email)
            };
            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
            _logger.LogInformation("User {UserName} logged in successfully.", model.Username);
            return RedirectToAction("MyProfile", "User");
          }

          ModelState.AddModelError(string.Empty, "用户名或密码无效。");
          return View(model);
      }
       // 推荐使用 Post 防止 CSRF 攻击
      
      public async Task<IActionResult> Logout()
      {
          // 关键代码:让身份验证 Cookie 失效
          await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

          _logger.LogInformation("User logged out.");

          // 登出后,通常重定向到主页或登录页
          return RedirectToAction("Index", "Home");
      }在Program.cs中
注册服务:
// 注册验证码服务为单例模式
builder.Services.AddSingleton<ICaptchaService, CaptchaService>();

// 添加分布式内存缓存,这是 Session 的基础
builder.Services.AddDistributedMemoryCache();



// 添加 Session 服务并配置
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(3);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// 添加 Cookie 认证服务
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
      options.LoginPath = "/Login/Login";
      options.AccessDeniedPath = "/Home/AccessDenied";
      options.ExpireTimeSpan = TimeSpan.FromMinutes(3);
      options.SlidingExpiration = true;
    });

app.UseAuthorization(); //启动认证。1. 常见用法

在登陆成功后就会跳转新的控制器

")]
public class UserController : Controller
{

   
    public IActionResult GetMyProfile()
    {
      if (User.Identity == null || !User.Identity.IsAuthenticated)
      {
            return Challenge(); // 如果未登录,挑战认证
      }

      // User 是 ControllerBase 提供的属性,代表当前登录的用户
      return Ok(new
      {
            Id = User.FindFirstValue(ClaimTypes.NameIdentifier),
            Username = User.Identity.Name,
            Department = User.FindFirstValue("Department"),
            Role = User.FindFirstValue(ClaimTypes.Role),
            BirthDate = User.FindFirstValue("BirthDate"),
            IsEmailVerified = User.FindFirstValue("IsEmailVerified"),
            Email = User.FindFirstValue(ClaimTypes.Email)
      });
    }

   
   
    public IActionResult GetAllUsers()
    {
      // ... 返回所有用户的逻辑 ...
      return Ok("Returned all users (for Admins/Managers).");
    }

    // 允许匿名访问,覆盖了控制器级别的
   
    public IActionResult GetPublicAnnouncement()
    {
      return Ok("This is a public announcement.");
    }
}这个代码就显示只有登陆是指定的角色才能访问ALL方法和有认证才能访问ME方法.
2. 常用 Policy 定义写法与场景

在Program.cs(或Startup.cs)中配置策略
// 添加 Cookie 认证服务
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
      options.LoginPath = "/Login/Login";
      options.AccessDeniedPath = "/Home/AccessDenied";
      options.ExpireTimeSpan = TimeSpan.FromMinutes(3);
      options.SlidingExpiration = true;
    });

builder.Services.AddAuthorization(options =>
{
    // 场景1: 基于角色 (等同于 )
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));

    // 场景2: 基于 Claim 的存在与否
    // 要求用户必须有 "department" 这个 Claim,值是什么无所谓
    options.AddPolicy("HasDepartment", policy => policy.RequireClaim("Department"));

    // 场景3: 基于 Claim 的具体值
    // 要求用户的 "email_verified" Claim 的值必须是 "True"
    options.AddPolicy("EmailVerified", policy => policy.RequireClaim("IsEmailVerified", "True"));

    // 场景4: 组合条件
    // 要求用户必须是 "Manager" 角色,并且属于 "Sales" 或 "HR" 部门
    options.AddPolicy("SalesOrHrManager", policy => policy
      .RequireRole("Manager")
      .RequireClaim("department", "Sales", "HR"));

    // 场景5: 自定义条件 (将在下一节实现)
    // 要求用户必须年满18岁
    options.AddPolicy("Over18", policy =>
      policy.AddRequirements(new MinimumAgeRequirement(18)));
});控制器的代码

")]
public class ReportsController : Controller
{
      // 必须是 Admin
      
      
      public IActionResult GetFinancialReport() => Ok("Financial Report");

      // 必须是销售部或人力资源部的经理
      
      
      public IActionResult GetPerformanceReport() => Ok("Team Performance Report");

      // 必须邮箱已验证
      
      
      public IActionResult GetConfidentialDocs() => Ok("Confidential Documents");
}3.自定义 Requirement + Handler

添加需求,要求大于18岁才能访问
a. 定义 Requirement
它只是一个数据容器,标记需要满足什么条件。
public class MinimumAgeRequirement:IAuthorizationRequirement
{
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int minimumAge)
    {
      MinimumAge = minimumAge;
    }
}b. 定义 Handler
这是真正的逻辑“回调”点,在这里检查用户是否满足 Requirement
public class MinimumAgeHandler:AuthorizationHandler<Requirement.MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Requirement.MinimumAgeRequirement requirement)
    {
      // 从当前用户的 Claims 中查找 "birthdate"
      var birthDateClaim = context.User.FindFirst(c => c.Type == "BirthDate");

      if (birthDateClaim == null)
      {
            // 没有找到 birthdate claim,无法判断,直接返回
            return Task.CompletedTask;
      }

      if (DateTime.TryParse(birthDateClaim.Value, out var birthDate))
      {
            var age = DateTime.Today.Year - birthDate.Year;
            // 如果生日还没到,年龄减一
            if (birthDate.Date > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            // 如果年龄满足要求
            if (age >= requirement.MinimumAge)
            {
                // 调用 Succeed,表示此 Requirement 已通过
                context.Succeed(requirement);
            }
      }

      return Task.CompletedTask;
    }c. 注册 Handler
//添加新的policy
   // 场景5: 自定义条件 (将在下一节实现)
   // 要求用户必须年满18岁
   options.AddPolicy("Over18", policy =>
       policy.AddRequirements(new MinimumAgeRequirement(18)));在Program.cs中注册,以便系统能找到它。
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();添加控制器的代码
// 必须年满18岁


public IActionResult GetAdultContent() => Ok("Adult Content");4. 资源级授权 (Resource-based)

这个场景非常适合“用户只能修改自己的信息,但管理员可以修改任何人的信息”。
a. 定义 Requirement和Operations
Operation:
    public static class MyUserProfileOperations
    {
      public static readonly OperationAuthorizationRequirement Read =
      new() { Name = nameof(Read) };
      public static readonly OperationAuthorizationRequirement Update =
            new() { Name = nameof(Update) };
    }Requirement:
public class MyUserProfileRequirement : IAuthorizationRequirement
{
   public string Name { get; set; }
   public MyUserProfileRequirement(string name)
   {
         Name = name;
   }
}b. 定义 Handler
Handler 会接收到要操作的资源实例 (UserModel)。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyUserProfileRequirement requirement, UserModel resource)
{
   var loggedInUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);

   if (loggedInUserId == null)
   {
         return Task.CompletedTask;
   }

   // 如果是管理员,允许任何操作
   if (context.User.IsInRole("Admin"))
   {
         context.Succeed(requirement);
         return Task.CompletedTask;
   }

   // 如果是用户本人,允许读取和更新自己的信息
   if (resource.Id.ToString() == loggedInUserId)
   {
         if (requirement.Name == MyUserProfileOperations.Read.Name ||
             requirement.Name == MyUserProfileOperations.Update.Name)
         {
             context.Succeed(requirement);
         }
   }

   return Task.CompletedTask;
}c. 注册 Handler
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();d. 在控制器中调用
你需要注入IAuthorizationService来手动触发授权检查。
public class UsersController : ControllerBase
{
    private readonly IAuthorizationService _authorizationService;
    private readonly IUserRepository _userRepository; // 假设有一个仓储来获取用户

    public UsersController(IAuthorizationService authorizationService, IUserRepository userRepository)
    {
      _authorizationService = authorizationService;
      _userRepository = userRepository;
    }

    // ... 其他 Action ...

    // GET: api/Users/5
   
    public async Task<IActionResult> GetUserById(int id)
    {
      var userToView = _userRepository.FindById(id);
      if (userToView == null) return NotFound();

      // 检查当前登录用户是否有权限读取目标用户信息
      var authorizationResult = await _authorizationService
            .AuthorizeAsync(User, userToView, UserProfileOperations.Read);

      if (!authorizationResult.Succeeded)
      {
            // 可以返回 Forbid() (403) 或 Challenge() (401)
            return Forbid();
      }

      return Ok(userToView); // 授权成功,返回用户信息
    }
}5. 动态策略提供器 (高级可选)

假设你想创建形如的动态策略,而不想预先定义所有部门。
a. 实现 IAuthorizationPolicyProvider
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

public class DepartmentPolicyProvider : IAuthorizationPolicyProvider
{
    private const string POLICY_PREFIX = "IsInDepartment:";
    private readonly DefaultAuthorizationPolicyProvider _fallbackProvider;

    public DepartmentPolicyProvider(IOptions options)
    {
      _fallbackProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    public Task GetDefaultPolicyAsync() => _fallbackProvider.GetDefaultPolicyAsync();
    public Task GetFallbackPolicyAsync() => _fallbackProvider.GetFallbackPolicyAsync();

    public Task GetPolicyAsync(string policyName)
    {
      if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
      {
            var department = policyName.Substring(POLICY_PREFIX.Length);
            var policy = new AuthorizationPolicyBuilder();
            policy.RequireClaim("department", department);
            return Task.FromResult(policy.Build());
      }

      return _fallbackProvider.GetPolicyAsync(policyName);
    }
}b. 注册提供器 (替换默认的)
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();c. 使用



public class ItAssetsController : ControllerBase
{
   
    public IActionResult GetItAssets() => Ok("List of servers and laptops.");
}6.自定义未授权/拒绝返回 (401/403)

如果你不希望默认跳转到登录页,而是返回 JSON,可以配置认证处理器事件。
builder.Services.AddAuthentication("Cookies")
    .AddCookie("Cookies", options =>
    {
      // ...
      options.Events.OnRedirectToLogin = context =>
      {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            context.Response.ContentType = "application/json";
            var json = System.Text.Json.JsonSerializer.Serialize(new { message = "用户未登录或认证已过期。" });
            return context.Response.WriteAsync(json);
      };
      options.Events.OnRedirectToAccessDenied = context =>
      {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            context.Response.ContentType = "application/json";
            var json = System.Text.Json.JsonSerializer.Serialize(new { message = "权限不足,无法访问此资源。" });
            return context.Response.WriteAsync(json);
      };
    });过滤器的作用和意义:优雅地解决问题
过滤器允许您将这些横切关注点从业务逻辑中抽离出来,放到独立的、可重用的类中。ASP.NET Core 的请求处理管道会在合适的时机自动调用这些过滤器。
这带来的核心意义是:
关注点分离 (Separation of Concerns):
控制器 (Controller) 只负责业务流程的编排。
服务 (Service) 只负责核心的业务逻辑实现。
过滤器 (Filter) 只负责处理通用的横切关注点。
各司其职,代码结构变得极其清晰。
代码重用和可维护性:
同一个过滤器(例如日志过滤器)可以轻松地应用到整个应用程序、某个控制器或单个 Action 上。
修改一个功能(如异常处理逻辑),只需要修改对应的过滤器类即可,所有地方都会生效。
声明式编程 (Declarative Programming):
您可以通过在控制器或 Action 上添加一个特性 (Attribute)(如 或 )来“声明”它需要某种行为,而不需要关心这个行为是如何实现的。这让代码的意图更加明显。
关于Filter的作用范围:

[*]标记在Action上,仅对当前的Action函数有效。
[*]标记在控制器上,对于当前控制器下的所有的Action有效。
[*]全局进行标记,对于整个项目的所有函数都生效。
builder.Services.AddControllers(option => option.Filters.Add<CustomResourceAsyncFilterAttribute>());
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页: [1]
查看完整版本: c#.net的学习(二)