Mark Johnson

Software EngineerLucky HusbandProud FatherHopeless NerdAmateur BuilderDisney FanaticNeglectful Blogger

ASP.NET MVC Model Binding with Knockout

Knockout is a JavaScript-based MVVM framework. If you’re reading this article I’m going to assume that you already know what Knockout is all about and are interested in using it in your ASP.NET MVC application. However, if you’re new to Knockout, I highly recommend checking out the official website at knockoutjs.com.

Let’s take a typical example of an ASP.NET MVC blogging application.

The model classes might look something like this:

public class Blog
{
    public Guid BlogId { get; set; }

    public string Name { get; set; }

    public List<Post> Posts { get; set; }
}
public class Post
{
    public Guid PostId { get; set; }

    public string Author { get; set; }

    public string Title { get; set; }

    public string Body { get; set; }

    public List<Comment> Comments { get; set; }
}
public class Comment
{
    public Guid CommentId { get; set; }

    public string Author { get; set; }

    public string Body { get; set; }
}

And the view might look something like this:

@model Blog
@{
    ViewBag.Title = Model.Name;
}
<h1>@Model.Name</h1>
<div>
@foreach (var post in Model.Posts)
{
    @Html.ActionLink(post.Title, "GetPost", new { PostId = post.PostId })<br />
}
</div>

In this example, bound to an instance of a Blog, the view will render a list of blog posts, each linking back to a controller action called “GetPost”, which is expected to render the Post.

That’s all fine and dandy, but what happens if we want to add some client side behavior using Knockout? Maybe we want to make this into a SPA and display blog posts without a page refresh. Or maybe we want to display a preview of the post when we mouseover the link.

First, rather than using serverside Razor to bind the data in the model to the view, let’s change that so we use Knockout bindings:

@model Blog
@{
    ViewBag.Title = Model.Name;
}
<h1 data-bind="text: Name"></h1>
<div data-bind="foreach: Posts">
    <a data-bind="text: Title"></a>
</div>

And next, to bind the model to that template using Knockout, we simply make a JavaScript call to ko.applyBindings:

<script type="text/javascript">
    ko.applyBindings(viewModel);
</script>

But where does the viewModel come from? In our Razor template, we used the “Model” keyword. But for our Knockout template, by the time we call applyBindings, we’re on the client side and no longer have access to the server-side model.

The easiest way is to render the contents of your server-side model right into your JavaScript as JSON. Let’s take a look at our completed view with that added:

@model Blog
@{
    ViewBag.Title = Model.Name;
}
<h1 data-bind="text: Name"></h1>
<div data-bind="foreach: Posts">
    <a data-bind="text: Title"></a>
</div>
<script type="text/javascript">
    var viewModel = @Html.Raw(Json.Encode(Model));
    ko.applyBindings(viewModel);
</script>

The secret sauce is inside the JavaScript block. Using Razor, we encode our model as JSON. When the Razor engine parses this view and renders it as HTML, the inline JavaScript block will contain all of the data in the server-side model as an object literal. So we simply assign that object to the viewModel variable and pass it to ko.applyBindings.

Now, the advantage of this approach is its simplicity and how nicely it fits into the ASP.NET MVC pattern. However, there are some potential disadvantages.

Rendering your entire model into an inline JavaScript block has the advantage of your page coming to life with the model already in memory. So there is no need to make any asynchronous calls out to the server to get the data you need. But this also has the disadvantage of increasing your initial payload size. You should also consider the security implications if your model contains any sensitive data.

If your model is relatively small this approach will likely be just fine, but there are more complex alternatives that can be used for larger scale applications. Sometime soon I’ll write another post with a few common ones.


2 Comments

  • I tried your solution but did not work. Please help if you can.
    1. My Views

    A] Index
    @model KnockOutJsMvcCreateArticle.Models.BookAuthorViewModel
    @using KnockOutJsMvcCreateArticle.Models
    @using Newtonsoft.Json;
    @{
    ViewBag.Title = “Index”;
    Layout = “~/Views/Shared/_Layout.cshtml”;
    }

    Index

    # of Authors @Model.Authors.Count

    @Html.Partial(“~/Views/Shared/_Author.cshtml”, Model.Authors)

    @section Scripts {

    }

    B] PartialView

    @using KnockOutJsMvcCreateArticle.Models
    @using Newtonsoft.Json;
    @model IEnumerable

    @{
    ViewBag.Title = “Authors”;
    }

    @Html.ActionLink(“Create New”, “Create”)
    @Model.Count()

    @Html.DisplayNameFor(model => model.FirstName)
    @Html.DisplayNameFor(model => model.LastName)

    var viewModel = @Html.Raw(Json.Encode(Model));
    ko.applyBindings(viewModel);

    2. My Controller

    public ActionResult Index()
    {

    Books = db.BookDB.ToList();
    Authors = db.AuthorDB.ToList();
    objBookAuthorViewModel.Authors = Authors;
    objBookAuthorViewModel.Books = Books;
    return View(“~/Views/Book/Index.cshtml”, objBookAuthorViewModel);
    }

    • your view model should be like this: var viewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));


Leave a comment

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