Background
For my custom Blog, I needed to be able to store & view images. This is one wheel that’s been invented several times, but I thought I’d include my implementation. I chose to store images in a DB, verses uploading to a folder. Once implemented, I saw the need to ensure the images get cached on the browser, otherwise my pages would load slowly. Scroll to the bottom for that discussion.
Database
In the database, I created a BlogMediaFiles table. The Image column is simply of type “image”:
CREATE TABLE [dbo].[BlogMediaFiles]( [BlogMediaFileId] [int] IDENTITY(1,1) NOT NULL, [Filename] [nvarchar](100) NOT NULL, [CreateDate] [datetime] NULL, [Image] [image] NULL,
View Model: MediaFileModel
For the Create and Edit pages, I create the following View Model (some fields removed):
public class MediaFileModel { public int BlogMediaFileId { get; set; } public string Filename { get; set; } [Required] [DataType(DataType.Upload)] [Display(Name = "Choose File")] public HttpPostedFileBase FileUpload { get; set; } }
The key item here is the FileUpload property, of type HttpPostedFileBase, with the DataType attributed added (passing DataType.Upload).
Controller: MediaController.cs
I then created the MediaController class. The HttpGet function is rather simple:
public ActionResult Create() { MediaFileModel model = new MediaFileModel(); return View(model); }
The HttpPost method looks like this:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(MediaFileModel model) { if (ModelState.IsValid) { if (model.FileUpload.ContentLength > 0) { byte[] imageData = null; using (var binaryReader = new BinaryReader(model.FileUpload.InputStream)) { imageData = binaryReader.ReadBytes(model.FileUpload.ContentLength); } // Create the DB entry BlogMediaFile mediaFile = new BlogMediaFile() { CreateDate = DateTime.Now, Image = imageData, Filename = model.Filename, }; db.BlogMediaFiles.Add(mediaFile); db.SaveChanges(); return RedirectToAction("Index"); } else { ModelState.AddModelError(null, "No file was uploaded."); } } return View(model); }
Razor View
In the Create view (Create.cshmtl), the Html.BeginForm needs to include enctype = "multipart/form-data":
&using (Html.BeginForm("Create", "Media", FormMethod.Post, new { enctype = "multipart/form-data" })) { ... }
In the form section, add the following for the upload field:
<div class="form-group"> @Html.LabelFor(m => m.FileUpload, new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.FileUpload, new { type = "file", @class = "form-control" }) @Html.ValidationMessageFor(m => m.FileUpload, "", new { @class = "text-danger" }) </div> </div>
Displaying Images - Controller Method
In order to display images, add the following method to your controller:
[AllowAnonymous] public ActionResult Image(int id) { using (MvcBlogEntities context = new MvcBlogEntities()) { BlogMediaFile file = context.BlogMediaFiles.FirstOrDefault(f => f.BlogMediaFileId == id); if (file != null) { return File(file.Image, "image/jpg"); } } return null; }
The input to this will be the BlogMediaFileId that gets generated when the record in the DB is created.
Recall that in the DB create script above, that field is set to IDENTITY(1,1).
Note also line 9 calls File returns a FileContentResult
.
Displaying Imgages - The HTML Code:
Now when you are ready to display an image, insert the following HTML:
<img src="/Media/Image/12" />
Where 12 is the BlogMediaFileId. For the Index page on the Media controller, I display all my uploaded images, along with their Id. I use this when adding the image to my blogs.
Add Caching
After creating my page, I noticed my Book Review section kept was slow to load because it would load the images every time. Since I knew my images would not be changing very frequently, I wanted to force caching on the browser. I did so by adding the OutputCache attribute to the Image method on the controller, as such:
[OutputCache(Duration = 3600, VaryByParam = "None")] public ActionResult Image(int id) { ... }
Finally...
The only thing remaining is the Edit page, which is pretty much patterned off of the Create page.
And that's pretty much all that was needed.