【问题标题】:How To Accept a File POST如何接受文件 POST
【发布时间】:2012-04-25 16:51:47
【问题描述】:

我正在使用 asp.net mvc 4 webapi beta 来构建一个休息服务。我需要能够接受来自客户端应用程序的 POST 图像/文件。这可以使用webapi吗?以下是我目前正在使用的操作。有谁知道这应该如何工作的示例?

[HttpPost]
public string ProfileImagePost(HttpPostedFile profileImage)
{
    string[] extensions = { ".jpg", ".jpeg", ".gif", ".bmp", ".png" };
    if (!extensions.Any(x => x.Equals(Path.GetExtension(profileImage.FileName.ToLower()), StringComparison.OrdinalIgnoreCase)))
    {
        throw new HttpResponseException("Invalid file type.", HttpStatusCode.BadRequest);
    }

    // Other code goes here

    return "/path/to/image.png";
}

【问题讨论】:

  • 这只适用于 MVC 而不是 WebAPI 框架。
  • 你应该可以从Request.Files获取项目
  • ApiController 不包含具有 Files 属性的 HttpRequestBase。它的 Request 对象是基于 HttpRequestMessage 类的。

标签: c# asp.net-mvc-4


【解决方案1】:

令我惊讶的是,你们中的许多人似乎都想在服务器上保存文件。将所有内容保存在内存中的解决方案如下:

[HttpPost("api/upload")]
public async Task<IHttpActionResult> Upload()
{
    if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 

    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
        var buffer = await file.ReadAsByteArrayAsync();
        //Do whatever you want with filename and its binary data.
    }

    return Ok();
}

【讨论】:

  • 如果您不想占用磁盘空间,将文件保存在内存中会很有用。但是,如果您允许上传大文件,那么将它们保存在内存中意味着您的网络服务器将占用大量内存,而这些内存不能用于为其他请求保留内容。这将导致在高负载下工作的服务器出现问题。
  • @W.Meints 我理解想要存储数据的原因,但我不明白为什么有人想要将上传的数据存储在服务器磁盘空间上。您应该始终将文件存储与网络服务器隔离 - 即使对于较小的项目也是如此。
  • 确保您发布的文件大小小于 64k,默认行为是忽略请求,否则,我在此问题上停留了一段时间。
  • 不幸的是,如果您还想读取表单数据,MultipartMemoryStreamProvider 也无济于事。想要创建类似 MultipartFormDataMemoryStreamProvider 的东西,但是 aspnetwebstack 内部有这么多的类和辅助类:(
  • File.WriteAllBytes(filename, buffer); 将其写入文件
【解决方案2】:

请参阅http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime,尽管我认为这篇文章使它看起来比实际情况要复杂一些。

基本上,

public Task<HttpResponseMessage> PostFile() 
{ 
    HttpRequestMessage request = this.Request; 
    if (!request.Content.IsMimeMultipartContent()) 
    { 
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 
    } 

    string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads"); 
    var provider = new MultipartFormDataStreamProvider(root); 

    var task = request.Content.ReadAsMultipartAsync(provider). 
        ContinueWith<HttpResponseMessage>(o => 
    { 

        string file1 = provider.BodyPartFileNames.First().Value;
        // this is the file name on the server where the file was saved 

        return new HttpResponseMessage() 
        { 
            Content = new StringContent("File uploaded.") 
        }; 
    } 
    ); 
    return task; 
} 

【讨论】:

  • 使用任务读取一个文件有什么好处?真正的问题,我刚刚开始使用任务。以我目前的理解,这段代码真的很适合上传多个文件的时候正确吗?
  • MultipartFormDataStreamProvider 不再具有 BodyPartFileNames 属性(在 WebApi RTM 中)。见asp.net/web-api/overview/working-with-http/…
  • 伙计们,你们能否解释一下为什么我们不能简单地使用 HttpContext.Current.Request.Files 访问文件,而需要使用这个奇特的 MultipartFormDataStreamProvider?完整问题:stackoverflow.com/questions/17967544.
  • 文件被保存为 BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f。它们不应该像 pic.jpg 一样保存在客户端上吗?
  • MultipartFormDataStreamProvider 不再暴露BodyPartFileNames 属性,我改用FileData.First().LocalFileName
【解决方案3】:

参见下面的代码,改编自this article,它演示了我能找到的最简单的示例代码。它包括文件和内存(更快)上传。

public HttpResponseMessage Post()
{
    var httpRequest = HttpContext.Current.Request;
    if (httpRequest.Files.Count < 1)
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

    foreach(string file in httpRequest.Files)
    {
        var postedFile = httpRequest.Files[file];
        var filePath = HttpContext.Current.Server.MapPath("~/" + postedFile.FileName);
        postedFile.SaveAs(filePath);
        // NOTE: To store in memory use postedFile.InputStream
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}

【讨论】:

  • 当 WebAPI 托管在自托管容器 OWIN 中时,HttpContext.Current 为 null。
  • 这样修复它:var httpRequest = System.Web.HttpContext.Current.Request;
  • 除非万不得已,否则不要在 WebAPI 中使用 System.Web。
  • 当然,System.Web 与 IIS 紧密耦合。如果您在 OWIN 管道或 .Net Core 中工作,则在 linux 或自托管下运行时,这些 API 将不可用。
  • 很好的答案。只有一个细节:如果您从 HTML 页面上传, 标签 必须 具有“name”属性,否则文件将不会出现在 HttpContext.Current .Request.Files.
【解决方案4】:

ASP.NET Core 方式现在是here:

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    // full path to file in temp location
    var filePath = Path.GetTempFileName();

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size, filePath});
}

【讨论】:

  • 这个问题被专门标记为asp.net-mvc-4,所以这个答案可能会令 .NET 新手感到困惑
【解决方案5】:

这是一个快速而肮脏的解决方案,它从 HTTP 正文中获取上传的文件内容并将其写入文件。我为文件上传添加了一个“基本”HTML/JS sn-p。

Web API 方法:

[Route("api/myfileupload")]        
[HttpPost]
public string MyFileUpload()
{
    var request = HttpContext.Current.Request;
    var filePath = "C:\\temp\\" + request.Headers["filename"];
    using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
    {
        request.InputStream.CopyTo(fs);
    }
    return "uploaded";
}

HTML 文件上传:

<form>
    <input type="file" id="myfile"/>  
    <input type="button" onclick="uploadFile();" value="Upload" />
</form>
<script type="text/javascript">
    function uploadFile() {        
        var xhr = new XMLHttpRequest();                 
        var file = document.getElementById('myfile').files[0];
        xhr.open("POST", "api/myfileupload");
        xhr.setRequestHeader("filename", file.name);
        xhr.send(file);
    }
</script>

【讨论】:

  • 请注意,这不适用于“正常”多部分表单上传。
  • @Tom 这是什么意思?
  • 这意味着它与禁用/不存在 JavaScript 的浏览器不兼容,例如网景 1.*.
【解决方案6】:

在我更新我的 webapi mvc4 项目中的所有 NuGet 之前,我使用了 Mike Wasson 的答案。完成后,我不得不重新编写文件上传操作:

    public Task<HttpResponseMessage> Upload(int id)
    {
        HttpRequestMessage request = this.Request;
        if (!request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads");
        var provider = new MultipartFormDataStreamProvider(root);

        var task = request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                FileInfo finfo = new FileInfo(provider.FileData.First().LocalFileName);

                string guid = Guid.NewGuid().ToString();

                File.Move(finfo.FullName, Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));

                return new HttpResponseMessage()
                {
                    Content = new StringContent("File uploaded.")
                };
            }
        );
        return task;
    }

显然 BodyPartFileNames 在 MultipartFormDataStreamProvider 中不再可用。

【讨论】:

  • 在 WebApi RTM 中,BodyPartFileNames 已更改为 FileData。请参阅asp.net/web-api/overview/working-with-http/… 处的更新示例
  • 为什么不直接使用 System.Web.HttpContext.Current.Request.Files 集合?
  • 我正在考虑使用您的方法有两个保留:1)这不是写两次吗:i)在ReadAsMultipartAsync和ii)在File.Move? 2)你能做async File.Move吗?
  • 1) 我两次写入都没有问题,url 是否被调用了两次? 2)你可以做 Task.Run(() => { File.Move(src, dest); });
【解决方案7】:

朝着同样的方向,我发布了一个使用 WebApi,c# 4 发送 Excel 文件的客户端和服务器片段:

public static void SetFile(String serviceUrl, byte[] fileArray, String fileName)
{
    try
    {
        using (var client = new HttpClient())
        {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                using (var content = new MultipartFormDataContent())
                {
                    var fileContent = new ByteArrayContent(fileArray);//(System.IO.File.ReadAllBytes(fileName));
                    fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = fileName
                    };
                    content.Add(fileContent);
                    var result = client.PostAsync(serviceUrl, content).Result;
                }
        }
    }
    catch (Exception e)
    {
        //Log the exception
    }
}

和服务器 webapi 控制器:

public Task<IEnumerable<string>> Post()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        string fullPath = HttpContext.Current.Server.MapPath("~/uploads");
        MyMultipartFormDataStreamProvider streamProvider = new MyMultipartFormDataStreamProvider(fullPath);
        var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);

            var fileInfo = streamProvider.FileData.Select(i =>
            {
                var info = new FileInfo(i.LocalFileName);
                return "File uploaded as " + info.FullName + " (" + info.Length + ")";
            });
            return fileInfo;

        });
        return task;
    }
    else
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
    }
}

还有自定义 MyMultipartFormDataStreamProvider,需要自定义 Filename:

PS:这段代码是从另一个帖子http://www.codeguru.com/csharp/.net/uploading-files-asynchronously-using-asp.net-web-api.htm

public class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public MyMultipartFormDataStreamProvider(string path)
        : base(path)
    {

    }

    public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
    {
        string fileName;
        if (!string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName))
        {
            fileName = headers.ContentDisposition.FileName;
        }
        else
        {
            fileName = Guid.NewGuid().ToString() + ".data";
        }
        return fileName.Replace("\"", string.Empty);
    }
}

【讨论】:

  • 你能在你的控制器中显示你如何称呼你static method SetFile吗?
  • 这是一个很好的答案。像这样扩展基本提供程序还使您能够控制流并为您提供比仅提供 path (即云存储)更大的灵活性。
【解决方案8】:
[HttpPost]
public JsonResult PostImage(HttpPostedFileBase file)
{
    try
    {
        if (file != null && file.ContentLength > 0 && file.ContentLength<=10485760)
        {
            var fileName = Path.GetFileName(file.FileName);                                        

            var path = Path.Combine(Server.MapPath("~/") + "HisloImages" + "\\", fileName);

            file.SaveAs(path);
            #region MyRegion
            ////save imag in Db
            //using (MemoryStream ms = new MemoryStream())
            //{
            //    file.InputStream.CopyTo(ms);
            //    byte[] array = ms.GetBuffer();
            //} 
            #endregion
            return Json(JsonResponseFactory.SuccessResponse("Status:0 ,Message: OK"), JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(JsonResponseFactory.ErrorResponse("Status:1 , Message: Upload Again and File Size Should be Less Than 10MB"), JsonRequestBehavior.AllowGet);
        }
    }
    catch (Exception ex)
    {

        return Json(JsonResponseFactory.ErrorResponse(ex.Message), JsonRequestBehavior.AllowGet);

    }
}

【讨论】:

  • 我认为用户需要一些解释......!
【解决方案9】:

这里有两种接受文件的方法。一个使用内存提供程序 MultipartMemoryStreamProvider 和一个使用 MultipartFormDataStreamProvider 保存到磁盘。请注意,这仅适用于一次上传一个文件。您可以肯定地扩展它以保存多个文件。第二种方法可以支持大文件。我已经测试了超过 200MB 的文件,它工作正常。使用内存方式不需要你保存到磁盘,但是如果超过一定的限制就会抛出内存不足的异常。

private async Task<Stream> ReadStream()
{
    Stream stream = null;
    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var buffer = await file.ReadAsByteArrayAsync();
        stream = new MemoryStream(buffer);
    }

    return stream;
}

private async Task<Stream> ReadLargeStream()
{
    Stream stream = null;
    string root = Path.GetTempPath();
    var provider = new MultipartFormDataStreamProvider(root);
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.FileData)
    {
        var path = file.LocalFileName;
        byte[] content = File.ReadAllBytes(path);
        File.Delete(path);
        stream = new MemoryStream(content);
    }

    return stream;
}

【讨论】:

    【解决方案10】:

    即使对于 .Net Core,这个问题也有很多很好的答案。我正在使用两个框架,提供的代码示例工作正常。所以我不会重复它。就我而言,重要的是如何使用 Swagger 的文件上传操作,如下所示:

    这是我的回顾:

    ASP .Net WebAPI 2

    • 上传文件使用:MultipartFormDataStreamProvider在这里查看答案
    • 如何use it with Swagger

    .NET Core

    【讨论】:

      【解决方案11】:

      预览版 Web API 也有类似的问题。尚未将该部分移植到新的 MVC 4 Web API,但也许这会有所帮助:

      REST file upload with HttpRequestMessage or Stream?

      请告诉我,明天可以坐下来尝试再次实施。

      【讨论】:

        【解决方案12】:

        API 控制器:

        [HttpPost]
        public HttpResponseMessage Post()
        {
            var httpRequest = System.Web.HttpContext.Current.Request;
        
            if (System.Web.HttpContext.Current.Request.Files.Count < 1)
            {
                //TODO
            }
            else
            {
        
            try
            { 
                foreach (string file in httpRequest.Files)
                { 
                    var postedFile = httpRequest.Files[file];
                    BinaryReader binReader = new BinaryReader(postedFile.InputStream);
                    byte[] byteArray = binReader.ReadBytes(postedFile.ContentLength);
        
                }
        
            }
            catch (System.Exception e)
            {
                //TODO
            }
        
            return Request.CreateResponse(HttpStatusCode.Created);
        }
        

        【讨论】:

          【解决方案13】:

          补充 Matt Frear 的回答 - 这将是一个 ASP NET Core 替代方案,用于直接从 Stream 读取文件,无需从磁盘保存和读取它:

          public ActionResult OnPostUpload(List<IFormFile> files)
              {
                  try
                  {
                      var file = files.FirstOrDefault();
                      var inputstream = file.OpenReadStream();
          
                      XSSFWorkbook workbook = new XSSFWorkbook(stream);
          
                      var FIRST_ROW_NUMBER = {{firstRowWithValue}};
          
                      ISheet sheet = workbook.GetSheetAt(0);
                      // Example: var firstCellRow = (int)sheet.GetRow(0).GetCell(0).NumericCellValue;
          
                      for (int rowIdx = 2; rowIdx <= sheet.LastRowNum; rowIdx++)
                         {
                            IRow currentRow = sheet.GetRow(rowIdx);
          
                            if (currentRow == null || currentRow.Cells == null || currentRow.Cells.Count() < FIRST_ROW_NUMBER) break;
          
                            var df = new DataFormatter();                
          
                            for (int cellNumber = {{firstCellWithValue}}; cellNumber < {{lastCellWithValue}}; cellNumber++)
                                {
                                   //business logic & saving data to DB                        
                                }               
                          }
                  }
                  catch(Exception ex)
                  {
                      throw new FileFormatException($"Error on file processing - {ex.Message}");
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-04-01
            • 1970-01-01
            • 2016-02-15
            • 1970-01-01
            • 2020-06-05
            • 2013-05-04
            • 1970-01-01
            • 2018-11-13
            相关资源
            最近更新 更多