MVC Series Part 1: Dynamically Adding Items Part 1

Introduction

Recently, I have been exposed to working on a project using ASP.NET MVC 5. My experience working with MVC for the first time was overall positive, but it did run into quite a few hair pulling scenarios. My background up to this point primarily dealt with C# WPF applications, but with a little bit of web development on the side.  In this series, I will discuss some of the major issues that led to many sleepless nights working on this project. And although these scenarios may not seem to be the most difficult to an experienced MVC developer, these are the problems that caused major hiccups to a first timer like myself.

Dynamically Adding Items

Click here to download code and sample project

In one of the first major issues I needed to attempt to solve was trying to add items dynamically to a View. Representing a collection already populated is fairly straight forward using MVC’s Razor, as represented here in my collection of classical books:

<dl class="dl-horizontal" id="booksContainer">
        @foreach ( var classicBook in @Model.ClassicBooks )
        {
            <dl class="dl-horizontal">
                <dt>
                    @Html.DisplayNameFor( model => classicBook.Title )
                </dt>
                <dd>
                    @Html.DisplayFor( model => classicBook.Title )
                </dd>

                <dt>
                    @Html.DisplayNameFor( model => classicBook.Author )
                </dt>
                <dd>
                    @Html.DisplayFor( model => classicBook.Author )
                </dd>
            </dl>
        }
</dl>

We can even go one step further and place this inside a ‘DisplayTemplate’ and push the display content into a partial view:
dynamic img 1

<dl class="dl-horizontal" id="booksContainer">
        @foreach ( var classicBook in @Model.ClassicBooks )
        {
            @Html.DisplayFor(model => classicBook);
        }
</dl>

But how do we add content on the fly?

Well, we could have a button that inserts javascript into our ‘booksContainer’ element and this would provide a seamless experience for the user. But what if we decide to change how the view looks? And how could we get this to post back to MVC server? What about if we decided to take in an image file as well?

Given I did not want to update the view in two places, I managed to find a solution that managed to do all of the above, linked here.

In summary, I was able to use an ajax call to retrieve my BookViewModel’s partial html view from the server and inject it into an html element:

  • Adding an editor partial view for the BookViewModel

dynamic img2

  • Including the html element that will take in new books:
    <div id="newBooks">
        @for ( int i = 0; i < @Model.NewBooks.Count(); i++ )
    
        {
    
            @Html.EditorFor( model => @Model.NewBooks[i] )
    
        }
    
    </div>
  • On the server, the HomeController’s method that simply returns my partial view:
    public ActionResult CreateNewBook()
    {
    
        var bookViewModel = new BookViewModel();
    
        return PartialView( "~/Views/Shared/EditorTemplates/BookViewModel.cshtml", bookViewModel );
    
    }
    
    
  • The ajax call that handles the add book button click and injects the returned partial view:
    @section Scripts {
        <script type="text/javascript">
    
            $("#addbook").on('click', function () {
    
                $.ajax({
                    async: false,
                    url: '/Home/CreateNewBook'
                }).success(function (partialView) {
    
                    $('#newBooks').append(partialView);
    
                });
            });
    
        </script>
    }
    

And now we can dynamically add items on button click! The user is able to add as many books as they want, click submit, and the collection of books will get received on HttpPost. But, when we put a break post on our post method, the NewBooks collection comes back as empty. Why is this happening?

Well the reason is because MVC can only bind back a collection of items if it can uniquely identify each one. This article actually describes how you can get a collection of items to bind appropriately on post back, linked here.

Unfortunately, this does not apply when items are getting added dynamically. Luckily, the previous article also solved this problem by creating a Html Helper class, BeginCollectionItem, that adds a unique identifier to every new inserted item. All we need to do is modify our BookViewModel editor template to include it:

@using ( Html.BeginCollectionItem( "NewBooks" ) )
{
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor( model => @Model.Title )
        </dt>
        <dd>
            @Html.EditorFor( model => @Model.Title )
        </dd>

        <dt>
            @Html.DisplayNameFor( model => @Model.Author )
        </dt>
        <dd>
            @Html.EditorFor( model => @Model.Author )
        </dd>

        <dt>
            @Html.DisplayName( "Book Cover File Image" )
        </dt>
        <dd>
            @Html.TextBoxFor( m => @Model.BookCoverUrl, null, new { type = "file", @class = "input-file" } )
        </dd>

    </dl>
}

And with that we can now dynamically add items and they will uniquely get posted back onto the server.

5 thoughts on “MVC Series Part 1: Dynamically Adding Items Part 1

  1. Pingback: MVC Series Part 1: Dynamically Adding Items Part 2 | //InterKnowlogy/ Blogs

  2. Pingback: MVC Series Part 2: AccountController Testing | //InterKnowlogy/ Blogs

  3. Pingback: MVC Series Part 3: Miscellaneous Issues | //InterKnowlogy/ Blogs

  4. Great post. I have been trying to solve this issue for a while without having to resort to angular or knockout. One thing though, why not use a Ajax.ActionLink to call CreateNewBook() and set the UpdateTargetId equal to newBooks and set the InsertionMode equal to InsertionMode.ReplaceAfter? That will get rid of the need for any javascript on the page :)

    • You are absolutely right! And this goes back to novice experience with MVC :)

      To clarify for others the change involves removing the original ‘input’ button to this Razor Ajax button:

      @Ajax.ActionLink( "Add Book", "CreateNewBook", new AjaxOptions
      {
      HttpMethod = "GET",
      InsertionMode = InsertionMode.InsertAfter,
      UpdateTargetId = "newBooks"
      } )

      Behind the scenes, this is how the button is represented as straight HTML:

      a data-ajax="true" data-ajax-method="GET" data-ajax-mode="after" data-ajax-update="#newBooks" href="/Home/CreateNewBook" rel="nofollow">Add Book

      This also allows us to remove all the scripting for the Index.cshtml page, but I was still having issues clicking the button without it redirecting me to a new page. Then I stumbled upon this article (scroll down to the ‘Ajax.ActionLink’ section), which mentioned including a jquery.unobtrusive-ajax.js file. This script will interrupt the submission process for items that have the data-ajax attribute, which we have seen Razor creates such an element when it renders to the web page.

      If there is a slicker way of doing this please let me know, this was a good learning experience :)

      Here is a link to the updated project with the Ajax.ActionLink button

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>