📜 ⬆️ ⬇️

Knockout MVC - The Power of Knockout.js for ASP.NET MVC

knockoutmvc For those who do not know, Knockout.js is a popular JavaScript library that allows you to implement the Model-View-View Model (MVVM) pattern on the client. On Habré already wrote a lot about him ( one , two , three , four , five , video ). You can master Knockout.js very quickly - after all, there is an interactive learning system , a lot of living examples (you can poke and look at the source code) and excellent documentation .

Very often, Knockout.js is used in conjunction with ASP.NET MVC - because the library greatly simplifies writing client logic. However, there are many typical problems for client-server development: the basic model and part of its processing logic have to be described both on the client (JavaScript) and on the server (C # / VB). In addition, there is a routine part related to client accessing server methods and transferring models to them for processing. But do not be sad! Now we have Knockout MVC - this is the .NET wrapper for Knockout.js, which generates all the necessary JavaScript code for us. It remains only to describe our model in C # and specify in MVVM-style for each desired html element to which property of the model you need to bind (or you can specify whole expressions - they will be translated into js). Thus, you can get a full-fledged cross-browser client web application without a single line of JavaScript!

And now a little more


Model


The model is written once on the server (say, in C #). On the client side, all JavaScript code is generated automatically. Moreover, we can describe not only ordinary properties, but also not very complex expressions that will be translated into JavaScript. And all this is done not only for the main model, but also for its submodels (on pure Knockout.js, this is not so easy to do). Well, for example, we describe this model:
public class Item { public string FirstName { get; set; } public string LastName { get; set; } public Expression<Func<string>> FullName() { return () => FirstName + " " + LastName; } } public class Model { public List<Items> Items { get; set; } } // ... var model = new Model { Items = new List<Item> { new Item {FirstName = "Annabelle", LastName = "Arnie"}, new Item {FirstName = "Bertie", LastName = "Brianna"}, new Item {FirstName = "Charles", LastName = "Cayenne"}, } }; 

and the following JavaScript is automatically generated by it:
 var viewModelJs = {"Items":[{"FirstName":"Annabelle","LastName":"Arnie"},{"FirstName":"Bertie","LastName":"Brianna"},{"FirstName":"Charles","LastName":"Cayenne"}]}; var viewModelMappingData = { 'Items': { create: function(options) { var data = ko.mapping.fromJS(options.data); data.FullName = ko.computed(function() { try { return this.FirstName()+' '+this.LastName()} catch(e) { return null; } ;}, data); return data; }}}; var viewModel = ko.mapping.fromJS(viewModelJs, viewModelMappingData); ko.applyBindings(viewModel); 

In this example, the resulting JavaScript is relatively simple, but with a slightly more complex model, writing it with pens is not so pleasant. And if you want to refactor it, then you will need to synchronously and very carefully change the JavaScript model and the C # model (in a normal application, the C # model will still be needed to describe the logic of actions on this model itself).

Data bindings


If you are working with ASP.NET MVC, then you probably use the Razor View Engine to describe the views. If not, then it is desirable to at least familiarize yourself with the functionality of this engine - it takes the layout of the site to a new level. Further examples of representations will go just under Razor.
Well, for example, I want to create a TextBox that will contain the value of some property A of our model. It is enough for me to write:
 @ko.Html.TextBox(m => mA) 

Brackets indicate a lambda expression that our model m matches a property to which I would like to bind data. And in this case we are writing an expression in C #, which means that IntelliSense helps us:
screen-IntelliSense
And if I accidentally write the name incorrectly, then I find out about it at the compilation stage - a red strip in the studio will show that somewhere I was wrong.
Well, now let's see an example more complicated. For example, I want part of a page to be displayed if and only if in my model two conditions are met simultaneously. All I need to write is:
 @using (ko.If(model => model.Condition1 && model.Condition2)) 

Next, the curly brackets indicate the corresponding part of the view. Simple, isn't it? After all, having written one such line, you can immediately forget about it - we do not need to track the state of Condition1 and Condition2, write js-code that will control the visibility of elements, etc. And if we go to rename one of the conditions or turn it into a computed property, then the refactoring magic will correct our expression itself (and, accordingly, the generated JavaScript will correct).
Knockout MVC contains many useful designs. For example, it is very easy to sort through the elements of a collection and, for each element, output something important:
 @using (var items = ko.Forearch(m => m.Items)) 

A complete description of all available syntax can be found in the documentation .
')

Accessing the server


Well, now let's try to create a button that calls some server method that performs operations on the model. We need to think about the following points in our JavaScript code:

Stop! Nothing needs to be done! You just need to write this line:
 @ko.Html.Button("Some text", "FooAction", "Foo") 

A thoughtful reader will ask: “And if I want to pass some parameters to the server method?” There is nothing simpler:
 @ko.Html.Button("Some text", "FooAction", "Foo", new { index = 0, caption = "Knockout MVC"}) 

And this is how we describe the corresponding method on the server:
 public class FooController : KnockoutController { public ActionResult FooAction(FooModel model, int index, string caption) { model.FooAction(index, caption); //     return Json(model); } } 

It's all! It is not necessary to write a single line! You can even forget about the fact that we have a client-server application with an accompanying technical routine - when developing we need to concentrate only on the logic of the model! See more about how it all works here.

Work with contexts


What is done in Knockout.js straight is not at all very convenient - it is work with contexts . Nested contexts occur when you use nested foreach and with constructs. If you have one level of nesting, then everything is fine. But if you write a complex application, in which there are 4-5 levels of nesting, then you can sometimes come up with constructions like $parents[2].name or $parentContext.$parentContext.$parentContext.$index . It is quite difficult to perceive such code, and when it comes to refactoring, it becomes completely sad. It's much easier to write in Knockout MVC - because now every context has its own name! And now the code looks like this:
 @using (var subModel = ko.With(m => m.SubModel)) { using (var subSubModel = subModel.With(m => m.SubSubModel)) { @subSubModel.Html.Span(m => ko.Model.A + " " + subModel.GetIndex() + " " + mB) } } 

The nested expression itself will turn into:
  <span data-bind="text : $parents[1].A()+' '+$parentContext.$index()+' '+B()"></span> 

You can see an example .

Other tasty buns


Among other things, the library has some additional features that can sometimes make life easier:

Summary


So let's summarize. Key features of Knockout MVC:

Links


Source: https://habr.com/ru/post/145903/


All Articles