@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.
- package/dist/index.js +83 -88
- package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
- package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
- package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
- package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
- 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
- package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
- 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
- package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
- package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
- 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
- package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
- 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
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
- package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
- package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
- package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
- package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
- package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
- package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
- package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
- package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
- package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
- 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
- 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
- package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
- package/knowledge/attributes.md +153 -0
- package/knowledge/config.md +147 -0
- package/knowledge/entity.md +115 -0
- package/knowledge/event.md +124 -0
- package/knowledge/plugin.md +136 -0
- package/knowledge/service.md +146 -0
- package/knowledge/sqlsugar.md +172 -0
- package/knowledge/vue-typescript.md +338 -0
- 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
|
+
```
|