2012-04-05 52 views
20

考虑使用ASP.NET Web API编写的Web服务来接受任何数字文件作为“多部分/混合”请求。辅助方法垫如下所示(假设_clientSystem.Net.Http.HttpClient一个实例):如何正确实现一个MediaTypeFormatter来处理'multipart/mixed'类型的请求?

public T Post<T>(string requestUri, T value, params Stream[] streams) 
{ 
    var requestMessage = new HttpRequestMessage(); 
    var objectContent = requestMessage.CreateContent(
     value, 
     MediaTypeHeaderValue.Parse("application/json"), 
     new MediaTypeFormatter[] {new JsonMediaTypeFormatter()}, 
     new FormatterSelector()); 

    var content = new MultipartContent(); 
    content.Add(objectContent); 
    foreach (var stream in streams) 
    { 
     var streamContent = new StreamContent(stream); 
     streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
     streamContent.Headers.ContentDisposition = 
      new ContentDispositionHeaderValue("form-data") 
      { 
       Name = "file", 
       FileName = "mystream.doc" 
      }; 
     content.Add(streamContent); 
    } 

    return _httpClient.PostAsync(requestUri, content) 
     .ContinueWith(t => t.Result.Content.ReadAsAsync<T>()).Unwrap().Result; 
} 

接受在ApiController的子类中的请求的方法,有一个签名如下:

public HttpResponseMessage Post(HttpRequestMessage request) 
{ 
    /* parse request using MultipartFormDataStreamProvider */ 
} 

理想情况下,我想这样定义它,根据'Content-Disposition'标题的'name'属性从'multipart/mixed'内容中提取联系人,源和目标。

public HttpResponseMessage Post(Contact contact, Stream source, Stream target) 
{ 
    // process contact, source and target 
} 

然而,我现有的签名,并带有错误消息张贴在InvalidOperationException数据到服务器的测试结果:

否“MediaTypeFormatter”可阅读 类型的对象“ HttpRequestMessage'与媒体类型'multipart/mixed'。

在互联网上有很多关于如何使用ASP.NET Web API和HttpClient发送和接收文件的例子。但是,我还没有发现任何显示如何处理这个问题。

我开始考虑实施自定义MediaTypeFormatter并将其注册为全局配置。然而,虽然在自定义MediaTypeFormatter中处理序列化XML和JSON很容易,但如何处理“多部分/混合”请求并不清楚,这几乎可以成为任何事情。

回答

13

看一看这个论坛:http://forums.asp.net/t/1777847.aspx/1?MVC4+Beta+Web+API+and+multipart+form+data

这里是(通过imran_ku07发布),可以帮助你实现一个自定义格式来处理的multipart/form-data的一小段代码:

public class MultiFormDataMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter 
{ 
    public MultiFormDataMediaTypeFormatter() : base() 
    { 
     this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); 
    } 

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

    protected override bool CanWriteType(Type type) 
    { 
     return false; 
    } 

    protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) 
    { 
     var contents = formatterContext.Request.Content.ReadAsMultipartAsync().Result; 
     return Task.Factory.StartNew<object>(() => 
     { 
      return new MultiFormKeyValueModel(contents); 
     }); 
    } 

    class MultiFormKeyValueModel : IKeyValueModel 
    { 
     IEnumerable<HttpContent> _contents; 
     public MultiFormKeyValueModel(IEnumerable<HttpContent> contents) 
     { 
      _contents = contents; 
     } 


     public IEnumerable<string> Keys 
     { 
      get 
      { 
       return _contents.Cast<string>(); 
      } 
     } 

     public bool TryGetValue(string key, out object value) 
     { 
      value = _contents.FirstDispositionNameOrDefault(key).ReadAsStringAsync().Result; 
      return true; 
     } 
    } 
} 

然后您需要将此格式化程序添加到您的应用程序中。如果做自我主机,你可以简单地添加它包括:

config.Formatters.Insert(0, new MultiFormDataMediaTypeFormatter()); 

在实例化HttpSelfHostServer类之前。

- 编辑 -

为了解析二进制流你需要另一个格式化。这是我用来解析我的一个工作项目中的图像的一个。

class JpegFormatter : MediaTypeFormatter 
{ 
    protected override bool CanReadType(Type type) 
    { 
     return (type == typeof(Binary)); 
    } 

    protected override bool CanWriteType(Type type) 
    { 
     return false; 
    } 

    public JpegFormatter() 
    { 
     SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg")); 
     SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg")); 
     SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png")); 
    } 

    protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) 
    { 
     return Task.Factory.StartNew(() => 
      { 
       byte[] fileBytes = new byte[stream.Length]; 
       stream.Read(fileBytes, 0, (int)fileBytes.Length); 

       return (object)new Binary(fileBytes); 
      }); 
    } 

    protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) 
    { 
     throw new NotImplementedException(); 
    } 
} 

在你的控制器/动作,你会想要做的线沿线的东西:

+0

这似乎工作的伟大与标准格式的数据,但是当有效载荷包含二进制流时,我遇到了一些挑战。我放弃了这个想法,并按照[这里]描述的方式分析了有效载荷(http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime)。该服务在IIS中托管。 – bloudraak 2012-04-11 18:32:39

+0

我提供的解决方案适用于.Net 4.5 Beta。现在,MS正在发布.Net 4.5RC,它还有一种类似但更清晰的方式来处理二进制数据。结帐这篇博文:http://byterot.blogspot.com/2012/04/aspnet-web-api-series-part-5.html – Jed 2012-07-18 18:13:32

0

这个帖子看看https://stackoverflow.com/a/17073113/1944993基兰Challa的答案是非常好的!

的重要组成部分:

定制在内存MultiaprtFormDataStreamProvider:

public class InMemoryMultipartFormDataStreamProvider : MultipartStreamProvider 
{ 
    private NameValueCollection _formData = new NameValueCollection(); 
    private List<HttpContent> _fileContents = new List<HttpContent>(); 

    // Set of indexes of which HttpContents we designate as form data 
    private Collection<bool> _isFormData = new Collection<bool>(); 

    /// <summary> 
    /// Gets a <see cref="NameValueCollection"/> of form data passed as part of the multipart form data. 
    /// </summary> 
    public NameValueCollection FormData 
    { 
     get { return _formData; } 
    } 

    /// <summary> 
    /// Gets list of <see cref="HttpContent"/>s which contain uploaded files as in-memory representation. 
    /// </summary> 
    public List<HttpContent> Files 
    { 
     get { return _fileContents; } 
    } 

    public override Stream GetStream(HttpContent parent, HttpContentHeaders headers) 
    { 
     // For form data, Content-Disposition header is a requirement 
     ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition; 
     if (contentDisposition != null) 
     { 
      // We will post process this as form data 
      _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName)); 

      return new MemoryStream(); 
     } 

     // If no Content-Disposition header was present. 
     throw new InvalidOperationException(string.Format("Did not find required '{0}' header field in MIME multipart body part..", "Content-Disposition")); 
    } 

    /// <summary> 
    /// Read the non-file contents as form data. 
    /// </summary> 
    /// <returns></returns> 
    public override async Task ExecutePostProcessingAsync() 
    { 
     // Find instances of non-file HttpContents and read them asynchronously 
     // to get the string content and then add that as form data 
     for (int index = 0; index < Contents.Count; index++) 
     { 
      if (_isFormData[index]) 
      { 
       HttpContent formContent = Contents[index]; 
       // Extract name from Content-Disposition header. We know from earlier that the header is present. 
       ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition; 
       string formFieldName = UnquoteToken(contentDisposition.Name) ?? String.Empty; 

       // Read the contents as string data and add to form data 
       string formFieldValue = await formContent.ReadAsStringAsync(); 
       FormData.Add(formFieldName, formFieldValue); 
      } 
      else 
      { 
       _fileContents.Add(Contents[index]); 
      } 
     } 
    } 

    /// <summary> 
    /// Remove bounding quotes on a token if present 
    /// </summary> 
    /// <param name="token">Token to unquote.</param> 
    /// <returns>Unquoted token.</returns> 
    private static string UnquoteToken(string token) 
    { 
     if (String.IsNullOrWhiteSpace(token)) 
     { 
      return token; 
     } 

     if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) 
     { 
      return token.Substring(1, token.Length - 2); 
     } 

     return token; 
    }} 

然后,您可以在 “MemoryMultiPartDataStreamProvider” 在你的WebAPI这样的:

var provider = await Request.Content.ReadAsMultipartAsync<InMemoryMultipartFormDataStreamProvider>(new InMemoryMultipartFormDataStreamProvider()); 

    //access form data 
    NameValueCollection formData = provider.FormData; 

    //access files 
    IList<HttpContent> files = provider.Files;