65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET MVC 中的通用存储库模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (17投票s)

2016年4月28日

CPOL

5分钟阅读

viewsIcon

52817

downloadIcon

1534

带有文档的员工的通用存储库应用程序。

引言

本文将指导您使用 MVC 框架中的通用存储库模式创建一个小型应用程序。本文主要面向初学者到中级程序员,以便他们至少能理解如何开发 ASP.NET MVC 应用程序。阅读本文后,您将能够理解以下内容:

  • 使用 MVC 存储库执行选择、插入、更新和删除操作的基本概念
  • 如何使用 jQuery 打开 Bootstrap 模型弹出窗口并将值传递给模型弹出窗口
  • 从模型窗口上传图像到所需的存储位置,并通过 jQuery AJAX 调用和通用处理程序显示模型窗口中的图像

对于实际应用,我将创建一个简单的 Employee 存储库应用程序,其中包含基本的员工信息及其文档。所有文档将存储在一个文件夹中,其存储位置在 web.config 文件的 appsetting 中指定。以下屏幕截图显示了员工图像上传和显示窗口的外观。

此外,您可以在 CodeProject 和其他教程网站上找到有关类似主题的类似文章。我建议您参考以下由一些专家程序员撰写的教程,以供您进一步参考。

  1. 在 ASP.NET MVC 应用程序中实现存储库和工作单元模式
  2. MVC 中的通用存储库和工作单元模式 - 作者:Ashish Shukla
  3. 使用 MVC 中的存储库模式执行 CRUD 操作 - 作者:Sandeep Singh Shekhawat

现在,我想简要讨论一下本文与我上面提到的文章列表有何不同。上面提到的文章链接 1 和 3 包含详细的解释,并且完全致力于解释存储库模式和工作单元模式。文章 2 简短直接,但内容不完整。虽然我正在撰写的文章没有包含更多的理论解释,但它简要介绍了主题,旨在提供详细的实现,以便任何对 MVC 有一点了解的人都能理解概念并立即开始工作。它还总结了以上所有文章中讨论的独立功能。此外,它还提供了关于打开模型弹出窗口以及上传和显示图像的附加技术,这些技术在我上面列出的任何文章中都找不到。

背景

存储库是应用程序数据与其业务逻辑之间的独立层,充当这两个组件之间的桥梁。它在应用程序的数据访问层和业务逻辑层之间进行通信,从而使业务逻辑层和数据访问层松耦合或隔离。存储库维护其实体和数据源中的数据并跟踪这些数据。它从数据库请求数据,维护检索到的数据与其实体之间的关系,并在实体中的数据发生任何更改时更新数据源。

存储库的应用有助于最小化应用程序中的代码重复。一旦创建了存储库,就可以在应用程序中根据需要多次使用。由于它将业务逻辑和数据访问层分开,因此单元测试会更加容易和独立。

自定义存储库的通用接口

ICustomRepository 是一个接口,它定义了任何使用此接口的类都必须实现的必要条件。由于我们正在执行 CRUD 操作,因此我的 ICustomRepository 接口包含以下声明:

    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 类

MyCustomRepository 是派生自通用存储库接口 ICustomRepository 的存储库类。每个接口声明的定义都在此类中实现。以下代码演示了 MyCustomRepository 的实现方式?

    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 模型

Employee 模型包含字段,用于解释员工的属性,如 EmployeeId、Employee Name、Date of Join、Current Salary amount、Contact number 和 Email。这些属性在属性和规范中进行了说明,每个实体的验证都通过数据注释进行了说明。

    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 列表

以下是包含 Employee 列表的页面截图。它包含执行员工 CRUD 操作的必要按钮和输入。它还可以搜索名称与搜索文本框中输入的名称匹配的员工。Bootstrap 模型框将显示员工文档,并上传所选员工的文档。分页使用了第三方库 PadgedList。

以下代码块显示了上面显示的员工列表视图的設計師代碼。当用户单击“Documents”按钮时,将显示一个 Bootstrap 模型窗口。模型窗口将包含一个只读文本框以显示要上传其文档的“EmployeeId”,一个文件浏览按钮,一个上传按钮和一个关闭按钮。如果员工已上传文档,则在模型打开时会在模型窗口中加载这些文档。

@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>

以下代码块包含 Employee 控制器的完整代码。存储库类 MyCustomRepository 在 Employee 控制器的构造函数中实例化。Index 操作方法根据此方法的参数呈现 Employee List 视图。每页的默认行数为 4,但可以通过更改变量 pageSize 的值来更改。员工列表中的记录可以根据从员工列表视图中的搜索文本框传递的 searchString 值进行过滤。

    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();
        }

    }

如果单击员工列表页面上的“Document”按钮,将打开一个类似如下所示的模型窗口。它包含 EmployeeId 以及已上传图像(如果有)的列表。可以逐个为选定的员工添加多个图像。上传文档的默认路径在 web.config 文件中的 appsetting 部分进行说明。您可以将默认文档上传路径更改为您方便的位置。

PhotoHandaler 用于显示来自本地源的图像

通用处理程序处理存储为本地资源的图像的显示。它从文件中读取图像并将其返回给调用方。

    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;
            }
        }
    }

通过本文,我尝试演示了 ASP.NET MVC 存储库模式,应用了 Bootstrap 样式,并使用 jQuery Ajax 从计算机位置加载图像。请随时评论和提出建议。

© . All rights reserved.