Creating A Database-Driven MVC Image Gallery With Thumbnails

What we will cover in this article - 
 
In this article, I am going to cover the following steps.
  • Store images in database
  • Retrieve images from the database
  • Create image thumbnail from the database images
  • Upload images through AJAX
  • MVC image gallery
  • FancyBox gallery
Create Database  
 
Let's begin with creating a database with your preferred name, and let's create two tables into it.
  1. CREATE TABLE [dbo].[WebFiles](  
  2.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  3.     [Data] [varbinary](maxNULL,  
  4.     [IsActive] [bitNOT NULL,  
  5.     [UpdateDate] [datetime] NOT NULL,  
  6.     [FileName] [nvarchar](maxNULL,  
  7.     [FileExt] [nvarchar](maxNULL,  
  8.     [FileLength] [intNOT NULL,  
  9.     [ContentType] [nvarchar](maxNULL   
  10.  CONSTRAINT [PK_WebFiles] PRIMARY KEY CLUSTERED   
  11. (  
  12.     [Id] ASC  
  13. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]  
  14. ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  
  15.   
  16.   
  17. CREATE TABLE [dbo].[Gallery](  
  18.     [Id] [int] IDENTITY(1,1) NOT NULL,  
  19.     [Title] [nvarchar](maxNULL,  
  20.     [WebImageId] [intNOT NULL,  
  21.     [IsActive] [bitNOT NULL,  
  22.     [OrderNo] [intNULL,  
  23.  CONSTRAINT [PK_Gallery] PRIMARY KEY CLUSTERED   
  24. (  
  25.     [Id] ASC  
  26. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]  
  27. ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]   
The "WebFiles" table will store all the files in separate table while the "Gallery " table will store the details of images. You can add additional columns in gallery if you need.
 
Create MVC Application
 
Now, create an MVC application with no authentication option.
 
 
 
Add New EDMX file
 
I am using Database-First approach for using Entity Framework. So, create an edmx file and add both the tables created in the database.
Now, it should look like the following.
 
 
Update Layout.cshtml page
 
Now, update your default layout.cshtml page. We need to add link to 'gallery admin page' and 'Gallery' page.
  1. <div class="navbar-collapse collapse">  
  2.                 <ul class="nav navbar-nav">  
  3.                     <li>@Html.ActionLink("Home", "Index", "Home")</li>  
  4.                     <li>@Html.ActionLink("Upload", "Index", "GalleryAdmin")</li>  
  5.                     <li>@Html.ActionLink("Gallery", "Index", "Gallery")</li>  
  6.                 </ul>  
  7.             </div>   
Image Upload (Gallery Admin)
 
For uploading images through AJAX and saving them in database, we need to create a new Controller. So, create a new Controller with the name GalleryAdminController, inside the Controllers folder
 
If not present, add Index action inside Controller as following.
  1. public ActionResult Index()  
  2. {  
  3.       return View();  
  4. }  
Create View for Index (index.cshtml)
  1. @{  
  2.     ViewBag.Title = "Gallery Admin";  
  3. }  
  4.   
  5. <h2>Gallery Admin</h2>  
  6. <div id="ImagePanel" data-url="@Url.Action("Create")">  
  7.     @Html.Action("Create")  
  8. </div>  
  9. <div class="row">  
  10.     <div class="col-md-offset-2 col-md-10">  
  11.         <div class="alert alert-danger" style="display:none" role="alert" id="errormessage"></div>  
  12.         <div class="alert alert-success" style="display:none" role="alert" id="successmessage"></div>  
  13.     </div>  
  14. </div>  
  15. <hr />  
  16. <div id="ListPanel" data-url="@Url.Action("_List")">  
  17.     @Html.Action("_List")  
  18. </div>  
Here, we need two partial views (action)- create and list. So, we can update them after successfully adding a new image. Now, add HttpGet Action for displaying 'create' partial page. Remember to use PartialView as return. 
  1. public ActionResult Create()  
  2. {  
  3.   ImageEditorViewModel vm = new ImageEditorViewModel();   
  4.   return PartialView(vm);  
  5. }  
 As we are using ViewModel, create a new class ImageEditorViewModel.
  1. public class ImageEditorViewModel  
  2. {  
  3.  public int Id { getset; }  
  4.  [Required]  
  5.  public string Caption { getset; }  
  6.   
  7.  [Required]           
  8.  public HttpPostedFileBase FileImage { getset; }  
  9.   
  10.  internal static Gallery getEnityModel(ImageEditorViewModel model)  
  11.  {  
  12.    return new Gallery  
  13.    {  
  14.      IsActive = true,  
  15.      Title = model.Caption,   
  16.      OrderNo = 0,  
  17.    };  
  18.  }   
  19. }  
Now, add a new View for 'create' as Create.cshtml. Remember to add jQuery validation library to enable client-side validation.
  1. @model ImageGallery.ViewModel.ImageEditorViewModel  
  2.   
  3. @using (Html.BeginForm("Create""GalleryAdmin", FormMethod.Post, new { enctype = "multipart/form-data", Id="frmAddImage" }))  
  4. {  
  5.     <div class="form-horizontal">  
  6.         <h4>Add Image Here</h4>  
  7.         <hr />  
  8.         @Html.ValidationSummary(true""new { @class = "text-danger" })  
  9.         <div class="form-group">  
  10.             @Html.LabelFor(model => model.Caption, htmlAttributes: new { @class = "control-label col-md-2" })  
  11.             <div class="col-md-10">  
  12.                 @Html.EditorFor(model => model.Caption, new { htmlAttributes = new { @class = "form-control" } })  
  13.                 @Html.ValidationMessageFor(model => model.Caption, ""new { @class = "text-danger" })  
  14.             </div>  
  15.         </div>  
  16.   
  17.   
  18.         <div class="form-group">  
  19.             <div class="col-md-2">  
  20.                 <label class="pull-right">  Image:</label>  
  21.             </div>  
  22.             <div class="col-md-9">  
  23.                 @Html.TextBoxFor(m => m.FileImage, new { type = "file" })  
  24.                 @Html.ValidationMessageFor(model => model.FileImage, ""new { @class = "text-danger" })  
  25.             </div>  
  26.         </div>   
  27.   
  28.         <div class="form-group">  
  29.             <div class="col-md-offset-2 col-md-10">  
  30.                 <input type="submit" id="btnAddImage" value="Create" class="btn btn-primary" />  
  31.             </div>  
  32.             
  33.         </div>   
  34.     </div>  
  35. }  
  36. @Scripts.Render("~/bundles/jqueryval")  
This will generate the UI for our View. Now, add some JavaScript code in create.cshtml file to post this form through AJAX. 
  1. <script type="text/javascript">  
  2.   
  3.     $(function () {  
  4.         $("#btnAddImage").on("click"function (e) {  
  5.             var $form = $("#frmAddImage");  
  6.             $form.validate();  
  7.             if (!$form.valid()) {  
  8.                 return false;  
  9.             }  
  10.   
  11.             e.preventDefault();  
  12.             var formData = new FormData();  
  13.             var data = $form.find(':input[name]:not(:disabled)').filter(':not(:checkbox), :checked').map(function () {  
  14.                 var input = $(this);  
  15.   
  16.                 if (input.attr('type') == "file") {  
  17.   
  18.                     formData.append(input.attr('name'), input[0].files[0]);  
  19.                     return {  
  20.                         name: input.attr('name'),  
  21.                         value: input[0].files[0]  
  22.                     };  
  23.                 } else {  
  24.                     formData.append(input.attr('name'), input.val());  
  25.                     return {  
  26.                         name: input.attr('name'),  
  27.                         value: input.val()  
  28.                     };  
  29.                 }  
  30.             }).get();  
  31.   
  32.             $.ajax({  
  33.                 url: '/GalleryAdmin/Create',  
  34.                 data: formData,  
  35.                 type: "POST",  
  36.                 contentType: false,  
  37.                 processData: false,  
  38.                 success: function (data, textStatus, jqXHR) {   
  39.                     if (data.success == true) {  
  40.                         $("#successmessage").html(data.Caption + " has been added successfully").show().delay(5000).fadeOut();  
  41.                         $("#ListPanel").load($("#ListPanel").data("url"));  
  42.                         $("#ImagePanel").load($("#ImagePanel").data("url"));  
  43.                         return true;  
  44.                     }  
  45.                     if (data.ExceptionMessage) {  
  46.                         $("#errormessage").html(data.ExceptionMessage).show().delay(5000).fadeOut();  
  47.                         return false;  
  48.                     }  
  49.                     if (data.ValidationMessage) {  
  50.                         $("#errormessage").html(data.ExceptionMessage).show().delay(5000).fadeOut();  
  51.                         return false;  
  52.                     }  
  53.                     return false;  
  54.                 },  
  55.                 error: function (jqXHR, textStatus, errorThrown) {  
  56.                     $("#errormessage").html(textStatus).show().delay(5000).fadeOut();  
  57.                 },  
  58.                 complete: function (jqXHR, textStatus) {  
  59.                 }  
  60.             });  
  61.   
  62.         });  
  63.     });  
  64. </script>  
Here, first I am validating the form, then adding all controls and file inputs in formdata object. Then, I am submitting the data to the server through AJAX post request. On success, I am refreshing the "create" partial view and "list" partial view. If there is any error, I am displaying message to UI.
 
Now, let's add an action method in GalleryAdminController to save this posted data in the database.
  1. [HttpPost]  
  2. public ActionResult Create(ImageEditorViewModel model)  
  3. {  
  4.   try  
  5.   {  
  6.      if (ModelState.IsValid)  
  7.      {  
  8.         ImageGalleryEntities db = new ImageGalleryEntities();   
  9.         var fileModel = WebFileViewModel.getEntityModel(model.FileImage);  
  10.         db.WebFiles.Add(fileModel);  
  11.         db.SaveChanges();  
  12.  
  13.         var entity = ImageEditorViewModel.getEnityModel(model);                    
  14.         entity.WebImageId = fileModel.Id;  
  15.         entity.OrderNo = db.Galleries.Count() > 0 ? db.Galleries.Max(x=> x.OrderNo) + 1 : 1;  
  16.         db.Galleries.Add(entity);  
  17.         db.SaveChanges();  
  18.   
  19.         return Json(new { success = true, Caption = model.Caption });  
  20.        }  
  21.   
  22.        return Json(new { success = false, ValidationMessage = "Please check validation messages" });  
  23.      }  
  24.      catch (Exception ex)  
  25.      {  
  26.         return Json(new { success = false, ExceptionMessage = "Some error here" });  
  27.       }  
  28.   }  
  29. }
This will save your records in Gallery and WebFiles Table. You need to add one for ViewModel for creating object of webfile and get information from posted file as WebFileViewModel.
  1. public class WebFileViewModel  
  2. {  
  3.        public static WebFile getEntityModel(HttpPostedFileBase file)  
  4.        {  
  5.            WebFile webfile = new WebFile();  
  6.            MemoryStream target = new MemoryStream();  
  7.            file.InputStream.CopyTo(target);  
  8.            byte[] data = target.ToArray();  
  9.            webfile.Data = data;  
  10.            webfile.ContentType = file.ContentType;  
  11.            webfile.FileExt = Path.GetExtension(file.FileName);   
  12.            webfile.FileLength = file.ContentLength;  
  13.            webfile.FileName = file.FileName;  
  14.            webfile.IsActive = true;  
  15.            webfile.UpdateDate = DateTime.Now;  
  16.            return webfile;  
  17.        }  
  18. }  
Now, let's create a list of images for displaying in admin page. For that, create an action method with name _List as following.
  1. public ActionResult _List()  
  2. {  
  3.    ImageGalleryEntities db = new ImageGalleryEntities();  
  4.    var list = db.Galleries.OrderBy(x => x.OrderNo)  
  5.                         .Select(x => new ImageList  
  6.                         {  
  7.                             Id = x.Id,  
  8.                             IsActive = x.IsActive,  
  9.                             OrderNo = x.OrderNo,  
  10.                             WebImageId = x.WebImageId,  
  11.                             Title = x.Title  
  12.                         }).ToList();  
  13.   
  14.     return PartialView(list);  
  15. }  
Then, ceate a View page _List.cshtml and add the following code.
  1. @model IEnumerable<ImageGallery.ViewModel.ImageList>  
  2.   
  3. <ul style="list-style:none;" class="row">  
  4.     @foreach (var item in Model)  
  5.     {  
  6.         <li class="col-md-3">  
  7.             <br />  
  8.             <img class="img" src="@Url.Action("GetImage", "DownloadFile", new { Area ="" , Id= item.WebImageId, t =2 })" 
  9.              style="width:100%;height:180px;" />  
  10.             <br />  
  11.             <label>@item.Title</label>  
  12.             <br />  
  13.             <br />  
  14.         </li>  
  15.     }  
  16. </ul>  
Here, I am using parameter  as 2 for creating thumbnails with the multiplier of 2. (Check below for DownloadFile Controller).
 
This will be all for admin page. When you run the application, it will be displaying something like this.



I
mages Download

Add a new Controller named DownloadFileController inside the Controllers folder, and add the following code inside this class.
  1. [OutputCache(Duration = 60, Location = OutputCacheLocation.Any, VaryByParam = "Id;t")]
  2. public ActionResult GetImage(int Id = 0, int t = 0)
  3. {
  4.    if (Id == 0)
  5.    {
  6.       return HttpNotFound();
  7.    }
  8.    ImageGalleryEntities db = new ImageGalleryEntities();
  9.    var model = db.WebFiles.Find(Id);
  10.    if (model != null)
  11.    {
  12.       if (t != 0)
  13.       {
  14.          byte[] img = getThumbNail(model.Data, t);
  15.          return File(img, model.ContentType, "thumb_" + model.FileName);
  16.       }
  17.       return File(model.Data, model.ContentType, model.FileName);
  18.    }
  19.    else
  20.    {
  21.       return HttpNotFound();
  22.    }
  23. }
In this action method, I am passing two parameters - 1st for image id and second for its size. I am passing a "not found" error when file is not found in the database or user has not passed anything in Id parameter. You can also replace it with any default "noimagefound" url.
 
Then, if we will not pass parameter t, it will return a full size image to client else it will return a thumbnail of the image in multiplier of t. Let's add getThumbNail method inside Controller.
  1. private byte[] getThumbNail(byte[] data, int multi = 1)
  2. {
  3.    using (var file = new MemoryStream(data))
  4.    {
  5.       int width = 200 * multi;
  6.       using (var image = Image.FromStream(file, true, true)) /* Creates Image from specified data stream */
  7.       {
  8.          int X = image.Width;
  9.          int Y = image.Height;
  10.          int height = (int)((width * Y) / X);
  11.          using (var thumb = image.GetThumbnailImage(width, height, () => false, IntPtr.Zero))
  12.          {
  13.             var jpgInfo = ImageCodecInfo.GetImageEncoders()
  14.                            .Where(codecInfo => codecInfo.MimeType == "image/png").First();
  15.             using (var encParams = new EncoderParameters(1))
  16.             {
  17.                using (var samllfile = new MemoryStream())
  18.                {
  19.                   long quality = 100;
  20.                   encParams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
  21.                   thumb.Save(samllfile, jpgInfo, encParams);
  22.                   return samllfile.ToArray();
  23.                }
  24.             };
  25.          };
  26.       };
  27.     };
  28. } 
Create Gallery

This part will be easier than the above one. We just need to list our images and then add reference for fancy box. For this, we will create a new Controller named GalleryController and add two action methods - Index and _List.
  1. public ActionResult Index()  
  2. {  
  3.     return View();  
  4. }  
  5.   
  6. public ActionResult _List()  
  7. {  
  8.     ImageGalleryEntities db = new ImageGalleryEntities();  
  9.     var list = db.Galleries.OrderBy(x => x.OrderNo)  
  10.                         .Select(x => new ImageList  
  11.                         {  
  12.                             Id = x.Id,  
  13.                             IsActive = x.IsActive,  
  14.                             OrderNo = x.OrderNo,  
  15.                             WebImageId = x.WebImageId,  
  16.                             Title = x.Title  
  17.                         }).ToList();  
  18.   
  19.     return PartialView(list);  
  20. }  
Then, create index.cshtml.
  1. <h2>Gallery</h2>  
  2. <hr />  
  3. <link href="~/Scripts/fancy-box/jquery.fancybox.min.css" rel="stylesheet" />  
  4.   
  5. @Html.Action("_List")  
  6.   
  7. <script src="~/Scripts/fancy-box/jquery.fancybox.min.js"></script>  
You need to download fancybox 3 and add reference of it's CSS and JS files as in the above code. In sample project, it is already included.
Then, create _List.cshtml.
  1. @model IEnumerable<ImageGallery.ViewModel.ImageList>  
  2.   
  3. <ul style="list-style:none;" class="row">  
  4.     @foreach (var item in Model)  
  5.     {  
  6.         <li class="col-md-3">  
  7.             <br />  
  8.             <a data-fancybox="gallery" data-caption="@item.Title" 
  9.                href="@Url.Action("GetImage", "DownloadFile", new { Area ="" , Id= item.WebImageId })"
  10.                <img class="img" src="@Url.Action("GetImage", "DownloadFile", new { Area ="" , Id= item.WebImageId, t =2 })" 
  11.                style="width:100%;height:180px;" />  
  12.             </a>  
  13.             <br />  
  14.             <label>@item.Title</label>  
  15.             <br />  
  16.             <br />  
  17.         </li>  
  18.     }  
  19. </ul>  
Your"Image Gallery" is ready and it will look like the following.
 
 
 
Summary 
 
The process of creating a gallery looks difficult but it is very easy. This article has covered most of the issues faced by the developers when creating gallery with storing images in database. You can download the complete source code from the above link or you can get it from GitHub (MVC Image Gallery).