Click here to Skip to main content
15,867,686 members
Articles / Web Development / HTML5

Generic Repository Pattern in ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
4.78/5 (17 votes)
28 Apr 2016CPOL5 min read 48.7K   1.4K   42   8
Generic repository application of employee with documents

Introduction

This article will guide you through creating a small application using generic repository pattern in MVC framework. This article is basically targeted for beginner to intermediate level programmer so that they could at least understand how to develop ASP.NET MVC app. After reading this article you will be in position to understand the followings:

  • Basic concept of performing select, insert, update, and delete operation with the use of MVC repository
  • How to open bootstrap model popup window and pass value to the model popup using jQuery
  • Upload the images in desired storage location from the model window and display images in the model window with the help of jQuery ajax call and generic handler

For the practical application, I am creating a simple application of Employee repository which contains basic employee information along with their documents. All documents will be stored in a folder, storage location of which is specified in the appsetting of web.config file. Following screenshot shows how the employee image upload and display window looks like:

Image 1

Moreover, you could find similar articles written in similar topics around CodeProject and other tutorial sites. I would request you to refer following tutorials which are written by some expert programmers for your further reference:

  1. Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application
  2. Generic Repository and UnitofWork patterns in MVC - By Ashish Shukla
  3. CRUD Operations Using the Repository Pattern in MVC - By Sandeep Singh Shekhawat

Now, I would like to shortly discuss about what this article makes difference from the list of articles those I mentioned above. Article links 1 and 3 that I mentioned above contains detail explanation and purely dedicated to explain Repository Pattern and Unit of Work Pattern. Article 2 is short and straight but incomplete. Although the article that I am writing does not include more theoretical explanations but it introduces the subject matter shortly and aims to provide details implementation so that anyone with little knowledge on MVC could understand the concept and start working immediately. It also summarizes all independent features discussed in above all articles in this single one. Moreover, it provides additional technique about opening model popup, and uploading and displaying images which is not available on any articles that I enlisted above.

Background

Repositories are independent layer between data and its business logic of an application which acts as a bridge between these two components. It communicates between data access layer and the business logic layer of an application and hence keeps business logic and data access layers loosely coupled or isolated. A repository maintains and keeps track of data in its entities and data source. It requests data from database, maintains the relationship of retrieved data with its entities, and also updates the data source for any change of data in entities.

The application of repository helps to minimize repetition of code in an application. Once a repository is created, it can be used as much times as you need within your application. Since it keeps business logic and data access layers separated, unit testing will be more easier and independent.

Generic interface for custom repository

ICustomRepository is an interface which defines requirements that any classes which uses this interface must implement. Since we are performing CRUD operation, my ICustomRepository interface contains following declarations:

C#
public interface ICustomRepository<T> where T : class
{
    IEnumerable<T> GetAllData();
    T SelectDataById(object id);
    void InsertRecord(T objRecord);
    void Update(T objRecord);
    void DeleteRecord(object id);
    void SaveRecord();
}

MyCustomRepository Class

MyCustomRepository is repository class which is derived from the generic repository interface ICustomRepository. The definition of each interface declaration is implemented in this class. Following code demonstrate how the MyCustomRepository is implemented?

C#
public class MyCustomRepository<T> : ICustomRepository<T> where T : class
{
    private EmployeeDbContext db = null;
    private IDbSet<T> dbEntity = null;

    public MyCustomRepository()
    {
        this.db = new EmployeeDbContext();
        dbEntity = db.Set<T>();
    }

    public MyCustomRepository(EmployeeDbContext _db)
    {
        this.db = _db;
        dbEntity = db.Set<T>();
    }

    public IEnumerable<T> GetAllData()
    {
        return  dbEntity.ToList();
    }

    public T SelectDataById(object id)
    {
        return dbEntity.Find(id);
    }

    public void InsertRecord(T objRecord)
    {
        dbEntity.Add(objRecord);
    }

    public void Update(T objRecord)
    {
        dbEntity.Attach(objRecord);
        db.Entry(objRecord).State = System.Data.Entity.EntityState.Modified;
    }

    public void DeleteRecord(object id)
    {
        T currentRecord = dbEntity.Find(id);
        dbEntity.Remove(currentRecord);
    }

    public void SaveRecord()
    {
        db.SaveChanges();
    }
}

Employee Model

The model employee contains fields to explain attributes of an employee such as EmployeeId, Employee Name, Date of Join, Current Salary amount, Contact number, and Email. These atrributes are mentioned in properties and specification and validation of each entities are mentioned with data annotation.

C#
public class Employee
{
    [Key]
    public int EmployeeId { get; set; }
    [Required,MaxLength(70)]
    [Display(Name ="Employee Name")]
    public string EmployeeName { get; set; }
    [Required]
    [DataType(DataType.Date)]
    [Display(Name = "Join Date")]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime? JoinDate { get; set; }
    [Required]
    [DisplayFormat(DataFormatString ="{0:N0}")]
    public decimal Salary { get; set; }
    [Required,MaxLength(30)]
    public string MobileNo { get; set; }
    [MaxLength(60)]
    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string Email { get; set; }
}

Employee List

Following is the screenshot of page containing Employee List. It contains the necessary buttons and input to perform CRUD operation over employees. It also searches employees whose name matches with the name entered in search textbox. A bootstrap modal box will be displayed to show and upload documents of selected employee. A third party library PadgedList is used for paging.

Image 2

Following code block reveals designer code of the above shown employee list view. When user clicks on the button "Documents" a bootstrap model window will be displayed. The model window will have a readonly textbox to show "EmployeeId" whose documents are supposed to be uploaded, a file browse button, upload button, and a close button. If the employee already have documents uploaded, it will be loaded on the model window when the model opens.

HTML
@model IEnumerable<RepositoryApp.Models.Employee>
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="~/Scripts/jquery-1.10.2.min.js"></script>

@{
    ViewBag.Title = "Employee List";
    var pagedlist = (PagedList.IPagedList)Model;
}

<div class="repository-app panel panel-primary">
    <div class="panel-heading"><strong> Employee List</strong></div>
    <div class="panel-body">
        <p>
            @Html.ActionLink("Add New Employee", "Create", "", htmlAttributes: new { @class = "btn btn-success" })
        </p>

        @using (Html.BeginForm("Index", "Employee", FormMethod.Post))
            {
            <p>
                Employee Name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
                <input type="submit" value="Search" />
            </p>
        }

        <table class="table table-striped">
            <tr>
                <th>@Html.DisplayNameFor(model => model.EmployeeName)</th>
                <th>@Html.DisplayNameFor(model => model.MobileNo)</th>
                <th>@Html.DisplayNameFor(model => model.JoinDate)</th>
                <th>@Html.DisplayNameFor(model => model.Salary)</th>
                <th>@Html.DisplayNameFor(model => model.Email)</th>
                <th></th>

            </tr>

          
            @foreach (var item in Model)
            {
                <tr>
                    <td>@Html.DisplayFor(mitem => item.EmployeeName)</td>
                    <td>@Html.DisplayFor(mitem => item.MobileNo)</td>
                    <td>@Html.DisplayFor(mitem => item.JoinDate)</td>
                    <td>@Html.DisplayFor(mitem => item.Salary)</td>
                    <td>@Html.DisplayFor(mitem => item.Email)</td>
                    <td>
                        @Html.ActionLink("Edit", "Edit", new { id = item.EmployeeId }, new { @class = "btn btn-primary", @style = "color:white" })
                        @Html.ActionLink("Details", "Details", new { id = item.EmployeeId }, new { @class = "btn btn-success", @style = "color:white" })
                        <a data-toggle="modal" data-id="@item.EmployeeId" title="Documents" class="open-AddEmpDocs btn btn-info" href="#addEmpDocs">
                            Documents
                        </a>

                        @Html.ActionLink("Delete", "Delete", new { id = item.EmployeeId }, new { @class = "btn btn-danger", @style = "color:white" })
                </tr>
            }

        </table>
    </div>

    <div class="panel-footer">
        Page @(pagedlist.PageCount < pagedlist.PageNumber ? 0 : pagedlist.PageNumber) of @pagedlist.PageCount

        @Html.PagedListPager(pagedlist, page => Url.Action("Index",
                new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))

    </div>

</div>

@using (Html.BeginForm("UploadDocuments", "Employee", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()
    <!-- Modal -->
    <div class="modal fade" id="addEmpDocs" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                    <h4 class="modal-title">Documents</h4>
                </div>
                <div class="modal-body">
                    <p>
                        Employee Id:<input type="text" id="empidspan" name="empidtoupload" class="form-control" readonly="readonly" />
                    </p>
                    <p>
                        <label class="control-label">Select Documents</label>
                        <input name="empdocs" type="file" class="form-control">
                    </p>
                    <div id="divdocumentcontain">
                    </div>
                </div>
                <div class="modal-footer">

                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <input type="submit" value="Upload" class="btn btn-primary"  />
                </div>
            </div><!-- /.modal-content -->
        </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->
}

<script>
    $(document).on("click", ".open-AddEmpDocs", function () {
        var myBookId = $(this).data('id');
        $(".modal-body #empidspan").val(myBookId);
        $('#addEmpDocs').modal('show');
       
        $.ajax({
            type: 'GET',
            dataType: 'html',
            url: '/Employee/EmployeesDocs',
            data: { id: myBookId },
            success: function (response) {
                $(".modal-body #divdocumentcontain").html(response);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                //alert(textStatus);
            }
        });
    });
</script>

Following code block contains complete code of the controller employee. The repository class MyCustomRepository is instantiated in the constructor of Employee controller. The Index action method renders the employee List view according to the parameters of this method. The default number of rows for each page is 4 but it can be changed by changing the value of variable pageSize. Records in employee list can be filtered according to searchString value which is passed from search textbox in employee list view.

C#
public class EmployeeController : Controller
{
    private ICustomRepository<Employee> empRepository = null;
    string docRootPath = System.Configuration.ConfigurationManager.AppSettings["DocumentRootPath"].ToString();
    public EmployeeController()
    {
        this.empRepository = new MyCustomRepository<Employee>();
    }

    // GET: Employee
    public ActionResult Index(string thisFilter, string searchString, int? page)
    {

        if (searchString != null)
        {
            page = 1;
        }
        else
        {
            searchString = thisFilter;
        }

        ViewBag.CurrentFilter = searchString;
        var employees = from emp in empRepository.GetAllData()
                        select emp;
        if (!String.IsNullOrEmpty(searchString))
        {
            employees = employees.Where(emp => emp.EmployeeName.ToUpper().Contains(searchString.ToUpper()));
        }

        int pageSize = 4;
        int pageNumber = (page ?? 1);
        return View(employees.ToPagedList(pageNumber, pageSize));

       // return View(employees);
    }

    public ActionResult Create()
    {
        return View(new Models.Employee());
    }

    [HttpPost]
    public ActionResult Create(Employee emp)
    {
        try
        {
            if (ModelState.IsValid)
            {
                empRepository.InsertRecord(emp);
                empRepository.SaveRecord();
                return RedirectToAction("Index");
            }
        }
        catch (DataException)
        {
            ModelState.AddModelError("", "Unable to save record.");
        }
        return View(emp);
    }

    public ActionResult Edit(int id)
    {
        return View(empRepository.SelectDataById(id));
    }

    [HttpPost]
    public ActionResult Edit(Employee emp)
    {
        try
        {
            if (ModelState.IsValid)
            {
                empRepository.Update(emp);
                empRepository.SaveRecord();
                return RedirectToAction("Index");
            }
        }
        catch (DataException)
        {

            ModelState.AddModelError("", "Unable to edit employee record.");
        }

        return View(emp);
    }

    public ActionResult Details(int id)
    {
        return View(empRepository.SelectDataById(id));
    }

    public ActionResult Delete(int id)
    {
        return View(empRepository.SelectDataById(id));
    }

    [HttpPost,ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteEmployee(int id)
    {
        try
        {
            if (ModelState.IsValid)
            {
                empRepository.DeleteRecord(id);
                empRepository.SaveRecord();
            }
        }
        catch (DataException)
        {
            ModelState.AddModelError("", "Unable to delete the record.");
        }
        return RedirectToAction("Index");

    }

    public ActionResult UploadDocuments()
    {
        return View("Index");
    }


    [HttpPost]
    public ActionResult UploadDocuments(string empidtoupload, HttpPostedFileBase empdocs)
    {
        try
        {
            if (empdocs != null && empdocs.ContentLength > 0)
            {
                var rootPath = docRootPath + empidtoupload + "/";

                if (!System.IO.Directory.Exists(rootPath))
                {
                    Directory.CreateDirectory(rootPath);
                }

                var fileName = Path.GetFileName(empdocs.FileName);
                var path = Path.Combine(rootPath, fileName);
                empdocs.SaveAs(path);
            }
        }
        catch (IOException ex)
        {
            throw new Exception(ex.Message);
        }

        return RedirectToAction("Index");

    }

    public string EmployeesDocs(string id)
    {

        var rootPath = docRootPath + id + "/";
        List<string> lstFiles = new List<string>();

        if (System.IO.Directory.Exists(rootPath))
        {
            DirectoryInfo di = new DirectoryInfo(rootPath);
            foreach (FileInfo fi in di.GetFiles())
            {

                lstFiles.Add(id+"/"+ fi.Name);
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.Append("<div>");
        foreach (string s in lstFiles)
        {
            var path = Path.Combine(rootPath, s.ToString());
            sb.AppendFormat("  <img src='{0}' width='500px' height='300px' alt='{1}'></img><br/>", "../Handallers/PhotoHandler.ashx?f=" + s.ToString(), id);
        }
        sb.Append("</div>");

        return sb.ToString();
    }

}

Image 3

If you click on "Document" button in employee list page, a model window similar to shown following opens. It contains EmployeeId and list of already uploaded images if any. More than one images can be added for the selected employee one by one. The default path to store uploaded documents is mentioned in web config file in the section appsetting. You can change the default document upload path to your convenient location.

Image 4

PhotoHandaler to show image from local source

The generic handler handles the display of images stored as a local resource. It reads image from file and returns to the calling member.

C#
public class PhotoHandler : IHttpHandler
{
    string docRootPath = System.Configuration.ConfigurationManager.AppSettings["DocumentRootPath"].ToString();
    public void ProcessRequest(HttpContext context)
    {
        string f = context.Request.QueryString.Get("f");
        f = docRootPath + f;
        Image image = Image.FromFile(f);
        context.Response.Clear();
        context.Response.ClearHeaders();
        image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
        context.Response.ContentType = "image/jpeg";
        HttpContext.Current.ApplicationInstance.CompleteRequest();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

Through this article, I tried to demonstrate ASP.NET MVC repository pattern, apply bootstrap styles, and used jQuery ajax to load imaged from computer location. Please feel free to comment and suggest.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) NLIC
Nepal Nepal
I am in profession of an enterprise software development. I have good knowledge in object oriented programming, windows programming, enterprise web application development , mobile app development, and database designing and programming.

Comments and Discussions

 
QuestionGood but not explanatory enough for a beginner Pin
Member 1263511721-Nov-17 2:20
Member 1263511721-Nov-17 2:20 
GeneralHow to work with Store Procedure in this project structure Pin
Shaik Layakuddin17-Oct-17 21:05
Shaik Layakuddin17-Oct-17 21:05 
QuestionWhat about adding a Unit of Work class Pin
Stéphane Savioz27-Jul-17 20:50
Stéphane Savioz27-Jul-17 20:50 
QuestionThank you so much Mr. Sharad Chandra Pyakurel Pin
Member 1048503725-May-17 1:49
Member 1048503725-May-17 1:49 
QuestionWhere is generic repository? Pin
Rahul Rajat Singh10-May-16 22:15
professionalRahul Rajat Singh10-May-16 22:15 
SuggestionData filtering Pin
Oshtri Deka28-Apr-16 22:51
professionalOshtri Deka28-Apr-16 22:51 
Data filtering should be done on repository level.
It is better practice than getting all records only to be filtered inside controller's action.
Consider expanding your interface with another method for filtered data

C#
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null)
{
    IQueryable<T> query = yourDBSet;

    if (filter != null)
    {
       query = query.Where(filter);
    }

    return query.ToList();
}


This way you can also add sorting functionality.
Mislim, dakle jeo sam.

QuestionAwesomely done! Pin
BigJim6128-Apr-16 5:48
BigJim6128-Apr-16 5:48 
AnswerRe: Awesomely done! Pin
Sharad Chandra Pyakurel28-Apr-16 7:26
Sharad Chandra Pyakurel28-Apr-16 7:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.