<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      c#.net的學習(二)

      ASP.CORE的學習(二)

      Filter的使用

      簡單來說,過濾器的作用和意義在于提供一種機制,用于在請求處理管道的特定階段執行代碼,從而優雅地處理那些“橫切關注點” (Cross-Cutting Concerns)。
      常見的過濾器的執行順序
      請求 -> [授權] -> [資源] -> [操作] -> Action方法 -> [操作] -> [結果] -> [資源] -> 響應
      異常過濾器像一個安全網,在上述任何一個環節(授權之后)出錯時都會被觸發。

      ResoureFilter

      • 接口:IResourceFilterIAsyncResourceFilter
      • 執行時機:在授權之后,但在模型綁定之前運行。它會"環繞"后續大部分管道,所以在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[key] = context.Result;
              }
          }
      }
      

      在控制器的代碼

        [Route("api/[controller]")]
        [ApiController]
      
        public class FilterController : ControllerBase
        {
            [HttpGet("Index"),CustomResourceFilter]
            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[key] = executedContext.Result;
               }
           }
      
       }
      

      控制器的代碼

       [HttpGet("ResourceAsyncFilter"), CustomResourceAsyncFilter]
       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.");
          }
      }
      

      控制器的代碼:

        [HttpGet("ActionFilter"),CustomActionFilter]
        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" );
        }
      }
      

      控制器的代碼:

       [HttpGet("AsyncActionFilter"), CustomAsyncActionFilter]
       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匿名。

      1. 擴展一個CustomAllowAnonymousAttribute
      2. CustomAllowAnonymousAttribute專門用來做匿名的
      3. 升級擴展的Filter,讓Filter來支持CustomAllowAnonymousAttribute匿名。
      4. 在擴展的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; // 標記異常已處理,防止進一步傳播
      
           }
      
       }
      

      控制器的代碼

       [HttpGet("ExceptionFilter"), CustomExceptionFilter]
       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; // 標記異常已處理,防止進一步傳播
              });
          }
      }
      

      控制器的代碼

       [HttpGet("AsyncExceptionFilter"), CustomAsyncExceptionFilter]
       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.");
                // 可以在這里添加自定義邏輯,比如記錄日志、修改響應數據等
            }
        }
      

      控制器的代碼

      [HttpGet("ResultFilter"), CustomResultFilter]
      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.");
              // 可以在這里添加自定義邏輯,比如記錄日志、修改響應數據等
          }
      }
      

      控制器的代碼

       [HttpGet("AsyncResultFilter"),CustomAsyncResultFilter]
       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; } 
           [Required(ErrorMessage ="用戶名為必填項")]
           [Display(Name ="用戶名")]
           public string Username { get; set; }
           [Required(ErrorMessage ="密碼為必填項")]
           [DataType(DataType.Password)]
           [Display(Name ="密碼")]
           public string Password { get; set; }
           // 為了授權填加的字段 
           //
           public  string Role { get; set; }
      
           public  string  Department { get; set; }
      
           public  DateTime BirthDate { get; set; }
      
           public  bool  IsEmailVerified { get; set; }
      
           public  string Email { get; set; }
       }
      

      填加數字驗證碼的服務
      接口代碼 ICaptchaService.cs

        public interface ICaptchaService
        {
            (string text, byte[] imageBytes) GenerateCaptcha();
        }
      

      實現數字驗證的代碼:
      要安裝SixLabors.ImageSharpSixLabors.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[i];
                      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[random.Next(chars.Length)]);
              }
              return sb.ToString();
          }
      }
      

      視圖的代碼

      @* filepath: Views/Login/Login.cshtml *@
      @model UserModel
      @{
          ViewData["Title"] = "登錄";
      }
      
      <h1>@ViewData["Title"]</h1>
      
      <div class="row">
          <div class="col-md-4">
              <form asp-action="Login" method="post">
                  @* 用于顯示摘要錯誤,如“用戶名或密碼無效” *@
                  <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      
                  <div class="form-group mb-3">
                      <label asp-for="Username" class="control-label"></label>
                      <input asp-for="Username" class="form-control" autocomplete="username" />
                      <span asp-validation-for="Username" class="text-danger"></span>
                  </div>
      
                  <div class="form-group mb-3">
                      <label asp-for="Password" class="control-label"></label>
                      <input asp-for="Password" class="form-control" autocomplete="current-password" />
                      <span asp-validation-for="Password" class="text-danger"></span>
                  </div>
      
                  @* --- 新增字段開始 --- *@
      
                  <div class="form-group mb-3">
                      <label asp-for="Email" class="control-label"></label>
                      <input asp-for="Email" class="form-control" autocomplete="email" />
                      <span asp-validation-for="Email" class="text-danger"></span>
                  </div>
      
                  <div class="form-group mb-3">
                      <label asp-for="Role" class="control-label"></label>
                      <input asp-for="Role" class="form-control" />
                      <span asp-validation-for="Role" class="text-danger"></span>
                  </div>
      
                  <div class="form-group mb-3">
                      <label asp-for="Department" class="control-label"></label>
                      <input asp-for="Department" class="form-control" />
                      <span asp-validation-for="Department" class="text-danger"></span>
                  </div>
      
                  <div class="form-group mb-3">
                      <label asp-for="BirthDate" class="control-label"></label>
                      @* Tag Helper 會自動為 DateTime 生成 type="date" *@
                      <input asp-for="BirthDate" class="form-control" />
                      <span asp-validation-for="BirthDate" class="text-danger"></span>
                  </div>
      
                  <div class="form-group form-check mb-3">
                      @* 對于布爾值,使用 form-check 樣式 *@
                      <input class="form-check-input" asp-for="IsEmailVerified" />
                      <label class="form-check-label" asp-for="IsEmailVerified"></label>
                      <span asp-validation-for="IsEmailVerified" class="text-danger d-block"></span>
                  </div>
      
                  @* --- 新增字段結束 --- *@
      
                  <div class="form-group mb-3">
                      <label for="captchaCode" class="control-label">驗證碼</label>
                      <div class="input-group">
                          @* 
                              關鍵點 1: 
                              使用手動的 name="captchaCode" 和 id="captchaCode"。
                              name="captchaCode" 必須與 Controller Action 的參數名匹配。
                          *@
                          <input name="captchaCode" id="captchaCode" class="form-control" style="width: 120px;" autocomplete="off" />
                          <div class="input-group-append ms-2">
                              @* 
                                  關鍵點 2: 
                                  使用 <img> 標簽,其 src 指向返回圖片流的 Action。
                              *@
                              <img id="captcha-image" src="@Url.Action("GetCaptchaImage", "Login")"
                                   alt="Captcha" style="cursor: pointer; border: 1px solid #ccc;" title="點擊刷新驗證碼" />
                          </div>
                      </div>
                      @* 
                          關鍵點 3: 
                          這個驗證標簽可以保留,它會顯示 ModelState 中鍵為 "CaptchaCode" 的錯誤。
                      *@
                      @Html.ValidationMessage("CaptchaCode", "", new { @class = "text-danger" })
                  </div>
      
                  <div class="form-group">
                      <input type="submit" value="登錄" class="btn btn-primary" />
                  </div>
              </form>
          </div>
      </div>
      
      @section Scripts {
          @{
              await Html.RenderPartialAsync("_ValidationScriptsPartial");
          }
      
          <script>
              document.addEventListener('DOMContentLoaded', function () {
                  const captchaImage = document.getElementById('captcha-image');
                  const captchaInput = document.getElementById('captchaCode');
      
                  if (captchaImage) {
                      captchaImage.addEventListener('click', function () {
                          // 通過給 URL 添加一個隨機的時間戳參數來強制瀏覽器重新加載圖片,避免緩存
                          const newSrc = '@Url.Action("GetCaptchaImage", "Login")?t=' + new Date().getTime();
                          this.src = newSrc;
      
                          // 刷新驗證碼后,清空用戶已輸入的文本
                          if(captchaInput) {
                              captchaInput.value = '';
                          }
                      });
                  }
              });
          </script>
      }
      

      填加退出的視圖代碼

      @* 只在用戶登錄后顯示登出按鈕 *@
      @if (User.Identity.IsAuthenticated)
      {
          <form asp-controller="Login" asp-action="Logout" method="post" class="form-inline">
              <button type="submit" class="nav-link btn btn-link text-dark">登出</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;
            }
      
            [HttpGet]
            public IActionResult Login()
            {
                return View();
            }
      
            [HttpGet]
            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");
            }
      
            [HttpPost]
            [ValidateAntiForgeryToken]
            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);
            }
            [HttpPost] // 推薦使用 Post 防止 CSRF 攻擊
            [ValidateAntiForgeryToken]
            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. [Authorize]常見用法

      在登陸成功后就會跳轉新的控制器

       [Authorize]
      [Route("[controller]")]
      public class UserController : Controller
      {
      
          [HttpGet("me")]
          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)
              });
          }
      
          [HttpGet("all")]
          [Authorize(Roles = "Admin,Manager")]
          public IActionResult GetAllUsers()
          {
              // ... 返回所有用戶的邏輯 ...
              return Ok("Returned all users (for Admins/Managers).");
          }
      
          [AllowAnonymous] // 允許匿名訪問,覆蓋了控制器級別的 [Authorize]
          [HttpGet("public-announcement")]
          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: 基于角色 (等同于 [Authorize(Roles="Admin")])
          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)));
      });
      

      控制器的代碼

        [Authorize]
        [Route("[controller]")]
        public class ReportsController : Controller
        {
            // 必須是 Admin
            [HttpGet("financial")]
            [Authorize(Policy = "AdminOnly")]
            public IActionResult GetFinancialReport() => Ok("Financial Report");
      
            // 必須是銷售部或人力資源部的經理
            [HttpGet("performance")]
            [Authorize(Policy = "SalesOrHrManager")]
            public IActionResult GetPerformanceReport() => Ok("Team Performance Report");
      
            // 必須郵箱已驗證
            [HttpGet("confidential-docs")]
            [Authorize(Policy = "EmailVerified")]
            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歲
      [HttpGet("adult-content")]
      [Authorize(Policy = "Over18")]
      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, MyUserProfileAuthorizationHandler>();
      

      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
          [HttpGet("{id}")]
          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. 動態策略提供器 (高級可選)

      假設你想創建形如[Authorize(Policy = "IsInDepartment:Sales")]的動態策略,而不想預先定義所有部門。

      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<AuthorizationOptions> options)
          {
              _fallbackProvider = new DefaultAuthorizationPolicyProvider(options);
          }
      
          public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => _fallbackProvider.GetDefaultPolicyAsync();
          public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => _fallbackProvider.GetFallbackPolicyAsync();
      
          public Task<AuthorizationPolicy?> 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<AuthorizationPolicy?>(policy.Build());
              }
      
              return _fallbackProvider.GetPolicyAsync(policyName);
          }
      }
      

      b. 注冊提供器 (替換默認的)

      builder.Services.AddSingleton<IAuthorizationPolicyProvider, DepartmentPolicyProvider>();
      

      c. 使用

      [Authorize(Policy = "IsInDepartment:IT")]
      [ApiController]
      [Route("api/it-assets")]
      public class ItAssetsController : ControllerBase
      {
          [HttpGet]
          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)(如 [Authorize] 或 [TypeFilter(typeof(MyFilter))])來“聲明”它需要某種行為,而不需要關心這個行為是如何實現的。這讓代碼的意圖更加明顯。

      關于Filter的作用范圍:

      1. 標記在Action上,僅對當前的Action函數有效。
      2. 標記在控制器上,對于當前控制器下的所有的Action有效。
      3. 全局進行標記,對于整個項目的所有函數都生效。
      builder.Services.AddControllers(option => option.Filters.Add<CustomResourceAsyncFilterAttribute>());
      
      posted @ 2025-08-28 15:44  飄雨的河  閱讀(20)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 熟女熟妇乱女乱妇综合网| 爆乳日韩尤物无码一区| 亚洲中文字幕综合小综合| 蜜臀91精品高清国产福利| 亚洲另类激情专区小说图片| 国偷自产一区二区三区在线视频| 日韩国产精品区一区二区| 国产精品成人中文字幕| 国产偷窥熟女高潮精品视频| 人人玩人人添人人澡超碰| 中文字幕乱妇无码AV在线| 亚洲精品无码乱码成人| 国产精品中文字幕第一区| 成人免费亚洲av在线| 少妇粗大进出白浆嘿嘿视频| 精品无码国产自产拍在线观看蜜| 美日韩精品综合一区二区| 蜜桃av多人一区二区三区| 日韩中文字幕免费在线观看 | 中文字幕无码专区一VA亚洲V专| 三级4级全黄60分钟| 噜噜久久噜噜久久鬼88| 国产精品人成视频免| 久久国产精品99久久蜜臀| 国产精品13页| 国产成人亚洲欧美二区综合| 精品无码三级在线观看视频 | 亚洲精品~无码抽插| 延寿县| 四虎在线成人免费观看| 中文字幕无码av不卡一区| 日本一区二区不卡精品| 亚洲国产精品久久久天堂麻豆宅男| 夜夜爽妓女8888888视频| 国产精品一区二区麻豆蜜桃| 亚洲av日韩在线资源| 国产中文三级全黄| 国产成人一区二区三区视频免费| 免费无码黄十八禁网站| 国产精品aⅴ免费视频| 91久久性奴调教国产免费|