Header Ads

ASP.NET MVC5: Upload Image/File as File

Uploading image or any type of file format as whether a file or into a database is always a topic of debate and confusing. Both the choices come with their set of advantages and disadvantages without a doubt. But, the ultimate decision depends on business requirement and resource costing and availability entirely.
Today, I shall be demonstrating uploading of image file as file on ASP.NET MVC5 platform. This article is not specific to image file only, you can use the provided solution with any type of file format as well.


Before moving to the coding part, let us observe some of the advantages and disadvantages of uploading image or any other file format data as a file.

Advantages (Pros)

  1. Storage capacity is not expensive as third party file cloud storage servers can be utilize which may or may not charge additional cost to meet your requirement.
  2. No additional code is needed to access the uploaded file.
  3. Performance to retrieve image/file via file path is much faster as compare to decode the base 64 code back to image from database.
  4. Image file can be directly edited via available Jquery plugins such as cropping and resizing the image file.
  5. Database will have less load on it which improves costing plans.
  6. Web server bandwidth will not be increased and costing plan will be further improved.

Disadvantages (Cons)

  1. Sensitive image or any other file format data is not fully secured even when you use third party cloud storage like Amazon S3 as link to the file is always public in order to get accessed. So, if unauthorized user somehow get access to direct link of the file, then, he/she can easily download it.
  2. Storing of uploaded files are not guaranteed in a sense that file link is broken/lost or file is not uploaded but link is available.
  3. Extra backups of the files are required along with backup of the database where file paths are stored.
  4. File integrity is not guaranteed because developer might forget to delete the actual file and only delete the file link from the database. This enables not only consistency issue especially in the distributed environment with many replication servers, but, also threatens end-user privacy & trust in a sense that user is under the impression that his/her file is deleted as the system shows but, in reality only link is deleted and actual file exist on the system, which means that targeted organization can illegally uses end-user files for any sort of activity without the end-user consent. So, there is no way for end-user to know if it's an actual unintentional bug in the system or intentional scam from the targeted organization to collect user file data. Remember Facebook image deletion scandal back in 2009 for reference (Facebook reference is used for only education/understanding purpose without the intention of harming the reputation of the organization).
  5. Dealing with file and path synchronization in distributed environment is difficult especially with multiple replication servers for backup.

Prerequisites:

Following are some prerequisites before you proceed any further in this tutorial:
  1. Knowledge of ASP.NET MVC5.
  2. Knowledge of HTML.
  3. Knowledge of Bootstrap.
  4. Knowledge of C# Programming.
You can download the complete source code for this tutorial or you can follow the step by step discussion below. The sample code is being developed in Microsoft Visual Studio 2015 Enterprise.

Download Now!

Let's begin now.

1) First create your SQL server database and name it "db_img". Then execute following script into your SQL server database i.e.

USE [db_img]
GO
/****** Object:  StoredProcedure [dbo].[sp_insert_file]    Script Date: 11/19/2018 8:11:10 AM ******/
DROP PROCEDURE [dbo].[sp_insert_file]
GO
/****** Object:  StoredProcedure [dbo].[sp_get_file_details]    Script Date: 11/19/2018 8:11:10 AM ******/
DROP PROCEDURE [dbo].[sp_get_file_details]
GO
/****** Object:  StoredProcedure [dbo].[sp_get_all_files]    Script Date: 11/19/2018 8:11:10 AM ******/
DROP PROCEDURE [dbo].[sp_get_all_files]
GO
/****** Object:  Table [dbo].[tbl_file]    Script Date: 11/19/2018 8:11:10 AM ******/
DROP TABLE [dbo].[tbl_file]
GO
/****** Object:  Table [dbo].[tbl_file]    Script Date: 11/19/2018 8:11:10 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[tbl_file](
 [file_id] [int] IDENTITY(1,1) NOT NULL,
 [file_name] [nvarchar](max) NOT NULL,
 [file_ext] [nvarchar](max) NOT NULL,
 [file_path] [nvarchar](max) NOT NULL,
 CONSTRAINT [PK_tbl_file] PRIMARY KEY CLUSTERED 
(
 [file_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO
/****** Object:  StoredProcedure [dbo].[sp_get_all_files]    Script Date: 11/19/2018 8:11:10 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:  <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[sp_get_all_files]
 
AS
BEGIN
/****** Script for SelectTopNRows command from SSMS  ******/
 SELECT [file_id]
    ,[file_name]
    ,[file_ext]
 FROM [db_img].[dbo].[tbl_file]
END

GO
/****** Object:  StoredProcedure [dbo].[sp_get_file_details]    Script Date: 11/19/2018 8:11:10 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:  <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[sp_get_file_details] 
 @file_id INT
AS
BEGIN
/****** Script for SelectTopNRows command from SSMS  ******/
 SELECT [file_id]
    ,[file_name]
    ,[file_ext]
    ,[file_path]
 FROM [db_img].[dbo].[tbl_file]
 WHERE [tbl_file].[file_id] = @file_id
END

GO
/****** Object:  StoredProcedure [dbo].[sp_insert_file]    Script Date: 11/19/2018 8:11:10 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:  <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[sp_insert_file]
 @file_name NVARCHAR(MAX),
 @file_ext NVARCHAR(MAX),
 @file_path NVARCHAR(MAX)
AS
BEGIN
/****** Script for SelectTopNRows command from SSMS  ******/
 INSERT INTO [dbo].[tbl_file]
           ([file_name]
           ,[file_ext]
           ,[file_path])
     VALUES
           (@file_name
           ,@file_ext
           ,@file_path)
END

GO

2) Create a new MVC web project and name it "MVCImageSaveFile".  

3) Open the "Views->Shared->_Layout.cshtml" file and replace following code in it i.e.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />

</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <center>
                <p><strong>Copyright &copy; @DateTime.Now.Year - <a href="http://wwww.asmak9.com/">Asma's Blog</a>.</strong> All rights reserved.</p>
            </center>
        </footer>
    </div>

    @*Scripts*@
    @Scripts.Render("~/bundles/jquery")

    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/bundles/bootstrap")

    @RenderSection("scripts", required: false)
</body>
</html>

In the above code, I have simply created a basic default layout page and linked the require libraries into it.  

4) Create a new "Helper_Code\Objects\ImgObj.cs" file and replace the following code in it i.e.

//-----------------------------------------------------------------------
// <copyright file="ImgObj.cs" company="None">
//     Copyright (c) Allow to distribute this code and utilize this code for personal or commercial purpose.
// </copyright>
// <author>Asma Khalid</author>
//-----------------------------------------------------------------------

namespace MVCImageSaveFile.Helper_Code.Objects
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    /// <summary>
    /// Image object class.
    /// </summary>
    public class ImgObj
    {
        #region Properties

        /// <summary>
        /// Gets or sets Image ID.
        /// </summary>
        public int FileId { get; set; }

        /// <summary>
        /// Gets or sets Image name.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Gets or sets Image extension.
        /// </summary>
        public string FileContentType { get; set; }

        #endregion
    }
}

In the above code, I have simply created an object class which will map my image file metadata from SQL database.

5) Now, create a new "Models\ImgViewModel.cs" file and replace the following code in it i.e.

//-----------------------------------------------------------------------
// <copyright file="ImgViewModel.cs" company="None">
//     Copyright (c) Allow to distribute this code and utilize this code for personal or commercial purpose.
// </copyright>
// <author>Asma Khalid</author>
//-----------------------------------------------------------------------

namespace MVCImageSaveFile.Models
{
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Web;
    using Helper_Code.Objects;

    /// <summary>
    /// Image view model class.
    /// </summary>
    public class ImgViewModel
    {
        #region Properties

        /// <summary>
        /// Gets or sets Image file.
        /// </summary>
        [Required]
        [Display(Name = "Upload File")]
        public HttpPostedFileBase FileAttach { get; set; }

        /// <summary>
        /// Gets or sets Image file list.
        /// </summary>
        public List<ImgObj> ImgLst { get; set; }

        #endregion
    }
}

In the above code, I have created my view model which I will attach with my view. Here, I have created HttpPostedFileBase type file attachment property which will capture uploaded image/file data from the end-user and image object type list property which will display the list of images that I have stored as a file on my server and store their file paths in my database.

6) Create a new "Controllers\ImgController.cs" file and replace the following code in it i.e.

//-----------------------------------------------------------------------
// <copyright file="ImgController.cs" company="None">
//     Copyright (c) Allow to distribute this code and utilize this code for personal or commercial purpose.
// </copyright>
// <author>Asma Khalid</author>
//-----------------------------------------------------------------------

namespace MVCImageSaveFile.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Helper_Code.Objects;
    using Models;

    /// <summary>
    /// Image controller class.
    /// </summary>
    public class ImgController : Controller
    {
        #region Private Properties

        /// <summary>
        /// Gets or sets database manager property.
        /// </summary>
        private db_imgEntities databaseManager = new db_imgEntities();

        #endregion

        #region Index view method.

        #region Get: /Img/Index method.

        /// <summary>
        /// Get: /Img/Index method.
        /// </summary>        
        /// <returns>Return index view</returns>
        public ActionResult Index()
        {
            // Initialization.
            ImgViewModel model = new ImgViewModel { FileAttach = null, ImgLst = new List<ImgObj>() };

            try
            {
                // Settings.
                model.ImgLst = this.databaseManager.sp_get_all_files().Select(p => new ImgObj
                {
                    FileId = p.file_id,
                    FileName = p.file_name,
                    FileContentType = p.file_ext
                }).ToList();
            }
            catch (Exception ex)
            {
                // Info
                Console.Write(ex);
            }

            // Info.
            return this.View(model);
        }

        #endregion

        #region POST: /Img/Index

        /// <summary>
        /// POST: /Img/Index
        /// </summary>
        /// <param name="model">Model parameter</param>
        /// <returns>Return - Response information</returns>
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Index(ImgViewModel model)
        {
            // Initialization.
            string filePath = string.Empty;
            string fileContentType = string.Empty;

            try
            {
                // Verification
                if (ModelState.IsValid)
                {
                    // Converting to bytes.
                    byte[] uploadedFile = new byte[model.FileAttach.InputStream.Length];
                    model.FileAttach.InputStream.Read(uploadedFile, 0, uploadedFile.Length);

                    // Initialization.
                    fileContentType = model.FileAttach.ContentType;
                    string folderPath = "~/Content/upload_files/";
                    this.WriteBytesToFile(this.Server.MapPath(folderPath), uploadedFile, model.FileAttach.FileName);
                    filePath = folderPath + model.FileAttach.FileName;

                    // Saving info.
                    this.databaseManager.sp_insert_file(model.FileAttach.FileName, fileContentType, filePath);
                }

                // Settings.
                model.ImgLst = this.databaseManager.sp_get_all_files().Select(p => new ImgObj
                {
                    FileId = p.file_id,
                    FileName = p.file_name,
                    FileContentType = p.file_ext
                }).ToList();
            }
            catch (Exception ex)
            {
                // Info
                Console.Write(ex);
            }

            // Info
            return this.View(model);
        }

        #endregion

        #endregion

        #region Download file methods

        #region GET: /Img/DownloadFile

        /// <summary>
        /// GET: /Img/DownloadFile
        /// </summary>
        /// <param name="fileId">File Id parameter</param>
        /// <returns>Return download file</returns>
        public ActionResult DownloadFile(int fileId)
        {
            // Model binding.
            ImgViewModel model = new ImgViewModel { FileAttach = null, ImgLst = new List<ImgObj>() };

            try
            {
                // Loading dile info.
                var fileInfo = this.databaseManager.sp_get_file_details(fileId).First();

                // Info.
                return this.GetFile(fileInfo.file_path);
            }
            catch (Exception ex)
            {
                // Info
                Console.Write(ex);
            }

            // Info.
            return this.View(model);
        }

        #endregion

        #endregion

        #region Helpers

        #region Get file method.

        /// <summary>
        /// Get file method.
        /// </summary>
        /// <param name="filePath">File path parameter.</param>
        /// <returns>Returns - File.</returns>
        private FileResult GetFile(string filePath)
        {
            // Initialization.
            FileResult file = null;

            try
            {
                // Initialization.
                string contentType = MimeMapping.GetMimeMapping(filePath);

                // Get file.
                file = this.File(filePath, contentType);
            }
            catch (Exception ex)
            {
                // Info.
                throw ex;
            }

            // info.
            return file;
        }

        #endregion

        #region Write to file

        /// <summary>
        /// Write content to file.
        /// </summary>
        /// <param name="rootFolderPath">Root folder path parameter</param>
        /// <param name="fileBytes">File bytes parameter</param>
        /// <param name="filename">File name parameter</param>
        private void WriteBytesToFile(string rootFolderPath, byte[] fileBytes, string filename)
        {
            try
            {
                // Verification.
                if (!Directory.Exists(rootFolderPath))
                {
                    // Initialization.
                    string fullFolderPath = rootFolderPath;

                    // Settings.
                    string folderPath = new Uri(fullFolderPath).LocalPath;

                    // Create.
                    Directory.CreateDirectory(folderPath);
                }

                // Initialization.                
                string fullFilePath = rootFolderPath + filename;

                // Create.
                FileStream fs = System.IO.File.Create(fullFilePath);

                // Close.
                fs.Flush();
                fs.Dispose();
                fs.Close();

                // Write Stream.
                BinaryWriter sw = new BinaryWriter(new FileStream(fullFilePath, FileMode.Create, FileAccess.Write));

                // Write to file.
                sw.Write(fileBytes);

                // Closing.
                sw.Flush();
                sw.Dispose();
                sw.Close();
            }
            catch (Exception ex)
            {
                // Info.
                throw ex;
            }
        }

        #endregion

        #endregion
    }
}

In the above code, I have created  databaseManager private property which will allow me to access my SQL database via entity framework. Then, I have created "GetFile(...)" helper method which will return the image file from my server base on the image file path store in my SQL database. I have also created "WriteBytesToFile(...)" helper method which will store uploaded file into my server provided file content path, which in my case is "~/Content/upload_files/". Then, I have created "DownloadFile(...)" method which will return image file stored in the SQL database base on the provided image file ID. Then, I have created GET "Index(...)" method which will retrieve list of images metadata from SQL database and send it to the view page. Finally, I have created POST "Index(...)" method which will receive input image file from the end-user, then store that image file into my server at "~/Content/upload_files/" file location as a file using "WriteBytesToFile(...)" helper method then store the file metadata and the file path into the SQL database.

7) Now, create a view "Views\Img\Index.cshtml" file and replace the following code in it i.e.

@using MVCImageSaveFile.Models

@model MVCImageSaveFile.Models.ImgViewModel

@{
    ViewBag.Title = "ASP.NET MVC5: Upload Image as File";
}


<div class="row">
    <div class="panel-heading">
        <div class="col-md-8">
            <h3>
                <i class="fa fa-file-text-o"></i>
                <span>ASP.NET MVC5: Upload Image as File</span>
            </h3>
        </div>
    </div>
</div>

<br />

<div class="row">
    <div class="col-md-6 col-md-push-2">
        <section>
            @using (Html.BeginForm("Index", "Img", FormMethod.Post, new { enctype = "multipart/form-data", @class = "form-horizontal", role = "form" }))
            {
                @Html.AntiForgeryToken()

                <div class="well bs-component">
                    <br />

                    <div class="row">
                        <div class="col-md-12">
                            <div class="col-md-8 col-md-push-2">
                                <div class="input-group">
                                    <span class="input-group-btn">
                                        <span class="btn btn-default btn-file">
                                            Browse&hellip;
                                            @Html.TextBoxFor(m => m.FileAttach, new { type = "file", placeholder = Html.DisplayNameFor(m => m.FileAttach), @class = "form-control" })
                                        </span>
                                    </span>
                                    <input type="text" class="form-control" readonly>
                                </div>
                                @Html.ValidationMessageFor(m => m.FileAttach, "", new { @class = "text-danger custom-danger" })
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-12">
                        </div>
                    </div>

                    <div class="form-group">
                        <div class="col-md-offset-5 col-md-10">
                            <input type="submit" class="btn btn-danger" value="Upload" />
                        </div>
                    </div>
                </div>
            }
        </section>
    </div>
</div>

<hr />

<div class="row">
    <div class="col-md-offset-4 col-md-8">
        <h3>List of Imagess </h3>
    </div>
</div>

<hr />

@if (Model.ImgLst != null &&
                 Model.ImgLst.Count > 0)
{
    <div class="row">
        <div class="col-md-offset-1 col-md-8">
            <section>
                <table class="table table-bordered table-striped">
                    <thead>
                        <tr>
                            <th style="text-align: center;">Sr.</th>
                            <th style="text-align: center;">Image Name</th>
                            <th style="text-align: center;"></th>
                        </tr>
                    </thead>

                    <tbody>
                        @for (int i = 0; i < Model.ImgLst.Count; i++)
                        {
                            <tr>
                                <td style="text-align: center;">@(i + 1)</td>

                                <td style="text-align: center;">
                                    <div class="input-group" style="height:40px;">
                                        <i class="fa fa-2x fa-paperclip text-navy"></i>
                                        <a class="download-file1" href="@Url.Action("DownloadFile", "Img", new { fileId = @Model.ImgLst[i].FileId })" target="_blank">
                                            @Model.ImgLst[i].FileName
                                        </a>
                                    </div>
                                </td>

                                <td style="text-align: center;">
                                    <div>
                                        <img src="@Url.Action("DownloadFile", "Img", new { fileId = @Model.ImgLst[i].FileId })" width="100" height="100" />
                                    </div>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            </section>
        </div>
    </div>
}

@section Scripts
{
    @*Scripts*@
    @Scripts.Render("~/bundles/bootstrap-file")

    @*Styles*@
    @Styles.Render("~/Content/Bootstrap-file/css")
}

In the above code, I have created a simple view for uploading image file to the server for storing file on server as file and store file path into SQL database and then display the uploaded image files list. I have created bootstrap style file upload control and a table to display the list of uploaded images on server as file.

8) Now, execute the project and you will be able to see the following in action i.e.


Before file upload your "~/Content/upload_files/" server folder will be emptied i.e.


After file upload, it will contain image file i.e.


Now type "http://{your_site_url}/Content/upload_files/no-img.png" URL in your browser and you will be able to see following i.e. this means that you can access the file as long as you know the link. It doesn't matter whether the user is log into the system or not.


Conclusion

In this article, you will learn about uploading of image file as a file on ASP.NET MVC5 platform. You will also learn to store image/file on your server into fix folder. You will also learn to retrieve image metadata stored in SQL server database along with the file path location and you will learn about advantages & disadvantages of storing image/file as file.

No comments