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






4.78/5 (17投票s)
带有文档的员工的通用存储库应用程序。
引言
本文将指导您使用 MVC 框架中的通用存储库模式创建一个小型应用程序。本文主要面向初学者到中级程序员,以便他们至少能理解如何开发 ASP.NET MVC 应用程序。阅读本文后,您将能够理解以下内容:
- 使用 MVC 存储库执行选择、插入、更新和删除操作的基本概念
- 如何使用 jQuery 打开 Bootstrap 模型弹出窗口并将值传递给模型弹出窗口
- 从模型窗口上传图像到所需的存储位置,并通过 jQuery AJAX 调用和通用处理程序显示模型窗口中的图像
对于实际应用,我将创建一个简单的 Employee 存储库应用程序,其中包含基本的员工信息及其文档。所有文档将存储在一个文件夹中,其存储位置在 web.config 文件的 appsetting 中指定。以下屏幕截图显示了员工图像上传和显示窗口的外观。
此外,您可以在 CodeProject 和其他教程网站上找到有关类似主题的类似文章。我建议您参考以下由一些专家程序员撰写的教程,以供您进一步参考。
- 在 ASP.NET MVC 应用程序中实现存储库和工作单元模式
- MVC 中的通用存储库和工作单元模式 - 作者:Ashish Shukla
- 使用 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">×</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 从计算机位置加载图像。请随时评论和提出建议。