如何将行添加到我的模型中的集合列表中?
为了简单起见,假设我有一个User
模型,其List<Email>
作为其属性之一。如何将行添加到我的模型中的集合列表中?
public class UserModel
{
public string UserName { get; set; }
public List<Email> Emails = new List<Email>();
}
public class Email
{
public string Address { get; set; }
}
在我看来,我的电子邮件列表:
<table>
@foreach(Email email in Model.Emails)
{
<tr>
<td>@Html.EditorFor(modelItem => email.Address)</td>
</tr>
}
</table>
现在让我们说,我希望用户能够点击一个按钮,添加一个新行的表,以便用户可以将新的电子邮件添加到绑定到其用户的列表中。我该怎么做呢?我是否需要通过javascript以某种方式添加新行,以便在页面发布时绑定到模型?我不知道如何解决这个问题,因为我对来自WebForms的MVC比较陌生。
首先,你的模型定义需要一些调整:
public class UserModel
{
public string UserName { get; set; }//not sure where to use this
//for listing
public List<Email> Emails { get; set; }
//for adding
public Email Email { get; set; }
public UserModel()
{
this.Emails = new List<Email>();//remember to populate in the controller
}
}
接下来,你可以做的是(不知道你的桌子上实现)显示当前电子邮件的列表,然后有一个表格节可以发布新邮件地址:
@model namespace.UserModel
@foreach(var email in Emails)
{
<div>@email.Address</div>
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>New Email Details</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Email.Address)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Email.Address)
@Html.ValidationMessageFor(model => model.Email.Address)
</div>
<p>
<input type="submit" value="Add Email" />
</p>
</fieldset>
}
一个疑难问题:网页上会有其他用户信息...因此您无法立即提交/回复添加电子邮件。只有在他们填写完整的表格后(例如,编辑用户名,可能是密码字段等)。忘记了那个关键部分。每个电子邮件的每个“行”实际上是一个输入文本框字段。因此,您可以在完整提交表单之前为每个需要添加的电子邮件添加每个“行”。 – 2012-03-28 21:29:49
@BryanDenny - 如果你希望有一个用户点击一个按钮来添加一个新的输入行,一旦输入一个电子邮件,那么必须使用JavaScript/jQuery来完成,因为它是动态的。 post_erasmus提供的答案对使用ajax的更加动态的方法有很好的建议。 – 2012-03-28 21:56:58
这是MVC和WebForms显着分歧的地方之一。
如果我这样做,我会使用AJAX提交新的电子邮件地址,并返回一个JSON对象或作为部分视图呈现的电子邮件表。这样你就不必重新加载整个页面。这里是一个例子,它将使用jQuery从AJAX调用中返回HTML,因为我不是MVC本地AJAX功能的粉丝。
原始景观:
@*HTML/Razor*@
@Html.Partial("EmailTable", Model.Emails)
@*HTML/Razor*@
管窥:EmailTable
@model List<Email>
<table id='UserEmails'>
@foreach(var email in Model)
{
<tr>
<td>@Html.EditorFor(modelItem => email.Address)</td>
</tr>
}
</table>
控制器动作:AddEmail
public ActionResult AddEmail(string email, object someUserIdentifier){
//if email is valid
//add email to user's data store
//get new UserModel, user
return PartialView("EmailTable", user.Emails);
}
jQuery来处理按钮点击
function AddEmail(e){
var newEmailForm = $("<form />").attr("action", urlToController + "/AddEmail/").submit(SaveEmail);
$("<input/>").attr({type: "text", id="NewEmailAddress"}).appendTo(newEmailForm);
$("<input/>").attr("type", "submit").click(SaveEmail).appendTo(newEmailForm);
newEmailForm = $("<td />").append(newEmailForm);
newEmailForm = $("<tr />").append(newEmailForm);
$('#UserEmails').append(newEmailForm);
}
function SaveEmail(e){
var newEmail = $("#NewEmailAddress").val();
if (/*newEmail is valid*/){
$.ajax({
url: urlToController + "/AddEmail/",
data: { email: newEmail, someUserIdentifer: null/*or something useful*/ },
success: function(newTable){
$('#UserEmails').replaceWith(newTable);
},
error: function(xhr, status, error){
//display error
}
});
}
else{
//tell user what a valid email address looks like
}
return false;
}
你有没有考虑过使用第三方工具呢?
我在CodeProject上找到了它,它似乎符合您的要求。是的,它会需要一些调整,但它应该做的工作就在JavaScript/jQuery的实现类似的功能
http://www.codeproject.com/Articles/277576/AJAX-based-CRUD-tables-using-ASP-NET-MVC-3-and-jTa
或者你可以花上几个小时。
一些经过研究,我发现这个博客帖子史蒂芬·安德森http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
这似乎做我想要什么(除了它是写在MVC2)。
此解决方案的主要限制是儿童对象有其自己的形式。这可以防止行为,例如在一篇文章中添加一个带有子项的新父项。 – andrewb 2015-07-21 05:36:11
我会用一个扩展方法,而不是你可以在其他情况下使用,以及:
扩展:
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