Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Before getting into implementation of this task I would very strongly recommend you reading the <a href="http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/" rel="nofollow"><em>Editing a variable length list, ASP.NET MVC 2-style</em></a> from Steven Sanderson.</p> <p>Ready? </p> <p>OK, now we could get into the implementation.</p> <p>The first thing is to define our view model for the task. You've already had it, just define the corresponding validation rules on it:</p> <pre><code>public class Person { [Required] public string FullName { get; set; } [Required] public string LastName { get; set; } [Required] public string Address{ get; set; } } </code></pre> <blockquote> <p>and I suppose that my model for this page should be List</p> </blockquote> <p>Yeas, absolutely.</p> <p>So let's go ahead and create our <code>PersonsController</code>:</p> <pre><code>public class PersonsController : Controller { public ActionResult Index() { var model = new[] { new Person() }; return View(model); } [HttpPost] public ActionResult Index(IEnumerable&lt;Person&gt; persons) { if (!ModelState.IsValid) { return View(persons); } // To do: do whatever you want with the data // In this example I am simply dumping it to the output // but normally here you would update your database or whatever // and redirect to the next step of the wizard return Content(string.Join(Environment.NewLine, persons.Select(p =&gt; string.Format("name: {0} address: {1}", p.FullName, p.Address)))); } public ActionResult BlankEditorRow() { return PartialView("_PersonEditorRow", new Person()); } } </code></pre> <p>And now let's define the view (<code>~/Views/Persons/Index.cshtml</code>):</p> <pre><code>@model IEnumerable&lt;Person&gt; @using (Html.BeginForm()) { &lt;div id="editorRows"&gt; @foreach (var item in Model) { Html.RenderPartial("_PersonEditorRow", item); } &lt;/div&gt; @Html.ActionLink( "Add another person", "BlankEditorRow", null, new { id = "addItem" } ) &lt;p&gt; &lt;button type="submit"&gt;Next step&lt;/button&gt; &lt;/p&gt; } &lt;script type="text/javascript"&gt; $('#addItem').click(function () { $.ajax({ url: this.href, cache: false, success: function (html) { $('#editorRows').append(html); } }); return false; }); $(document).delegate('a.deleteRow', 'click', function () { $(this).parents('div.editorRow:first').remove(); return false; }); &lt;/script&gt; </code></pre> <p>and the corresponding partial view (<code>~/Views/Persons/_PersonEditorRow.cshtml</code>):</p> <pre><code>@model Person &lt;div class="editorRow"&gt; @using(Html.BeginCollectionItem("persons")) { &lt;div&gt; @Html.LabelFor(x =&gt; x.FullName) @Html.EditorFor(x =&gt; x.FullName) @Html.ValidationMessageFor(x =&gt; x.FullName) &lt;/div&gt; &lt;div&gt; @Html.LabelFor(x =&gt; x.LastName) @Html.EditorFor(x =&gt; x.LastName) @Html.ValidationMessageFor(x =&gt; x.LastName) &lt;/div&gt; &lt;div&gt; @Html.LabelFor(x =&gt; x.Address) @Html.EditorFor(x =&gt; x.Address) @Html.ValidationMessageFor(x =&gt; x.Address) &lt;/div&gt; &lt;a href="#" class="deleteRow"&gt;delete&lt;/a&gt; } &lt;/div&gt; </code></pre> <p>Remark: The <code>Html.BeginCollectionItem</code> helper used here is taken from Steven Sanderson's blog post that I have linked to previously in my answer and which you have already read and are familiar with. Here's the source code for completeness:</p> <pre><code>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 &gt; 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("&lt;input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" /&gt;", 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&lt;string&gt; 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&lt;string&gt;)httpContext.Items[key]; if (queue == null) { httpContext.Items[key] = queue = new Queue&lt;string&gt;(); 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; } } } </code></pre> <hr> <p>UPDATE:</p> <p>My bad, I've just noticed that your question is tagged with <code>asp.net-mvc-2</code>. So I guess my Razor views do not apply to your case. Still, everything else should work the same. All you need to do is update the views so that they use the WebForms view engine:</p> <p>Here's the <code>~/Views/Persons/Index.aspx</code>:</p> <pre><code>&lt;%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage&lt;IEnumerable&lt;Person&gt;&gt;" %&gt; &lt;% using (Html.BeginForm()) { %&gt; &lt;div id="editorRows"&gt; &lt;% foreach (var item in Model) { %&gt; &lt;% Html.RenderPartial("_PersonEditorRow", item); %&gt; &lt;% } %&gt; &lt;/div&gt; &lt;%= Html.ActionLink( "Add another person", "BlankEditorRow", null, new { id = "addItem" } ) %&gt; &lt;p&gt; &lt;button type="submit"&gt;Next step&lt;/button&gt; &lt;/p&gt; &lt;% } %&gt; &lt;script type="text/javascript"&gt; $('#addItem').click(function () { $.ajax({ url: this.href, cache: false, success: function (html) { $('#editorRows').append(html); } }); return false; }); $(document).delegate('a.deleteRow', 'click', function () { $(this).parents('div.editorRow:first').remove(); return false; }); &lt;/script&gt; </code></pre> <p>and finally the (<code>~/Views/Persons/_PersonEditorRow.ascx</code>) partial:</p> <pre><code>&lt;%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl&lt;Person&gt;" %&gt; &lt;div class="editorRow"&gt; &lt;% using(Html.BeginCollectionItem("persons")) { %&gt; &lt;div&gt; &lt;%= Html.LabelFor(x =&gt; x.FullName) %&gt; &lt;%= Html.EditorFor(x =&gt; x.FullName) %&gt; &lt;%= Html.ValidationMessageFor(x =&gt; x.FullName) %&gt; &lt;/div&gt; &lt;div&gt; &lt;%= Html.LabelFor(x =&gt; x.LastName) %&gt; &lt;%= Html.EditorFor(x =&gt; x.LastName) %&gt; &lt;%= Html.ValidationMessageFor(x =&gt; x.LastName) %&gt; &lt;/div&gt; &lt;div&gt; &lt;%= Html.LabelFor(x =&gt; x.Address) %&gt; &lt;%= Html.EditorFor(x =&gt; x.Address) %&gt; &lt;%= Html.ValidationMessageFor(x =&gt; x.Address) %&gt; &lt;/div&gt; &lt;a href="#" class="deleteRow"&gt;delete&lt;/a&gt; &lt;% } %&gt; &lt;/div&gt; </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload