Monday, October 14, 2013

MVC 4 Images

During a recent development cycle I needed to have the ability to display an image on the MVC View which was dynamically pulled from a data source (e.g. not stored in the Content folder).  After some looking around the consensus seemed to be that a Url.Action invoking a method in a controller was the best way to approach the problem.  I didn't at first have concerns about the solution - it was a trivial amount of code and the round trip from the data source and the page didn't seem to make rendering the page any less responsive.
 <div class="editor-field">  
  <input type="file" class="find_file_button" name="offerImage" />  
   
  <image id="offerImage" class="imagePreview" src="@Url.Action("GetImage", "Offer", new { id = Model.ImageId, imageType="Image"}) />  
 </div>  
What you see above is the cshtml file invoking the method of "GetImage" located within the OfferController class.  Some parameters are passed - in this case a type of image (for this solution two images were stored for a each entity) as well as some hint on how to find that right image.  The "GetImage" method is pretty much what you'd expect - as I covered this in a prior entry I won't get deep into what is going on.
public ActionResult GetImage(int id, string imageType)  
 {  
   DisplayImageModel disiplayImageMode 
      = _rewardsRespository.GetDisplayImageModel(id, imageType);  
   
   if ( displayImageModel != null)  
   {  
    return File(displayImageMode.ImageBytes, displayImageModel.ImageMimeType);  
   }  
   return HttpNotFound();  
 }  
What really bugged me about this solution was primarily:
  1. When the cshtml page was being processed by the IIS server it had to stop and make another method call to obtain the image data associated with the model being presented.  This step also invoked another call to the data store to obtain the image.
  2. The model, when it was being populated from the data store, already had the capability to load up the image(s) in a property of type byte[].  Doing this could prevent another call and another dip into the data store.
So really how could I take the already loaded up data in the model and have it display the images on the page?  Well I ran into a solution quite by accident - why not load up the image as part of the page?

In order prevent these irritations a small change to the model is necessary - adding the appropriate properties to store the image data in a byte[] could prevent the need to pull this from the repository as a separate call as well as have it readily available in the model that is passed to the view.  For added compatibility - but certainly not required - the MIME type of the image was also retrieved and placed in the model.
So far pretty easy.  The code in the repository that loaded up the model was altered to pull the image(s) from the data source and convert them to an array of bytes.
 public byte[] ThumbnailImage { get; set; }  
 public string ThumbnailImageType { get; set; }  
   
 public byte[] OffierImage { get; set; }  
 public string OfferImageType { get; set; }  
   
Once the image(s) are loaded in the model it is now necessary to encode and embed those images in the resulting HTML that is downloaded by the browser.
<div class="customeroffer_thumbnail">  
 @{  
   string imageSrc = null;  
     
   if (Model.ThumbnailImage != null)  
   {  
    string thumbBase64 =   
      Convert.ToBase64String(Model.ThumbnailImage);  
   
    imageSrc = string.Format("data:{0};base64,{1}, Model.ThumbnailImageType, thumbBase64);  
   }  
 }  
   
 <img id="thumbnailImage" class="imagePreview" src="@imageSrc" />  
 </div>  

There is really only one step - encode the property containing the image into Base64 encoded string.  Then place that encoded string, along with the MIME type into the <img> tag as illustrated above.  In fact this could be done in the model ahead of time by the repository if you prefer.  Doing that would simplify this code even further and avoid having any in-line code in your cshtml file (after proof reading this - this really looks like some old school ASP code).

The potential downside to this solution is the the resulting HTML file is considerably larger and could take longer to render in the browser.  This wasn't my experience - as the same amount of data has to be downloaded - and it's either in the HTML file or it's another request the browser makes to the web server to pull the image from the file system on the web server.