如何在模型中的行集列表中添加行?

时间:2012-03-28 21:03:59

标签: asp.net-mvc-3 razor

为简单起见,假设我有一个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相对较新。

5 个答案:

答案 0 :(得分:11)

经过一番研究,我发现了Steven Anderson的这篇博文http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

它似乎完全符合我的要求(除了它是用MVC2编写的)。

答案 1 :(得分:4)

这是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;
}

答案 2 :(得分:1)

我会使用扩展方法,您也可以在其他情况下使用:

扩展:

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 &lt;input /&gt; 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 &lt;input /&gt; 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 &lt;input /&gt;. 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 &lt;input /&gt;</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 =&gt; email.Address)替换为:

@Html.EditorForMany(x => x.Emails, x => x.Index, false);
@Html.EditorForManyIndexField(x => x.Index)

(注意:如果你不在<tr>, <tbody> or <ul>或类似的代码,那么代码将是@ Html.EditorForMany(x =&gt; x.Emails,x =&gt; x.Index),你不需要@ Html.EditorForManyIndexField(x =&gt; x.Emails,x =&gt; x.Index)或@ Html.EditorForManyIndexField(x =&gt; 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

答案 3 :(得分:0)

首先,您的模型定义需要进行一些调整:

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>
}

答案 4 :(得分:0)

您是否考虑过使用第三方工具?

我在CodeProject上找到了它,它似乎符合您的要求。是的,它需要一些调整,但它应该做的工作

http://www.codeproject.com/Articles/277576/AJAX-based-CRUD-tables-using-ASP-NET-MVC-3-and-jTa

或者,你可以花几个小时在JavaScript / jQuery中实现类似的功能。