我會用一個擴展方法,而不是你可以在其他情況下使用,以及:
擴展:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class HtmlHelperExtensions
{
/// <summary>
/// Generates a GUID-based editor template, rather than the index-based template generated by Html.EditorFor()
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="html"></param>
/// <param name="propertyExpression">An expression which points to the property on the model you wish to generate the editor for</param>
/// <param name="indexResolverExpression">An expression which points to the property on the model which holds the GUID index (optional, but required to make Validation* methods to work on post-back)</param>
/// <param name="includeIndexField">
/// True if you want this helper to render the hidden <input /> for you (default). False if you do not want this behaviour, and are instead going to call Html.EditorForManyIndexField() within the Editor view.
/// The latter behaviour is desired in situations where the Editor is being rendered inside lists or tables, where the <input /> would be invalid.
/// </param>
/// <returns>Generated HTML</returns>
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> propertyExpression, Expression<Func<TValue, string>> indexResolverExpression = null, bool includeIndexField = true) where TModel : class
{
var items = propertyExpression.Compile()(html.ViewData.Model);
var htmlBuilder = new StringBuilder();
var htmlFieldName = ExpressionHelper.GetExpressionText(propertyExpression);
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);
}
if (includeIndexField)
{
htmlBuilder.Append(_EditorForManyIndexField<TValue>(htmlFieldNameWithPrefix, guid, indexResolverExpression));
}
htmlBuilder.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
}
return new MvcHtmlString(htmlBuilder.ToString());
}
/// <summary>
/// Used to manually generate the hidden <input />. To be used in conjunction with EditorForMany(), when "false" was passed for includeIndexField.
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="html"></param>
/// <param name="indexResolverExpression">An expression which points to the property on the model which holds the GUID index (optional, but required to make Validation* methods to work on post-back)</param>
/// <returns>Generated HTML for hidden <input /></returns>
public static MvcHtmlString EditorForManyIndexField<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, string>> indexResolverExpression = null)
{
var htmlPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var first = htmlPrefix.LastIndexOf('[');
var last = htmlPrefix.IndexOf(']', first + 1);
if (first == -1 || last == -1)
{
throw new InvalidOperationException("EditorForManyIndexField called when not in a EditorForMany context");
}
var htmlFieldNameWithPrefix = htmlPrefix.Substring(0, first);
var guid = htmlPrefix.Substring(first + 1, last - first - 1);
return _EditorForManyIndexField<TModel>(htmlFieldNameWithPrefix, guid, indexResolverExpression);
}
private static MvcHtmlString _EditorForManyIndexField<TModel>(string htmlFieldNameWithPrefix, string guid, Expression<Func<TModel, string>> indexResolverExpression)
{
var htmlBuilder = new StringBuilder();
htmlBuilder.AppendFormat(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldNameWithPrefix, guid);
if (indexResolverExpression != null)
{
htmlBuilder.AppendFormat(@"<input type=""hidden"" name=""{0}[{1}].{2}"" value=""{1}"" />", htmlFieldNameWithPrefix, guid, ExpressionHelper.GetExpressionText(indexResolverExpression));
}
return new MvcHtmlString(htmlBuilder.ToString());
}
}
一個屬性添加到模型,該EditorForMany助手將存儲生成指數如果沒有這個,Html.Validation *方法將無法工作(請參閱here深入瞭解爲什麼好奇)。
public class UserModel
{
public string UserName { get; set; }
public List<Email> Emails = new List<Email>();
}
public class Email
{
public string Address { get; set; }
public string Index { get; set; }
}
替代@ Html.EditorFor(modelItem => email.Address)有:
@Html.EditorForMany(x => x.Emails, x => x.Index, false);
@Html.EditorForManyIndexField(x => x.Index)
(注:如果你不是在<tr>, <tbody> or <ul>
或類似的代碼將是@ Html.EditorForMany( x => x.Emails,x => x.Index),您不需要@ Html.EditorForManyIndexField(x => x.Emails,x => x.Index)或@ Html.EditorForManyIndexField(x => x.Index )。如果沒有設置Indexfield,你的桌子將會格式不對,因此我們這樣做。)
現在我們所有的專業人士瑕疵解決了!你會看到Html.EditorForMany()使用GUID而不是索引號碼。這消除了我們需要告訴我們的AJAX端點使用了哪些索引;因爲我們的AJAX端點只會生成一個新的GUID。 Html.EditorForMany()也爲我們無縫地生成.Index字段。
剩下要做的就是讓我們的AJAX端點啓動並運行。爲此,我在Controller中定義了一個新的動作。
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult AddEmail()
{
var user = new UserModel();
user.Emails.Add(new Email());
return View(user);
}
創建一個新視圖Views \ Shared \ AddEmail.cshml;
@model DynamicListBinding.Models.UserModel
@{
Layout = null;
}
@Html.EditorForMany(x => x.Emails, x => x.Index, false);
榮譽給馬特原創article
綁定到模型只是意味着表單元素具有相同的名稱。但類型也需要能夠匹配。 – 2012-03-28 21:16:34