简体   繁体   English

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

[英]How can I add rows to a collection list in my Model?

For the sake of simplicity, let's say I have a User model that has a List<Email> as one of its properties. 为简单起见,假设我有一个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; }
}

In my view, I have a list of the emails: 在我看来,我有一个电子邮件列表:

<table>
@foreach(Email email in Model.Emails)
{
    <tr>
        <td>@Html.EditorFor(modelItem => email.Address)</td>
    </tr>
}
</table>

Now let's say I want the user to be able to click a button that adds a new row to the table so that the user can add a new Email to the List that is bound to their User. 现在假设我希望用户能够单击向表中添加新行的按钮,以便用户可以将新电子邮件添加到绑定到其用户的列表中。 How do I do this? 我该怎么做呢? Do I need to add the new row via javascript in a certain way so that it gets bound to the model when the page is posted? 我是否需要通过javascript以某种方式添加新行,以便在发布页面时绑定到模型? I have no idea how to approach this as I'm relatively new to MVC coming from WebForms. 我不知道如何处理这个问题,因为我对来自WebForms的MVC相对较新。

After some researching, I found this blog post by Steven Anderson http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ 经过一番研究,我发现这篇博文由Steven Anderson 撰写http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

It appears to be doing exactly what I want (except it is written in MVC2). 它看起来正是我想要的(除了它是用MVC2编写的)。

This is one of those places where MVC and WebForms dramatically diverge. 这是MVC和WebForms显着不同的地方之一。

If I were doing this, I'd use AJAX to submit the new email address and return either a JSON object or the table of emails rendered as a Partial View. 如果我这样做,我会使用AJAX提交新的电子邮件地址并返回JSON对象或作为部分视图呈现的电子邮件表。 That way you don't have to reload the whole page. 这样您就不必重新加载整个页面。 Here's and example that would return the HTML from the AJAX call, using jQuery because I'm not a fan of MVC's native AJAX functionality. 以下是使用jQuery从AJAX调用返回HTML的示例,因为我不是MVC本机AJAX功能的粉丝。

Original View: 原始视图:

@*HTML/Razor*@
@Html.Partial("EmailTable", Model.Emails)
@*HTML/Razor*@

Partial View: EmailTable 部分视图:EmailTable

@model List<Email>
<table id='UserEmails'>
@foreach(var email in Model)
{
    <tr>
        <td>@Html.EditorFor(modelItem => email.Address)</td>
    </tr>
}
</table>

Controller Action: AddEmail 控制器操作: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 to handle the button click 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;
}

I would use an extension method instead that you can use in other cases as well: 我会使用扩展方法,你也可以在其他情况下使用:

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 &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());
    }
}

Add a property to the model, which the EditorForMany helper will store the generated index in. Without this, the Html.Validation* methods will not work (see here for a deep-dive into “why” for the curious). 向模型添加一个属性,EditorForMany帮助器将存储生成的索引。如果没有这个,Html.Validation *方法将无法工作(请参阅此处深入了解好奇的“为什么”)。

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

Substitute @Html.EditorFor(modelItem => email.Address) with: 用@ Html.EditorFor(modelItem => email.Address)代替:

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

(Note: If you are not in a <tr>, <tbody> or <ul> or similar the code would be @Html.EditorForMany(x => x.Emails, x => x.Index) and you would not need @Html.EditorForManyIndexField(x => x.Emails, x => x.Index) or @Html.EditorForManyIndexField(x => x.Index). Without setting Indexfield yourself your table would be badly formatted and therefore we do it like this.) (注意:如果你不在<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,您的表格格式将会很糟糕,因此我们这样做。)

Now all of our problems are solved! 现在我们所有的问题都解决了! You'll see that Html.EditorForMany() uses GUIDs rather than numbers for indexes. 您将看到Html.EditorForMany()使用GUID而不是索引的数字。 This removes the need for us to tell our AJAX endpoint which indexes as been used; 这使我们无需告诉我们的AJAX端点使用了哪些索引; as our AJAX endpoint will instead just generate a new GUID. 因为我们的AJAX端点只会生成一个新的GUID。 Html.EditorForMany() also takes care of seamlessly producing the .Index field for us as well. Html.EditorForMany()还负责为我们无缝生成.Index字段。

All that's left to do is to get our AJAX endpoint up and running. 剩下要做的就是让我们的AJAX端点启动并运行。 To do this, I define a new action in my Controller. 为此,我在Controller中定义了一个新动作。

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult AddEmail()
{
    var user = new UserModel();
    user.Emails.Add(new Email()); 
    return View(user);
}

Create a new view Views\\Shared\\AddEmail.cshml; 创建一个新视图Views \\ Shared \\ AddEmail.cshml;

@model DynamicListBinding.Models.UserModel
@{
    Layout = null;
}
@Html.EditorForMany(x => x.Emails, x => x.Index, false);

Kudos to Matt for original article 感谢Matt的原创文章

First, your model definition needs some tweaking: 首先,您的模型定义需要一些调整:

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

Next, what you can do is (not sure on your table implementation) display a list of the current emails, and then have a form section that can post a new email to add: 接下来,您可以做的是(不确定您的表实现)显示当前电子邮件的列表,然后有一个表单部分可以发布要添加的新电子邮件:

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

Have you considered using a third party tools for this? 您是否考虑过使用第三方工具?

I have found this on CodeProject and it appears to meet your requirements. 我在CodeProject上找到了它,它似乎符合您的要求。 Yes it'll require a bit of tweaking, but it should do the job 是的,它需要一些调整,但它应该做的工作

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

Alternatively you can spend hours on implementing similar functionality in JavaScript/jQuery. 或者,您可以花费数小时在JavaScript / jQuery中实现类似的功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM