Tôi sẽ sử dụng một phương pháp mở rộng thay vào đó bạn có thể sử dụng trong các trường hợp khác cũng như:
Extension:
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());
}
}
Thêm một tài sản với mô hình, mà helper EditorForMany sẽ lưu trữ các chỉ số tạo Nếu không có điều này, các phương thức Html.Validation * sẽ không hoạt động (xem here để tìm hiểu sâu hơn về "lý do" cho tò mò).
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; }
}
thay thế @ Html.EditorFor (modelItem => email.Address) với:
@Html.EditorForMany(x => x.Emails, x => x.Index, false);
@Html.EditorForManyIndexField(x => x.Index)
(Lưu ý: Nếu bạn đang không ở trong một <tr>, <tbody> or <ul>
hoặc tương tự mã sẽ là @ Html.EditorForMany (x => x.Emails, x => x.Index) và bạn sẽ không cần @ Html.EditorForManyIndexField (x => x.Emails, x => x.Index) hoặc @ Html.EditorForManyIndexField (x => x.Index Nếu không có thiết lập Indexfield cho mình bảng của bạn sẽ được định dạng sai và do đó chúng tôi làm điều đó như thế này.)
Bây giờ tất cả các chuyên nghiệp của chúng tôi blems được giải quyết! Bạn sẽ thấy rằng Html.EditorForMany() sử dụng GUID thay vì số cho các chỉ mục. Điều này loại bỏ sự cần thiết phải cho chúng tôi để nói cho điểm cuối AJAX của chúng tôi mà chỉ mục như được sử dụng; như điểm cuối AJAX của chúng tôi thay vào đó sẽ chỉ tạo ra một GUID mới. Html.EditorForMany() cũng chăm sóc liên tục tạo ra trường .Index cho chúng ta.
Tất cả những gì còn lại cần làm là bắt đầu và chạy điểm cuối AJAX của chúng tôi. Để làm điều này, tôi xác định một hành động mới trong Controller của tôi.
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult AddEmail()
{
var user = new UserModel();
user.Emails.Add(new Email());
return View(user);
}
Tạo chế độ xem mới Chế độ xem \ Shared \ AddEmail.cshml;
@model DynamicListBinding.Models.UserModel
@{
Layout = null;
}
@Html.EditorForMany(x => x.Emails, x => x.Index, false);
Kudos Matt cho ban article
Ràng buộc với mô hình chỉ có nghĩa là các yếu tố hình thức có cùng tên. Nhưng loại cần phải có thể được kết hợp là tốt. –