MVC pass JSON ViewModel to View

asp.net-mvc-3jsonpknockout.jsviewmodel

I have an MVC application that I'm using various JsonResult endpoints to populate the javascript ViewModel.

I have been using several jQuery Ajax requests to populate the model, but I'd like as much of the inital model to be passed to the view on the server.

The ViewModel has 3-5 pieces (depending on where the user is in the application):

  1. Basic page links, these don't change very often and could be the exact same throughout the entire user's session
  2. User notifications.
  3. User data.
  4. (optional) Viewable data
  5. (optional) misc data

I'm currently using this code to load the first three pieces:

$(document).ready(function () {

    ko.applyBindings(viewModel);
    @Html.Raw(ViewBag.Script)

    // Piece 1.  Almost always the same thing
    postJSON('@Url.Action("HomeViewModelJson", "Home")', function (data) {

        if (data == null)
            return;

        for (var i in data.Tabs) {
            viewModel.tabs.push({ name: data.Tabs[i] });
        }

        for (var i in data.Buttons) {
            viewModel.metroButtons.push({ name: data.MetroButtons[i] });
        }

        for (var i in data.Ribbons) {
            viewModel.ribbons.push(data.Ribbons[i]);
        }
        ApplyButtonThemes();
    });
});


// Piece 2.  Changes constantly. OK as is
postJSON('@Url.Action("GetNotifications", "NotificationAsync")', function (nots) {
    viewModel.notifications.removeAll();

    ko.utils.arrayForEach(nots, function (item) {
        item.readNotification = function () {
            hub.markNotificationAsRead(this.Id);
            return true;
        };
        viewModel.notifications.push(item);
    });
});

// Piece 3. Changes but should also be loaded at startup
postJSON('@Url.Action("GetUser", "UserAsync")', function (user) {
    viewModel.user(koifyObject(user));
});


postJSON = function(url, data, callback) {
    if($.isFunction(data)) {
        callback = data;
        data = {};
    }
    $.ajax({
        'type': 'POST',
        'url': url,
        'contentType': 'application/json',
        'data': ko.toJSON(data),
        'dataType': 'json',
        'success': callback
    });
};

I tried doing something like this, but I'm finding that by using the @Html.Action("HomeViewModelJson", "Home") is causing the HTTP headers to get changed and the whole page is sent as if it were JSON

       (function (data) {

            if (data == null)
                return;

            for (var i in data.Tabs) {
                viewModel.tabs.push({ name: data.Tabs[i] });
            }

            for (var i in data.MetroButtons) {
                viewModel.metroButtons.push({ name: data.MetroButtons[i] });
            }

            for (var i in data.Ribbons) {
                viewModel.ribbons.push(data.Ribbons[i]);
            }
            ApplyMetroButtonThemes();
        })('@Html.Action("HomeViewModelJson", "Home")');

What I'd like to do is use the existing JsonResult endpoints to get Json data into my ViewModel on the server side, before the page is sent to the user.

Are there any options that will allow me to do that w/o rewriting my controllers?

Best Answer

When rendering the main view you are using a view model, right? In this view model simply populate the properties that you don't want to be fetched with AJAX before returning the view:

public ActionResult Index()
{
    MyViewModel model = ...
    model.Prop1 = ...
    model.Prop2 = ...
    return View(model);
}

for example if you have the following action that is used for the AJAX requests:

public JsonResult GetProp1()
{
    Property1ViewModel model = ...
    return Json(model, JsonRequestBehavior.AllowGet);
}

you could use it from the main action to populate individual properties:

model.Prop1 = (Property1ViewModel)GetProp1().Data;
model.Prop2 = (Property2ViewModel)GetProp2().Data;

and then inside the corresponding view you could use the Json.Encode method to serialize the entire model into a JSON string:

@model MyViewModel
<script type="text/javascript">
    var model = @Html.Raw(Json.Encode(Model));
    // You could use model.Prop1 and model.Prop2 here
</script>

or you could also serialize individual properties if you don't need all of them:

@model MyViewModel
<script type="text/javascript">
    var prop1 = @Html.Raw(Json.Encode(Model.Prop1));
</script>
Related Topic