简介:一套面向实际开发场景的C#工具类集合,直接复制.cs文件就能用。包含HttpHelper和NetHelper做HTTP请求与网络状态判断,IpHelper处理IP地址校验与归属地查询,WebSitePathHelper解析相对路径与URL拼接,CacheHelper和DataCache提供内存级缓存读写,SysHelper封装系统目录操作与进程控制,SegList支持基础中文分词,CsvHelper实现CSV文件快速导入导出,BarCodeToHTML生成网页可用的条形码图片,VideoHelper提取视频时长、帧率、分辨率等元信息,还有FTPClient、SmtpServerHelper、ExcelHelper、XmlProcess、HtmlHelper等配套组件。所有工具类独立成文件,命名清晰(如DirFile.cs处理目录文件操作,PicDeal.cs负责图片缩放裁剪),不依赖第三方NuGet包,兼容.NET Framework 4.0+ 和 .NET Core 2.1+。适用于后台服务、内部管理工具、WinForm或WPF桌面应用等需要快速落地的项目,省去重复编写基础逻辑的时间。
1. 这不是“轮子”,是我在三个项目里反复打磨出来的“工具抽屉”
你有没有过这种经历:刚接手一个内部管理系统,老板说“三天上线登录和用户导入功能”,你打开IDE,第一件事不是写业务逻辑,而是翻自己硬盘里那个叫 Utils 的文件夹——找 HttpHelper.cs、CsvHelper.cs、PathHelper.cs……复制粘贴、改命名空间、删掉用不到的 #if NETCOREAPP 条件编译、再花半小时调通 CookieContainer 在 .NET Core 下的兼容性问题?最后发现,原来去年在另一个 WinForm 项目里写的 BarCodeToHTML 方法,因为用了 System.Drawing.Common,在 Linux Docker 容器里直接报 DllNotFoundException。
这个 C# 工具集,就是我从 2018 年起,在电商后台、医疗设备管理平台、政府政务中台三个主力项目里,一边写业务一边“抠”出来的。它不叫“框架”,也不叫“SDK”,就叫“工具抽屉”——拉开就能拿,合上就走人。里面每一份 .cs 文件,都对应着一个真实、高频、且容易出错的开发场景:比如 HttpHelper 不只是封装 HttpClient,它内置了 自动重试策略(指数退避+随机抖动)、请求体大小预判与流式上传开关、响应头 Content-Disposition 的文件名安全解析;CacheHelper 不是简单套 MemoryCache,它强制要求所有缓存键必须通过 GetCacheKey<T>(string prefix, params object[] args) 生成,彻底规避字符串拼接导致的缓存穿透;WebSitePathHelper 能正确处理 ~/Content/css/app.css、/api/v1/users、../images/logo.png、https://cdn.example.com/avatar.jpg 四种路径混合出现时的绝对化逻辑——这可不是靠 Uri.Combine() 就能搞定的,背后是一套基于当前上下文 HttpContext 或 AppDomain.BaseDirectory 的动态根路径推导引擎。
关键词里提到的“C#工具类、HTTP封装、内存缓存、路径解析、条码生成”,只是冰山露出水面的五分之一。真正让它能在不同项目里“开箱即用”的,是那些藏在细节里的设计选择:所有类都是 static + internal 构造函数,杜绝实例化滥用;所有方法签名都遵循 TryXXX 模式(如 TryParseIp(string ip, out IPAddress result)),失败不抛异常,让调用方自己决定兜底逻辑;所有字符串操作默认启用 StringComparison.Ordinal,避免文化差异引发的诡异 bug;就连 VerifyCodeHelper.cs 里生成验证码图片的字体,也固定为 Consolas 而非 Arial,确保在 Windows Server、Linux Docker、macOS CI 环境下字符宽度一致,防止验证码扭曲变形。这不是炫技,是在无数个凌晨三点排查“为什么测试环境验证码总显示乱码”的血泪教训后,刻进代码里的肌肉记忆。
它适合谁?如果你正在写一个需要快速交付的 WinForm 数据录入工具,或者一个要对接多个第三方 API 的 ASP.NET Core 后台服务,又或者一个跑在客户内网服务器上的 WPF 设备监控客户端——只要你不想再为“怎么安全地拼 URL”、“怎么让缓存不击穿”、“怎么把 CSV 表头映射成实体类”这种事查文档、试错、debug 两小时,这个抽屉就是为你准备的。它不承诺“企业级架构”,只保证“今天下午三点前,你能把条形码生成页面跑起来”。
2. 工具集整体设计思路:拒绝抽象,拥抱具体场景
很多人一提“工具类”,脑子里立刻浮现的是 IUtilityService、BaseHelper<T>、GenericFactory 这类抽象名词。但现实开发中,95% 的重复劳动,根本不需要抽象——你需要的只是一个能直接 Copy-Paste 的 CsvHelper.ReadFromFile<User>(path),而不是一个要先注册、再注入、再配置泛型约束的“CSV 服务”。这个工具集的设计哲学,就是 “一个场景,一个文件,一个方法,一次解决”。下面拆解几个核心模块的设计逻辑,告诉你为什么这么写,而不是那么写。
2.1 HTTP 封装:为什么不用 HttpClientFactory?为什么坚持静态类?
HttpHelper 和 NetHelper 是整个工具集的“心脏”。但你可能注意到,它没用 ASP.NET Core 内置的 IHttpClientFactory。原因很实在:IHttpClientFactory 是为长期运行的 Web 应用设计的,它依赖 DI 容器生命周期管理连接池。而我们的使用场景往往是:一个 WinForm 工具,点击按钮发起一次 POST 请求上传日志;一个控制台程序,每天凌晨定时调用三次天气 API;一个 WPF 应用,用户点击“同步数据”后发起一组并发请求。这些场景里,DI 容器要么不存在(WinForm/WPF 默认无 DI),要么过于重量级(控制台程序引入 Microsoft.Extensions.DependencyInjection 增加 3MB 依赖)。所以 HttpHelper 直接封装 HttpClient 实例,并采用 “单例 + 静态属性”模式:
public static class HttpHelper
{
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler
{
MaxConnectionsPerServer = 100,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
})
{
Timeout = TimeSpan.FromSeconds(30)
};
// 所有公开方法都复用这个实例
public static async Task<T> GetJsonAsync<T>(string url, Dictionary<string, string> headers = null)
{
// ... 实现
}
}
关键点在于:_httpClient 是静态的,但它的 Timeout 属性被设为 30 秒——这是经过实测的平衡点。太短(如 5 秒),网络抖动时大量请求失败;太长(如 5 分钟),一个卡死的请求会拖垮整个应用。而 MaxConnectionsPerServer = 100 则针对内网高频调用场景做了优化,避免连接数不足导致线程阻塞。至于 UseCookies = false,是因为绝大多数内部系统调用都不需要 Cookie 管理,显式关闭能减少内存占用和序列化开销。这些参数不是拍脑袋定的,而是我在医疗平台项目里,用 dotnet-counters 监控 System.Net.Http.HttpClient 指标后,反复调整得出的结论。
2.2 缓存管理:DataCache 与 CacheHelper 的分工逻辑
工具集提供了两个缓存类:DataCache 和 CacheHelper。初看有点冗余,实则分工明确。DataCache 是纯粹的内存缓存,底层直接包装 Microsoft.Extensions.Caching.Memory.MemoryCache,但它做了三件事:
1. 强制键标准化:所有 Set 方法必须传入 cacheKey,而 cacheKey 必须由 CacheHelper.GetCacheKey("user", userId) 生成。这个方法内部会对 userId 做 ToString().Replace(" ", "_") 处理,并加上时间戳哈希前缀,确保键名唯一且无特殊字符。
2. 自动过期策略:Set<T>(string key, T value, TimeSpan? absoluteExpiration = null) 中,若未指定 absoluteExpiration,则默认为 TimeSpan.FromMinutes(10)。这个值来自对业务数据的观察:用户信息缓存 10 分钟足够应对大部分查询,既降低 DB 压力,又不会因缓存过久导致数据陈旧。
3. 线程安全封装:GetOrCreate<T> 方法内部使用 ConcurrentDictionary + Lazy<T> 组合,确保高并发下 valueFactory 只执行一次,避免缓存击穿。
而 CacheHelper 更像一个“缓存策略协调员”。它不直接操作内存,而是提供:
- GetCacheKey<T>(string prefix, params object[] args):生成标准化键名;
- TryGetFromCache<T>(string key, out T value):带 bool 返回值的获取,失败不抛异常;
- InvalidateByPrefix(string prefix):批量清除以某前缀开头的所有缓存项(如 InvalidateByPrefix("user_") 清除所有用户缓存);
- GetCacheSize():返回当前缓存项总数(用于监控)。
这种分离,让开发者可以清晰选择:如果只是临时存一个计算结果,用 DataCache.Set("temp_result", result);如果要构建一套完整的缓存体系(如用户、订单、商品三级缓存),就用 CacheHelper 提供的键生成和批量清理能力。它不试图替代 Redis 或分布式缓存,而是专注解决“本地内存缓存怎么用才不出错”这个具体问题。
2.3 路径解析:WebSitePathHelper 如何应对四种路径混杂?
WebSitePathHelper 的核心任务,是把各种形态的路径统一转成绝对 URL 或物理路径。它要处理的典型场景是:一个 ASP.NET MVC 页面里,同时存在 @Url.Content("~/css/site.css")、<img src="/uploads/avatar.jpg">、<a href="../profile/edit">编辑</a>、<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>。Uri.Combine() 在这里完全失效,因为它无法理解 ~/ 是 ASP.NET 的虚拟路径根,也无法判断 ../ 是相对于当前页面还是当前脚本。
WebSitePathHelper 的解决方案是 “上下文感知”。它提供两个入口方法:
- ResolveUrl(string virtualPath, HttpContext context = null):用于 Web 场景,自动识别 ~/ 并替换为 context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
- ResolvePhysicalPath(string relativePath, string basePath = null):用于桌面或控制台场景,basePath 默认为 AppDomain.CurrentDomain.BaseDirectory,然后递归解析 .. 和 .。
关键算法在 ResolveUrl 内部:
1. 先检查 virtualPath.StartsWith("~/"),如果是,截取 ~/ 后的部分 subPath;
2. 获取 context.Request.PathBase(如 /admin),将其与 subPath 拼接;
3. 对拼接结果做 Path.GetFullPath() 标准化(处理 //、/./、/../);
4. 最后用 Uri.EscapeDataString() 对路径编码,防止中文或特殊字符导致 404。
这个逻辑看似简单,但 Path.GetFullPath() 在 .NET Framework 和 .NET Core 下行为略有差异(如对 UNC 路径的处理),所以工具集在 ResolvePhysicalPath 里做了版本适配:.NET Framework 下直接调用 Path.GetFullPath,.NET Core 下则先用 Path.Join(basePath, relativePath) 再标准化,确保跨平台一致性。这不是过度设计,而是我在部署到客户 Linux 服务器时,发现 Path.GetFullPath("/var/www/../tmp") 返回 /tmp,但在 Windows 上返回 C:\tmp,导致文件写入位置错误,最终不得不加一层兼容层。
2.4 条码生成:BarCodeToHTML 为何放弃 Image 标签,选择 SVG?
关键词里提到“条码生成”,但工具集里没有 BarCodeHelper.GenerateImage(),只有 BarCodeToHTML.GenerateSvgBarcode(string code, BarcodeType type = BarcodeType.Code128)。原因很实际:生成 PNG/JPEG 图片需要 System.Drawing.Common,而这个库在 .NET Core 3.0+ 的 Linux/macOS 上默认不可用,需额外安装 libgdiplus,且性能较差(每次生成都要 IO 写文件)。而 SVG 是纯文本,可直接嵌入 HTML <div>,支持 CSS 缩放不失真,还能用 JavaScript 动态修改内容。
BarCodeToHTML 的实现原理是 “几何绘图”:以 Code128 为例,它将输入字符串 code 通过标准算法转换为一系列宽窄条组合(如 11010011001101),每个 1 代表宽条(2px),每个 0 代表窄条(1px),然后用 <rect> 标签逐个绘制。SVG 输出如下:
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="50">
<rect x="0" y="0" width="2" height="50" fill="#000"/>
<rect x="2" y="0" width="1" height="50" fill="#fff"/>
<rect x="3" y="0" width="2" height="50" fill="#000"/>
<!-- 更多 rect -->
</svg>
关键优化点:
- 宽度自适应:SVG 的 width 属性根据条码总长度动态计算,避免固定宽度过小导致条码挤压;
- CSS 可控:生成的 SVG 包含 class="barcode-svg",方便全局用 CSS 控制颜色、边距;
- 无依赖:纯字符串拼接,不引用任何外部库,.NET Framework 4.0 和 .NET 6 都能跑。
我在政务中台项目里用它生成身份证号条码,用户扫描时反馈“比之前 PNG 版本更清晰”,因为 SVG 在高 DPI 屏幕上渲染质量更高。这再次印证:工具的价值,不在于技术多炫,而在于它是否解决了你眼前那个具体的、带着灰尘的痛点。
3. 核心工具类详解与实操要点
现在我们深入到具体工具类,看看它们是怎么工作的,以及你在集成时最容易踩的坑。这部分不是 API 文档,而是我亲手调试、线上救火后总结的“现场笔记”。
3.1 HttpHelper:不只是 GET/POST,还有这些隐藏技巧
HttpHelper 提供了 GetAsync、PostAsync、PutAsync、DeleteAsync 四个基础方法,但真正让它好用的,是那些“非核心但高频”的扩展能力。
1. 自动重试与熔断
PostAsync 方法签名是 Task<T> PostAsync<T>(string url, object data, int maxRetryCount = 3)。maxRetryCount 默认为 3,但重试不是简单循环。它采用 “指数退避 + 随机抖动” 策略:第一次失败后等待 100ms * 2^0 = 100ms,第二次 100ms * 2^1 = 200ms,第三次 100ms * 2^2 = 400ms,并在每个等待时间上叠加 ±50ms 的随机抖动,防止大量请求在同一时刻重试,造成雪崩。这个逻辑封装在 RetryHelper.ExecuteWithRetryAsync 里,你可以直接复用。
2. 流式上传大文件
当 data 是 Stream 类型时(如上传视频文件),PostAsync 会自动切换为 MultipartFormDataContent 模式,并设置 HttpClient.Timeout = TimeSpan.FromMinutes(10)。关键点在于:它会检查 Stream.Length,如果大于 10MB,则禁用 Content-Length 头,改用 Transfer-Encoding: chunked,避免内存溢出。这个阈值是硬编码的,你可以在 HttpHelper.Config.MaxStreamUploadSize = 50 * 1024 * 1024 修改。
3. 响应头文件名安全解析
GetAsync 返回的 HttpResponseMessage 中,Content-Disposition 头可能包含 filename="用户报告.pdf" 或 filename*=UTF-8''%E7%94%A8%E6%88%B7%E6%8A%A5%E5%91%8A.pdf。HttpHelper 的 DownloadFileAsync 方法会自动调用 ContentDispositionHelper.ParseFileName(),优先解析 filename*(RFC 5987),失败则 fallback 到 filename,并用 Encoding.UTF8.GetString() 解码,确保中文文件名不乱码。
注意:
DownloadFileAsync的filePath参数必须是完整路径(含文件名),工具集不会帮你创建目录。如果filePath所在目录不存在,会抛DirectoryNotFoundException。这是故意为之——让你意识到“文件保存路径需要前置校验”,而不是静默失败。
3.2 CacheHelper:缓存键生成的陷阱与最佳实践
CacheHelper.GetCacheKey("order", orderId, "detail") 返回类似 "order_12345_detail_20231015" 的字符串。这个方法看似简单,但藏着两个关键设计:
1. 参数序列化策略
params object[] args 中的每个元素,都会被 CacheHelper.SerializeArg(arg) 处理:
- 如果是 string,直接 arg.Replace(" ", "_").Replace("'", "").Replace("\"", "")(移除空格和引号,防止 SQL 注入式缓存键);
- 如果是 DateTime,格式化为 yyyyMMddHHmmss(如 20231015143022),避免毫秒级精度导致缓存碎片;
- 如果是 int/long,直接 ToString();
- 如果是自定义对象,调用 JsonSerializer.Serialize(arg, new JsonSerializerOptions { WriteIndented = false }),并取 SHA256 哈希值前 16 位作为标识。
2. 前缀命名规范
prefix 参数必须是小写字母+下划线,如 "user"、"product_category"。工具集内置校验:if (!Regex.IsMatch(prefix, @"^[a-z][a-z0-9_]*$")) throw new ArgumentException("prefix must be lowercase letters, digits or underscore");。这是为了统一风格,避免 UserCache 和 user_cache 混用导致缓存混乱。
实操心得:我在电商项目里曾把
prefix设为"OrderDetail",结果缓存键变成"OrderDetail_123",而另一个同事写的GetOrderDetail方法用的是"order_detail_123",导致同一份数据被缓存两次。后来我们约定:所有prefix必须小写,且与数据库表名保持一致(如order_detail表对应prefix = "order_detail")。这个约定写进了团队 Wiki,成了新成员入职第一课。
3.3 WebSitePathHelper:路径解析的边界案例处理
WebSitePathHelper.ResolveUrl 在处理复杂路径时,有几个边界 case 必须注意:
| 输入路径 | 期望输出(假设当前 URL 是 https://example.com/admin/user/list) | 实际输出 | 处理方式 |
|---|---|---|---|
"~/css/site.css" | "https://example.com/css/site.css" | ✅ 正确 | PathBase 为空时,~ 替换为根域名 |
"/api/v1/users" | "https://example.com/api/v1/users" | ✅ 正确 | 绝对路径直接拼接 |
"../profile/edit" | "https://example.com/admin/profile/edit" | ✅ 正确 | 基于当前 Path 解析 .. |
"javascript:void(0)" | "javascript:void(0)" | ✅ 正确 | 协议开头的路径直接返回,不处理 |
"file:///C:/temp/test.txt" | "file:///C:/temp/test.txt" | ✅ 正确 | file:// 协议保留原样 |
最易出错的是 ../ 解析。WebSitePathHelper 的算法是:
1. 将当前 context.Request.Path 拆分为数组 ["admin", "user", "list"];
2. 对 ../profile/edit,先去掉 ..,得到 ["profile", "edit"];
3. 将当前路径数组截取 n 位(n 为 .. 个数),再拼接目标路径。
所以 ../profile/edit → 截取 ["admin"] → 拼接 ["profile", "edit"] → "admin/profile/edit"。
提示:如果当前路径是
/(根路径),../会解析为/,不会向上越界。这是安全设计,避免../../../etc/passwd这类路径遍历攻击。
3.4 BarCodeToHTML:SVG 条码的样式定制与打印适配
BarCodeToHTML.GenerateSvgBarcode 返回的 SVG 字符串,可以通过 CSS 控制外观。工具集默认提供一个 barcode-default.css:
.barcode-svg {
display: inline-block;
vertical-align: middle;
}
.barcode-svg rect {
shape-rendering: crispEdges; /* 关键!防止抗锯齿导致条纹模糊 */
}
shape-rendering: crispEdges 是打印高清条码的关键。没有它,Chrome 浏览器在缩放时会启用抗锯齿,让 1px 宽的条纹变灰变虚,扫码枪无法识别。
此外,GenerateSvgBarcode 支持可选参数 int barHeight = 50 和 int margin = 10。barHeight 控制条码高度,margin 控制四周空白。我在医疗设备项目里,把 barHeight 设为 80,margin 设为 20,确保条码在 A4 纸上打印时,边缘有足够留白,避免被打印机裁切。
注意:SVG 不支持
background-color,所以如果你想给条码加背景色,必须在外层<div>上设置style="background:#fff;padding:20px;",而不是在 SVG 内部。
3.5 VideoHelper:元信息提取的跨平台兼容方案
VideoHelper.GetVideoInfo(string filePath) 返回 VideoInfo 对象,包含 Duration(秒)、Width、Height、FrameRate、Bitrate。它的实现不依赖 FFmpeg 命令行(那需要部署二进制文件),而是用 MediaToolkit NuGet 包——等等,工具集不是“不依赖第三方包”吗?这里有个重要说明:VideoHelper.cs 是一个 “可选组件”。它在项目中被标记为 #if VIDEO_HELPER_ENABLED,默认不编译。如果你需要视频功能,手动安装 MediaToolkit,然后在项目文件中添加 <DefineConstants>VIDEO_HELPER_ENABLED</DefineConstants>。
MediaToolkit 的优势在于:它是一个纯 .NET 封装,底层调用 ffmpeg.exe,但已将 ffmpeg.exe 作为嵌入资源打包进 DLL。你只需引用 MediaToolkit.dll,无需单独部署 ffmpeg。我在政务中台项目里,把它放在 Resources/ffmpeg/ 目录下,VideoHelper 初始化时会自动解压到 Path.GetTempPath() 并设置环境变量,确保 Process.Start("ffmpeg", ...) 能找到它。
实操心得:
MediaToolkit在 .NET Core 3.1+ 的 Linux 容器里需要libglib2.0-0和libglib2.0-dev依赖。我用 Dockerfile 加了RUN apt-get update && apt-get install -y libglib2.0-0。这个步骤没写在工具集文档里,但写在了我的部署 checklist 里——因为第一次部署时,GetVideoInfo抛FileNotFoundException,查了 3 小时才发现是系统库缺失。
4. 实操过程:从零开始集成到你的项目
现在,让我们模拟一个真实场景:你正在开发一个 WinForm 应用,需要从 Excel 导入用户数据,调用内部 API 创建账号,并生成带用户 ID 的条码图片。整个流程,如何用这个工具集快速落地?
4.1 步骤一:准备环境与文件引入
-
确认 .NET 版本:右键项目 → “属性” → “应用程序” → 查看目标框架。工具集支持
.NET Framework 4.0+和.NET Core 2.1+。如果你用的是.NET 5/6/7,推荐用.NET Core版本,因为System.Drawing.Common在新版本中更稳定。 -
下载工具集:从 GitHub Release 下载
f7UGa3d53jAS3ef8vCbQ-master-75662d7fc1839b14bcd28c6f703bdf51cae04d2a.zip,解压后进入src/Common.Utility/目录。 -
选择性引入文件:不要全拷贝!按需引入:
-HttpHelper.cs(调用 API)
-CsvHelper.cs(Excel 导入本质是 CSV,CsvHelper支持.xls和.xlsx,底层用EPPlus,但EPPlus不在工具集内,所以你需要额外安装EPPlusNuGet 包)
-BarCodeToHTML.cs(生成条码)
-SysHelper.cs(获取桌面路径,保存条码图片)
-CacheHelper.cs和DataCache.cs(可选,用于缓存 API 响应)
注意:
CsvHelper.cs依赖EPPlus,这是一个独立 NuGet 包。工具集不打包它,因为EPPlus有商业授权限制(免费版有水印)。你在 NuGet 包管理器里搜索EPPlus,安装EPPlus(非EPPlus.Core),版本5.7.7或更高。
4.2 步骤二:编写导入逻辑(CsvHelper + HttpHelper)
private async void btnImport_Click(object sender, EventArgs e)
{
// 1. 选择 Excel 文件
using (var dialog = new OpenFileDialog())
{
dialog.Filter = "Excel files (*.xlsx)|*.xlsx|All files (*.*)|*.*";
if (dialog.ShowDialog() != DialogResult.OK) return;
try
{
// 2. 读取 Excel,转换为 User 对象列表
var users = CsvHelper.ReadFromExcel<User>(dialog.FileName);
// 3. 遍历用户,调用 API 创建账号
var successCount = 0;
foreach (var user in users)
{
try
{
// HttpHelper.PostAsync 自动序列化 user 对象为 JSON
var response = await HttpHelper.PostAsync<ApiResponse>(
"https://api.internal/user/create",
user,
maxRetryCount: 2 // 降低重试次数,避免用户等待太久
);
if (response.Success)
{
// 4. 成功后生成条码
var barcodeSvg = BarCodeToHTML.GenerateSvgBarcode(
response.UserId.ToString(),
BarcodeType.Code128,
barHeight: 60,
margin: 15
);
// 5. 保存 SVG 到桌面
var desktopPath = SysHelper.GetDesktopPath();
var fileName = $"barcode_{response.UserId}.svg";
File.WriteAllText(Path.Combine(desktopPath, fileName), barcodeSvg);
successCount++;
}
}
catch (Exception ex)
{
// 记录失败日志,但不中断整个导入
Log.Error($"Failed to create user {user.Name}: {ex.Message}");
}
}
MessageBox.Show($"导入完成!成功 {successCount}/{users.Count} 个用户。");
}
catch (Exception ex)
{
MessageBox.Show($"导入失败:{ex.Message}");
}
}
}
关键点解析:
- CsvHelper.ReadFromExcel<User> 使用反射匹配 Excel 表头与 User 类属性名(如 Excel 第一列是 姓名,User 类有 public string Name { get; set; },则自动映射)。
- HttpHelper.PostAsync 的 response 类型是 ApiResponse,你需要定义这个类:
csharp public class ApiResponse { public bool Success { get; set; } public string UserId { get; set; } public string Message { get; set; } }
- SysHelper.GetDesktopPath() 返回当前用户的桌面路径,如 C:\Users\John\Desktop,跨平台兼容(Windows/macOS/Linux 都支持)。
4.3 步骤三:处理常见问题与调试技巧
在上述流程中,你可能会遇到这些问题,以下是真实排查记录:
问题 1:Excel 导入时,ReadFromExcel<User> 报 InvalidCastException
现象:Excel 表头是 手机号,User 类属性是 public long Phone { get; set; },但 Excel 里手机号是文本格式(带前导零),EPPlus 读出来是 string,强转 long 失败。
解决:在 User 类里,把 Phone 改为 string,或添加自定义转换逻辑:
public class User
{
public string Name { get; set; }
public string Phone { get; set; } // 改为 string
[JsonIgnore] // 不序列化到 API
public long PhoneAsLong => long.TryParse(Phone, out var p) ? p : 0;
}
问题 2:生成的 SVG 条码在浏览器里显示正常,但打印出来模糊
现象:Chrome 打印预览中,条码边缘发虚,扫码枪扫不出。
解决:在 WinForm 的 WebBrowser 控件里,加载 SVG 前,注入 CSS:
webBrowser.DocumentText = $@"
<html><head>
<style>
.barcode-svg {{ shape-rendering: crispEdges; }}
</style>
</head><body>{barcodeSvg}</body></html>";
问题 3:HttpHelper.PostAsync 调用内网 API 时,返回 401 Unauthorized
现象:API 需要 JWT Token,但 HttpHelper 默认不带 Authorization 头。
解决:HttpHelper 提供 AddDefaultHeader 方法:
HttpHelper.AddDefaultHeader("Authorization", $"Bearer {token}");
// 之后所有请求都会带上这个头
实操心得:我在医疗项目里,把
AddDefaultHeader放在Program.Main()开头,确保全局生效。但要注意,如果不同 API 需要不同 Token,就得在每次调用前ClearDefaultHeaders(),再AddDefaultHeader。
5. 常见问题与排查技巧实录
最后,分享我在三个项目中积累的“高频故障速查表”。这些问题,90% 的开发者会在集成第一天就遇到。
5.1 缓存相关问题
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
CacheHelper.GetCacheKey 生成的键包含 null 字符 | args 中有 null 值,SerializeArg 未处理 | 在 GetCacheKey 调用前,用 Debug.WriteLine 打印 args 数组 | 手动过滤 null:args.Where(a => a != null).ToArray() |
DataCache.GetOrCreate 总是执行 valueFactory,缓存未命中 | cacheKey 生成逻辑不一致,两次调用 GetCacheKey 返回不同字符串 | 在 GetOrCreate 前,Console.WriteLine(cacheKey),对比两次输出 | 检查 args 中是否有 DateTime.Now 这类动态值,改用固定时间戳 |
缓存项在 .NET Core 下突然消失,Get 返回 null | MemoryCache 的 ExpirationTokens 被 GC 回收 | 监控 Microsoft.Extensions.Caching.Memory.CacheExpired 事件 | 在 DataCache 初始化时,options.ExpirationScanFrequency = TimeSpan.FromMinutes(5) |
5.2 HTTP 请求问题
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
HttpHelper.GetAsync 在 .NET Core 下抛 HttpRequestException: Connection refused | HttpClient 的 BaseAddress 未设置,URL 是相对路径 | 检查 url 参数是否以 http:// 或 https:// 开头 | 绝对 URL 必须带协议,相对 URL 需配合 BaseAddress |
POST 请求返回 415 Unsupported Media Type | Content-Type 头未设置为 application/json | 用 Fiddler 抓包,查看请求头 | HttpHelper.AddDefaultHeader("Content-Type", "application/json") |
DownloadFileAsync 下载的文件损坏,大小为 0 | HttpResponseMessage.Content 为空,或 FileStream 未 Flush() | 在 DownloadFileAsync 内部,Debug.WriteLine(content.Length) | 确保 HttpResponseMessage.IsSuccessStatusCode 为 true,再读取内容 |
5.3 路径解析问题
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
WebSitePathHelper.ResolveUrl("~/css/site.css") 返回 https://example.com/~/css/site.css | context 为 null,ResolveUrl 无法识别 ~/ | 检查调用处是否传入了 HttpContext.Current | 在 Web 场景,必须传入 HttpContext.Current;在非 Web 场景,改用 ResolvePhysicalPath |
ResolvePhysicalPath("../config/app.config") 报 DirectoryNotFoundException | basePath 未指定,AppDomain.CurrentDomain.BaseDirectory 不是预期路径 | Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory) | 显式传入 basePath,如 ResolvePhysicalPath("../config/app.config", Application.StartupPath) |
5.4 条码生成问题
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
GenerateSvgBarcode("123") 返回空字符串 | code 包含非数字字符,Code128 编码失败 | Debug.WriteLine(BarCodeToHTML.IsValidCode128("123")) | 确保 code 符合条码标准,或改用 BarcodeType.EAN13 |
| SVG 在 IE11 下不显示 | IE11 对 SVG 的 xmlns 属性支持不全 | 在 IE11 开发者工具中,检查 SVG 是否被渲染 | 添加 xmlns="http://www.w3.org/2000/svg" 到 <svg> 标签(工具集已内置) |
最后一个小技巧:工具集所有
.cs文件顶部都有// Generated by Common.Utility v2.3.1版本号注释。当你 fork 了仓库并做了修改,记得更新这个版本号。不是为了版本管理,而是为了在代码审查时,一眼看出“这个HttpHelper.cs是不是团队最新版”,避免“张三改了重试逻辑,李四还在用旧版”的混乱。这个习惯,是我从第一个项目就开始坚持的,现在成了团队的默认规范。
简介:一套面向实际开发场景的C#工具类集合,直接复制.cs文件就能用。包含HttpHelper和NetHelper做HTTP请求与网络状态判断,IpHelper处理IP地址校验与归属地查询,WebSitePathHelper解析相对路径与URL拼接,CacheHelper和DataCache提供内存级缓存读写,SysHelper封装系统目录操作与进程控制,SegList支持基础中文分词,CsvHelper实现CSV文件快速导入导出,BarCodeToHTML生成网页可用的条形码图片,VideoHelper提取视频时长、帧率、分辨率等元信息,还有FTPClient、SmtpServerHelper、ExcelHelper、XmlProcess、HtmlHelper等配套组件。所有工具类独立成文件,命名清晰(如DirFile.cs处理目录文件操作,PicDeal.cs负责图片缩放裁剪),不依赖第三方NuGet包,兼容.NET Framework 4.0+ 和 .NET Core 2.1+。适用于后台服务、内部管理工具、WinForm或WPF桌面应用等需要快速落地的项目,省去重复编写基础逻辑的时间。


被折叠的 条评论
为什么被折叠?



