application/x-www-form-urlencoded与 multipart/form-data:

  Fom表单中如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。但是如果有type=file的话,就要用到multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每个部分加上 Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件 name)等信息,并加上分割符(boundary)

 

 

multipart/form-data被webapi能够识别,需要自定义MediaTypeFormatter,关于webapi中的媒体类型格式化器(Media-type Formatter),它是一种能够做以下工作的对象:

  • Read CLR objects from an HTTP message body
    从HTTP消息体读取CLR(公共语言运行时)对象
  • Write CLR objects into an HTTP message body
    将CLR对象写入HTTP消息体

Web API提供了用于JSON和XML的媒体类型格式化器。框架已默认将这些格式化器插入到消息处理管线之中。客户端在HTTP请求的Accept报头中可以请求JSON或XML。

以下代码是multipart/form-data的格式化方法:

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using MultipartDataMediaFormatter.Converters;
using MultipartDataMediaFormatter.Infrastructure;
using MultipartDataMediaFormatter.Infrastructure.Logger;

namespace MultipartDataMediaFormatter
{
    public class FormMultipartEncodedMediaTypeFormatter : MediaTypeFormatter
    {
        private const string SupportedMediaType = "multipart/form-data";

        public FormMultipartEncodedMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue(SupportedMediaType));
        }

        public override bool CanReadType(Type type)
        {
            return true;
        }

        public override bool CanWriteType(Type type)
        {
            return true;
        }

        public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);

            //need add boundary
            //(if add when fill SupportedMediaTypes collection in class constructor then receive post with another boundary will not work - Unsupported Media Type exception will thrown)
            if (headers.ContentType == null)
                headers.ContentType = new MediaTypeHeaderValue(SupportedMediaType);
            
            if (!String.Equals(headers.ContentType.MediaType, SupportedMediaType, StringComparison.OrdinalIgnoreCase))
                throw new Exception("Not a Multipart Content");
            
            if (headers.ContentType.Parameters.All(m => m.Name != "boundary"))
                headers.ContentType.Parameters.Add(new NameValueHeaderValue("boundary", "MultipartDataMediaFormatterBoundary1q2w3e"));
        }

        public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content,
                                                               IFormatterLogger formatterLogger)
        {
            var httpContentToFormDataConverter = new HttpContentToFormDataConverter();
            FormData multipartFormData = await httpContentToFormDataConverter.Convert(content);

            IFormDataConverterLogger logger;
            if (formatterLogger != null)
                logger = new FormatterLoggerAdapter(formatterLogger);
            else 
                logger = new FormDataConverterLogger();

            var dataToObjectConverter = new FormDataToObjectConverter(multipartFormData, logger);
            object result = dataToObjectConverter.Convert(type);

            logger.EnsureNoErrors();

            return result;
        }

        public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
                                                TransportContext transportContext)
        {
                if (!content.IsMimeMultipartContent())
                    throw new Exception("Not a Multipart Content");

                var boudaryParameter = content.Headers.ContentType.Parameters.FirstOrDefault(m => m.Name == "boundary" && !String.IsNullOrWhiteSpace(m.Value));
                if (boudaryParameter == null)
                    throw new Exception("multipart boundary not found");
                
                var objectToMultipartDataByteArrayConverter = new ObjectToMultipartDataByteArrayConverter();
                byte[] multipartData = objectToMultipartDataByteArrayConverter.Convert(value, boudaryParameter.Value);

                await writeStream.WriteAsync (multipartData, 0, multipartData.Length);
                
                content.Headers.ContentLength = multipartData.Length;
        }
    }
}

 

以IIs为宿主的webapi,加入以下代码

GlobalConfiguration.Configuration.Formatters.Add(new FormMultipartEncodedMediaTypeFormatter());    

如果webapi是自我宿主,加入以下代码

new HttpSelfHostConfiguration(<url>).Formatters.Add(new FormMultipartEncodedMediaTypeFormatter());      

 

发送对象时使用FormMultipartEncodedMediaTypeFormatter(测试代码如下)

private ApiResult<T> PostModel<T>(T model, string url)
{
 var mediaTypeFormatter = new FormMultipartEncodedMediaTypeFormatter();
 using (new WebApiHttpServer(BaseApiAddress, mediaTypeFormatter))
 using (var client = CreateHttpClient(BaseApiAddress))
 using (HttpResponseMessage response = client.PostAsync(url, model, mediaTypeFormatter).Result)
 {
  if (response.StatusCode != HttpStatusCode.OK)
  {
    var err = response.Content.ReadAsStringAsync().Result;
    Assert.Fail(err);
  }

  var resultModel = response.Content.ReadAsAsync<ApiResult<T>>(new[] { mediaTypeFormatter }).Result;
  return resultModel;
  }
}

 

使用MultipartDataMediaFormatter.Infrastructure.FormData这个类访问原始http数据

[HttpPost]
public void PostFileBindRawFormData(MultipartDataMediaFormatter.Infrastructure.FormData formData)
  {
      HttpFile file;
      formData.TryGetValue(<key>, out file);
  }

绑定自定义Model

//model example
public class PersonModel
{
   public string FirstName {get; set;}

   public string LastName {get; set;}

   public DateTime? BirthDate {get; set;}

   public HttpFile AvatarImage {get; set;}

   public List<HttpFile> Attachments {get; set;}

   public List<PersonModel> ConnectedPersons {get; set;}
}

//api controller example
[HttpPost]
public void PostPerson(PersonModel model)
{
   //do something with the model
}

 实例下载

posted on 2015-10-22 15:48  skyEci  阅读(4673)  评论(2编辑  收藏  举报