WinForms 文件上传到共享文件夹:UNC 直写与可靠传输实践
简介:面向C# WinForms开发者的局域网文件上传实战工程,基于VS2010搭建。它针对把本地文件写入服务器共享文件夹这一内网常见需求,演示了完整链路:用OpenFileDialog选文件,通过UNC路径配合NetworkCredential建立连接,用FileStream读写并借BackgroundWorker异步回报进度,最后做异常捕获和文件类型、大小校验。压缩包共23个文件,约36KB,以6个.cs源码文件为核心,同时包含.sln与.csproj工程配置、exe程序、resx与resources资源以及AssemblyInfo等,可直接打开解决方案对照阅读。已有1429人学习。代码结构简洁,便于理解共享目录访问权限、流式传输与UI进度更新的配合方式,也可作为内网文件传输小工具的原型参考。
1. WinForms 上传文件到共享文件夹:一行 File.Copy 撑不住的真实场景
接到「WinForms 上传文件到共享文件夹」这个需求时,很多人的第一反应是File.Copy一行搞定。真到现场就不是这么回事了:工控机跑在本地账号下,共享目录在另一台机器上,凭据不通直接甩一个 1326;扫描件动辄几十兆,进度条一卡,操作员以为程序死了直接点叉;传到一半网络抖一下,共享目录里留下一堆半截 PDF,下游系统照着读全报错。这套东西要解决的就是「文件上传到服务器」里最土但最难绕开的那一段:不装服务端、不走 HTTP,把本地文件可靠地落到 UNC 共享目录里,还要能看进度、能重试、能取消、换台机器照样跑。适合手上正在写内部工具、被现场环境反复折磨过的 WinForms 开发者。
2. 工程结构与技术选型:三层拆分让上传逻辑和窗体彻底解耦
先把工程骨架定下来,后面所有讨论才有落点。这里最容易犯的错是把上传逻辑直接写进MainForm.cs的按钮事件里,初期跑得飞快,等到要加断点续传、要换认证方式、要在另一个窗体复用同一套逻辑时,就会发现连单元测试都没法写——窗体依赖SynchronizationContext,脱离消息循环一跑就抛异常。
2.1 目录结构:把上传内核从窗体里抽出来的理由
我一般会拆成三个项目:UI 只负责画界面和收事件,Core 放纯逻辑且不引用System.Windows.Forms,Infrastructure 放日志、配置、重试这些横切关注点。这样 Core 可以在控制台里跑测试,也可以被 WPF 或 Windows 服务复用。
FileUploader/ ├─ FileUploader.sln ├─ src/ │ ├─ FileUploader.UI/ WinForms 宿主 │ │ ├─ Forms/MainForm.cs 只做控件编排与事件转发 │ │ ├─ Forms/MainForm.Designer.cs │ │ └─ Program.cs │ ├─ FileUploader.Core/ 与 UI 无关的上传内核 │ │ ├─ IFileUploader.cs 上传契约 │ │ ├─ SmbFileUploader.cs UNC 直写实现 │ │ ├─ UploadRequest.cs 一次上传的入参 │ │ ├─ UploadProgress.cs 进度快照 │ │ ├─ UploadQueue.cs 并发调度与取消 │ │ ├─ NameResolver.cs 重名与非法字符处理 │ │ └─ UncPath.cs UNC 路径规范化 │ └─ FileUploader.Infrastructure/ │ ├─ AppSettings.cs appsettings.json 绑定 │ ├─ FileLogger.cs 按天滚动的文本日志 │ ├─ RetryPolicy.cs 指数退避重试 │ └─ NetworkSession.cs 凭据与会话(第 4 章展开) └─ tests/ └─ FileUploader.Core.Tests/关键点在于IFileUploader这层抽象。它只有三个参数:请求、进度回调、取消令牌。后面就算客户要求改成走 HTTP 接口上传到对象存储,也只是多一个HttpFileUploader实现,UI 层一行都不用动。
public interface IFileUploader { // 返回结果里带上最终落地路径和耗时,方便 UI 展示与日志留痕 Task<UploadResult> UploadAsync(UploadRequest request, IProgress<UploadProgress> progress, CancellationToken ct); }UploadRequest里我倾向放四个字段:LocalPath(本地源文件绝对路径)、TargetRoot(\\server\share这一层)、SubFolder(可空,按日期分目录时用)、Overwrite(是否允许覆盖)。把「根目录」和「子目录」分开存,是因为根目录通常来自配置文件而子目录来自业务规则,混成一个字符串之后做路径拼接很容易多一个或少一个反斜杠,这种低级错误排查起来能耗掉半天。
2.2 技术选型:UNC 直写、映射盘符、中转接口怎么选
把文件传到共享目录至少有四条路,选错了后面全是返工。这张表是我踩过几轮之后留下的判断依据:
| 方式 | 适用场景 | 优点 | 隐藏坑 |
|---|---|---|---|
UNC 直写\\server\share | 内网、客户端和服务端同域或可互认凭据 | 零服务端成本,吞吐高,支持大文件 | 凭据绑定进程身份,跨机器容易 1326 |
映射盘符Z:\ | 临时脚本、运维手工操作 | 路径短,调试直观 | 盘符被占用、会话隔离、服务里基本不可用 |
| HTTP 中转接口 | 需要审计、限流、多端一致 | 权限收口在服务端,客户端不碰共享 | 要写服务端,大文件得做分片 |
| SFTP/FTP | 跨网段、跨系统 | 协议通用 | 多一套账号体系,WinForms 端要引第三方库 |
内部工具、客户端只有几十台、共享目录本来就在内网文件服务器上,那 UNC 直写是最划算的。它的根本限制是「用谁的身份去连」——这一条决定了第 4 章要花那么多篇幅讲凭据。
映射盘符这条路我不建议用在正式产品里,原因很实在:net use映射出来的盘符是跟着登录会话走的,同一台机器上另一个用户登录上来根本看不见,做成 Windows 服务更是直接失效。开发机上测得好好的,装成服务就找不到路径,血泪经验。
2.3 跑起来的最小闭环:配置项、运行环境与第一次连通性验证
工程是 .NET 6 之后的 WinForms,csproj 里两个关键属性不能少,少了会在非 Windows 目标框架上报错:
<PropertyGroup> <TargetFramework>net8.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <Nullable>enable</Nullable> </PropertyGroup>配
本文还有配套的精品资源,点击获取