|
| 1 | +# .NET 每周分享第 40 期 |
| 2 | + |
| 3 | +## 卷首语 |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +今年 `.NET Conf` 日期已经确定下来,11 月 14 到 16 号,有下面几个重要的内容: |
| 8 | + |
| 9 | +1. 拥抱 `.NET` 生态 |
| 10 | +2. 一系列现场环节 |
| 11 | +3. 内容需求提交 |
| 12 | +4. 探索其他主题,比如云原生,Blazor, .NET MAUI 和智能化应用 |
| 13 | +5. 颁奖 |
| 14 | +6. 全球本地活动 |
| 15 | + |
| 16 | +## 行业资讯 |
| 17 | + |
| 18 | +1、[Nuget 包中 Microsoft 签名更新](https://devblogs.microsoft.com/nuget/microsoft-author-signing-certificate-update-2023/) |
| 19 | + |
| 20 | + |
| 21 | + |
| 22 | +公开发行的 `Nuget` 包通常需要作者的证书来进行签名,使用者可以通过certififcate 的 thumbprint 来校验包的一致性。最近微软更新了使用的证书,相应的开发者需要在 `nuget.config` 中增加相应的证书 thumbprint. |
| 23 | + |
| 24 | + |
| 25 | +2、[Moq 中的恶意代码](https://github.com/moq/moq/issues/1372) |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +`Moq` 是 .NET 社区中广泛使用的一个单元测试库。最近的版本(4.20)包含了一个 [SponsorLink](https://github.com/devlooped/SponsorLink) 包,它会在编译阶段扫描用户 `git` 账户下的信息,并且上传到服务端用来检查是否为 `Sponsor`。这个违反了很多国家和公司的隐私条款,因此在社区得到了广泛的讨论。幸运的是最新的版本已经移除了该功能。 |
| 30 | + |
| 31 | +3、[Visual Studio 支持文件比较](https://devblogs.microsoft.com/visualstudio/new-in-visual-studio-compare-files-with-solution-explorer/) |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +文件比较是一项日常开发过程中常见的工作,之前一般会借助命令行工具或者第三方软件来进行比较。最近 `Visual Studio` 引入文件比较的功能,它有两种方式 |
| 36 | +1. 选择多个文件,然后在上下文菜单中选择比较 |
| 37 | +2. 选择一个文件,然后在文件选择对话框中选择文件进行比较 |
| 38 | + |
| 39 | +## 文章推荐 |
| 40 | + |
| 41 | +1、[C# 中的 Feature Gate](https://github.com/microsoft/FeatureManagement-Dotnet) |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +`Microsfot.FeatureManagement` 和 `Microsoft.FeatureManagement.AspNetCore` 是微软维护的 **Feature Gate** 功能的包,主要是借助 `IConfiguration` 接口来实现功能的开关。这样应用程序无需重新编译,部署或者重启来完成功能的打开和关闭。 |
| 46 | + |
| 47 | + |
| 48 | +2、[C# 中 Class 和 Struct 比较](https://blog.ndepend.com/class-vs-struct-in-c-making-informed-choices/) |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +C# 中关于 `Class` 和 `Struct` 的比较数不胜数,这边文章给了一个详细的比较 |
| 53 | + |
| 54 | +1. Performance 考虑 |
| 55 | +2. 复杂对象使用 class |
| 56 | +3. 轻量级数据用 struct |
| 57 | +4. 避免可变 struct |
| 58 | +5. 注意过量拷贝 |
| 59 | +6. 涉及接口使用 class |
| 60 | +7. 引用乐类型使用 class |
| 61 | +8. 避免嵌套 struct |
| 62 | + |
| 63 | +3、[ASP.NET Core 中使用 ProblemDetail 的 Response](https://timdeschryver.dev/blog/translating-exceptions-into-problem-details-responses) |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +在网络应用程序遇到一场的时候,我们通常会返回如下的 response. |
| 68 | + |
| 69 | +```text |
| 70 | +HTTP/1.1 500 Internal Server Error |
| 71 | +Content-Length: 0 |
| 72 | +Connection: close |
| 73 | +Date: Mon, 24 Jul 2023 13:52:12 GMT |
| 74 | +Server: Kestrel |
| 75 | +Alt-Svc: h3=":5099"; ma=86400 |
| 76 | +``` |
| 77 | + |
| 78 | +这个做法的问题是我们无法知道具体的错误是什么。在 [RFC3986](https://datatracker.ietf.org/doc/html/rfc7807) 中确定使用 `Problem Details` 方式来组织异常。虽然这仍然处于草稿阶段,但是很多应用程序已经接受这个标准。 |
| 79 | +在 `ASP.NET Core 8.0` 预览版中,也支持这样的处理操作 |
| 80 | + |
| 81 | +1. 首先添加 `IApplicationBuilder.UseExceptionHandler()` 拓展方法 |
| 82 | + |
| 83 | +```csharp |
| 84 | +builder.Services.AddProblemDetails(); |
| 85 | +var app = builder.Build(); |
| 86 | +app.UseStatusCodePages(); |
| 87 | +app.UseExceptionHandler(); |
| 88 | +``` |
| 89 | + |
| 90 | +2. 实现 `IExceptionHandler` 接口 |
| 91 | + |
| 92 | +```csharp |
| 93 | +public class ExceptionToProblemDetailsHandler : Microsoft.AspNetCore.Diagnostics.IExceptionHandler |
| 94 | +{ |
| 95 | + public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) |
| 96 | + { |
| 97 | + httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; |
| 98 | + await httpContext.Response.WriteAsJsonAsync(new ProblemDetails |
| 99 | + { |
| 100 | + Title = "An error occurred", |
| 101 | + Detail = exception.Message, |
| 102 | + Type = exception.GetType().Name, |
| 103 | + Status = (int)HttpStatusCode.BadRequest |
| 104 | + }, cancellationToken: cancellationToken); |
| 105 | + |
| 106 | + return true; |
| 107 | + } |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +3. 注册实例对象 |
| 112 | + |
| 113 | +```csharp |
| 114 | +builder.Services.AddExceptionHandler<ExceptionToProblemDetailsHandler>(); |
| 115 | +``` |
| 116 | + |
| 117 | +4、[Powershell 中 Foreach-Object 中 Parallel 参数](https://www.youtube.com/watch?v=w_4Slu19DcY&list=WL&index=1&t=1940s&ab_channel=JackedProgrammer) |
| 118 | + |
| 119 | +在 `Powershell Core` 中的 `ForeEach-Object` 提供了 `-Parallel` 这个参数,该参数用来进行并发处理请求,提高效率。但是有几个注意点: |
| 120 | + |
| 121 | +1. 并不是 `-Parallel` 就能完全提高运行效率 |
| 122 | +2. 并行使用全局变量 |
| 123 | + |
| 124 | +```powershell |
| 125 | +$result = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) |
| 126 | +
|
| 127 | +$logNames=@("Application", "System", "Windows PowerShell") |
| 128 | +$logNames | ForEach-Object -Parallel { |
| 129 | + $logs = Get-EventLog -LogName $_ -Newest 100 |
| 130 | + $result = $using:result |
| 131 | + $result.AddRange($logs) | Out-Null |
| 132 | +} |
| 133 | +Write-Output $result.Count |
| 134 | +``` |
| 135 | + |
| 136 | +- `result` 类型包含 `Synchronized` 关键字 |
| 137 | +- 在并行方法体中使用 `$using:result` 来获取全局变量 |
| 138 | + |
| 139 | +3. 并行调用定义方法 |
| 140 | + |
| 141 | +```powershelll |
| 142 | +function SayHello { |
| 143 | + Write-Output "hello" |
| 144 | +} |
| 145 | +
|
| 146 | +$funcDef = ${function:SayHello}.ToString() |
| 147 | +$array = 1..10 |
| 148 | +$array | foreach-object -parallel { |
| 149 | + ${function:SayHello} = $using:funcDef |
| 150 | + SayHello |
| 151 | +} |
| 152 | +``` |
| 153 | +这里定义了 `SayHello` 方法,通过 `funcDef` 保存函数定义。然后在并行方法中使用 `$using:FuncDef` 来找到方法。 |
| 154 | + |
| 155 | +5、[Moq 迁移到 NSubstitue](https://timdeschryver.dev/blog/a-cheat-sheet-to-migrate-from-moq-to-nsubstitute#automate-migration) |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +当上次 `Moq` 出现隐私问题之后,社区一直在鼓励大家迁移到 `NSubstitute`,这里有一份迁移清单。 |
| 160 | + |
| 161 | +6、[Unit Test 的建议](https://ardalis.com/mastering-unit-tests-dotnet-best-practices-naming-conventions/) |
| 162 | + |
| 163 | +我们都知道单元测试的重要性,那么单元测试有哪些 Best Practice 呢 ? |
| 164 | + |
| 165 | +- 清晰和可读性 |
| 166 | +- 简单 |
| 167 | +- 隔离 |
| 168 | +- 可重复性 |
| 169 | +- 快速 |
| 170 | +- 可维护性 |
| 171 | +- 覆盖率 |
| 172 | + |
| 173 | +那么单元测试命名规则 |
| 174 | +- ClassNameMethodName.DoesXGivenY |
| 175 | +- Given_Precondition_When_Action_Then_ExpectedResult |
| 176 | + |
| 177 | +7、[.NET SDK 装箱操作的优化](https://pvs-studio.com/en/blog/posts/csharp/1060/) |
| 178 | + |
| 179 | +这是一篇 `.NET` SDK 在版本迭代中对装箱操作的优化,比如这样一段代码 |
| 180 | +```csharp |
| 181 | +string Foo(int a) |
| 182 | +{ |
| 183 | + return "The value is " + a; |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +一般而言,`+` 操作符的定义为 `string string.operator + (string leeft, object right)`,按照我们的理解,这里会发生一次装箱操作,因为 `a` 为 `int` 类型,而方法的接受的数据类型为 `object`,所以要进行装箱。而且 `IL` 代码也是同样如此 |
| 188 | +```IL |
| 189 | +.method private hidebysig static void Foo(string str, |
| 190 | + int32 a) cil managed |
| 191 | +{ |
| 192 | + .... |
| 193 | + IL_0001: ldarg.0 |
| 194 | + IL_0002: ldarg.1 |
| 195 | + IL_0003: box [mscorlib]System.Int32 |
| 196 | + IL_0008: call string [mscorlib]System.String::Concat(object, |
| 197 | + object) |
| 198 | + IL_000d: stloc.0 |
| 199 | + IL_000e: ret |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +但是如果使用 `Visual Studio 2019` 编译这段代码,我们可以发现生成的 IL 代码却变成了这样 |
| 204 | + |
| 205 | +```IL |
| 206 | +.method private hidebysig static void Foo(string str, |
| 207 | + int32 a) cil managed |
| 208 | +{ |
| 209 | + .... |
| 210 | + IL_0001: ldarg.0 |
| 211 | + IL_0002: ldarga.s a |
| 212 | + IL_0004: call instance string [mscorlib]System.Int32::ToString() |
| 213 | + IL_0009: call string [mscorlib]System.String::Concat(string, |
| 214 | + string) |
| 215 | + IL_000e: stloc.0 |
| 216 | + IL_000f: ret |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +这样我们可以发现,这里没有装箱操作,只有 `Int32.ToString()` 方法。 |
| 221 | + |
| 222 | +## 开源项目 |
| 223 | + |
| 224 | +1、[Sisk](https://github.com/sisk-http/core) |
| 225 | + |
| 226 | + |
| 227 | + |
| 228 | +在 `.NET` 生态中,`ASP.NET` 和 `ASP.NET Core` 占据了服务器端框架的主要市场,而且凭借优异的性能和官方支持背书,得到了广大用户的使用。但是有时候我们并不希望我们的服务端需要这么重量级的框架,那么可以选择 `Sisk` 这个开源包,它的目标就是一个轻量级,无依赖,简单易用的网络开发框架。 |
| 229 | + |
| 230 | +```csharp |
| 231 | +using Sisk.Core.Http; |
| 232 | + |
| 233 | +using static Sisk.Core.Routing.RouteMethod; |
| 234 | + |
| 235 | +var http = HttpServer.Emit( |
| 236 | + insecureHttpPort: 5000, |
| 237 | + host: out _, |
| 238 | + configuration: out _, |
| 239 | + router: out var router); |
| 240 | + |
| 241 | +router.SetRoute(Get, "/", _ => new HttpResponse(200) |
| 242 | +{ |
| 243 | + Content = new StringContent("Hello World") |
| 244 | +}); |
| 245 | + |
| 246 | +router.SetRoute(Get, "/hi/<name>", req => |
| 247 | +{ |
| 248 | + var name = req.Query["name"]; |
| 249 | + return new(200) |
| 250 | + { |
| 251 | + Content = new HtmlContent($"<h1>Hello, {name}</h1>") |
| 252 | + }; |
| 253 | +}); |
| 254 | + |
| 255 | +http.Start(); |
| 256 | +Console.WriteLine($"Http server is listening on {http.ListeningPrefixes[0]}"); |
| 257 | +Console.ReadKey(); |
| 258 | +``` |
| 259 | + |
| 260 | +2、[NBomber](https://github.com/PragmaticFlow/NBomber) |
| 261 | + |
| 262 | + |
| 263 | + |
| 264 | +NBomber 是一个开源你的负载测试框架,比如 HTTP,WebSocket 等等。它的客户端逻辑全部由 `C#` 代码实现 |
| 265 | + |
| 266 | +```csharp |
| 267 | +var scenario = Scenario.Create("hello_world_scenario", async context => |
| 268 | +{ |
| 269 | + // you can define and execute any logic here, |
| 270 | + // for example: send http request, SQL query etc |
| 271 | + // NBomber will measure how much time it takes to execute your logic |
| 272 | + await Task.Delay(1_000); |
| 273 | + |
| 274 | + return Response.Ok(); |
| 275 | +}) |
| 276 | +.WithLoadSimulations( |
| 277 | + Simulation.Inject(rate: 10, |
| 278 | + interval: TimeSpan.FromSeconds(1), |
| 279 | + during: TimeSpan.FromSeconds(30)) |
| 280 | +); |
| 281 | + |
| 282 | +NBomberRunner |
| 283 | + .RegisterScenarios(scenario) |
| 284 | + .Run(); |
| 285 | +``` |
| 286 | + |
| 287 | +它有以下这点特点 |
| 288 | +- 无任何协议的依赖 |
| 289 | +- 无语义模型的依赖 |
| 290 | +- 灵活的配置和简单的 API |
| 291 | +- 分布式集群支持 |
| 292 | +- 实时报告 |
| 293 | +- CI/CD 集成 |
| 294 | +- 插件和拓展支持 |
| 295 | +- 数据源支持 |
| 296 | + |
0 commit comments