@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,340 @@
|
|
|
1
|
+
# 23. JSON 序列化
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 23.1 概述
|
|
6
|
+
|
|
7
|
+
JSON 是一種輕量級的資料交換格式,在前後端資料互動中廣泛應用。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 23.2 序列化庫
|
|
12
|
+
|
|
13
|
+
| 庫 | 說明 |
|
|
14
|
+
|---|------|
|
|
15
|
+
| `System.Text.Json` | .NET Core 內建,Furion 框架**預設實作** |
|
|
16
|
+
| `Newtonsoft.Json` | 使用人數最多,需安裝 `Microsoft.AspNetCore.Mvc.NewtonsoftJson`(Furion 4.6.5+ 已內建) |
|
|
17
|
+
|
|
18
|
+
Furion 抽象出 `IJsonSerializerProvider` 介面,統一不同序列化工具的設定與用法差異。
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 23.3 IJsonSerializerProvider 介面
|
|
23
|
+
|
|
24
|
+
```csharp
|
|
25
|
+
public interface IJsonSerializerProvider
|
|
26
|
+
{
|
|
27
|
+
string Serialize(object value, object jsonSerializerOptions = default);
|
|
28
|
+
T Deserialize<T>(string json, object jsonSerializerOptions = default);
|
|
29
|
+
object Deserialize(string json, Type returnType, object jsonSerializerOptions = default);
|
|
30
|
+
object GetSerializerOptions();
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
預設實作:`SystemTextJsonSerializerProvider`(啟動時自動註冊)。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 23.4 基本使用
|
|
39
|
+
|
|
40
|
+
```csharp
|
|
41
|
+
// 取得實例
|
|
42
|
+
private readonly IJsonSerializerProvider _json;
|
|
43
|
+
public MyService(IJsonSerializerProvider json) => _json = json;
|
|
44
|
+
// 或靜態方式
|
|
45
|
+
var json = JSON.GetJsonSerializer();
|
|
46
|
+
|
|
47
|
+
// 序列化
|
|
48
|
+
var str = _json.Serialize(new { Id = 1, Name = "Furion" });
|
|
49
|
+
|
|
50
|
+
// 反序列化
|
|
51
|
+
var obj = _json.Deserialize<MyModel>(jsonString);
|
|
52
|
+
|
|
53
|
+
// 自訂選項
|
|
54
|
+
var str = _json.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> ⚠️ `System.Text.Json` 預設反序列化**大小寫敏感**,需設定 `PropertyNameCaseInsensitive = true`。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 23.5 常見設定速查
|
|
62
|
+
|
|
63
|
+
以下同時列出 `System.Text.Json`(STJ)和 `Newtonsoft.Json`(NJ)的設定方式。
|
|
64
|
+
|
|
65
|
+
### 23.5.1 替換為 Newtonsoft.Json
|
|
66
|
+
|
|
67
|
+
```csharp
|
|
68
|
+
services.AddControllersWithViews().AddNewtonsoftJson();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
建立 `NewtonsoftJsonSerializerProvider`(實作 `IJsonSerializerProvider, ISingleton`):
|
|
72
|
+
|
|
73
|
+
```csharp
|
|
74
|
+
public class NewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ISingleton
|
|
75
|
+
{
|
|
76
|
+
public string Serialize(object value, object opts = null)
|
|
77
|
+
=> JsonConvert.SerializeObject(value, (opts ?? GetSerializerOptions()) as JsonSerializerSettings);
|
|
78
|
+
|
|
79
|
+
public T Deserialize<T>(string json, object opts = null)
|
|
80
|
+
=> JsonConvert.DeserializeObject<T>(json, (opts ?? GetSerializerOptions()) as JsonSerializerSettings);
|
|
81
|
+
|
|
82
|
+
public object Deserialize(string json, Type type, object opts = null)
|
|
83
|
+
=> JsonConvert.DeserializeObject(json, type, (opts ?? GetSerializerOptions()) as JsonSerializerSettings);
|
|
84
|
+
|
|
85
|
+
public object GetSerializerOptions()
|
|
86
|
+
=> App.GetOptions<MvcNewtonsoftJsonOptions>()?.SerializerSettings;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 23.5.2 屬性名原樣輸出(大寫開頭)
|
|
91
|
+
|
|
92
|
+
```csharp
|
|
93
|
+
// STJ
|
|
94
|
+
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
|
95
|
+
|
|
96
|
+
// NJ
|
|
97
|
+
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 23.5.3 時間格式化(時間戳轉時間)
|
|
101
|
+
|
|
102
|
+
```csharp
|
|
103
|
+
// STJ(4.6.5+,4.9.1.31+ 預設值為 yyyy-MM-dd HH:mm:ss)
|
|
104
|
+
options.JsonSerializerOptions.Converters.AddDateTimeTypeConverters("yyyy-MM-dd HH:mm:ss");
|
|
105
|
+
// UTC 自動轉本地:AddDateTimeTypeConverters("yyyy-MM-dd HH:mm:ss", true)
|
|
106
|
+
|
|
107
|
+
// NJ(4.9.1.3+)
|
|
108
|
+
options.SerializerSettings.Converters.AddDateTimeTypeConverters("yyyy-MM-dd HH:mm:ss");
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> 4.9.1.3+ 還支援自動將 13/10 位時間戳轉換為 `DateTime` / `DateTimeOffset` 型別。
|
|
112
|
+
|
|
113
|
+
### 23.5.4 忽略循環參考
|
|
114
|
+
|
|
115
|
+
```csharp
|
|
116
|
+
// STJ(.NET 6+)
|
|
117
|
+
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
|
118
|
+
|
|
119
|
+
// NJ
|
|
120
|
+
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 23.5.5~23.5.7 其他 STJ 設定
|
|
124
|
+
|
|
125
|
+
```csharp
|
|
126
|
+
options.JsonSerializerOptions.IncludeFields = true; // 包含成員欄位
|
|
127
|
+
options.JsonSerializerOptions.AllowTrailingCommas = true; // 允許尾隨逗號
|
|
128
|
+
options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; // 允許註解
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 23.5.8 處理中文亂碼
|
|
132
|
+
|
|
133
|
+
```csharp
|
|
134
|
+
// STJ
|
|
135
|
+
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
|
|
136
|
+
|
|
137
|
+
// NJ:無需設定
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 23.5.9 不區分大小寫
|
|
141
|
+
|
|
142
|
+
```csharp
|
|
143
|
+
// STJ
|
|
144
|
+
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
|
145
|
+
|
|
146
|
+
// NJ:無需設定
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 23.5.10 忽略特定屬性
|
|
150
|
+
|
|
151
|
+
```csharp
|
|
152
|
+
[System.Text.Json.Serialization.JsonIgnore] // STJ
|
|
153
|
+
[Newtonsoft.Json.JsonIgnore] // NJ
|
|
154
|
+
public string PropertyName { get; set; }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 23.5.11 動態物件屬性名大寫問題
|
|
158
|
+
|
|
159
|
+
```csharp
|
|
160
|
+
// NJ
|
|
161
|
+
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 23.5.12 忽略所有 null 屬性
|
|
165
|
+
|
|
166
|
+
```csharp
|
|
167
|
+
// STJ
|
|
168
|
+
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
|
169
|
+
|
|
170
|
+
// NJ
|
|
171
|
+
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 23.5.13 忽略所有預設值屬性
|
|
175
|
+
|
|
176
|
+
```csharp
|
|
177
|
+
// STJ
|
|
178
|
+
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
|
|
179
|
+
|
|
180
|
+
// NJ
|
|
181
|
+
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 23.5.14 控制屬性序列化順序
|
|
185
|
+
|
|
186
|
+
```csharp
|
|
187
|
+
[System.Text.Json.Serialization.JsonPropertyOrder(0)] // STJ
|
|
188
|
+
[Newtonsoft.Json.JsonProperty(Order = 0)] // NJ
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 23.5.15 重新命名序列化名稱
|
|
192
|
+
|
|
193
|
+
```csharp
|
|
194
|
+
[System.Text.Json.Serialization.JsonPropertyName("newName")] // STJ
|
|
195
|
+
[Newtonsoft.Json.JsonProperty("newName")] // NJ
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 23.5.16 JSON 字串縮排
|
|
199
|
+
|
|
200
|
+
```csharp
|
|
201
|
+
// STJ
|
|
202
|
+
options.JsonSerializerOptions.WriteIndented = true;
|
|
203
|
+
|
|
204
|
+
// NJ
|
|
205
|
+
options.SerializerSettings.Formatting = Formatting.Indented;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 23.5.17 long 轉 string(防止 JS 精度溢位)
|
|
209
|
+
|
|
210
|
+
```csharp
|
|
211
|
+
// STJ
|
|
212
|
+
options.JsonSerializerOptions.Converters.AddLongTypeConverters();
|
|
213
|
+
// 4.9.1.23+ 可設定超過 17 位才轉換
|
|
214
|
+
// options.JsonSerializerOptions.Converters.AddLongTypeConverters(overMaxLengthOf17: true);
|
|
215
|
+
|
|
216
|
+
// NJ
|
|
217
|
+
options.SerializerSettings.Converters.AddLongTypeConverters();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
> `Dictionary<,>` 含 `long` 的情況 STJ 不支援,需改用 NJ。
|
|
221
|
+
|
|
222
|
+
### 23.5.18 String 轉 Number
|
|
223
|
+
|
|
224
|
+
```csharp
|
|
225
|
+
// STJ
|
|
226
|
+
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
|
|
227
|
+
|
|
228
|
+
// NJ:無需設定
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 23.5.19 Number / Boolean 轉 String(4.9.7.29+)
|
|
232
|
+
|
|
233
|
+
```csharp
|
|
234
|
+
// STJ
|
|
235
|
+
options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 23.5.20 DateOnly / TimeOnly 支援(4.7.9+)
|
|
239
|
+
|
|
240
|
+
```csharp
|
|
241
|
+
// STJ
|
|
242
|
+
options.JsonSerializerOptions.Converters.AddDateOnlyConverters();
|
|
243
|
+
options.JsonSerializerOptions.Converters.AddTimeOnlyConverters();
|
|
244
|
+
|
|
245
|
+
// NJ
|
|
246
|
+
options.SerializerSettings.Converters.AddDateOnlyConverters();
|
|
247
|
+
options.SerializerSettings.Converters.AddTimeOnlyConverters();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 23.5.21 Clay 流變物件序列化
|
|
251
|
+
|
|
252
|
+
```csharp
|
|
253
|
+
// STJ
|
|
254
|
+
options.JsonSerializerOptions.Converters.AddClayConverters();
|
|
255
|
+
|
|
256
|
+
// NJ
|
|
257
|
+
options.SerializerSettings.Converters.AddClayConverters();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 23.5.22 DateTimeOffset 反序列化異常(NJ)
|
|
261
|
+
|
|
262
|
+
```csharp
|
|
263
|
+
// NJ(處理 0001-01-01 00:00:00 報錯)
|
|
264
|
+
options.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
|
|
265
|
+
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
|
|
266
|
+
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal });
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 23.5.23 繼承/衍生類別序列化
|
|
270
|
+
|
|
271
|
+
```csharp
|
|
272
|
+
// .NET 7 之前 STJ 需手動處理
|
|
273
|
+
var json = JsonSerializer.Serialize<object>(value);
|
|
274
|
+
var json = JsonSerializer.Serialize(value, value.GetType());
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 23.5.24 列舉與字串互轉
|
|
278
|
+
|
|
279
|
+
```csharp
|
|
280
|
+
// STJ 局部
|
|
281
|
+
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
282
|
+
public Gender Gender { get; set; }
|
|
283
|
+
|
|
284
|
+
// STJ 全域
|
|
285
|
+
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
|
286
|
+
|
|
287
|
+
// NJ 局部
|
|
288
|
+
[JsonConverter(typeof(StringEnumConverter))]
|
|
289
|
+
public Gender Gender { get; set; }
|
|
290
|
+
|
|
291
|
+
// NJ 全域
|
|
292
|
+
options.SerializerSettings.Converters.Add(new StringEnumConverter());
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 23.5.25 檢查 JSON 有效性
|
|
296
|
+
|
|
297
|
+
```csharp
|
|
298
|
+
// 原生方式
|
|
299
|
+
try { using var doc = JsonDocument.Parse(jsonString); }
|
|
300
|
+
catch (JsonException) { /* 無效 */ }
|
|
301
|
+
|
|
302
|
+
// Furion 靜態類(4.9.1.8+)
|
|
303
|
+
bool isValid = JSON.IsValid(jsonString);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 23.5.26 自訂型別序列化轉換器
|
|
307
|
+
|
|
308
|
+
```csharp
|
|
309
|
+
// STJ
|
|
310
|
+
public class LongToStringJsonConverter : JsonConverter<long>
|
|
311
|
+
{
|
|
312
|
+
public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
|
|
313
|
+
=> reader.GetInt64();
|
|
314
|
+
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
|
|
315
|
+
=> writer.WriteStringValue(value.ToString());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// NJ
|
|
319
|
+
public class LongToStringJsonConverter : JsonConverter<long>
|
|
320
|
+
{
|
|
321
|
+
public override long ReadJson(JsonReader reader, Type type, long existingValue, bool hasExisting, JsonSerializer serializer)
|
|
322
|
+
=> JValue.ReadFrom(reader).Value<long>();
|
|
323
|
+
public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer)
|
|
324
|
+
=> writer.WriteValue(value.ToString());
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
> 可空型別(如 `long?`)需建立獨立的轉換器。
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 23.6 DataTable / DataSet / Tuple / JArray / JObject / JToken
|
|
333
|
+
|
|
334
|
+
`System.Text.Json` 不支援這些複雜型別,需替換為 `Newtonsoft.Json`(見 23.5.1)。
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 23.7 STJ vs NJ 完整差異對比
|
|
339
|
+
|
|
340
|
+
請參閱微軟官方文件:[從 Newtonsoft.Json 遷移到 System.Text.Json](https://docs.microsoft.com/zh-tw/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# 24. 即時通訊(SignalR)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 24.1 概述
|
|
6
|
+
|
|
7
|
+
即時通訊(IM)是指在網路上進行即時傳遞文字、文件、語音與視訊的系統。應用場景包括:聊天工具、網遊、直播、訂單推送、協同辦公等。
|
|
8
|
+
|
|
9
|
+
Furion 框架整合了微軟 **SignalR** 庫來簡化即時通訊開發。SignalR 已內建於 .NET SDK 中,支援 Web、App、Console、Desktop 等多平台。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 24.4 註冊 SignalR 服務
|
|
14
|
+
|
|
15
|
+
```csharp
|
|
16
|
+
// .NET 6+
|
|
17
|
+
var builder = WebApplication.CreateBuilder(args).Inject();
|
|
18
|
+
builder.Services.AddSignalR();
|
|
19
|
+
|
|
20
|
+
var app = builder.Build();
|
|
21
|
+
app.MapHubs(); // 註冊集線器
|
|
22
|
+
app.MapControllers();
|
|
23
|
+
app.Run();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```csharp
|
|
27
|
+
// .NET 5 Startup 模式
|
|
28
|
+
services.AddSignalR();
|
|
29
|
+
|
|
30
|
+
app.UseEndpoints(endpoints =>
|
|
31
|
+
{
|
|
32
|
+
endpoints.MapHubs();
|
|
33
|
+
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 24.6 集線器 Hub 定義
|
|
40
|
+
|
|
41
|
+
### Hub 方式
|
|
42
|
+
|
|
43
|
+
```csharp
|
|
44
|
+
[MapHub("/hubs/chathub")]
|
|
45
|
+
public class ChatHub : Hub
|
|
46
|
+
{
|
|
47
|
+
public Task SendMessage(string user, string message)
|
|
48
|
+
=> Clients.All.SendAsync("ReceiveMessage", user, message);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Hub\<T\> 強型別方式
|
|
53
|
+
|
|
54
|
+
```csharp
|
|
55
|
+
public interface IChatClient
|
|
56
|
+
{
|
|
57
|
+
Task ReceiveMessage(string user, string message);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[MapHub("/hubs/chathub")]
|
|
61
|
+
public class StronglyTypedChatHub : Hub<IChatClient>
|
|
62
|
+
{
|
|
63
|
+
public async Task SendMessage(string user, string message)
|
|
64
|
+
=> await Clients.All.ReceiveMessage(user, message);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> 使用 `Hub<IChatClient>` 可獲得編譯期檢查,避免魔法字串。
|
|
69
|
+
|
|
70
|
+
### [MapHub] 設定連線位址
|
|
71
|
+
|
|
72
|
+
```csharp
|
|
73
|
+
[MapHub("/hubs/chathub")]
|
|
74
|
+
public class ChatHub : Hub { }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Hub 進階設定
|
|
78
|
+
|
|
79
|
+
```csharp
|
|
80
|
+
[MapHub("/hubs/chathub")]
|
|
81
|
+
public class ChatHub : Hub
|
|
82
|
+
{
|
|
83
|
+
public static void HttpConnectionDispatcherOptionsSettings(HttpConnectionDispatcherOptions options) { }
|
|
84
|
+
public static void HubEndpointConventionBuilderSettings(HubEndpointConventionBuilder builder) { }
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 24.7 取得 Hub 實例
|
|
91
|
+
|
|
92
|
+
```csharp
|
|
93
|
+
// 建構子注入(單例,可在任何地方使用)
|
|
94
|
+
public HomeController(IHubContext<NotificationHub> hubContext) { }
|
|
95
|
+
|
|
96
|
+
// 強型別
|
|
97
|
+
public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext) { }
|
|
98
|
+
|
|
99
|
+
// HttpContext 解析
|
|
100
|
+
var hubContext = context.RequestServices.GetRequiredService<IHubContext<ChatHub>>();
|
|
101
|
+
|
|
102
|
+
// IHost 中解析
|
|
103
|
+
var hubContext = host.Services.GetService(typeof(IHubContext<ChatHub>));
|
|
104
|
+
|
|
105
|
+
// 泛型轉換(反射場景)
|
|
106
|
+
await CommonHubContextMethod((IHubContext)myHubContext);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 24.8 服務端與客戶端雙工通訊
|
|
112
|
+
|
|
113
|
+
| 方法 | 說明 |
|
|
114
|
+
|------|------|
|
|
115
|
+
| `Clients.All.方法(參數)` | 觸發所有客戶端 |
|
|
116
|
+
| `Clients.Caller.方法(參數)` | 觸發呼叫者客戶端 |
|
|
117
|
+
| `Clients.Others.方法(參數)` | 觸發呼叫者以外的客戶端 |
|
|
118
|
+
| `Clients.User("userId").方法(參數)` | 觸發特定使用者 |
|
|
119
|
+
| `Clients.Users("u1","u2").方法(參數)` | 觸發多個使用者 |
|
|
120
|
+
| `Clients.Group("group").方法(參數)` | 觸發分組內客戶端 |
|
|
121
|
+
| `Clients.Groups("g1","g2").方法(參數)` | 觸發多個分組 |
|
|
122
|
+
| `Clients.GroupExcept("group").方法(參數)` | 觸發分組外的客戶端 |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 24.9 自訂使用者唯一標識
|
|
127
|
+
|
|
128
|
+
```csharp
|
|
129
|
+
public class YourUserIdProvider : IUserIdProvider
|
|
130
|
+
{
|
|
131
|
+
public virtual string GetUserId(HubConnectionContext connection)
|
|
132
|
+
{
|
|
133
|
+
// 透過 connection.User 取得 JWT 授權的使用者
|
|
134
|
+
return connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 註冊
|
|
139
|
+
builder.Services.AddSingleton<IUserIdProvider, YourUserIdProvider>();
|
|
140
|
+
|
|
141
|
+
// 使用
|
|
142
|
+
Clients.User(userId).方法(參數);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 24.11 各客戶端連線 API
|
|
148
|
+
|
|
149
|
+
### 24.11.1 JavaScript 客戶端
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm install @microsoft/signalr
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
const connection = new signalR.HubConnectionBuilder()
|
|
157
|
+
.withUrl("/chathub")
|
|
158
|
+
.configureLogging(signalR.LogLevel.Information)
|
|
159
|
+
.build();
|
|
160
|
+
|
|
161
|
+
async function start() {
|
|
162
|
+
try {
|
|
163
|
+
await connection.start();
|
|
164
|
+
console.log("SignalR Connected.");
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.log(err);
|
|
167
|
+
setTimeout(start, 5000);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
connection.onclose(async () => await start());
|
|
172
|
+
start();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
或透過 CDN 引入:
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 24.11.2 TypeScript 客戶端(Vue 3.2+)
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm i @microsoft/signalr @types/node
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { HubConnectionBuilder } from "@microsoft/signalr";
|
|
189
|
+
import { ref } from "vue";
|
|
190
|
+
|
|
191
|
+
const messages = ref("");
|
|
192
|
+
|
|
193
|
+
const connection = new HubConnectionBuilder()
|
|
194
|
+
.withUrl("https://localhost:7260/chatHub")
|
|
195
|
+
.build();
|
|
196
|
+
|
|
197
|
+
connection.start().then(() => connection.send("SendMessage", "Hello"));
|
|
198
|
+
|
|
199
|
+
connection.on("ReciveMessage", (msg: string) => {
|
|
200
|
+
console.log("msg", msg);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const sendMsg = async () => {
|
|
204
|
+
await connection.send("SendMessage", messages.value);
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 24.11.3 .NET 客戶端
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
Install-Package Microsoft.AspNetCore.SignalR.Client
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```csharp
|
|
215
|
+
var connection = new HubConnectionBuilder()
|
|
216
|
+
.WithUrl("http://localhost:53353/ChatHub")
|
|
217
|
+
.Build();
|
|
218
|
+
|
|
219
|
+
connection.Closed += async (error) =>
|
|
220
|
+
{
|
|
221
|
+
await Task.Delay(new Random().Next(0, 5) * 1000);
|
|
222
|
+
await connection.StartAsync();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
connection.On<string, string>("ReceiveMessage", (user, message) =>
|
|
226
|
+
{
|
|
227
|
+
Dispatcher.Invoke(() => messagesList.Items.Add($"{user}: {message}"));
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await connection.StartAsync();
|
|
231
|
+
await connection.InvokeAsync("SendMessage", user, message);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 24.11.4 Java 客戶端
|
|
235
|
+
|
|
236
|
+
```groovy
|
|
237
|
+
// Gradle
|
|
238
|
+
implementation 'com.microsoft.signalr:signalr:7.0.0'
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```java
|
|
242
|
+
HubConnection hubConnection = HubConnectionBuilder.create(url).build();
|
|
243
|
+
hubConnection.send("Send", input);
|
|
244
|
+
hubConnection.on("Send", (message) -> {
|
|
245
|
+
System.out.println("New Message: " + message);
|
|
246
|
+
}, String.class);
|
|
247
|
+
```
|