在上一篇"在ASP.NET MVC4中实现同页面增删改查,无弹出框01,Repository的搭建"中,已经搭建好了Repository层,本篇就剩下增删改查的界面了......今天的阳光真特么好,写完本篇,好出去在阳光下溜溜狗、散散步什么的,正所谓文武之道一张一弛,走神了,进入正题。
首先是一个View Model,在这里定义验证规则,提交和保存数据的时候还必须和领域模型映射。
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication3.Models
{
public class ProductVm
{
public int Id { get; set; }
[Required(ErrorMessage = "必填")]
[Display(Name = "名称")]
[StringLength(6, ErrorMessage = "最大长度6位")]
public string Name { get; set; }
[Required(ErrorMessage = "必填")]
[Display(Name = "分类")]
[StringLength(6, ErrorMessage = "最大长度6位")]
public string Category { get; set; }
[Required(ErrorMessage = "必填")]
[Display(Name = "价格")]
[Range(typeof(Decimal), "0", "9999", ErrorMessage = "{0} 必须是数字介于 {1} 和 {2}之间.")]
public decimal Price { get; set; }
}
}
创建HomeController
using System.Linq;
using System.Web.Mvc;
using MvcApplication3.Models;
namespace MvcApplication3.Controllers
{
public class ProductController : Controller
{
static readonly IProductRepository repository = new ProductRepository();
#region 显示查询
/// <summary>
/// 显示主界面
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
return View();
}
private string _name = string.Empty; //用来接收查询中有关Name的值
private string _category = string.Empty;//用来接收查询中有关Category的值
/// <summary>
/// 根据查询参数获取所有产品,以json返回
/// </summary>
/// <returns></returns>
public ActionResult GetProdutJson()
{
//page和rows是datagrid传递的默认参数
int pageIndex = int.Parse(Request["page"]);
int pageSize = int.Parse(Request["rows"]);
//如果查询中包括Name
if (!string.IsNullOrEmpty(Request["name"]))
{
_name = Request["name"];
}
//如果查询中包括Category
if (!string.IsNullOrEmpty(Request["category"]))
{
_category = Request["category"];
}
//初始化查询实例
var queryParams = new ProductParam
{
PageIndex = pageIndex,
PageSize = pageSize,
Name = _name,
Category = _category
};
//根据查询实例查找对应的数据
int totalNum = 0;
var products = repository.LoadProductPageData(queryParams, out totalNum);
//投影出需要传递给datagrid的数据
var result = from p in products
select new {p.Id, p.Name, p.Category, p.Price};
//datagrid所需要的格式{total:10, rows=[]}
var jsonResult = new {total = totalNum, rows = result};
return Json(jsonResult, JsonRequestBehavior.AllowGet);
}
#endregion
#region 添加
/// <summary>
/// 添加显示,返回一个强类型部分视图
/// </summary>
/// <returns></returns>
public ActionResult AddProduct()
{
return PartialView("_AddProduct", new ProductVm());
}
/// <summary>
/// 添加提交
/// </summary>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddProduct(ProductVm productVm)
{
if (ModelState.IsValid)
{
Product dbProduct = new Product();
dbProduct.Name = productVm.Name;
dbProduct.Category = productVm.Category;
dbProduct.Price = productVm.Price;
repository.Add(dbProduct);
return Json(new { msg = true });
}
else
{
return PartialView("_AddProduct", new ProductVm());
}
}
#endregion
#region 修改
/// <summary>
/// 修改显示
/// </summary>
/// <returns></returns>
public ActionResult EditProduct(int id)
{
var dbProduct = repository.GetById(id);
ProductVm productVm = new ProductVm();
productVm.Id = dbProduct.Id;
productVm.Name = dbProduct.Name;
productVm.Category = dbProduct.Category;
productVm.Price = dbProduct.Price;
return PartialView("_EditProduct", productVm);
}
/// <summary>
/// 修改提交
/// </summary>
/// <param name="productVm"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditProduct(ProductVm productVm)
{
if (ModelState.IsValid)
{
var dbProduct = repository.GetById(productVm.Id);
dbProduct.Name = productVm.Name;
dbProduct.Category = productVm.Category;
dbProduct.Price = productVm.Price;
repository.Update(dbProduct);
return Json(new { msg = true });
}
else
{
return PartialView("_EditProduct", productVm);
}
}
#endregion
#region 删除
/// <summary>
/// 删除
/// </summary>
/// <returns></returns>
[HttpPost]
public ActionResult DeleteProducts()
{
//获取前台传来的Id字符串
var strIds = Request["ids"];
//去除最后一位分隔符
strIds = strIds.Substring(0, strIds.Length - 1);
//把字符串转换成数组
string[] ids = strIds.Split('_');
repository.DeleteBatch(ids);
return Json(new { msg = true });
}
#endregion
}
}
在前台页面会使用jQuery EasyUI的datagrid显示数据和分页,而datagrid在向服务端发送异步请求的时候默认带了page
和rows
这2个参数。GetProdutJson
方法用来获取查询、分页后的json格式。在前台,当页面初次加载的时候,不带任何查询数据,服务端只要接收page
和rows
这2个参数,把它们封装成一个类。
namespace MvcApplication3.Models
{
public class PageParam
{
public int PageSize { get; set; }
public int PageIndex { get; set; }
}
}
而当在前台输入搜索条件的时候,搜索条件参数以及page
和rows
这2个参数都传值给了服务端,为此我们再封装一个派生于PageParam
的类。
namespace MvcApplication3.Models
{
public class ProductParam : PageParam
{
public string Name { get; set; }
public string Category { get; set; }
}
}
所以,在GetProdutJson
方法内,最终是把从前台接收到的分页参数或查询参数,实例化成ProductParam
的一个实例,再把该实例作为实参传值给Repository的LoadProductPageData
方法。
主界面Home/Index.cshtml
进入主界面Home/Index.cshtml之前,先设置_Layout.cshtml。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
<link href="~/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
<link href="~/Content/themes/icon.css" rel="stylesheet" />
<link href="~/Content/themes/gray/easyui.css" rel="stylesheet" />
@RenderSection("styles", required:false)
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/jqueryval")
<script src="~/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
@RenderBody()
@RenderSection("scripts", required: false)
</body>
</html>
用到了bootstrap, jQuery EasyUI, jQuery等相关的css,js文件,该有的要有,顺序要放对。
Product/Index.cshtml需要呈现的包括:
○ 添加区域:放在一个div中,div中再放一个iframe,需要的时候通过iframe链接到添加页面
○ 修改区域:放在一个div中,div中再放一个iframe,需要的时候通过iframe链接到修改页面
○ 搜索区域:使用bootstrap显示元素
○ 列表区域:使用datagrid显示,支持分页
Product/Index.cshtml需要承担的工作包括:
○ 页面加载的时候隐藏添加和修改区域
○ 限制搜索区域有关名称和类别的长度
○ 页面加载显示全部数据列表
○ 点击搜索按钮显示符合搜素条件的数据列表
○ 点击清空按钮让搜索条件清空
○ datagrid添加:让添加区域内的iframe指向ProductController的AddProduct
方法,渲染强类型添加视图
○ datagrid修改:让修改区域内的iframe指向ProductController的EditProduct
方法,渲染强类型修改视图,并传递当前Product的Id
○ datagrid删除:支持批量删除,把勾选行所在的Id拼接成"1_2_"的形式传递给PrductContrlller的DeleteProduct
方法,返回json
○ 提供给子视图页面调用的添加成功、添加取消、修改成功、修改取消方法,Home/Index.cshtml为父页面,添加和修改区域内的iframe指向的视图页面是子页面
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<!--添加开始-->
<div class="addContent" id="addContent">
<iframe id="frameAdd" src="" scrolling="yes" frameborder="0" height="100%" width="100%" οnlοad="this.height=this.contentWindow.document.documentElement.scrollHeight"></iframe>
</div>
<!--添加结束-->
<!--编辑开始-->
<div class="editContent" id="editContent">
<iframe id="frameEdit" src="" scrolling="yes" frameborder="0" height="100%" width="100%" οnlοad="this.height=this.contentWindow.document.documentElement.scrollHeight"></iframe>
</div>
<!--编辑结束-->
<!--搜索开始-->
<div id="tb" style="padding: 3px">
<form class="form-inline" role="form">
<div class="form-group">
<label class="sr-only" for="name">名称</label>
<input type="text" class="form-control" name="name" id="name" placeholder="输入名称" maxlength="6" />
</div>
<div class="form-group">
<label class="sr-only" for="category">分类</label>
<input type="text" class="form-control" name="category" id="category" placeholder="输入分类" maxlength="6" />
</div>
<input type="button" id="btnSearch" class="btn btn-primary" value="搜索" />
<input type="button" id="emptySearch" class="btn btn-default" value="清空" />
</form>
</div>
<!--搜索结束—>
<!--表格开始-->
<div class="ctable" id="ctable">
<table id="tt"></table>
</div>
<!--表格结束-->
@section scripts
{
<script src="~/Scripts/jquery.easyui.min.js"></script>
<script src="~/Scripts/easyui-lang-zh_CN.js"></script>
<script type="text/javascript">
$(function() {
//隐藏元素
initialHide();
//限制搜索条件中名称和分类的长度
limitInputLength($('#name'));
limitInputLength($('#category'));
//显示列表
initData();
//搜索
$('#tb').on("click", "#btnSearch", function () {
initData(initQuery());
});
//清空搜索
$('#emptySearch').on("click", function () {
emptySearch();
});
});
//显示列表
function initData(params) {
$('#tt').datagrid({
url: '@Url.Action("GetProdutJson", "Product")',
height: 360,
width: 900,
fitColumns: false,
nowrap: true,
showFooter: true,
idField: 'ID',
loadMsg: '正在加载信息...',
pagination: true,
singleSelect: false,
queryParams: params,
pageSize: 10,
pageNumber: 1,
pageList: [10, 20, 30],
columns: [//p.Id, p.Name, p.Category, p.Price
[
{ field: 'ck', checkbox: true, align: 'center', width: 30 },
{ field: 'Id', title: '编号', align: 'center' },
{ field: 'Name', title: '名称', align: 'center' },
{ field: 'Price', title: '价格', align: 'center' },
{ field: 'Category', title: '分类', align: 'center' }
]
],
toolbar: [
{
id: 'btnAdd',
text: '添加',
iconCls: 'icon-add',
handler: function () {
showAdd();
}
}, '-', {
id: 'btnUpdate',
text: '修改',
iconCls: 'icon-edit',
handler: function () {
var rows = $('#tt').datagrid("getSelections");
if (rows.length != 1) {
$.messager.alert("提示", "只能选择一行进行编辑");
return;
}
showEdit(rows[0].Id);
}
}, '-', {
id: 'btnDelete',
text: '删除',
iconCls: 'icon-remove',
handler: function () {
var rows = $('#tt').datagrid("getSelections");
if (rows.length < 1) {
$.messager.alert("提示", "请选一行删除");
return;
}
$.messager.confirm("提示信息", "确定要删除吗?", function (r) {
if (r) {
var strIds = "";
for (var i = 0; i < rows.length; i++) {
strIds += rows[i].Id + '_'; //1_2_3
}
$.post("@Url.Action("DeleteProducts", "Product")", { ids: strIds }, function (data) {
if (data.msg) {
$.messager.alert("提示", "删除成功");
initData();
$('#tt').datagrid("clearSelections");
}
});
}
});
}
}
],
OnBeforeLoad: function (param) {
return true;
}
});
}
//添加
function showAdd() {
$("#frameAdd").attr("src", "@Url.Action("AddProduct", "Product")");
$("#addContent").css("display", "block");
}
//修改
function showEdit(productId) {
var url = "/Product/EditProduct?id=" + productId;
$("#frameEdit").attr("src", url);
$('.addContent').css("display", "none");
$("#editContent").css("display", "block");
}
//隐藏元素
function initialHide() {
$('.addContent').css("display", "none");
$('.editContent').css("display", "none");
}
//限制文本框长度
function limitInputLength(_input) {
var _max = _input.attr('maxlength');
_input.bind('keyup change', function () {
if ($(this).val().length > _max) {
($(this).val($(this).val().substring(0, _max)));
}
});
}
//子窗口添加成功后调用
function refreshAfterAdd() {
initData();
$('#tt').datagrid("clearSelections");
$("#addContent").css("display", "none");
}
//子窗口取消添加调用
function cancelAdd() {
$('.addContent').css("display", "none");
$('#tt').datagrid("clearSelections");
}
//子窗口修改成功后调用
function refreshAfterEdit() {
initData();
$('#tt').datagrid("clearSelections");
$("#editContent").css("display", "none");
}
//子窗口取消修改调用
function candelEdit() {
$("#editContent").css("display", "none");
$('#tt').datagrid("clearSelections");
}
//获取查询表单的值组成json
function initQuery() {
var queryParams = {
name: $('#name').val(),
category: $('#category').val()
};
return queryParams;
}
//清空搜索条件
function emptySearch() {
$('#name').val("");
$('#category').val("");
}
</script>
}
添加强类型视图页Product/_AddProduct.cshtml
是一个针对ProductVm强类型视图。添加成功或取消都会调用父视图提供的方法。
@model MvcApplication3.Models.ProductVm
@{
ViewBag.Title = "_AddProduct";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section styles
{
<style type="text/css">
.errormsg {
color: red;
}
.vcenter {
display: table-cell;
vertical-align: middle;
float: none;
border: 0px solid red;
height: 38px;
}
.bk {
background-color: #F8F8F8;
padding: 2px;
border-radius: 5px;
}
form {
width: 900px;
/*height: 450px;*/
}
.control-label {
position: relative;
top: 5px;
}
.col-sm-6 {
height: 40px;
}
.col-sm-2 {
height: 40px;
}
.col-sm-4 {
height: 40px;
}
#wrapper {
width: 550px;
clear: both;
float: left;
margin-top: 10px;
}
</style>
}
<div id="wrapper">
@using (Html.BeginForm("AddProduct", "Product", FormMethod.Post, new { id = "addForm", @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<div class="form-group">
@Html.LabelFor(s => s.Name,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Name,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Name)
</div>
</div>
<div class="form-group bk">
@Html.LabelFor(s => s.Price,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Price,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Price)
</div>
</div>
<div class="form-group">
@Html.LabelFor(s => s.Category,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Category,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Category)
</div>
</div>
<div class="form-group bk">
<div style="text-align: center;" class="col-sm-6 col-sm-offset-2">
<input type="button" id="up" class="btn btn-primary" value="添加"/>
<input type="button" id="cancel" class="btn btn-default" value="取消"/>
</div>
</div>
}
</div>
@section scripts
{
<script type="text/javascript">
$(function() {
//提交
$('#up').on('click', function () {
if ($('#addForm').valid()) {
$.ajax({
cache: false,
url: '@Url.Action("AddProduct","Product")',
type: 'POST',
dataType: 'json',
data: $('#addForm').serialize(),
success: function (data) {
if (data.msg) {
$('input[type=text]').val("");
self.parent.refreshAfterAdd();
}
},
error: function (xhr, status) {
alert("添加失败,状态码:" + status);
}
});
}
});
//点击取消按钮关闭窗口
$('#cancel').on('click', function () {
$('input[type=text]').val("");
self.parent.cancelAdd();
});
});
</script>
}
修改强类型视图页Product/_EditProduct.cshtml
是一个针对ProductVm强类型视图。修改成功或取消都会调用父视图提供的方法。
@model MvcApplication3.Models.ProductVm
@{
ViewBag.Title = "_EditProduct";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section styles
{
<style type="text/css">
.errormsg {
color: red;
}
.vcenter {
display: table-cell;
vertical-align: middle;
float: none;
border: 0px solid red;
height: 38px;
}
.bk {
background-color: #F8F8F8;
padding: 2px;
border-radius: 5px;
}
form {
width: 900px;
/*height: 450px;*/
}
.control-label {
position: relative;
top: 5px;
}
.col-sm-6 {
height: 40px;
}
.col-sm-2 {
height: 40px;
}
.col-sm-4 {
height: 40px;
}
#wrapper {
width: 550px;
clear: both;
float: left;
margin-top: 10px;
}
</style>
}
<div id="wrapper">
@using (Html.BeginForm("EditProduct", "Product", FormMethod.Post, new { id = "editForm", @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(s => s.Id)
<div class="form-group">
@Html.LabelFor(s => s.Name,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Name,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Name)
</div>
</div>
<div class="form-group bk">
@Html.LabelFor(s => s.Price,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Price,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Price)
</div>
</div>
<div class="form-group">
@Html.LabelFor(s => s.Category,new { @class="col-sm-2 control-label" })
<div class="col-sm-6">
@Html.TextBoxFor(s => s.Category,new { @class="form-control input-sm"})
</div>
<div class="col-sm-4 vcenter">
@Html.ValidationMessageFor(s => s.Category)
</div>
</div>
<div class="form-group bk">
<div style="text-align: center;" class="col-sm-6 col-sm-offset-2">
<input type="button" id="up" class="btn btn-primary" value="修改"/>
<input type="button" id="cancel" class="btn btn-default" value="取消"/>
</div>
</div>
}
</div>
@section scripts
{
<script type="text/javascript">
$(function() {
//提交
$('#up').on('click', function () {
if ($('#editForm').valid()) {
$.ajax({
cache: false,
url: '@Url.Action("EditProduct","Product")',
type: 'POST',
dataType: 'json',
data: $('#editForm').serialize(),
success: function (data) {
if (data.msg) {
self.parent.refreshAfterEdit();
}
},
error: function (xhr, status) {
alert("修改失败,状态码:" + status);
}
});
}
});
//点击取消按钮关闭窗口
$('#cancel').on('click', function () {
self.parent.candelEdit();
});
});
</script>
}