在此示例ASP.Net MVC 4程序中,我让用户填写有关赛马的详细信息。比赛的名称以及所涉及的赛马名单。每匹马都有一个名字和年龄。
该表单使用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}]", 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);
}
}
模型.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")
数据流与此代码一起工作。一个人可以在页面上创建和删除任意数量的马条目,并且在提交表单时,所有输入的值都将提供给action方法。
但是,如果用户未输入有关Horse[Required]
条目的信息,ModelState.IsValid
则将再次为false,以再次显示该表单,但是不会显示Horse属性的验证消息。验证错误确实会显示在ValidationSummary
列表中。
例如,如果将其Race Name
与空白一起留空,Horse's Name
则将显示前者的验证消息。后者将<span>
使用“ field-validation-valid”类进行验证。
我非常确定,这是因为EditorForMany
每次创建页面时该方法都会为每个属性创建新的Guid,因此验证消息无法与正确的字段匹配。
我该怎么做才能解决此问题?我是否需要放弃引导索引的创建,或者可以对EditorForMany
方法进行更改以允许正确传递验证消息?
我非常确定,这是因为
EditorForMany
每次创建页面时该方法都会为每个属性创建新的Guid,因此验证消息无法与正确的字段匹配。
是的; 这就是这里正在发生的事情。
要解决此问题,我们需要进行修改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
,而应将控制器操作更改为;
[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)
否则,name
当嵌套使用时,生成的属性会中断EditorForMany()
。
由于这个原因,我将更新博客文章以使用的上述版本EditorForMany()
,而不是接受htmlFieldName
参数。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句