2012-03-28 74 views
9

爲了簡單起見,假設我有一個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比較陌生。

+0

綁定到模型只是意味着表單元素具有相同的名稱。但類型也需要能夠匹配。 – 2012-03-28 21:16:34

回答

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

一個疑難問題:網頁上會有其他用戶信息...因此您無法立即提交/回覆添加電子郵件。只有在他們填寫完整的表格後(例如,編輯用戶名,可能是密碼字段等)。忘記了那個關鍵部分。每個電子郵件的每個「行」實際上是一個輸入文本框字段。因此,您可以在完整提交表單之前爲每個需要添加的電子郵件添加每個「行」。 – 2012-03-28 21:29:49

+0

@BryanDenny - 如果你希望有一個用戶點擊一個按鈕來添加一個新的輸入行,一旦輸入一個電子郵件,那麼必須使用JavaScript/jQuery來完成,因爲它是動態的。 post_erasmus提供的答案對使用ajax的更加動態的方法有很好的建議。 – 2012-03-28 21:56:58

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

相關問題