編碼為multipart/form-data自定義類型(包括文件)如何自動綁定到webapi的action的參數里


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
}

 實例下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM