在这种情况下,使用服务器资源跟踪更改不是一个好主意。在购物篮、列表或批量编辑等场景中,最好在客户端跟踪更改。
您在服务器端生成视图的要求并不意味着您需要跟踪
DbContext
. 从服务器获取索引视图和创建视图,但跟踪客户端上的更改。然后要保存,请将所有数据发布到服务器,以根据您拥有的跟踪信息保存更改。
客户端更改跟踪的机制取决于需求和场景,例如,您可以使用HTML输入跟踪更改,您可以使用Cookie跟踪更改,您可以像JavaScript对象那样在浏览器内存中跟踪更改,如角度场景。
这是本文,我将展示一个使用html输入和模型绑定的示例。要了解有关此主题的更多信息,请参阅Phill Haack的这篇文章:
Model Binding To A List
.
例子
在下面的示例中,我描述了客户列表的列表编辑场景。简单来说,我想:
-
你有一份客户名单,你要在客户那里编辑。您可能需要添加、编辑或删除项目。
-
添加新项时,新行的行模板应来自服务器。
-
删除时,通过单击行上的复选框将项目标记为已删除。
-
添加/编辑时,希望在单元格附近显示验证错误。
-
要在结尾保存更改,请单击“保存”按钮。
要实现上述场景,您需要创建以下模型、操作和视图:
可跟踪<t>模型
这个类是一个帮助我们进行客户端跟踪和列表编辑的模型:
public class Trackable<T>
{
public Trackable() { }
public Trackable(T model) { Model = model; }
public Guid Index { get; set; } = Guid.NewGuid();
public bool Deleted { get; set; }
public bool Added { get; set; }
public T Model { get; set; }
}
客户模型
客户模式:
public class Customer
{
[Display(Name ="Id")]
public int Id { get; set; }
[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="First Name")]
public string FirstName { get; set; }
[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="Last Name")]
public string LastName { get; set; }
[EmailAddress]
[Required]
[Display(Name ="Email Name")]
public string Email { get; set; }
}
index.cshtml视图
索引视图负责呈现
List<Trackable<Customer>>
. 在渲染每个记录时,我们使用
RowTemplate
查看。与添加新项时使用的视图相同。
在这个视图中,我们有一个用于保存的提交按钮和一个用于添加新行的按钮,这些新行使用ajax调用create操作。
以下是索引视图:
@model IEnumerable<Trackable<Customer>>
<h2>Index</h2>
<form method="post" action="Index">
<p>
<button id="create">New Customer</button>
<input type="submit" value="Save All">
</p>
<table class="table" id="data">
<thead>
<tr>
<th>
Delete
</th>
<th>
@Html.DisplayNameFor(x => x.Model.FirstName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.LastName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.Email)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
await Html.RenderPartialAsync("RowTemplate", item);
}
</tbody>
</table>
</form>
@section Scripts{
<script>
$(function () {
$('#create').click(function (e) {
e.preventDefault();
$.ajax({
url: 'Create',
method: 'Get',
success: function (data) {
$('#data tbody tr:last-child').after(data);
},
error: function (e) { alert(e); }
});
});
});
</script>
}
rowtemplate.cshtml视图
此视图负责呈现客户记录。在这个视图中,我们首先呈现
Index
在一个隐藏的,然后设置一个前缀
[index]
对于字段,然后呈现字段,包括再次索引、添加、删除和模型ID:
以下是rowtemplate视图:
@model Trackable<Customer>
<tr>
<td>
@Html.HiddenFor(x => x.Index)
@{Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[{Model.Index}]";}
@Html.HiddenFor(x => x.Index)
@Html.HiddenFor(x => x.Model.Id)
@Html.HiddenFor(x => x.Added)
@Html.CheckBoxFor(x => x.Deleted)
</td>
<td>
@Html.EditorFor(x => x.Model.FirstName)
@Html.ValidationMessageFor(x => x.Model.FirstName)
</td>
<td>
@Html.EditorFor(x => x.Model.LastName)
@Html.ValidationMessageFor(x => x.Model.LastName)
</td>
<td>
@Html.EditorFor(x => x.Model.Email)
@Html.ValidationMessageFor(x => x.Model.Email)
</td>
</tr>
客户控制器
public class CustomerController : Controller
{
private static List<Customer> list;
}
它将具有以下操作。
[获取]索引操作
在此操作中,可以从数据库加载数据并将其赋形为
列表<可跟踪<客户>>
然后传给
索引
观点:
[HttpGet]
public IActionResult Index()
{
if (list == null)
{
list = Enumerable.Range(1, 5).Select(x => new Customer()
{
Id = x,
FirstName = $"A{x}",
LastName = $"B{x}",
Email = $"A{x}@B{x}.com"
}).ToList();
}
var model = list.Select(x => new Trackable<Customer>(x)).ToList();
return View(model);
}
[获取]创建操作
此操作负责返回新行模板。它将由使用ajax的索引视图中的按钮调用:
[HttpGet]
public IActionResult Create()
{
var model = new Trackable<Customer>(new Customer()) { Added = true };
return PartialView("RowTemplate", model);
}
[发布]索引操作
此操作负责从客户端接收跟踪项并保存它们。它收到的模型是
列表<可跟踪<客户>>
. 它首先删除已删除行的验证错误消息。然后删除那些既被删除又被添加的内容。然后检查模型状态是否有效,尝试对数据源应用更改。
项目有
Deleted
属性为true时,将删除具有
Added
为真
删除
由于false是新项,其余项将被编辑。然后无需从数据库中加载所有项,只需使用for循环,调用
db.Entry
为每个项目设置状态并最终保存更改。
[HttpPost]
public IActionResult Index(List<Trackable<Customer>> model)
{
//Cleanup model errors for deleted rows
var deletedIndexes = model.
Where(x => x.Deleted).Select(x => $"[{x.Index}]");
var modelStateDeletedKeys = ModelState.Keys.
Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));
//Removing rows which are added and deleted
model.RemoveAll(x => x.Deleted && x.Added);
//If model state is not valid, return view
if (!ModelState.IsValid)
return View(model);
//Deleted rows
model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
{
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list.RemoveAt(i);
});
//Added rows
model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
{
list.Add(x.Model);
});
//Edited rows
model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
{
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list[i] = x.Model;
});
//Reditect to action index
return RedirectToAction("Index");
}