Returning an image in Web API method with fallback result image
How to return an image in Web API method
Imagine the following scenario. You are building a social network like website and you need to create your own API to enable other developers to integrate with your web location really easy.
One of the things that most popular social networks now provide in the API is providing profile image. In the following text I will demonstrate how this is doable with Microsoft Web API and ASP.NET.
First we need to create a controller which inherits ApiController and method which will return an image. To make this acceptable by browser, return type of the method needs to be HttpResponseMessage.
public class ImageController : ApiController { [HttpGet] public HttpResponseMessage Get(string id) { } }
To make things easier for demo, I'm just going to use relative path to fetch the image. Ideally it should be user ID or some other key based on which image will be retrieved.
From relative path I can get physical path and then just write the bitmap content to return stream. The small problem which occurs is that you need to set the proper image type when writing bitmap to stream.
In my previous article Getting System.Drawing.Imaging.ImageFormat from a string I explained how this problem can be overcome with using of reflection and simple LINQ query.
One more thing that was included in the following API controller class in a fall back in case image is not found. This is a good practice, so that consumer of the service does not get some error, but a nice image not found picture, something similar to Google+ and YouTube API behavior.
Do not pass file path as a parameter as it is a security risk and it is used here only for example. Use different approach like passing hashed filename for example and matching it with hashed filenames in target folder on your server. A lot easier but more restricting way is to look for files in a specific folder like /images/.
About possible security risks about passing the filepath as a query string please check URL in refernce lists at the end of this article.
So here goes the whole thing.
public static ImageFormat GetImageFormat(string extension) { ImageFormat result = null; PropertyInfo prop = typeof(ImageFormat).GetProperties().Where(p => p.Name.Equals(extension, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (prop != null) { result = prop.GetValue(prop) as ImageFormat; } return result; } [HttpGet] public HttpResponseMessage Get(string id) { MemoryStream ms = new MemoryStream(); HttpContext context = HttpContext.Current; //Limit access only to images folder at root level string filePath = context.Server.MapPath(string.Concat("/content/images/", id)); string extension = Path.GetExtension(id); if (File.Exists(filePath)) { if (!string.IsNullOrWhiteSpace(extension)) { extension = extension.Substring(extension.IndexOf(".") + 1); } ImageFormat format = GetImageFormat(extension); //If invalid image file is requested the following line wil throw an exception new Bitmap(filePath).Save(ms, format != null ? format as ImageFormat : ImageFormat.Bmp); } else { new Bitmap(context.Server.MapPath("/content/images/fallback.png")).Save(ms, ImageFormat.Png); } HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); result.Content = new ByteArrayContent(ms.ToArray()); result.Content.Headers.ContentType = new MediaTypeHeaderValue(string.Format("image/{0}", Path.GetExtension(id))); return result; }
This approach will work, but in case you have big images, this method might consume a lot of memory since Bitmap class when holds an existing image can consume big amount of memory. There is no big reason to consume a lot of memory.
We can just send back file content if extension is contained in FileFormat static class as a property, otherwise we send back a fall-back image.
public static ImageFormat GetImageFormat(string extension) { ImageFormat result = null; PropertyInfo prop = typeof(ImageFormat).GetProperties().Where(p => p.Name.Equals(extension, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (prop != null) { result = prop.GetValue(prop) as ImageFormat; } return result; } private MemoryStream CopyFileToMemory(string path) { MemoryStream ms = new MemoryStream(); FileStream fs = new FileStream(path, FileMode.Open); fs.Position = 0; fs.CopyTo(ms); fs.Close(); fs.Dispose(); return ms; } [HttpGet] public HttpResponseMessage Get(string id) { MemoryStream ms = null; HttpContext context = HttpContext.Current; //Limit access only to images folder at root level string filePath = context.Server.MapPath(string.Concat("/Content/images/", id)); string extension = Path.GetExtension(id); if (File.Exists(filePath)) { if (!string.IsNullOrWhiteSpace(extension)) { extension = extension.Substring(extension.IndexOf(".") + 1); } //If requested file is an image than load file to memory if (GetImageFormat(extension) != null) { ms = CopyFileToMemory(filePath); } } if (ms == null) { extension = "png"; ms = CopyFileToMemory(context.Server.MapPath("/content/images/fallback.png")); } HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); result.Content = new ByteArrayContent(ms.ToArray()); result.Content.Headers.ContentType = new MediaTypeHeaderValue(string.Format("image/{0}", extension)); return result; }
References
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article