gpt4 book ai didi

c# - 从 WebAPI 发送大文件。内容长度为 0

转载 作者:太空狗 更新时间:2023-10-29 22:35:54 24 4
gpt4 key购买 nike

我正在尝试将大文件 (GB) 从一个 WebAPI (.NET Core) 发送到另一个 WebApi (.Net Core)。

我已经设法发送较小的文件作为多部分请求的一部分,就像在此处的上一篇文章中一样:link

要发送更大的文件,我需要(我认为)将此文件作为 StreamContent 发送,但是我在接收请求的 API 中得到 Content length = 0。

enter image description here即使我发送(用于测试)较小的文件 (10 Mb) 也会出现问题。

客户端代码:

    [HttpPost("UploadFiles")]
public async Task<IActionResult> Post(IFormFile file)
{
var filePath = Path.GetTempFileName();

using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
await file.CopyToAsync(stream);
using (var formDataContent = new MultipartFormDataContent())
{
using (var httpClient = new HttpClient())
{
formDataContent.Add(CreateFileContent(stream, "myfile.test", "application/octet-stream"));

var response = await httpClient.PostAsync(
"http://localhost:56595/home/upload",
formDataContent);

return Json(response);
}
}
}
}

internal static StreamContent CreateFileContent(Stream stream, string fileName, string contentType)
{
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = "\"file\"",
FileName = "\"" + fileName + "\"",
};
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
return fileContent;
}

服务端代码:

    [HttpPost]
public ActionResult Upload()
{
IFormFile fileFromRequest = Request.Form.Files.First();

string myFileName = fileFromRequest.Name;

// some code

return Ok();
}

问题出在哪里?

为了创建多部分请求,我使用了以下建议:

HttpClient StreamContent append filename twice

POST StreamContent with Multiple Files

最佳答案

我终于明白了:

有两个问题:

<强>1。流指针位置

在客户端代码中,更改为:

await file.CopyToAsync(stream);

对此:

await file.CopyToAsync(stream);
stream.Position = 0;

问题是来自请求的文件被复制到流中并且指针的左位置在流的末尾。这就是为什么从客户端发送的请求具有适当长度的流,但实际上当它开始读取它时,它不能(读取 0 个字节)。

<强>2。服务器处理请求的方式错误。

我使用了来自 dotnetcoretutorials.com 的代码


下面的工作代码:

客户端:

    [HttpPost("UploadFiles")]
public async Task<IActionResult> Post(IFormFile file)
{
var filePath = Path.GetTempFileName();
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
await file.CopyToAsync(stream);
stream.Position = 0;
using (var formDataContent = new MultipartFormDataContent())
{
using (var httpClient = new HttpClient())
{
formDataContent.Add(CreateFileContent(stream, "myfile.test", "application/octet-stream"));

var response = await httpClient.PostAsync(
"http://localhost:56595/home/upload",
formDataContent);
return Json(response);
}
}
}
}

internal static StreamContent CreateFileContent(Stream stream, string fileName, string contentType)
{
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = "\"file\"",
FileName = "\"" + fileName + "\"",
};
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
return fileContent;
}

服务器端:

Controller :

            [HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> Upload()
{
var viewModel = new MyViewModel();
try
{
FormValueProvider formModel;
using (var stream = System.IO.File.Create("c:\\temp\\myfile.temp"))
{
formModel = await Request.StreamFile(stream);
}

var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
valueProvider: formModel);

if (!bindingSuccessful)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
}
catch(Exception exception)
{
throw;
}
return Ok(viewModel);
}

Controller 方法的辅助类:

    public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec says 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (string.IsNullOrWhiteSpace(boundary.ToString()))
{
throw new InvalidDataException("Missing content-type boundary.");
}

if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}

return boundary.ToString();
}

public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}

public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.ToString())
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.ToString());
}

public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.ToString())
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.ToString()));
}
}

public static class FileStreamingHelper
{
private static readonly FormOptions _defaultFormOptions = new FormOptions();

public static async Task<FormValueProvider> StreamFile(this HttpRequest request, Stream targetStream)
{
if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
{
throw new Exception($"Expected a multipart request, but got {request.ContentType}");
}

// Used to accumulate all the form url encoded key value pairs in the
// request.
var formAccumulator = new KeyValueAccumulator();
string targetFilePath = null;

var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, request.Body);

var section = await reader.ReadNextSectionAsync();
while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);

if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
await section.Body.CopyToAsync(targetStream);
}
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
// Content-Disposition: form-data; name="key"
//
// value

// Do not limit the key name length here because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
var encoding = GetEncoding(section);
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
{
value = String.Empty;
}
formAccumulator.Append(key.ToString(), value);

if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
{
throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
}
}
}
}

// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}

// Bind form data to a model
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);

return formValueProvider;
}

private static Encoding GetEncoding(MultipartSection section)
{
MediaTypeHeaderValue mediaType;
var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
// UTF-7 is insecure and should not be honored. UTF-8 will succeed in
// most cases.
if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
{
return Encoding.UTF8;
}
return mediaType.Encoding;
}
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}

var jqueryFormValueProviderFactory = context.ValueProviderFactories
.OfType<JQueryFormValueProviderFactory>()
.FirstOrDefault();
if (jqueryFormValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
}
}

public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}

其他想法:

  • (在客户端)行:

    fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);

不是发送文件所必需的。

  • (在客户端)当 MediaTypeHeaderValue 是以下之一时发送文件:

    应用/x-ms下载

    应用程序/json

    应用程序/八位字节流

  • (在服务器端)要在服务器端使用带有 contentDisposition.FileNameStar 的行,您需要将它们更改为 contentDisposition.FileNameStar.ToString()

  • (在服务器端)服务器端问题中使用的代码可以处理较小的文件 (Mb),但要发送 GB 文件,我们需要粘贴在答案中的代码。

  • 部分代码取自aspnet core docs

关于c# - 从 WebAPI 发送大文件。内容长度为 0,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45948506/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com