Dim provider = New MultipartFormDataStreamProvider(uploadPath),在哪一行代码写入文件,如何设置保存的文件名
文件写入时机与自定义文件名的实现
在 MultipartFormDataStreamProvider
中,文件写入发生在调用 ReadAsMultipartAsync
方法期间。当解析器遇到文件内容时,会自动将其写入到指定目录。默认情况下,文件名由系统生成(通常是 GUID),但你可以通过自定义 Provider 来控制保存的文件名。
文件写入的具体位置
当执行以下代码时:vb
Await content.ReadAsMultipartAsync(provider)
MultipartFormDataStreamProvider
会:
- 解析请求内容
- 为每个文件创建临时文件(使用 GUID 命名)
- 将文件内容写入到这些临时文件
- 完成后,你可以通过
provider.FileData
访问文件元数据和本地路径
自定义保存的文件名
要控制保存的文件名,需继承 MultipartFormDataStreamProvider
并重写 GetLocalFileName
方法:vb
Public Class CustomMultipartFormDataStreamProvider
Inherits MultipartFormDataStreamProvider
Public Sub New(uploadPath As String)
MyBase.New(uploadPath)
End Sub
Public Overrides Function GetLocalFileName(contentHeaders As HttpContentHeaders) As String
If contentHeaders.ContentDisposition IsNot Nothing AndAlso
Not String.IsNullOrEmpty(contentHeaders.ContentDisposition.FileName) Then
' 获取客户端上传的原始文件名
Dim clientFileName = contentHeaders.ContentDisposition.FileName.Trim('"')
' 清理文件名,移除非法字符
Dim invalidChars = Path.GetInvalidFileNameChars()
Dim cleanFileName = String.Join("_", clientFileName.Split(invalidChars))
' 返回自定义文件名(保留扩展名)
Dim fileExtension = Path.GetExtension(cleanFileName)
Return $"{Guid.NewGuid()}{fileExtension}" ' 使用GUID+原始扩展名
' 或者直接使用原始文件名:Return cleanFileName
End If
' 默认行为(使用GUID)
Return MyBase.GetLocalFileName(contentHeaders)
End Function
End Class
使用自定义 Provider
现在可以用自定义 Provider 控制文件名:
Public Async Function ProcessUploadAsync(context As HttpContext) As Task
Dim uploadPath = context.Server.MapPath("~/Uploads")
If Not Directory.Exists(uploadPath) Then
Directory.CreateDirectory(uploadPath)
End If
' 使用自定义Provider
Dim provider = New CustomMultipartFormDataStreamProvider(uploadPath)
' 解析请求内容(此时文件会按自定义规则保存)
Dim content = New StreamContent(context.Request.InputStream)
content.Headers.ContentType = MediaTypeHeaderValue.Parse(context.Request.ContentType)
Await content.ReadAsMultipartAsync(provider)
' 处理上传的文件
For Each file In provider.FileData
Dim fileInfo = New FileInfo(file.LocalFileName)
Dim originalFileName = Path.GetFileName(file.Headers.ContentDisposition.FileName.Trim('"'))
Console.WriteLine($"上传文件: {originalFileName}")
Console.WriteLine($" 保存路径: {file.LocalFileName}")
Console.WriteLine($" 大小: {fileInfo.Length} 字节")
Next
End Function
文件名策略选择
-
使用 GUID(推荐):
Return $"{Guid.NewGuid()}{fileExtension}"
优点:避免文件名冲突,安全性高
缺点:原始文件名丢失(但可通过元数据保留) -
使用原始文件名:
Return cleanFileName
优点:保留原始文件名
缺点:可能导致文件覆盖,需处理重名问题 -
添加时间戳:
Return $"{DateTime.Now:yyyyMMddHHmmss}_{cleanFileName}"
优点:减少冲突可能性
缺点:文件名较长
关键注意事项
-
文件覆盖问题:
如果使用原始文件名,需检查文件是否已存在:Dim fullPath = Path.Combine(uploadPath, cleanFileName) If File.Exists(fullPath) Then ' 添加序号或时间戳 cleanFileName = $"{Path.GetFileNameWithoutExtension(cleanFileName)}_1{fileExtension}" fullPath = Path.Combine(uploadPath, cleanFileName) End If Return cleanFileName
路径安全:
-
永远不要直接使用客户端提供的文件名作为路径,防止目录遍历攻击:
' 错误(不安全): Return contentHeaders.ContentDisposition.FileName ' 可能包含../等恶意路径 ' 正确(清理后): Return cleanFileName
大文件处理:
-
对于大文件,考虑在保存后移动到永久存储位置,或使用云存储服务(如 Azure Blob、AWS S3)。
总结
- 文件写入时机:在
ReadAsMultipartAsync
执行期间自动写入。 - 自定义文件名:通过继承
MultipartFormDataStreamProvider
并重写GetLocalFileName
方法实现。 - 最佳实践:使用 GUID 避免冲突,同时保留原始文件名的元数据。