接受文件上传和附加参数的 WebAPI 方法

Posted

技术标签:

【中文标题】接受文件上传和附加参数的 WebAPI 方法【英文标题】:WebAPI method that takes a file upload and additional arguments 【发布时间】: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 中。然后模型绑定器发挥其魔力。 【参考方案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()"

以上是关于接受文件上传和附加参数的 WebAPI 方法的主要内容,如果未能解决你的问题,请参考以下文章

webapi接口上传大文件

c#webapi 怎么接受表单参数

WebClient和HttpClient, 以及webapi上传图片

Owin WebAPI上传文件

Java Jersey 如何以文件为参数上传文件

php上传文件为啥一定要用post