2012-03-28 29 views
9

Vì mục đích đơn giản, giả sử tôi có một mô hình UserList<Email> là một trong các thuộc tính của nó.Làm cách nào để thêm hàng vào danh sách bộ sưu tập trong Mô hình của tôi?

public class UserModel 
{ 
    public string UserName { get; set; } 
    public List<Email> Emails = new List<Email>(); 
} 

public class Email 
{ 
    public string Address { get; set; } 
} 

Theo quan điểm của tôi, tôi có một danh sách các email:

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

Bây giờ chúng ta hãy nói rằng tôi muốn người dùng có thể bấm vào một nút có thêm một hàng mới vào bảng để người dùng có thể thêm một Email mới vào Danh sách bị ràng buộc với Người dùng của họ. Làm thế nào để tôi làm điều này? Tôi có cần phải thêm hàng mới qua javascript theo một cách nhất định để nó bị ràng buộc với mô hình khi trang được đăng không? Tôi không có ý tưởng làm thế nào để tiếp cận này như tôi là tương đối mới với MVC đến từ WebForms.

+0

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. –

Trả lời

0

Đầu tiên, định nghĩa mô hình của bạn cần một số tinh chỉnh:

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

Tiếp theo, những gì bạn có thể làm là (không chắc chắn về việc thực hiện bảng) hiển thị danh sách các email hiện tại, và sau đó có một phần hình thức mà có thể đăng email mới để thêm:

@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

One gotcha problem: Sẽ có thông tin người dùng khác trên trang ... vì vậy bạn không thể gửi/đăng lại ngay lập tức tại Thêm email. Chỉ sau khi họ điền vào biểu mẫu đầy đủ (ví dụ: chỉnh sửa tên người dùng, có thể là trường mật khẩu, v.v.). Quên phần quan trọng đó. Mỗi "hàng" cho mỗi Email thực sự là một trường textbox đầu vào. Vì vậy, bạn có thể thêm mỗi "hàng" cho mỗi Email bạn cần thêm trước khi biểu mẫu được gửi đầy đủ. –

+0

@BryanDenny - Nếu bạn muốn người dùng nhấp vào nút để thêm hàng nhập mới khi email được nhập thì điều đó sẽ phải được thực hiện với javascript/jquery vì nó là động. Câu trả lời từ post_erasmus có một gợi ý tốt cho một cách tiếp cận năng động hơn bằng cách sử dụng ajax. –

4

Đây là một trong những nơi mà MVC và WebForms phân kỳ đáng kể.

Nếu tôi làm điều này, tôi sẽ sử dụng AJAX để gửi địa chỉ email mới và trả về đối tượng JSON hoặc bảng email được hiển thị dưới dạng Chế độ xem một phần. Bằng cách đó bạn không phải tải lại toàn bộ trang. Dưới đây là ví dụ sẽ trả về HTML từ cuộc gọi AJAX, sử dụng jQuery vì tôi không phải là fan của chức năng AJAX gốc của MVC.

Original Xem:

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

Partial View: EmailTable

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

điều khiển hành động: 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 để xử lý các nút bấm

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

Bạn có cân nhắc sử dụng công cụ của bên thứ ba cho việc này không?

Tôi đã tìm thấy điều này trên CodeProject và nó dường như đáp ứng yêu cầu của bạn. Có nó sẽ đòi hỏi một chút tinh chỉnh, nhưng nó phải thực hiện công việc

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

Hoặc bạn có thể bỏ ra hàng giờ về việc thực hiện chức năng tương tự trong JavaScript/jQuery.

11

Sau khi một số nghiên cứu, tôi thấy bài blog này bởi Steven Anderson http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Nó dường như được làm chính xác những gì tôi muốn (ngoại trừ nó được viết bằng MVC2).

+0

Hạn chế chính của giải pháp này là các đối tượng trẻ em có hình thức riêng của chúng. Điều này ngăn chặn hành vi như thêm một phụ huynh mới với trẻ em trong một bài đăng. – andrewb

1

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

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

Các vấn đề liên quan