2012-09-11 52 views
9

我有使用Razor爲我的MVC應用程序編輯頁面。如何使用Razor爲ASP.NET MVC 4中的List屬性創建對象的編輯表單

我有一個像型號:

public class MyModelObject 
{ 
    public int Id { get; set; } 

    public string Name { get; set; } 

    public string Description { get; set; } 

    public List<MyOtherModelObject> OtherModelObjects { get; set; } 
} 

而且MyOtherModelObject樣子:

public class MyOtherModelObject 
{ 
    public string Name { get; set; } 

    public string Description { get; set; } 
} 

我正在編輯頁面MyModelObject。我需要一種方法在MyModelObject的編輯頁面上爲表單添加空間,以便用戶創建/添加儘可能多的MyOtherModelObject實例,如用戶所期望的其他模型對象列表。

我在想用戶可以點擊一個按鈕,它會爲另一個返回表單元素的PartialView(沒有表單標籤,因爲這是爲了編輯頁面上的表單的一部分)返回一個操作。當用戶添加了他們想要的所有MyOtherModelObjects並填寫數據時,他們應該能夠將他們的編輯保存到現有的MyModelObject中,將HttpPost保存到Edit動作中,並且希望所有MyOtherModelObjects都將在正確的列表中。

我還需要用戶在添加項目後能夠重新排列項目。

有誰知道如何使這項工作?使用此解決方案實施示例項目還是在線示例演練?

+0

你可能會看看http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx(儘管這是Razor的語法,還是MVC4它可能會給一個想法) –

+0

乍一看。看起來它可以用於列表,但它是否適用於另一個對象的一部分的List,並且它能夠綁定Model? – DaveH

回答

19

這個blog post包含一步一步的指導說明如何實現這一目標。


UPDATE:

正如在評論部分我將舉例說明如何一步一步適應上述條款,以您的方案要求。

型號:

public class MyOtherModelObject 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

public class MyModelObject 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
    public List<MyOtherModelObject> OtherModelObjects { get; set; } 
} 

控制器:

public class HomeController : Controller 
{ 
    public ActionResult Index() 
    { 
     var model = new MyModelObject 
     { 
      Id = 1, 
      Name = "the model", 
      Description = "some desc", 
      OtherModelObjects = new[] 
      { 
       new MyOtherModelObject { Name = "foo", Description = "foo desc" }, 
       new MyOtherModelObject { Name = "bar", Description = "bar desc" }, 
      }.ToList() 
     }; 
     return View(model); 
    } 

    [HttpPost] 
    public ActionResult Index(MyModelObject model) 
    { 
     return Content("Thank you for submitting the form"); 
    } 

    public ActionResult BlankEditorRow() 
    { 
     return PartialView("EditorRow", new MyOtherModelObject()); 
    } 
} 

視圖(~/Views/Home/Index.cshtml):

@model MyModelObject 

@using(Html.BeginForm()) 
{ 
    @Html.HiddenFor(x => x.Id) 
    <div> 
     @Html.LabelFor(x => x.Name) 
     @Html.EditorFor(x => x.Name) 
    </div> 
    <div> 
     @Html.LabelFor(x => x.Description) 
     @Html.TextBoxFor(x => x.Description) 
    </div> 
    <hr/> 
    <div id="editorRows"> 
     @foreach (var item in Model.OtherModelObjects) 
     { 
      @Html.Partial("EditorRow", item); 
     } 
    </div> 
    @Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" }) 

    <input type="submit" value="Finished" /> 
} 

部分(~/Views/Home/EditorRow.cshtml):

@model MyOtherModelObject 

<div class="editorRow"> 
    @using (Html.BeginCollectionItem("OtherModelObjects")) 
    { 
     <div> 
      @Html.LabelFor(x => x.Name) 
      @Html.EditorFor(x => x.Name) 
     </div> 
     <div> 
      @Html.LabelFor(x => x.Description) 
      @Html.EditorFor(x => x.Description) 
     </div> 
     <a href="#" class="deleteRow">delete</a> 
    } 
</div> 

腳本:

$('#addItem').click(function() { 
    $.ajax({ 
     url: this.href, 
     cache: false, 
     success: function (html) { 
      $('#editorRows').append(html); 
     } 
    }); 
    return false; 
}); 

$('a.deleteRow').live('click', function() { 
    $(this).parents('div.editorRow:first').remove(); 
    return false; 
}); 

備註:該BeginCollectionItem定製助手從我鏈接到同一條款而採取的,但是我在這裏提供它的答案的完整性的考慮:

public static class HtmlPrefixScopeExtensions 
{ 
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; 

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName) 
    { 
     var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName); 
     string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString(); 

     // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync. 
     html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex))); 

     return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex)); 
    } 

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix) 
    { 
     return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix); 
    } 

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName) 
    { 
     // We need to use the same sequence of IDs following a server-side validation failure, 
     // otherwise the framework won't render the validation error messages next to each item. 
     string key = idsToReuseKey + collectionName; 
     var queue = (Queue<string>)httpContext.Items[key]; 
     if (queue == null) 
     { 
      httpContext.Items[key] = queue = new Queue<string>(); 
      var previouslyUsedIds = httpContext.Request[collectionName + ".index"]; 
      if (!string.IsNullOrEmpty(previouslyUsedIds)) 
       foreach (string previouslyUsedId in previouslyUsedIds.Split(',')) 
        queue.Enqueue(previouslyUsedId); 
     } 
     return queue; 
    } 

    private class HtmlFieldPrefixScope : IDisposable 
    { 
     private readonly TemplateInfo templateInfo; 
     private readonly string previousHtmlFieldPrefix; 

     public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix) 
     { 
      this.templateInfo = templateInfo; 

      previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix; 
      templateInfo.HtmlFieldPrefix = htmlFieldPrefix; 
     } 

     public void Dispose() 
     { 
      templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix; 
     } 
    } 
} 
+1

不完全。它討論瞭如何執行可變長度的對象列表,但沒有討論如何爲可變長度的另一個對象的一部分對象列表執行此操作。 – DaveH

+0

@DaveH,但這將是輕而易舉的實施。所有你需要做的就是引入一個新的視圖模型,它有一個'IEnumerable '屬性列表,你試圖編輯,就像你在'MyModelObject'中的問題一樣。 –

+1

你說得輕鬆嗎?以及這將如何綁定回父對象MyModelObject。還有很大一部分答案需要解決,這可能涉及重寫MVC的默認ModelBind行爲。這樣做是否存在安全問題?我正在處理這個示例,並在其他幾個論壇帖子上找到了解決單個對象列表的問題,但還沒有完全看到我已經有效地解決了這個問題。 – DaveH

相關問題