在此示例ASP.Net MVC 4程序中,我让用户填写了有关赛马的详细信息。比赛的名称以及所涉及的赛马名单。每匹马都有一个名字和年龄。
该表单使用ajax和javascript允许用户即时添加和删除马输入字段,然后在按下“提交”按钮时立即全部提交。
为了使这个过程对我来说很容易,我使用了html helper制作的Matt Lunn。
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string htmlFieldName = null) where TModel : class
{
var items = expression.Compile()(html.ViewData.Model);
var sb = new StringBuilder();
if (String.IsNullOrEmpty(htmlFieldName))
{
var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
}
foreach (var item in items)
{
var dummy = new { Item = item };
var guid = Guid.NewGuid().ToString();
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
sb.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
}
return new MvcHtmlString(sb.ToString());
}
尽管我不了解所有详细信息(请阅读博客文章),但我确实知道它会将索引值更改为guid而不是顺序整数。这使我可以删除列表中间的项目,而无需重新计算索引。
这是我的MCVE代码的其余部分
HomeController.cs
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var model = new Race();
//start with one already filled in
model.HorsesInRace.Add(new Horse() { Name = "Scooby", Age = 10 });
return View(model);
}
[HttpPost]
public ActionResult Index(Race postedModel)
{
if (ModelState.IsValid)
//model is valid, redirect to another page
return RedirectToAction("ViewHorseListing");
else
//model is not valid, show the page again with validation errors
return View(postedModel);
}
[HttpGet]
public ActionResult AjaxMakeHorseEntry()
{
//new blank horse for ajax call
var model = new List<Horse>() { new Horse() };
return PartialView(model);
}
}
Models.cs
public class Race
{
public Race() { HorsesInRace = new List<Horse>(); }
[Display(Name = "Race Name"), Required]
public string RaceName { get; set; }
[Display(Name = "Horses In Race")]
public List<Horse> HorsesInRace { get; set; }
}
public class Horse
{
[Display(Name = "Horse's Name"), Required]
public string Name { get; set; }
[Display(Name = "Horse's Age"), Required]
public int Age { get; set; }
}
Index.cshtml
@model CollectionAjaxPosting.Models.Race
<h1>Race Details</h1>
@using (Html.BeginForm())
{
@Html.ValidationSummary()
<hr />
<div>
@Html.DisplayNameFor(x => x.RaceName)
@Html.EditorFor(x => x.RaceName)
@Html.ValidationMessageFor(x => x.RaceName)
</div>
<hr />
<div id="horse-listing">@Html.EditorForMany(x => x.HorsesInRace)</div>
<button id="btn-add-horse" type="button">Add New Horse</button>
<input type="submit" value="Enter Horses" />
}
<script type="text/javascript">
$(document).ready(function () {
//add button logic
$('#btn-add-horse').click(function () {
$.ajax({
url: '@Url.Action("AjaxMakeHorseEntry")',
cache: false,
method: 'GET',
success: function (html) {
$('#horse-listing').append(html);
}
})
});
//delete-horse buttons
$('#horse-listing').on('click', 'button.delete-horse', function () {
var horseEntryToRemove = $(this).closest('div.horse');
horseEntryToRemove.prev('input[type=hidden]').remove();
horseEntryToRemove.remove();
});
});
</script>
View /共享/EditorTemplates/Horse.cshtml
@model CollectionAjaxPosting.Models.Horse
<div class="horse">
<div>
@Html.DisplayNameFor(x => x.Name)
@Html.EditorFor(x => x.Name)
@Html.ValidationMessageFor(x => x.Name)
</div>
<div>
@Html.DisplayNameFor(x => x.Age)
@Html.EditorFor(x => x.Age)
@Html.ValidationMessageFor(x => x.Age)
</div>
<button type="button" class="delete-horse">Remove Horse</button>
<hr />
</div>
View /主页/AjaxMakeHorseEntry.cshtml
@model IEnumerable<CollectionAjaxPosting.Models.Horse>
@Html.EditorForMany(x => x, "HorsesInRace")
数据流与此代码一起工作。一个人可以在页面上创建和删除任意数量的马条目,并且在提交表单时,所有输入的值都将提供给action方法。
但是,如果用户未在马条目中输入
[Required]
信息,则ModelState.IsValid
将为false,再次显示该表单,但是不会为Horse属性显示验证消息。验证错误确实显示在ValidationSummary
列表中。例如,如果
Race Name
与一个Horse's Name
留为空白,则将显示前者的验证消息。后者将具有类别为“field-validation-valid”的验证<span>
。我非常确定,这是因为每次创建页面时
EditorForMany
方法都会为每个属性创建新的Guid,因此验证消息无法与正确的字段匹配。我该怎么做才能解决此问题?我是否需要放弃引导索引的创建,或者是否可以对
EditorForMany
方法进行更改以允许正确传递验证消息? 最佳答案
是的;这就是这里正在发生的事情。
要解决此问题,我们需要修改EditorForMany()
,以便它重新使用GUID代替一项,而不是生成一个新项。反过来,这意味着我们需要跟踪将哪些GUID分配给了哪个项目,以便可以重复使用它。
前者可以通过对EditorForMany()
进行内部修改来实现。后者要求我们:
EditorForMany()
添加参数以告诉帮助程序哪个属性包含要重用的GUID(如果有)。 这使得
EditorForMany
帮助程序看起来像这样;public static class HtmlHelperExtensions
{
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> propertyExpression, Expression<Func<TValue, string>> indexResolverExpression = null, string htmlFieldName = null) where TModel : class
{
htmlFieldName = htmlFieldName ?? ExpressionHelper.GetExpressionText(propertyExpression);
var items = propertyExpression.Compile()(html.ViewData.Model);
var htmlBuilder = new StringBuilder();
var htmlFieldNameWithPrefix = html.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
Func<TValue, string> indexResolver = null;
if (indexResolverExpression == null)
{
indexResolver = x => null;
}
else
{
indexResolver = indexResolverExpression.Compile();
}
foreach (var item in items)
{
var dummy = new { Item = item };
var guid = indexResolver(item);
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, propertyExpression.Parameters);
if (String.IsNullOrEmpty(guid))
{
guid = Guid.NewGuid().ToString();
}
else
{
guid = html.AttributeEncode(guid);
}
htmlBuilder.Append(String.Format(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldNameWithPrefix, guid));
if (indexResolverExpression != null)
{
htmlBuilder.Append(String.Format(@"<input type=""hidden"" name=""{0}[{1}].{2}"" value=""{1}"" />", htmlFieldNameWithPrefix, guid, ExpressionHelper.GetExpressionText(indexResolverExpression)));
}
htmlBuilder.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
}
return new MvcHtmlString(htmlBuilder.ToString());
}
}
然后,我们还需要更改模型,以添加用于存储GUID的属性。
public class Race
{
public Race() { HorsesInRace = new List<Horse>(); }
[Display(Name = "Race Name"), Required]
public string RaceName { get; set; }
[Display(Name = "Horses In Race")]
public List<Horse> HorsesInRace { get; set; }
}
public class Horse
{
[Display(Name = "Horse's Name"), Required]
public string Name { get; set; }
[Display(Name = "Horse's Age"), Required]
public int Age { get; set; }
// Note the addition of Index here.
public string Index { get; set; }
}
...最后,将我们对
EditorForMany()
的用法更改为使用新的签名;Index.cshtml;
<div id="horse-listing">@Html.EditorForMany(x => x.HorsesInRace, x => x.Index)</div>
AjaxMakeHorseEntry.cshtml;
@Html.EditorForMany(x => x, x => x.Index, "HorsesInRace")
...,这将使验证消息出现。
顺便说一句,我建议不要将
htmlFieldName
参数用于EditorForMany
,而应将 Controller 操作更改为;[HttpGet]
public ActionResult AjaxMakeHorseEntry()
{
var model = new Race();
model.HorsesInRace.Add(new Horse());
return PartialView(model);
}
...那么您的AjaxMakeHorseEntry.cshtml View 将是公正的;
@model Models.Race
@Html.EditorForMany(x => x.HorsesInRace, x => x.Index)
否则,当嵌套使用
name
时,生成的EditorForMany()
属性会中断。由于这个原因,我将更新博客文章以使用以上版本的
EditorForMany()
,而不是接受htmlFieldName
参数。关于c# - 每次使用新的Guid索引时,如何获取验证消息以呈现在集合属性上?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34949320/