I am digging into WebApi (v1) and one of the issues I was facing was images – specifically how to handle images. I spent a fair amount of time researching the possibilities over the last couple of days and this is what I think I think.
1) You can include your image in your JSON payload like this:
- public class Person
- {
- public Int32 PersonId { get; set; }
- public String FirstName { get; set; }
- public byte[] Image { get; set; }
- }
or you can include the imageUri in your JSON payload like this:
- public class Person
- {
- public Int32 PersonId { get; set; }
- public String FirstName { get; set; }
- public String ImageUri { get; set; }
- }
The advantage of the former is that you can make 1 POST or GET call and get all of the data. The disadvantages are that the image gets serialized into a large size and your service can get bogged down processing images when the rest of the payload is very small.
The advantage of the later is that you can separate the image into its own location – in fact not even use a Controller, and the processing is snappy-pappy. The downside is that you need to make 2 GETs and POSTs for any given person (in this example). For the POST, there is the additional factor that you have to wait for the 1st post to complete so you can get some kind of association key returned so that both POSTs can be associated together I was talking to fellow TRINUG member Ian Hoppes about problem and he came up with a great idea – have the client generate a GUID and then push both POSTs at the same time.
In any event, if you separate the payload into two POSTS, you need a way of handling the image alone. In WebApi V1, there seems to be 3 different ways you can do this:
- A Controller
- A Handler
- A Formatter
I wasn’t sure what was the <right> answer so I posted this question to stack overflow – and no one answered it (at the time of this writing). Left to my own devices, I decided to use an image controller so that the API has consistent implementation.
I newed up an Image Controller and added 3 methods: GET, POST, and DELETE. The GET was pretty straight foreward (I used a Int32 on the parameter because I haven’t implemented the GUID yet):
- public HttpResponseMessage Get(int id)
- {
- var result = new HttpResponseMessage(HttpStatusCode.OK);
- String filePath = HostingEnvironment.MapPath("~/Images/HT.jpg");
- FileStream fileStream = new FileStream(filePath, FileMode.Open);
- Image image = Image.FromStream(fileStream);
- MemoryStream memoryStream = new MemoryStream();
- image.Save(memoryStream, ImageFormat.Jpeg);
- result.Content = new ByteArrayContent(memoryStream.ToArray());
- result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
-
- return result;
- }
The DELETE was event easier:
- public void Delete(int id)
- {
- String filePath = HostingEnvironment.MapPath("~/Images/HT.jpg");
- File.Delete(filePath);
- }
The POST, not so easy. My first attempt was to read the steam and push it into an Image:
- public void Post()
- {
- var result = new HttpResponseMessage(HttpStatusCode.OK);
- if (Request.Content.IsMimeMultipartContent())
- {
- StreamContent content = (StreamContent)Request.Content;
- Task<Stream> task = content.ReadAsStreamAsync();
- Stream readOnlyStream = task.Result;
- Byte[] buffer = new Byte[readOnlyStream.Length];
- readOnlyStream.Read(buffer, 0, buffer.Length);
- MemoryStream memoryStream = new MemoryStream(buffer);
- Image image = Image.FromStream(memoryStream);
- }
- else
- {
- throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
- }
- }
No dice, because the stream includes things other than the image binrary. My second attempt was to use the MutlipartFormDataStreamProvider (which is the most popular way apparently) like so:
- public Task<HttpResponseMessage> Post(int id)
- {
- if (!Request.Content.IsMimeMultipartContent())
- {
- throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
- }
-
- string root = HttpContext.Current.Server.MapPath("~/Images");
- var provider = new MultipartFormDataStreamProvider(root);
-
- var task = Request.Content.ReadAsMultipartAsync(provider).
- ContinueWith<HttpResponseMessage>(t =>
- {
- if (t.IsFaulted || t.IsCanceled)
- {
- Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
- }
-
- foreach (MultipartFileData file in provider.FileData)
- {
- //Trace.WriteLine(file.Headers.ContentDisposition.FileName);
- //Trace.WriteLine("Server file path: " + file.LocalFileName);
- }
- return Request.CreateResponse(HttpStatusCode.OK);
- });
-
- return task;
- }
I don’t like this because I am writing to disk first – and I don’t need to use the file system – in fact, it might be locked down in some scenarios. So finally, I cobbled together some posts and found a way to read the parsed stream and create an image without using disk:
- public HttpResponseMessage Post()
- {
- var result = new HttpResponseMessage(HttpStatusCode.OK);
- if (Request.Content.IsMimeMultipartContent())
- {
- Request.Content.ReadAsMultipartAsync<MultipartMemoryStreamProvider>(new MultipartMemoryStreamProvider()).ContinueWith((task) =>
- {
- MultipartMemoryStreamProvider provider = task.Result;
- foreach (HttpContent content in provider.Contents)
- {
- Stream stream = content.ReadAsStreamAsync().Result;
- Image image = Image.FromStream(stream);
- var testName = content.Headers.ContentDisposition.Name;
- String filePath = HostingEnvironment.MapPath("~/Images/");
- String[] headerValues = (String[])Request.Headers.GetValues("UniqueId");
- String fileName = headerValues[0] + ".jpg";
- String fullPath = Path.Combine(filePath, fileName);
- image.Save(fullPath);
- }
- });
- return result;
- }
- else
- {
- throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
- }
-
-
- }
Note that I am using a disk right now on the POST and GET but I am going to swap it out with Azure Blob Storage. That way I can just create the image and push it over to Azure without my local disk being used at all.
Sure enough, this works like a champ. The GET

and the POST (using Fiddler – you have to use the most recent Fiddler so you get the [Upload File] link):


Another note is that I am pushing the UniqueID of the image in the request header – not in the content’s header.
This works:
- (String[])Request.Headers.GetValues("UniqueId");
This does not:
- var testName = content.Headers.GetValues("UniqueId");