表单使用ajax和javascript来允许此人动态添加和删除马输入字段,然后在按下提交按钮时立即提交.
为了让这个过程变得简单,我使用Matt Lunn制造的html helper.
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}]",guid))); } return new MvcHtmlString(sb.ToString()); }
虽然我不了解所有细节(请阅读博客文章),但我知道它将索引值更改为guids而不是顺序整数.这允许我删除列表中间的项目,而无需重新计算索引.
这是我的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>
查看/共享/ 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>
查看/主页/ AjaxMakeHorseEntry.cshtml
@model IEnumerable<CollectionAjaxPosting.Models.Horse> @Html.EditorForMany(x => x,"HorsesInRace")
数据流与此代码一起使用.一个人能够在页面上尽可能多地创建和删除马条目,并且在提交表单时,所有输入的值都被赋予操作方法.
但是,如果用户未输入马条目的[必需]信息,则ModelState.IsValid将显示为false,同时显示该表单,但不会显示Horse属性的验证消息.验证错误确实显示在ValidationSummary列表中.
例如,如果将“种族名称”留空,并且还有一个“马”的名称,则会显示前者的验证消息.后者将进行验证< span>使用“field-validation-valid”类.
我非常确定这是因为EditorForMany方法在每次创建页面时为每个属性创建新的guid,因此验证消息无法与正确的字段匹配.
我该怎么做才能解决这个问题?我是否需要放弃guid索引创建,还是可以对EditorForMany方法进行更改以允许验证消息正确传递?
解决方法
I’m very sure this is caused because the
EditorForMany
method creates new guids for each property each time the page is created,so validation messages can’t be matched to the correct field.
是的;这正是这里发生的事情.
要解决这个问题,我们需要修改EditorForMany(),以便它重新使用项目的GUID,而不是生成新项目.反过来,这意味着我们需要跟踪GUID分配给哪个项目,以便可以重复使用.
前者可以通过对EditorForMany()的内部修改来完成.后者要求我们:
>将属性添加到我们的模型中,可以在其中存储指定的GUID
>向EditorForMany()添加一个参数,告诉助手哪个属性包含要重用的GUID(如果有).
这使得EditorForMany帮助器看起来像这样;
public static class HtmlHelperExtensions { public static MvcHtmlString EditorForMany<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,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}"" />",guid,ExpressionHelper.GetExpressionText(indexResolverExpression))); } htmlBuilder.Append(html.EditorFor(singleItemExp,guid))); } return new MvcHtmlString(htmlBuilder.ToString()); } }
public class Race { public Race() { HorsesInRace = new List<Horse>(); } [Display(Name = "Race Name"),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")
…然后应该出现验证消息.
顺便说一句,我建议不要为EditorForMany使用htmlFieldName参数,而是将控制器操作更改为;
[HttpGet] public ActionResult AjaxMakeHorseEntry() { var model = new Race(); model.HorsesInRace.Add(new Horse()); return PartialView(model); }
…然后你的AjaxMakeHorseEntry.cshtml视图就是这样;
@model Models.Race @Html.EditorForMany(x => x.HorsesInRace,x => x.Index)