@elf-express/admin-net-mcp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/index.js +83 -88
  2. package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
  3. package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
  4. package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
  5. package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
  6. package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
  7. package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
  8. package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
  9. package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
  10. package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
  11. package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
  12. package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
  13. package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
  14. package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
  15. package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
  16. package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
  17. package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
  18. package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
  19. package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
  20. package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
  21. package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
  22. package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
  23. package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
  24. package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
  25. package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
  26. package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
  27. package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
  28. package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
  29. package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
  30. package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
  31. package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
  32. package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
  33. package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
  34. package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
  35. package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
  36. package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
  37. package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
  38. package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
  39. package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
  40. package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
  41. package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
  42. package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
  43. package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
  44. package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
  45. package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
  46. package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
  47. package/knowledge/attributes.md +153 -0
  48. package/knowledge/config.md +147 -0
  49. package/knowledge/entity.md +115 -0
  50. package/knowledge/event.md +124 -0
  51. package/knowledge/plugin.md +136 -0
  52. package/knowledge/service.md +146 -0
  53. package/knowledge/sqlsugar.md +172 -0
  54. package/knowledge/vue-typescript.md +338 -0
  55. package/package.json +3 -2
@@ -0,0 +1,581 @@
1
+ # 5.3 篩選器/攔截器/過濾器/AOP
2
+
3
+ ## 5.3.1 關於篩選器
4
+
5
+ 篩選器又名過濾器、攔截器,在 ASP.NET Core 中,可在請求處理管線中特定階段之前或之後執行程式碼。篩選器是非常經典的面向切面程式設計方式,也就是所謂的 AOP 操作。
6
+
7
+ 通俗來說就是可以在控制器 Action 執行前後進行切面操作,或回傳 Result 結果前後進行操作。
8
+
9
+ ---
10
+
11
+ ## 5.3.2 應用場景
12
+
13
+ 透過自訂篩選器可以實現錯誤處理、快取處理、授權處理、日誌記錄、實現工作單元交易(Uow)等等切面操作,從而使業務邏輯和系統行為邏輯進行分離。
14
+
15
+ ### 5.3.2.1 篩選器優點
16
+
17
+ - 易擴充,易整合
18
+ - 業務和系統邏輯分離,不干擾業務程式碼
19
+ - 可實現介面多維度控制,如請求參數竄改、回傳值竄改、限流、分散式交易支援
20
+
21
+ ---
22
+
23
+ ## 5.3.3 支援攔截應用
24
+
25
+ - Mvc/WebAPI 控制器/Action
26
+ - Razor Pages 頁面
27
+ - 框架提供的動態 WebAPI
28
+ - 所有請求資源
29
+ - 全域例外
30
+
31
+ ---
32
+
33
+ ## 5.3.4 篩選器類型
34
+
35
+ ### 5.3.4.1 介面類型
36
+
37
+ **授權篩選器**:最先執行,用於確定是否已針對請求為使用者授權。如果請求未獲授權,授權篩選器可以讓管線短路。
38
+
39
+ - `IAuthorizationFilter` / `IAsyncAuthorizationFilter` / `AuthorizeFilter`
40
+
41
+ **資源篩選器**:授權後執行,如果需要使大部分請求管線短路,它將會很有用。
42
+
43
+ - `IResourceFilter` / `IAsyncResourceFilter`
44
+
45
+ **操作篩選器**:在呼叫操作方法之前和之後執行程式碼,可以更改傳遞的參數、回傳結果等,不可在 Razor Pages 中使用。
46
+
47
+ - `IActionFilter` / `IAsyncActionFilter`
48
+
49
+ **例外篩選器**:在向回應主體寫入任何內容之前,對未經處理的例外套用全域策略。
50
+
51
+ - `IExceptionFilter` / `IAsyncExceptionFilter`
52
+
53
+ **結果篩選器**:在執行操作結果之前和之後立即執行程式碼,僅當操作方法成功執行時才會執行。
54
+
55
+ - `IResultFilter` / `IAsyncResultFilter`
56
+ - `IAlwaysRunResultFilter` / `IAsyncAlwaysRunResultFilter`:針對所有操作結果執行攔截,即使 `IResourceFilter` 設定了 Result 仍會執行
57
+
58
+ **Razor Pages 篩選器**:允許 Razor Pages 在執行 Razor 頁面處理常式前後執行程式碼。
59
+
60
+ - `IPageFilter` / `IAsyncPageFilter`
61
+
62
+ ### 5.3.4.2 特性 Attribute 類型
63
+
64
+ - **授權特性篩選器**:`Attribute + IAsyncAuthorizationFilter`
65
+ - **操作特性篩選器**:`ActionFilterAttribute`
66
+ - **例外特性篩選器**:`ExceptionFilterAttribute`
67
+ - **結果特性篩選器**:`ResultFilterAttribute`
68
+ - **服務特性篩選器**:`ServiceFilterAttribute`(支援相依性注入)
69
+ - **類型特性篩選器**:`TypeFilterAttribute`(不支援相依性注入但可傳入自訂建構函式參數)
70
+ - **組合特性篩選器**:`Attribute + 介面類型方式`
71
+
72
+ > **篩選器選用技巧**:當你不需要全域篩選器的時候使用特性篩選器,否則使用介面類型篩選器。另外盡可能使用帶 `IAsync` 開頭的非同步篩選器。
73
+ >
74
+ > **同步非同步篩選器**:篩選器介面的同步和非同步版本任意實現一個即可。執行時會先查看是否實現了非同步介面,如果是則呼叫該介面;如果不是,則呼叫同步介面。如果同時實現,則僅呼叫非同步方法。
75
+
76
+ ---
77
+
78
+ ## 5.3.5 篩選器註冊
79
+
80
+ ASP.NET Core 提供了多種篩選器註冊方式。通常不同的註冊方式執行順序不同:服務類型註冊最先執行,其次是 Mvc Filter 方式,最後是特性方式。相同方式中按照註冊前後決定執行順序,先註冊先執行。
81
+
82
+ 也可透過 `IOrderedFilter` 介面重寫執行順序,其 `Order` 屬性值越高的先執行。
83
+
84
+ ### 5.3.5.1 在 Startup.cs 中註冊
85
+
86
+ 這種方式表示全域註冊,應用所有控制器/Action:
87
+
88
+ ```csharp
89
+ public void ConfigureServices(IServiceCollection services)
90
+ {
91
+ // Mvc 方式註冊一,全域執行
92
+ services.AddControllersWithViews(options =>
93
+ {
94
+ options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader", "Result filter added to MvcOptions.Filters"));
95
+ options.Filters.Add(typeof(MySampleActionFilter));
96
+ options.Filters.Add(new SampleGlobalActionFilter());
97
+ });
98
+
99
+ // Mvc 方式註冊二,全域執行
100
+ services.Configure<MvcOptions>(options =>
101
+ {
102
+ options.Filters.Add<TFilter>();
103
+ });
104
+
105
+ // Mvc 註冊方式三,全域執行,Furion 框架提供方式
106
+ services.AddMvcFilter<TFilter>();
107
+ }
108
+ ```
109
+
110
+ ### 5.3.5.2 特性方式註冊
111
+
112
+ 這種方式表示局部註冊,只作用於特定的控制器/Action。
113
+
114
+ **直接貼方式:**
115
+
116
+ ```csharp
117
+ public class AddHeaderAttribute : ResultFilterAttribute { /* ... */ }
118
+
119
+ [AddHeader]
120
+ public class SampleController : Controller { }
121
+ ```
122
+
123
+ **透過 `[ServiceFilter]` 方式**(適用於自訂特性篩選器包含建構函式注入服務的情境,需先在 `ConfigureServices` 中註冊):
124
+
125
+ ```csharp
126
+ // 篩選器定義
127
+ public class MyActionFilterAttribute : ActionFilterAttribute
128
+ {
129
+ private readonly PositionOptions _settings;
130
+ public MyActionFilterAttribute(IOptions<PositionOptions> options) { }
131
+ }
132
+
133
+ // Startup.cs 中註冊
134
+ services.AddScoped<MyActionFilterAttribute>();
135
+
136
+ // 使用
137
+ [ServiceFilter(typeof(MyActionFilterAttribute))]
138
+ public IActionResult Index2() { /* ... */ }
139
+ ```
140
+
141
+ **透過 `[TypeFilter]` 方式**(不支援相依性注入但可傳遞基元類型建構函式參數):
142
+
143
+ ```csharp
144
+ public class MyLogFilterAttribute : ActionFilterAttribute
145
+ {
146
+ public MyLogFilterAttribute(string message, int level) { }
147
+ }
148
+
149
+ [TypeFilter(typeof(MyLogFilterAttribute), Arguments = new object[] { "Message", 10 })]
150
+ public IActionResult Index2() { /* ... */ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 5.3.6 授權篩選器
156
+
157
+ 透過授權篩選器可以實現在所有請求到達控制器/Action 之前進行驗證,如果授權失敗,直接跳轉到登入或者回傳 401。
158
+
159
+ ### 5.3.6.1 介面定義方式
160
+
161
+ ```csharp
162
+ using Microsoft.AspNetCore.Authorization;
163
+ using Microsoft.AspNetCore.Mvc;
164
+ using Microsoft.AspNetCore.Mvc.Authorization;
165
+ using Microsoft.AspNetCore.Mvc.Controllers;
166
+ using Microsoft.AspNetCore.Mvc.Filters;
167
+
168
+ namespace WebApplication4.Filters;
169
+
170
+ public class MyAuthorizationFilter : IAsyncAuthorizationFilter
171
+ {
172
+ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
173
+ {
174
+ var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
175
+ var controllerType = actionDescriptor!.ControllerTypeInfo;
176
+ var methodType = actionDescriptor.MethodInfo;
177
+
178
+ // 是否匿名存取
179
+ var allowAnonymous = context.Filters.Any(u => u is IAllowAnonymousFilter)
180
+ || controllerType.IsDefined(typeof(AllowAnonymousAttribute), true)
181
+ || methodType.IsDefined(typeof(AllowAnonymousAttribute), true);
182
+
183
+ if (!allowAnonymous)
184
+ {
185
+ // MVC 專案跳轉到登入頁
186
+ if (typeof(Controller).IsAssignableFrom(controllerType.AsType()))
187
+ {
188
+ context.Result = new RedirectResult("~/Login");
189
+ }
190
+ else
191
+ {
192
+ context.Result = new UnauthorizedResult();
193
+ }
194
+ }
195
+ else await Task.CompletedTask;
196
+ }
197
+ }
198
+ ```
199
+
200
+ **全域註冊:**
201
+
202
+ ```csharp
203
+ services.Configure<MvcOptions>(options =>
204
+ {
205
+ options.Filters.Add<MyAuthorizationFilter>();
206
+ });
207
+
208
+ // 或使用 Furion 框架提供的簡易方式
209
+ services.AddMvcFilter<MyAuthorizationFilter>();
210
+ ```
211
+
212
+ **局部特性方式:**
213
+
214
+ ```csharp
215
+ [TypeFilter(typeof(MyAuthorizationFilter))]
216
+ public IActionResult Get() { /* ... */ }
217
+
218
+ // 或使用 [ServiceFilter](需先 services.AddScoped<MyAuthorizationFilter>())
219
+ [ServiceFilter(typeof(MyAuthorizationFilter))]
220
+ public IActionResult Get() { /* ... */ }
221
+ ```
222
+
223
+ ### 5.3.6.2 特性定義方式(組合)
224
+
225
+ ```csharp
226
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
227
+ public class MyAuthorizationFilterAttribute : Attribute, IAsyncAuthorizationFilter
228
+ {
229
+ public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
230
+ {
231
+ // 程式碼同上
232
+ }
233
+ }
234
+
235
+ // 使用
236
+ [MyAuthorizationFilter]
237
+ public IActionResult Get() { /* ... */ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## 5.3.7 資源篩選器
243
+
244
+ 資源篩選器使用頻率較少,通常用來處理資源快取或者阻止模型(值)繫結操作。
245
+
246
+ ### 5.3.7.1 介面定義方式
247
+
248
+ ```csharp
249
+ public class MyResourceFilter : IAsyncResourceFilter
250
+ {
251
+ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
252
+ {
253
+ var valueProviderFactories = context.ValueProviderFactories;
254
+
255
+ var formValueProviderFactory = valueProviderFactories
256
+ .OfType<FormValueProviderFactory>()
257
+ .FirstOrDefault();
258
+ if (formValueProviderFactory != null)
259
+ {
260
+ context.ValueProviderFactories.Remove(formValueProviderFactory);
261
+ }
262
+
263
+ var resourceContext = await next();
264
+ }
265
+ }
266
+ ```
267
+
268
+ 註冊方式同授權篩選器(全域 `services.AddMvcFilter<MyResourceFilter>()`、局部 `[TypeFilter]` / `[ServiceFilter]`)。
269
+
270
+ ### 5.3.7.2 特性定義方式(組合)
271
+
272
+ ```csharp
273
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
274
+ public class MyResourceFilterAttribute : Attribute, IAsyncResourceFilter
275
+ {
276
+ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
277
+ {
278
+ // 程式碼同上
279
+ }
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ## 5.3.8 操作篩選器
286
+
287
+ 操作篩選器是使用頻率最高的篩選器,通常用來控制進入 Action 之前(此時模型繫結已完成)和 Action 執行之後(此時 Result 還未回傳)。
288
+
289
+ 可以使用操作篩選器實現各種操作,如竄改參數、竄改回傳值、統一參數驗證、稽核日誌、實現資料庫交易自動開啟關閉等等。
290
+
291
+ ### 5.3.8.1 介面定義方式
292
+
293
+ ```csharp
294
+ public class MyActionFilter : IAsyncActionFilter
295
+ {
296
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
297
+ {
298
+ //============== 執行方法之前取得資料 ====================
299
+ var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
300
+ var method = actionDescriptor!.MethodInfo;
301
+ var httpContext = context.HttpContext;
302
+ var httpRequest = httpContext.Request;
303
+
304
+ // 取得客戶端 IPv4 位址
305
+ var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
306
+
307
+ // 取得請求的 Url 位址
308
+ var requestUrl = httpRequest.GetRequestUrlAddress();
309
+
310
+ // 取得來源 Url 位址
311
+ var refererUrl = httpRequest.GetRefererUrlAddress();
312
+
313
+ // 取得請求參數(可以自由竄改)
314
+ var parameters = context.ActionArguments;
315
+
316
+ // 取得操作人(必須授權存取才有值)
317
+ var userId = httpContext.User?.FindFirstValue("userId");
318
+
319
+ var requestedTime = DateTimeOffset.Now;
320
+
321
+ //============== 執行方法之後取得資料 ====================
322
+ var actionContext = await next();
323
+
324
+ var returnResult = actionContext.Result;
325
+ var isRequestSucceed = actionContext.Exception == null;
326
+ var stackTrace = EnhancedStackTrace.Current();
327
+ }
328
+ }
329
+ ```
330
+
331
+ **全域註冊:** `services.AddMvcFilter<MyActionFilter>();`
332
+
333
+ **局部特性方式:** `[TypeFilter(typeof(MyActionFilter))]` 或 `[ServiceFilter(typeof(MyActionFilter))]`
334
+
335
+ ### 5.3.8.2 ActionFilterAttribute 方式
336
+
337
+ ```csharp
338
+ public class MyActionAttribute : ActionFilterAttribute
339
+ {
340
+ public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
341
+ {
342
+ return base.OnActionExecutionAsync(context, next);
343
+ }
344
+
345
+ public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
346
+ {
347
+ return base.OnResultExecutionAsync(context, next);
348
+ }
349
+ }
350
+
351
+ // 使用
352
+ [MyAction]
353
+ public IActionResult Get() { /* ... */ }
354
+ ```
355
+
356
+ ### 5.3.8.3 特性定義方式(組合)
357
+
358
+ ```csharp
359
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
360
+ public class MyActionFilterAttribute : Attribute, IAsyncActionFilter
361
+ {
362
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
363
+ {
364
+ // 程式碼同上
365
+ }
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 5.3.9 例外篩選器
372
+
373
+ 例外篩選器使用頻率僅次於操作篩選器,更多用於程式出現例外時記錄日誌或回傳統一的頁面。
374
+
375
+ ### 5.3.9.1 介面定義方式
376
+
377
+ ```csharp
378
+ public class MyExceptionFilter : IAsyncExceptionFilter
379
+ {
380
+ public async Task OnExceptionAsync(ExceptionContext context)
381
+ {
382
+ if (context.ExceptionHandled) return;
383
+
384
+ var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
385
+ var method = actionDescriptor!.MethodInfo;
386
+ var exception = context.Exception;
387
+ var stackTrace = EnhancedStackTrace.Current();
388
+
389
+ // 1. MVC 回傳自訂錯誤頁面
390
+ // 2. WebAPI 回傳 context.Result = new JsonResult(...)
391
+ // 3. 記錄日誌
392
+
393
+ await Task.CompletedTask;
394
+ }
395
+ }
396
+ ```
397
+
398
+ **全域註冊:** `services.AddMvcFilter<MyExceptionFilter>();`
399
+
400
+ ### 5.3.9.2 ExceptionFilterAttribute 方式
401
+
402
+ ```csharp
403
+ public class MyExceptionAttribute : ExceptionFilterAttribute
404
+ {
405
+ public override Task OnExceptionAsync(ExceptionContext context)
406
+ {
407
+ return base.OnExceptionAsync(context);
408
+ }
409
+ }
410
+
411
+ // 使用
412
+ [MyException]
413
+ public IActionResult Get() { /* ... */ }
414
+ ```
415
+
416
+ ### 5.3.9.3 特性定義方式(組合)
417
+
418
+ ```csharp
419
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
420
+ public class MyExceptionFilterAttribute : Attribute, IAsyncExceptionFilter
421
+ {
422
+ public async Task OnExceptionAsync(ExceptionContext context)
423
+ {
424
+ // 程式碼同上
425
+ }
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ## 5.3.10 結果篩選器
432
+
433
+ 結果篩選器常用於對回傳的結果附加更多資料,比如 Mvc 中的 `ViewBag`、`ViewData`,主要用來控制輸出到瀏覽器的介面檢視物件。
434
+
435
+ ### 5.3.10.1 介面定義方式
436
+
437
+ ```csharp
438
+ public class MyResultFilter : IAsyncResultFilter
439
+ {
440
+ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
441
+ {
442
+ var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
443
+ var routeData = context.RouteData;
444
+
445
+ // 判斷如果是 MVC 檢視,可以動態新增資料到頁面中
446
+ if (context.Result is ViewResult viewResult)
447
+ {
448
+ viewResult.TempData["Name"] = "Furion";
449
+ viewResult.ViewData["Version"] = 1;
450
+ }
451
+
452
+ // 也可以強制替換 Result
453
+ // context.Result = new ContentResult("....");
454
+
455
+ // 執行下一個結果過濾器
456
+ // 如果要短路,設定 context.Cancel = true; 即可
457
+ var resultContext = await next();
458
+ var result = resultContext.Result;
459
+ }
460
+ }
461
+ ```
462
+
463
+ **全域註冊:** `services.AddMvcFilter<MyResultFilter>();`
464
+
465
+ ### 5.3.10.2 ResultFilterAttribute 方式
466
+
467
+ ```csharp
468
+ public class MyResultAttribute : ResultFilterAttribute
469
+ {
470
+ public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
471
+ {
472
+ return base.OnResultExecutionAsync(context, next);
473
+ }
474
+ }
475
+
476
+ // 使用
477
+ [MyResult]
478
+ public IActionResult Get() { /* ... */ }
479
+ ```
480
+
481
+ ### 5.3.10.3 特性定義方式(組合)
482
+
483
+ ```csharp
484
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
485
+ public class MyResultFilterAttribute : Attribute, IAsyncResultFilter
486
+ {
487
+ public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
488
+ {
489
+ // 程式碼同上
490
+ }
491
+ }
492
+ ```
493
+
494
+ ### 5.3.10.4 IAlwaysRunResultFilter
495
+
496
+ `IAlwaysRunResultFilter` 和 `IAsyncAlwaysRunResultFilter` 介面宣告了一個針對所有操作結果執行的 `IResultFilter` 實作。這包括由以下物件產生的操作結果:設定短路的授權篩選器和資源篩選器、例外篩選器。
497
+
498
+ ---
499
+
500
+ ## 5.3.11 RazorPages 篩選器
501
+
502
+ Razor Pages 篩選器僅支援 Razor Pages 專案類型。
503
+
504
+ ```csharp
505
+ public class MyPageFilter : IAsyncPageFilter
506
+ {
507
+ public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
508
+ {
509
+ var routeData = context.RouteData;
510
+ var actionDescriptor = context.ActionDescriptor;
511
+ var methodDescriptor = context.HandlerMethod;
512
+
513
+ await next.Invoke();
514
+ }
515
+
516
+ public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
517
+ {
518
+ return Task.CompletedTask;
519
+ }
520
+ }
521
+ ```
522
+
523
+ **全域註冊:** `services.Configure<MvcOptions>(options => { options.Filters.Add(new MyPageFilter()); });`
524
+
525
+ ---
526
+
527
+ ## 5.3.12 篩選器取消和短路
528
+
529
+ 通常篩選器支援多個,只要呼叫 `await next()` 方法都會進入下一個篩選器,但如果透過 `context.Result = new XXXResult()` 之後,就可以使其短路。
530
+
531
+ 例外情況:
532
+
533
+ - 在 `IResultFilter` / `IAsyncResultFilter` 結果篩選器中,使用 `context.Cancel = true;` 設定短路
534
+ - 在 `IExceptionFilter` / `IAsyncExceptionFilter` 例外篩選器中,使用 `context.ExceptionHandled = true;` 設定短路
535
+
536
+ ---
537
+
538
+ ## 5.3.13 篩選器執行順序控制
539
+
540
+ ### 5.3.13.1 不同類型篩選器執行順序
541
+
542
+ ```
543
+ IAuthorizationFilter → IResourceFilter → IActionFilter → IExceptionFilter → IResultFilter → IAlwaysRunResultFilter
544
+ ```
545
+
546
+ ### 5.3.13.2 相同類型篩選器執行順序
547
+
548
+ 預設透過 `services.Configure<MvcOptions>(...)` 方式先註冊先執行,之後到特性方式。
549
+
550
+ 也可以控制執行順序:
551
+
552
+ **IOrderedFilter 方式:**
553
+
554
+ ```csharp
555
+ public class MyActionFilter : IAsyncActionFilter, IOrderedFilter
556
+ {
557
+ public int Order => 1000; // 值越大,越優先執行
558
+
559
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
560
+ {
561
+ // ....
562
+ }
563
+ }
564
+ ```
565
+
566
+ **特性方式:**
567
+
568
+ ```csharp
569
+ [MyActionFilter(Order = 1000)]
570
+ public class ControllerFiltersController : Controller { /* ... */ }
571
+ ```
572
+
573
+ ---
574
+
575
+ ## 5.3.14 篩選器相依性注入
576
+
577
+ 篩選器支援建構函式相依性注入服務,使用前提是在 `Startup.cs` 中註冊:
578
+
579
+ ```csharp
580
+ services.AddScoped<MyActionFilterAttribute>();
581
+ ```
@@ -0,0 +1,129 @@
1
+ # 5.4 請求稽核日誌(Audit)
2
+
3
+ > **小知識**:Furion 提供了非常強大的 `LoggingMonitor` 稽核日誌功能,可直接使用:LoggingMonitor 文件。
4
+
5
+ ---
6
+
7
+ ## 5.4.1 稽核日誌
8
+
9
+ 在一個企業應用系統中,使用者對系統所有的操作包括請求、資料庫操作等等都應該記錄起來,那麼這些日誌我們稱為操作日誌,也可以說稽核日誌。
10
+
11
+ > **關於資料庫操作稽核日誌**:如需實現 SQL 操作、資料庫操作的稽核日誌可查閱【9.23 審計日誌章節】。
12
+
13
+ ---
14
+
15
+ ## 5.4.2 請求稽核日誌
16
+
17
+ ### 實現原理
18
+
19
+ 結合【5.3 篩選器】實現請求稽核日誌功能。
20
+
21
+ 請求稽核日誌通常指的是記錄請求位址、來源位址、操作人、傳遞參數等。主要透過 `IAsyncActionFilter` 篩選器實現。
22
+
23
+ ### 定義 RequestAuditFilter 並實現 IAsyncActionFilter
24
+
25
+ ```csharp
26
+ using Microsoft.AspNetCore.Http;
27
+ using Microsoft.AspNetCore.Mvc.Filters;
28
+ using System;
29
+ using System.Security.Claims;
30
+ using System.Threading.Tasks;
31
+
32
+ namespace Furion.Web.Core
33
+ {
34
+ public class RequestAuditFilter : IAsyncActionFilter
35
+ {
36
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
37
+ {
38
+ //============== 執行方法之前取得資料 ====================
39
+
40
+ // 取得控制器、路由資訊
41
+ var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
42
+
43
+ // 取得請求的方法
44
+ var method = actionDescriptor.MethodInfo;
45
+
46
+ // 取得 HttpContext 和 HttpRequest 物件
47
+ var httpContext = context.HttpContext;
48
+ var httpRequest = httpContext.Request;
49
+
50
+ // 取得客戶端 IPv4 位址
51
+ var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
52
+
53
+ // 取得請求的 Url 位址
54
+ var requestUrl = httpRequest.GetRequestUrlAddress();
55
+
56
+ // 取得來源 Url 位址
57
+ var refererUrl = httpRequest.GetRefererUrlAddress();
58
+
59
+ // 取得請求參數(寫入日誌,需序列化成字串後儲存)
60
+ var parameters = context.ActionArguments;
61
+
62
+ // 取得操作人(必須授權存取才有值)
63
+ // "userId" 為你儲存的 claims type,JWT 授權對應的是 payload 中儲存的鍵名
64
+ var userId = httpContext.User?.FindFirstValue("userId");
65
+
66
+ // 請求時間
67
+ var requestedTime = DateTimeOffset.Now;
68
+
69
+ //============== 執行方法之後取得資料 ====================
70
+ var actionContext = await next();
71
+
72
+ // 取得回傳的結果
73
+ var returnResult = actionContext.Result;
74
+
75
+ // 判斷是否請求成功,沒有例外就是請求成功
76
+ var isRequestSucceed = actionContext.Exception == null;
77
+
78
+ // 取得呼叫堆疊資訊
79
+ var stackTrace = EnhancedStackTrace.Current();
80
+
81
+ // 這裡寫入日誌,或儲存到資料庫中
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### 註冊 RequestAuditFilter 篩選器
88
+
89
+ ```csharp
90
+ services.AddMvcFilter<RequestAuditFilter>();
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 5.4.3 LoggingMonitor 稽核日誌
96
+
97
+ > Furion 提供了非常強大的 `LoggingMonitor` 稽核日誌功能,可直接使用。
98
+
99
+ 輸出範例:
100
+
101
+ ```
102
+ ┏━━━━━━━━━━━ Logging Monitor ━━━━━━━━━━━
103
+ ┣ Furion.Application.TestLoggerServices.GetPerson (Furion.Application)
104
+
105
+ ┣ 控制器名稱: TestLoggerServices
106
+ ┣ 操作名稱: GetPerson
107
+ ┣ 路由資訊: [area]: ; [controller]: test-logger; [action]: person
108
+ ┣ 請求方式: POST
109
+ ┣ 請求位址: https://localhost:44316/api/test-logger/person/11
110
+ ┣ 來源位址: https://localhost:44316/api/index.html
111
+ ┣ 瀏覽器標識: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
112
+ ┣ 客戶端 IP 位址: 0.0.0.1
113
+ ┣ 伺服端 IP 位址: 0.0.0.1
114
+ ┣ 伺服端執行環境: Development
115
+ ┣ 執行耗時: 31ms
116
+ ┣ ━━━━━━━━━━━━━━━ 授權資訊 ━━━━━━━━━━━━━━━
117
+ ┣ JWT Token: Bearer eyJhbGci...
118
+
119
+ ┣ UserId (integer): 1
120
+ ┣ Account (string): admin
121
+ ┣ ━━━━━━━━━━━━━━━ 參數列表 ━━━━━━━━━━━━━━━
122
+ ┣ Content-Type:
123
+
124
+ ┣ id (Int32): 11
125
+ ┣ ━━━━━━━━━━━━━━━ 回傳資訊 ━━━━━━━━━━━━━━━
126
+ ┣ 類型: Furion.Application.Persons.PersonDto
127
+ ┣ 回傳值: {"Id":11,"Name":null,"Age":0,...}
128
+ ┗━━━━━━━━━━━ Logging Monitor ━━━━━━━━━━━
129
+ ```