【问题标题】:WebAPI method that takes a file upload and additional arguments接受文件上传和附加参数的 WebAPI 方法
【发布时间】:2014-07-06 22:10:44
【问题描述】:

我想上传一个文件并随文件一起发送一些附加信息,比如说一个字符串 foo 和一个 int bar。

如何编写接收文件上传、字符串和 int 的 ASP.NET WebAPI 控制器方法?

我的 JavaScript:

var fileInput = document.querySelector("#filePicker");
var formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("foo", "hello world!");
formData.append("bar", 42);

var options = {
   url: "/api/foo/upload",
   data: formData,
   processData: false // Prevents JQuery from transforming the data into a query string
};
$.ajax(options);

我的 WebAPI 控制器可以像这样访问文件:

public async Task<HttpResponseMessage> Upload()
{
    var streamProvider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(streamProvider);
    var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync();
}

但我不清楚如何获取字符串和 int。我想我可能会说 streamProvider.Content[1] 或其他什么,但这感觉超级讨厌。

编写接受文件上传、字符串和 int 的 WebAPI 操作的正确方法是什么?

【问题讨论】:

  • 我可能遗漏了一些东西...为什么不能只为 Upload 方法设置 foo 和 bar 参数并让绑定魔法发生?
  • 我专注于在 FormData 中发送数据(例如,作为 $.post(url, formData) 中的数据;模型绑定器永远找不到它们。结果,我可以发布文件单独作为表单数据,然后将其他参数放在 URL 中。然后模型绑定器发挥其魔力。

标签: asp.net asp.net-mvc file-upload asp.net-web-api


【解决方案1】:

您可以创建自己的MultipartFileStreamProvider 来访问其他参数。

ExecutePostProcessingAsync 中,我们以多部分形式循环遍历每个文件并加载自定义数据(如果您只有一个文件,那么CustomData 列表中将只有一个对象)。

class MyCustomData
{
    public int Foo { get; set; }
    public string Bar { get; set; }
}

class CustomMultipartFileStreamProvider : MultipartMemoryStreamProvider
{
    public List<MyCustomData> CustomData { get; set; }

    public CustomMultipartFileStreamProvider()
    {
        CustomData = new List<MyCustomData>();
    }

    public override Task ExecutePostProcessingAsync()
    {
        foreach (var file in Contents)
        {
            var parameters = file.Headers.ContentDisposition.Parameters;
            var data = new MyCustomData
            {
                Foo = int.Parse(GetNameHeaderValue(parameters, "Foo")),
                Bar = GetNameHeaderValue(parameters, "Bar"),
            };

            CustomData.Add(data);
        }

        return base.ExecutePostProcessingAsync();
    }

    private static string GetNameHeaderValue(ICollection<NameValueHeaderValue> headerValues, string name)
    {
        var nameValueHeader = headerValues.FirstOrDefault(
            x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

        return nameValueHeader != null ? nameValueHeader.Value : null;
    }
}

然后在你的控制器中:

class UploadController : ApiController
{
    public async Task<HttpResponseMessage> Upload()
    {
        var streamProvider = new CustomMultipartFileStreamProvider();
        await Request.Content.ReadAsMultipartAsync(streamProvider);

        var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync();
        var customData = streamProvider.CustomData;

        return Request.CreateResponse(HttpStatusCode.Created);
    }
}

【讨论】:

  • 谢谢。我最终只是使用“简单”参数传递了参数,例如$.post("/api/foo?id=23&bar=hello", fileData).那行得通..前提是您没有传递复杂的对象或数组。我更喜欢你的解决方案,所以我将其标记为答案。
  • 在我们的尝试中,我们在键/值对的值部分获取参数的名称 - 键字段简单包含“名称”。自 5 月以来有什么变化吗?
【解决方案2】:

我认为这里的答案非常好。因此,其他人可以看到一个简单的示例,说明如何以摘要形式传递除了文件之外的数据,其中包括一个 Javascript 函数,该函数使 WebAPI 调用 FileUpload 控制器,以及来自 FileUpload 控制器的 sn-p(在 VB. net) 读取从 Javascript 传递的附加数据。

Javascript:

            function uploadImage(files) {
            var data = new FormData();
            if (files.length > 0) {
                data.append("UploadedImage", files[0]);
                data.append("Source", "1")
                var ajaxRequest = $.ajax({
                    type: "POST",
                    url: "/api/fileupload/uploadfile",
                    contentType: false,
                    processData: false,
                    data: data
                });

文件上传控制器:

        <HttpPost> _
    Public Function UploadFile() As KeyValuePair(Of Boolean, String)
        Try
            If HttpContext.Current.Request.Files.AllKeys.Any() Then
                Dim httpPostedFile = HttpContext.Current.Request.Files("UploadedImage")
                Dim source = HttpContext.Current.Request.Form("Source").ToString()

所以在Javascript中可以看到,传递的附加数据是“Source”键,值是“1”。正如 Chandrika 上面回答的那样,控制器通过“System.Web.HttpContext.Current.Request.Form("Source").ToString()”读取这个传递的数据。

请注意,Form("Source") 使用 ()(与 [])作为控制器代码在 VB.net 中。

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    您可以通过这种方式提取多个文件和多个属性:

    public async Task<HttpResponseMessage> Post()
    {
        Dictionary<string,string> attributes = new Dictionary<string, string>();
        Dictionary<string, byte[]> files = new Dictionary<string, byte[]>();
    
        var provider = new MultipartMemoryStreamProvider();
        await Request.Content.ReadAsMultipartAsync(provider);
        foreach (var file in provider.Contents)
        {
            if (file.Headers.ContentDisposition.FileName != null)
            {
                var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
                var buffer = await file.ReadAsByteArrayAsync();
                files.Add(filename, buffer);
            } else
            {
                foreach(NameValueHeaderValue p in file.Headers.ContentDisposition.Parameters)
                {
                    string name = p.Value;
                    if (name.StartsWith("\"") && name.EndsWith("\"")) name = name.Substring(1, name.Length - 2);
                    string value = await file.ReadAsStringAsync();
                    attributes.Add(name, value);
                }
            }
        }
        //Your code here  
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
    

    【讨论】:

      【解决方案4】:

      var receipents = HttpContext.Current.Request.Params["Receipents"]; var 参与者 = HttpContext.Current.Request.Params["Participants"];

              var file = HttpContext.Current.Request.Files.Count > 0 ?  HttpContext.Current.Request.Files[0] : null;
      
              if (file != null && file.ContentLength > 0)
              {
                  var fileName = Path.GetFileName(file.FileName);
      
                  var path = Path.Combine(
                      HttpContext.Current.Server.MapPath("~/uploads"),
                      fileName
                  );
      
                  file.SaveAs(path);
              }
      

      【讨论】:

      • 将参数和文件放在一起的最简单解决方案。
      • 一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决问题的原因,答案会更有帮助。
      【解决方案5】:

      您可以通过以下方式做到这一点: jQuery 方法:

          var data = new FormData();
      
          data.append("file", filesToUpload[0].rawFile);
          var doc = {};            
          doc.DocumentId = 0; 
          $.support.cors = true;
          $.ajax({
              url: '/api/document/uploaddocument',
              type: 'POST',
              contentType: 'multipart/form-data',
              data: data,
              cache: false,
              contentType: false,
              processData: false,
              success: function (response) {
                  docId = response.split('|')[0];
                  doc.DocumentId = docId;
                  $.post('/api/document/metadata', doc)
                      .done(function (response) {
                      });
                alert('Document save successfully!');
              },
              error: function (e) {
                  alert(e);
              }
          });
      

      调用您的“UploadDocuement”网络 API

      [Route("api/document/uploaddocument"), HttpPost]
      [UnhandledExceptionFilter]
      [ActionName("UploadDocument")]
      public Task<HttpResponseMessage> UploadDocument()
      {
          // Check if the request contains multipart/form-data.
          if (!Request.Content.IsMimeMultipartContent())
          {
              Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate()
              {
                  return new HttpResponseMessage()
                  {
                      StatusCode = HttpStatusCode.BadRequest,
                      Content = "In valid file & request content type!".ToStringContent()
                  };
              });
              return mytask;
          }
      
      
          string root = HttpContext.Current.Server.MapPath("~/Documents");
          if (System.IO.Directory.Exists(root))
          {
              System.IO.Directory.CreateDirectory(root);
          }
          var provider = new MultipartFormDataStreamProvider(root);
      
          var task = Request.Content.ReadAsMultipartAsync(provider).
          ContinueWith<HttpResponseMessage>(o =>
          {
              if (o.IsFaulted || o.IsCanceled)
                  throw new HttpResponseException(HttpStatusCode.InternalServerError);
      
              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("\"", "")));
      
              string sFileName = provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "");
      
              FileInfo FInfos = new FileInfo(Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));
      
              Document dbDoc = new Document()
              {
                  DocumentID = 0                
      
              };
      
              context.DocumentRepository.Insert(dbDoc);
              context.Save();
      
              return new HttpResponseMessage()
              {
                  Content = new StringContent(string.Format("{0}|File uploaded.", dbDoc.DocumentID))
              };
          }
          );
          return task;
      
      }
      

      通过以下方式调用您的元数据 Web api:

      [Route("api/document/metadata"), HttpPost]
      [ActionName("Metadata")]
      public Task<HttpResponseMessage> Metadata(Document doc)
      {
          int DocId = Convert.ToInt32(System.Web.HttpContext.Current.Request.Form["DocumentId"].ToString());
              
          Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate()
          {
              return new HttpResponseMessage()
              {
                  Content = new StringContent("metadata updated")
              };
          });
          return mytask;
      }
      

      【讨论】:

      • 所以,如果我理解正确,您将进行 2 次调用:一个用于文件,另一个用于元数据(例如,我的问题的“附加参数”部分)。有没有办法在一次通话中做到这一点?
      • 在 UploadDocument web api "System.Web.HttpContext.Current.Request.Form["DocumentId"].ToString()" 中试试这个
      • 在上面的示例中,我进行了 2 个调用,一个用于文件,另一个用于元数据,您可以通过一个调用来获取它,如下所示在 UploadDocument web api "System.Web.HttpContext.Current .Request.Form["DocumentId"].ToString()"
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-31
      • 1970-01-01
      • 2016-06-21
      • 1970-01-01
      • 2016-01-27
      • 1970-01-01
      相关资源
      最近更新 更多