【问题标题】:unable to configure Web API for content type multipart无法为内容类型多部分配置 Web API
【发布时间】:2016-03-10 03:33:44
【问题描述】:

我正在开发 Web API - Web API 2。我的基本需求是创建一个 API 来更新用户的个人资料。在这种情况下,ios 和 android 将在 multipart/form-data 中向我发送请求。他们会向我发送一些带有图像的参数。但是每当我尝试创建 API 时,我的模型每次都为空。

我也在 WebApiConfig 中添加了这一行:

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));

这是我的课:

public class UpdateProfileModel
{
   public HttpPostedFileBase ProfileImage { get; set; }
   public string Name { get; set; }
}

这是我的控制器:

[Route("api/Account/UpdateProfile")]
[HttpPost]
public HttpResponseMessage UpdateProfile(UpdateProfileModel model)
{
}

我什至没有在我的模型中获取参数值。我做错了吗?

与此相关的答案都对我没有帮助。大约是第 3 天,我尝试了几乎所有的方法和方法。但我无法实现它。

虽然我可以使用它,但如下所示,但这似乎不是一个好方法。所以我避免它..

var httpRequest = HttpContext.Current.Request;
if (httpRequest.Form["ParameterName"] != null)
{
    var parameterName = httpRequest.Form["ParameterName"];
}

对于文件我可以这样做:

if (httpRequest.Files.Count > 0)
{
     //i can access my files here and save them
}

如果您有任何好的方法,请提供帮助,或者请解释为什么我无法在模型中获取此值。

非常感谢提前

【问题讨论】:

  • 查看此链接asp.net/web-api/overview/advanced/sending-html-form-data-part-2 我不认为 multipart/form-data 是一个糟糕的选择。如果您仍然对安全性不安全,请添加一些不记名令牌或一些预定义的身份验证。
  • 感谢您回复@justcode。我从来没有说过这是不好的选择。我只是不想每次都通过请求和检查其中的空值来获取参数。我希望它们像我们通常在其他 api 中那样直接绑定到我的模型...
  • 比,在我看来,base64 是替代选项。不知道有没有别的办法。
  • ohk.. 实际上我知道的一个人说 multipart/form-data 也可以,但他不记得这些步骤。因此,如果有人可以帮助并指导我朝着正确的方向前进,我就将其发布在这里。否则我必须使用 Base64...
  • 您可以尝试将 [FormData] 添加到参数中。 IIRC,将其标记为 [FormData] 与使用 Form["propertyName"] 相同

标签: c# asp.net-web-api asp.net-mvc-5 asp.net-web-api2


【解决方案1】:

【讨论】:

  • @ali 此链接适用于 MVC 而不是 Web APi。我已经为 MVC 做到了这一点,但我无法为 Web API 实现它(当我使用 multipart/form-data 时)。
【解决方案2】:

所以,对我有用的是 -

[Route("api/Account/UpdateProfile")]
[HttpPost]
public Task<HttpResponseMessage> UpdateProfile(/* UpdateProfileModel model */)
{
     string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);
        await Request.Content.ReadAsMultipartAsync(provider);
        foreach (MultipartFileData file in provider.FileData)
        {

        }
}

还有-

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));

不是必需的。

我猜 multipart/form-data 是在表单提交后的某个地方在内部处理的。

这里描述的很清楚-

http://www.asp.net/web-api/overview/advanced/sending-html-form-data-part-2

【讨论】:

  • 感谢您的回答,但是您已经删除了参数模型,那么我将如何获取其中的值?
  • 您可以使用MultipartFormDataStreamProvider 类阅读内容。 URL 描述了一切。
  • 是的,但我仍然需要从中读取所有值和文件(我试图避免)。模型验证也不起作用。
  • 在这种情况下,我建议你最终编写自己的 mediatypeformatter 并处理自己的模型绑定,即序列化/反序列化逻辑。
【解决方案3】:

您的控制器中不能有这样的参数,因为没有处理 Multipart/Formdata 的内置媒体类型格式化程序。除非您创建自己的格式化程序,否则您可以通过 MultipartFormDataStreamProvider 访问文件和可选字段:

发布方式

 public async Task<HttpResponseMessage> Post()
{
    HttpResponseMessage response;

        //Check if request is MultiPart
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

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

        //This write the file in your App_Data with a random name
        await Request.Content.ReadAsMultipartAsync(provider);

        foreach (MultipartFileData file in provider.FileData)
        {
            //Here you can get the full file path on the server
            //and other data regarding the file
            tempFileName = file.LocalFileName;
        }

        // You values are inside FormData. You can access them in this way
        foreach (var key in provider.FormData.AllKeys)
        {
            foreach (var val in provider.FormData.GetValues(key))
            {
                Trace.WriteLine(string.Format("{0}: {1}", key, val));
            }
        }

        //Or directly (not safe)    
        string name = provider.FormData.GetValues("name").FirstOrDefault();


        response = Request.CreateResponse(HttpStatusCode.Ok);              

    return response;
}

以下是更详细的示例列表: Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME

【讨论】:

  • 非常感谢您的回答。我仍在寻找一种方法,以便我可以使用预定义的格式化程序,或者我将尝试创建一个以便我的模型值绑定到参数。
【解决方案4】:

默认情况下,api 中没有内置的媒体类型格式化程序可以处理 multipart/form-data 并执行模型绑定。内置的媒体类型格式化程序是:

 JsonMediaTypeFormatter: application/json, text/json
 XmlMediaTypeFormatter: application/xml, text/xml
 FormUrlEncodedMediaTypeFormatter: application/x-www-form-urlencoded
 JQueryMvcFormUrlEncodedFormatter: application/x-www-form-urlencoded

这就是为什么大多数答案都涉及直接从控制器内部的请求中读取数据的原因。但是,Web API 2 格式化程序集合是开发人员的起点,而不是所有实现的解决方案。已经创建了其他解决方案来创建将处理多部分表单数据的 MediaFormatter。一旦创建了 MediaTypeFormatter 类,它就可以在 Web API 的多个实现中重复使用。

How create a MultipartFormFormatter for ASP.NET 4.5 Web API

您可以下载并构建 web api 2 源代码的完整实现,并看到媒体格式化程序的默认实现不会原生处理多部分数据。 https://aspnetwebstack.codeplex.com/

【讨论】:

  • 非常感谢您的回答。如果它适合我​​的需要,我会检查并尝试实现这个格式化程序。
【解决方案5】:

JPgrassi 提供的答案是您将如何拥有 MultiPart 数据。我认为需要添加的东西很少,所以我想写我自己的答案。

MultiPart 表单数据,顾名思义,不是单一类型的数据,而是指定表单将作为 MultiPart MIME 消息发送,因此您无法使用预定义的格式化程序来读取所有内容。您需要使用 ReadAsync 函数读取字节流并获取不同类型的数据,识别它们并反序列化它们。

有两种读取内容的方法。第一种是读取并将所有内容保存在内存中,第二种方法是使用提供程序将所有文件内容流式传输到一些随机名称的文件(带有 GUID)并以本地路径的形式提供句柄来访问文件(提供的示例jpgrassi 正在做第二个)。

第一种方法:将所有内容保存在内存中

//Async because this is asynchronous process and would read stream data in a buffer. 
//If you don't make this async, you would be only reading a few KBs (buffer size) 
//and you wont be able to know why it is not working
public async Task<HttpResponseMessage> Post()
{

if (!request.Content.IsMimeMultipartContent()) return null;

        Dictionary<string, object> extractedMediaContents = new Dictionary<string, object>();

        //Here I am going with assumption that I am sending data in two parts, 
        //JSON object, which will come to me as string and a file. You need to customize this in the way you want it to.           
        extractedMediaContents.Add(BASE64_FILE_CONTENTS, null);
        extractedMediaContents.Add(SERIALIZED_JSON_CONTENTS, null);

        request.Content.ReadAsMultipartAsync()
                .ContinueWith(multiPart =>
                {
                    if (multiPart.IsFaulted || multiPart.IsCanceled)
                    {
                        Request.CreateErrorResponse(HttpStatusCode.InternalServerError, multiPart.Exception);
                    }

                    foreach (var part in multiPart.Result.Contents)
                    {
                        using (var stream = part.ReadAsStreamAsync())
                        {
                            stream.Wait();
                            Stream requestStream = stream.Result;

                            using (var memoryStream = new MemoryStream())
                            {
                                requestStream.CopyTo(memoryStream);
                                //filename attribute is identifier for file vs other contents.
                                if (part.Headers.ToString().IndexOf("filename") > -1)
                                {                                        
                                    extractedMediaContents[BASE64_FILE_CONTENTS] = memoryStream.ToArray();
                                }
                                else
                                {
                                    string jsonString = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());
                                   //If you need just string, this is enough, otherwise you need to de-serialize based on the content type. 
                                   //Each content is identified by name in content headers.
                                   extractedMediaContents[SERIALIZED_JSON_CONTENTS] = jsonString;
                                }
                            }
                        }
                    }
                }).Wait();

        //extractedMediaContents; This now has the contents of Request in-memory.
}

第二种方法:使用提供程序(由 jpgrassi 提供)

注意,这只是文件名。如果你想处理文件或存储在不同的位置,你需要再次流式读取文件。

 public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;

    //Check if request is MultiPart
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }
    //This specifies local path on server where file will be created
    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    var provider = new MultipartFormDataStreamProvider(root);

    //This write the file in your App_Data with a random name
    await Request.Content.ReadAsMultipartAsync(provider);

    foreach (MultipartFileData file in provider.FileData)
    {
        //Here you can get the full file path on the server
        //and other data regarding the file
        //Point to note, this is only filename. If you want to keep / process file, you need to stream read the file again.
        tempFileName = file.LocalFileName;
    }

    // You values are inside FormData. You can access them in this way
    foreach (var key in provider.FormData.AllKeys)
    {
        foreach (var val in provider.FormData.GetValues(key))
        {
            Trace.WriteLine(string.Format("{0}: {1}", key, val));
        }
    }

    //Or directly (not safe)    
    string name = provider.FormData.GetValues("name").FirstOrDefault();


    response = Request.CreateResponse(HttpStatusCode.Ok);              

return response;
}

【讨论】:

  • 非常感谢您的回答。我仍在寻找一种方法,以便我可以使用预定义的格式化程序,或者我将尝试创建一个以便我的模型值绑定到参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-16
  • 2014-03-08
  • 2015-03-29
  • 2016-02-07
  • 2021-04-25
  • 2012-11-09
相关资源
最近更新 更多