ASP.Net Web Api + KnockoutJs + MVC4 - Tying it together
Asked Answered
R

2

9

I am starting a new project, and keen to make use of the KnockoutJS + Web Api which are new to me, I have a good understanding of the Web Api, but Knockout is tough to get my head around at the moment.

This is my initial thoughts of how I want my app to work:

  • I have a standard MVC controller such as LeadsController
  • LeadsController has an Action called ListLeads, this doesn't actually return any data though, but just returns a view with a template to display data from Knockout.
  • The ListLeads view calls my api controller LeadsApiController via ajax to get a list of leads to display
  • The leads data is then mapped to a KnockoutJs ViewModel (I don't want to replicate my view models from server side into JavaScript view models)
  • I want to use external JavaScript files as much as possible rather than bloating my HTML page full of JavaScript.

I have seen lots of examples but most of them return some initial data on the first page load, rather than via an ajax call.

So my question is, how would create my JavaScript viewModel for Knockout when retrieved from ajax, where the ajax url is created using Url.Content().

Also, what if I need additional computed values on this ViewModel, how would I extend the mapped view model from server side.

If I haven't explained myself well, please let me know what your not sure of and I'll try and update my question to be more explicit.

Republican answered 17/8, 2012 at 0:48 Comment(0)
G
5

I think your design is a good idea. In fact, I am developing an application using exactly this design right now!

You don't have to embed the initial data in your page. Instead, when your page loads, create an empty view model, call ko.applyBindings, then start an AJAX call which will populate the view model when it completes:

$(function () {
    var viewModel = {
        leads: ko.observableArray([]) // empty array for now
    };

    ko.applyBindings(viewModel);

    $.getJSON("/api/Leads", function (data) {
        var newLeads = ko.mapping.fromJS(data)(); // convert to view model objects
        viewModel.leads(newLeads); // replace the empty array with a populated one
    });
});

You'll want to put a "Loading" message somewhere on your page until the AJAX call completes.

To generate the "/api/Leads" URL, use Url.RouteUrl:

<script>
    var apiUrl = '@Url.RouteUrl("DefaultApi", new { httproute = "", controller = "Leads" })';
</script>

(That's assuming your API route configured in Global.asax or App_Start\RouteConfig.cs is named "DefaultApi".)

The knockout mapping plugin is used above to convert the AJAX JSON result into a knockout view model. By default, the generated view model will have one observable property for each property in the JSON. To customise this, such as to add additional computed properties, use the knockout mapping plugin's "create" callback.

After getting this far in my application, I found I wanted more meta-data from the server-side view models available to the client-side code, such as which properties are required, and what validations are on each property. In the knockout mapping "create" callbacks, I wanted this information in order to automatically generate additional properties and computed observables in the view models. So, on the server side, I used some MVC framework classes and reflection to inspect the view models and generate some meta-data as JavaScript which gets embeded into the relevant views. On the client side, I have external JavaScript files which hook up the knockout mapping callbacks and generate view models according the meta-data provided in the page. My advice is to start out by writing the knockout view model customisations and other JavaScript by hand in each view, then as you refactor, move generic JavaScript functions out into external files. Each view should end up with only the minimal JavaScript that is specific to that view, at which point you can consider writing some C# to generate that JavaScript from your server-side view model annotations.

Green answered 17/8, 2012 at 12:9 Comment(4)
Hi Joe.Your approach is absolutely correct and a lot of tutorials/blogs i have been looking at have a similar solution.I have been evaluating this approach for a large web application that we are starting.My problem with it is writing a lot of JS which i feel is difficult to manage.Also what is the point of using MVC & WebAPI controllers and why use a MVC controller for passing an empty view? seems like its creating duplicity.Billfold
MCV returns the static HTML for the page. Web API returns the view model JSON data for the page. If you don't like an "empty" view but still want to use Knockout, you can instead serialize the view model data to JSON on the server and inject it into the HTML view, as long as you don't want to use AJAX and don't mind that the HTML is no longer cacheable.Green
There are lots of solutions nowadays for managing large JS applications. In the year since I wrote this answer, we now have Breeze.js and Durandal to organize everything and implement the plumbing. Check out Breeze's TempHire demo project.Green
@JoeDaley Hi, I have just read your answer and would like to know that how are you handling server side validation for a view that uses knockoutjs i.e. how & what to do if model is not valid as most of the validation done using data annotations at model level is not usable in view now for this knockoutjs approach. please help.Avantgarde
E
1

For the url issue add this in your _Layout.cshtml in a place where it is before the files that will use it:

<script>
    window._appRootUrl = '@Url.Content("~/")';
</script>

Then you can use the window._appRootUrl to compose urls with string concatenation or with the help of a javascript library like URI.js.

As for the additional computed values, you may want to use a knockout computed observable. If that is not possible or you prefer to do it in .Net you should be able to create a property with a getter only, but this won't update when you update other properties on the client if it depends on them.

Equally answered 17/8, 2012 at 2:43 Comment(1)
Thank you for the answer, will look into this this afternoon. I was referring to using a knockout computed observable...was just wondering how i convert a ViewModel created by the knockout mapping plugin to add new computed values to it...do i extend it somehow? Thanks.Republican

© 2022 - 2024 — McMap. All rights reserved.