我正在尝试创建.Net标准“客户端”类,以将文件(有时非常大)上载到Controller。我想通过将文件分成多个块并一次上传一个文件来实现。目的是让其他应用程序使用此功能,而不是直接与Web Api通信。
我已经让Controller工作了。我已验证它是否可以使用支持大块节省的Kendo-ui控件正常工作。
我遇到的问题是,从客户端类发布时,控制器的IEnumerable<IFormFile> files
参数始终为空
控制器
[Route("api/Upload")]
public ActionResult ChunkSave(IEnumerable<IFormFile> files, string metaData, Guid id)
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(metaData));
var serializer = new DataContractJsonSerializer(typeof(ChunkMetaData));
ChunkMetaData somemetaData = serializer.ReadObject(ms) as ChunkMetaData;
// The Name of the Upload component is "files"
if (files != null)
{
// If this is the first chunk, try to delete the file so that we don't accidently
// and up appending new bytes to the old file.
if (somemetaData.ChunkIndex == 0)
{
_io.DeleteFile(id, Path.GetFileName(somemetaData.FileName));
}
foreach (var file in files)
{
// Some browsers send file names with full path. This needs to be stripped.
_io.AppendToFile(id, Path.GetFileName(somemetaData.FileName), file.OpenReadStream());
}
}
FileResult fileBlob = new FileResult();
fileBlob.uploaded = somemetaData.TotalChunks - 1 <= somemetaData.ChunkIndex;
fileBlob.fileUid = somemetaData.UploadUid;
return new JsonResult(fileBlob);
}
客户:
public class FileTransferClient
{
HttpClient Client { get; set; }
public FileTransferClient(Uri apiUrl)
{
this.Client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true })
{
BaseAddress = apiUrl
};
this.Client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<bool> UploadFile(Guid id, Stream file, string name, string contentType)
{
bool ret = true;
int chunckSize = 2097152; //2MB
int totalChunks = (int)(file.Length / chunckSize);
if (file.Length % chunckSize != 0)
{
totalChunks++;
}
for (int i = 0; i < totalChunks; i++)
{
long position = (i * (long)chunckSize);
int toRead = (int)Math.Min(file.Length - position + 1, chunckSize);
byte[] buffer = new byte[toRead];
await file.ReadAsync(buffer, 0, toRead);
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StringContent(id.ToString()), "id");
var meta = JsonConvert.SerializeObject(new ChunkMetaData
{
UploadUid = id.ToString(),
FileName = name,
ChunkIndex = i,
TotalChunks = totalChunks,
TotalFileSize = file.Length,
ContentType = contentType
});
content.Add(new StringContent(meta), "metaData");
using (var ms = new MemoryStream(buffer))
{
content.Add(new StreamContent(ms),"files");
var response = await Client.PostAsync("/api/Upload", content).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
ret = false;
break;
}
}
}
return ret;
}
}
答案 0 :(得分:1)
您的参数为空,因为您不是发送文件数组,而是发送一个文件。因此,绑定失败,并且您将获得null。分块的行为(实际上甚至没有做)不等于IEnumerable<IFormFile>
;仍然只是IFormFile
。
虽然由于要同时发送文件上传和其他一些帖子数据而需要以multipart/form-data
的形式发送,但我认为您误会了它的实际作用。它只是意味着请求主体包含多个不同的mime类型,而不是意味着它正在将文件上传为多个部分,这似乎就是您在想的那样。
流传输上载的实际行为发生在服务器端。它与服务器选择如何处理要上传的文件有关,而与用户如何上传无关。更具体地说,任何类型的模型绑定,尤其是对IFormFile
的模型绑定,都将导致文件先被后台处理到磁盘,然后传递到您的操作中。换句话说,如果您接受IFormFile
,则说明您已经输掉了这场战斗。它已经从客户端完全转移到您的服务器。
ASP.NET Core docs向您展示了如何实际流式传输上传内容,毫不奇怪的是,其中涉及到很多代码,您目前还没有。基本上,您必须完全在操作上关闭模型绑定,并自己手动解析请求主体,请小心地对来自流的读取进行实际分块,而不要执行将所有内容立即强制进入内存的操作。
答案 1 :(得分:1)
问题是我使用StreamContent
而不是ByteArrayContent
来代表我的文件块。这就是我最终得到的:
public async Task<FileData> UploadFileAsync(Guid id, string name, Stream file)
{
Guid uid = Guid.NewGuid();
int chunckSize = 2097152; //2MB
int totalChunks = (int)(file.Length / chunckSize);
if (file.Length % chunckSize != 0)
{
totalChunks++;
}
for (int i = 0; i < totalChunks; i++)
{
long position = (i * (long)chunckSize);
int toRead = (int)Math.Min(file.Length - position + 1, chunckSize);
byte[] buffer = new byte[toRead];
await file.ReadAsync(buffer, 0, buffer.Length);
using (MultipartFormDataContent form = new MultipartFormDataContent())
{
form.Add(new ByteArrayContent(buffer), "files", name);
form.Add(new StringContent(id.ToString()), "id");
var meta = JsonConvert.SerializeObject(new ChunkMetaData
{
UploadUid = id.ToString(),
FileName = name,
ChunkIndex = i,
TotalChunks = totalChunks,
TotalFileSize = file.Length,
ContentType = "application/unknown"
});
form.Add(new StringContent(meta), "metaData");
var response = await Client.PostAsync("/api/Upload", form).ConfigureAwait(false);
return response.IsSuccessStatusCode;
}
}
return true;
}