C#中的可为空泛型返回值

当在 C# 8.0 中启用 NRT(Nullable Reference Types)时,代码中的 null 引用检查会得到加强,以帮助开发人员更好地识别和处理可能的空引用异常。在这种情况下,使用 T? 表示可空类型,其中 T 可以是引用类型或值类型。但是,如果使用 where T : struct 限定类型参数 T 为值类型,则 T? 表示 Nullable<T>。 var a = GetA<int>(); var b = GetB<int>(); T? GetA<T>() { return default; } T? GetB<T>() where T:struct { return default; } 在上述代码中,GetA<T>() 方法返回类型为 T?,因为 T 没有被限定为值类型,所以 T? 表示可空类型 T。由于 int 是值类型,因此 GetA<int>() 返回 0,而不是 null。 相反,GetB<T>() 方法限定类型参数 T 为值类型,因此 T? 表示 Nullable<T>。由于 int 是值类型,因此 GetB<int>() 返回 null,而不是 0。...

2023年8月4日

在 ASP.NET Core 中使用 Serilog

添加 Serilog 包引用 $ dotnet add package Serilog.AspNetCore $ dotnet add package Serilog.Sinks.Async 通过上方指令我们添加了以下两个包: Serilog.AspNetCore Serilog.AspNetCore 是基于 Serilog 框架的一个扩展库,用于在 ASP.NET Core 应用程序中使用 Serilog 来记录日志。它提供了一些方便的方法来集成 Serilog 框架到 ASP.NET Core 应用程序中,并支持从请求上下文中自动提取一些默认的日志信息,例如 HTTP 请求方法、路径和响应状态码等。通过使用 Serilog.AspNetCore,您可以轻松地将高质量日志记录添加到您的 ASP.NET Core 应用程序中。 Serilog.Sinks.Async Serilog.Sinks.Async 是 Serilog 库中的一个 sinks 扩展,用于异步地将日志事件写入目标存储。通过使用 Serilog.Sinks.Async,您可以避免在应用程序中引入 IO 操作的性能损失,从而提高应用程序的整体性能。 配置 Serilog 支持使用 appsettings.json 进行配置 在 appsettings.json 根节点下新增 Serilog 节点来配置 Serilog { "Serilog": { // 引用的 Serilog 的 sink,这里使用了 console、file、async 三种 "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Async"], // Serilog 对于不同的 logger 进行最小记录级别的配置 "MinimumLevel": { // 默认为 Information "Default": "Information", "Override": { // 对于 Microsoft 和 System 命名空间下的 logger,级别为 Warning "Microsoft": "Warning", "System": "Warning" } }, // 日志输出配置 "WriteTo": [ // 将日志输出到 Console { "Name": "Console" }, // 将日志输出到 File,同时使用 Async wrapper,可以让日志以异步方式写入文件 { "Name": "Async", // 使用 Async wrapper 提高日志写入文件性能 "Args": { "configure": [ { "Name": "File", "Args": { "path": "Logs/log....

2023年5月8日

如何正确在 ASP.NET Core 中返回流

最近有位朋友说他在接口中使用 File(Stream stream, string contentType) 方法报错,报错内容是 2023-03-30 18:00:36.8882|ERROR|Microsoft.AspNetCore.Server.Kestrel|Connection id "OHMPHOM94BVEL", Request id "OHMPHOM94BVEL:00000004": An unhandled exception was thrown by the application. 大致推测是请求接口前或者接口后出的异常,也没有详细信息,于是要来了一份代码,代码(精简版)如下: // ExcelDocument.cs public class ExcelDocument { public static Stream GetFileStream() { using var fs = new FileStream("appsettings.json", FileMode.Open, FileAccess.Read); var ms = new MemoryStream(); fs.CopyTo(ms); return ms; } } // HomeController.cs [HttpGet("GetFile")] public IActionResult GetFile() { var stream = ExcelDocument.GetFileStream(); // 原本是application/vnd.ms-excel, 我这里写测试返回的appsettings.json就换了一下 return File(stream, "application/json"); } 这段代码看着没啥问题,我试着请求了一下,果然有报错,我的报错如下:...

2023年3月30日

Newtonsoft.Json 小技巧

Newtonsoft.Json 是一个非常受欢迎的 .NET JSON 框架 一般来说大部分用户用到的方法主要就是 JsonConvert.SerializeObject 和 JsonConvert.DeserializeObject 方法 前者用于将 .NET 对象 序列化为 JSON 字符串,后者则是将 JSON 字符串反序列化为 .NET 对象 下面我将讲述一些你可能用过或者没用过的一些小技巧 填充对象 我现在有一个 {"name":"fissssssh"} JSON 对象,当将它转为Dictionary<string,string>后,可以通过键名name获取属性值 然而这个 JSON 现在变成 {"Name":"fissssssh"}, 无法再通过键名 name 获取属性值 Dictionary 是支持替换键名比较器的,但是 JsonConvert.DeserializeObject 只会调用其无参构造函数 我们可以手动将 Dictionary 创建出来, 然后通过 JsonConvert.PopulateObject 方法将 JSON 字符串序列化并填充至指定对象,代码如下 var json = "{\"Name\":\"fissssssh\"}"; // 创建一个键名忽略大小写的字典对象 var dict = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase); JsonConvert.PopulateObject(json, dict); // 使用 name 获取 JSON 中的 Name 属性值 var name = dict["name"]; 从流进行反序列化 有些情况下我们需要反序列化来自网络请求或者文件中 JSON,它们通常是以流的形式持有,反序列化先将其读取为字符串,当 JSON 内容过大的时候会产生一个巨大的字符串对象,如果此操作比较频繁则会对性能产生比较大的影响...

2023年3月12日

生成 HTTP 查询字符串

什么是 HTTP 查询字符串 例如此 URL:https://example.com:80/query?key1=value2&key2=value2 对其拆解我们可以得到以下部分: https:协议 :// example.com:域名 :80:端口 /query:路径 ?key1=value2&key2=value2:参数(也称作查询字符串) 如何生成 HTTP 查询字符串 暴力拼接 略 System.Web.HttpUtility var query = HttpUtility.ParseQueryString(string.Empty); query["a+b"] = "a%b"; query["b"] = "2+1"; var queryString = query.ToString(); // a+b=a%25b&b=2%2b1 HttpUtility.ParseQueryString(string.Empty) 会返回一个空的NameValueCollection,你只需要往里面填充参数然后调用ToString()即可生成查询字符串 您不能使用 new NameValueCollection() 来达到同样的效果,因为 HttpUtility.ParseQueryString(string) 返回的实际是 HttpQSCollection 类型,该类型是 NameValueCollection 的派生类型且不对外公开,所以你也无法通过 new 关键字来创建 HttpQSCollection 类型 此方法生成的查询字符串只会转义 value 且不包含前导字符 ? Microsoft.AspNetCore.Http.QueryString 此方法仅适用于 SDK 为 Microsoft.NET.Sdk.Web 的项目 var queryString = QueryString.Create(new Dictionary<string, string?> { ["a+b"] = "a%b", ["b"] = "2+1", })....

2023年2月4日

ASP.NET Core 中使用 Dapr 发布订阅

定义 subpub 组件 我们使用 Dapr 初始化时安装的 redis 作为 pubsub 的实现 创建文件 ~/.dapr/components/pubsub.yaml (Windows 用户为 %USERPROFILE%\.dapr\components\pubsub.yaml ),内容如下 Dapr 初始化后 ~/.dapr/components 文件夹会自动创建,里面有一个 statestore.yaml 的组件定义。如果没有该文件夹也不用担心,手动创建即可 apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" 创建项目 创建 ASP.NET Core WebAPI 项目 $ dotnet new webapi --no-openapi --no-https 安装 Dapr SDK dotnet CLI $ dotnet add package Dapr.AspNetCore 程序包管理器控制台 Install-Package Dapr.AspNetCore 也可以在 Visual Studio 的 Nuget 包管理器中搜索安装...

2022年12月14日

如何在 ASP.NET Core WEB API 中启动后台任务

引言 有时候我们可能想在接口中开启一个后台任务,就像这样: public class MyController : Controller { private readonly MyDependency _dep; public MyController(MyDependency d) { _dep = d; } public IActionResult MyAction() { Task.Run(() => _dep.DoHeavyAsyncWork()); return Json("Your job is started!"); } } 事实上,这段代码很符合语义,接口也能成功返回。 但是其中有一个潜在问题,随着return语句响应本次请求,ASP.NET Core会开始释放本次请求的依赖,比如控制器和控制器依赖的各种服务……,这就会导致Task.Run语句中的所依赖的定义在控制器中的服务会被释放掉,这可能会导致一些未知的问题。这些潜在问题可能会让你的应用程序随时爆炸! 让后台任务拥有自己的生存期 为了保证后台服务的依赖不随控制器释放而释放,我们需要定义一个新的服务并将其注册为单例服务,因为单例服务永远不会被释放。 创建一个名为CannonService的类 CannonService.cs public class CannonService { private readonly ILogger<CannonService> _logger; private readonly IServiceProvider _serviceProvider; public CannonService(ILogger<CannonService> logger, IServiceProvider serviceProvider) { _logger = logger; _serviceProvider = serviceProvider; } public void Fire(Delegate dg, Action<Exception>?...

2022年6月27日