How does one correctly implement a MediaTypeFormatter to handle requests of type 'multipart/mixed'?
asp.net-web-api, c#-4.0
Solution
Have a look at this forum: http://forums.asp.net/t/1777847.aspx/1?MVC4+Beta+Web+API+and+multipart+form+data
Here is a snippet of code (posted by imran_ku07) that might help you implement a custom formatter to handle the 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;
}
}
}
You then need to add this formatter to your application. If doing self-host you can simply add it by including:
config.Formatters.Insert(0, new MultiFormDataMediaTypeFormatter());
before instantiating the HttpSelfHostServer class.
-- EDIT --
To parse binary streams you'll need another formatter. Here is one that I am using to parse images in one of my work projects.
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();
}
}
In your controller/action you'll want to do something along the lines of:
public HttpResponseMessage UploadImage(Binary File) {
//do something with your file
}
Problem
Consider a web service written in ASP.NET Web API to accept any number files as a 'multipart/mixed' request. The helper method mat look as follows (assuming `_client` is an instance of `System.Net.Http.HttpClient`): ``` 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; } ``` The method that accepts the request in the subclass of ApiController has a signature as follows: ``` public HttpResponseMessage Post(HttpRequestMessage request) { /* parse request using MultipartFormDataStreamProvider */ } ``` Ideally, I'd like to define it like this, where contact, source and target are extracted from the 'multipart/mixed' content based on the 'name' property of the 'Content-Disposition' header. ``` public HttpResponseMessage Post(Contact contact, Stream source, Stream target) { // process contact, source and target } ``` However, with my existing signature, posting the data to the server results in an `InvalidOperationException` with an error message of: No 'MediaTypeFormatter' is available to read an object of type 'HttpRequestMessage' with the media type 'multipart/mixed'. There are a number of examples on the internet how to send and receive files using the ASP.NET Web API and HttpClient. However, I have not found any that show how to deal with this problem. I started looking at implementing a custom `MediaTypeFormatter` and register it with the global configuration. However, while it is easy to deal with serializing XML and JSON in a custom `MediaTypeFormatter`, it is unclear how to deal with 'multipart/mixed' requests which can pretty much be anything.