But first, why Knockout? Is it still even a thing?
So here it is: I was at the DDD Conference at Microsoft's UK office in Reading, Berks a few weeks ago and attended a session on Blazor delivered by Jospeh Woodward, which was pretty good. During the session, Joseph got interactive and asked for a show of hands: "Who here still uses jQuery?" The question was asked in the same tone that you might use when asking "who here still smokes in the car with children in the back?" No one (including me, even though I do - still use jQuery extensively, that is) put their hand up, perhaps sensing the possible reaction if they did:
It just so happened that this question built on a comment from Steve Sanderson to me when he showed how to use databinding in Blazor to avoid having to deal with the DOM directly. And it all got me to thinking that perhaps its about time that I should explore alternatives to using jQuery solely to update parts of pages.
As it happens, I have a large ASP.NET Web Pages LOB application (written in VB, would you believe?) that needs to be rewritten from the ground up. I have no interest in converting thie whole thing into a Single Page Application, so full SPA frameworks (which I would have to learn) are not required, but I could use an SPA approach to specific features, similar to the one demonstrated in the Knockout tuorial. I am happy using Razor Pages routing to navigate between features and to use server-side code to render as much of the initial state as possible. All I really need is a client-side framework that will help keep views in sync with the current state of their underlying model. There are plenty of choices for this, but Knockout is mature, stable, and pretty much all the questions about its use have been asked and answered somewhere, just a Google search away. I've written about using Knockout a couple of times in the past, so I have a reasonable idea how it works.
This was missing in my case and a full repair to my Visual Studio installation was needed.
This section assumes that you have already created a Razor Pages application. Once you have TypeScript support sorted in your IDE, you need to configure it for the application. You can accomplish this by adding a tsconfig.json file to the root of the project:
This is the default content generated by Visual Studio which will need a bit of modification:
The contents of the file provide instructions to the TypeScript compiler. I add
"compileOnSave": true, before the
to ensure that the compiler runs whenever i make a change to a
.ts file, and I remove wwwroot from the
because I will place my TypeScript file there. I also change the target to ES6
(which means that I won't be able to
support IE11, but that is my pleasure), and add an entry for
module with a
amd. This covers the module loader which I discuss later. The resulting tsconfig.json file should look like this:
"compileOnSave": true) and when you build the project
in Visual Studio. At the moment, that process will result in an error
Error TS18003 No inputs were found in config file 'PathTo/tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '["node_modules"]'.
If there are no TypeScript files in the locations specified in the
tsconfig.json file (which is everywhere in the project when no
include paths are specified), the compiler
returns the error above. To prevent this error, you just need to add an empty .ts file to the project somewhere. If you want to follow this article along,
simply add a file named webmail.ts to wwwroot/js.
Because you have nodejs installed, you can use its package manager to install Knockout.
There are countless other options for obtaining external libraries, some of which I may explore at a later date
- if they haven't already gone out of fashion. The command for installing Knockout
using the node package manager is
npm install knockout. If you are using Visual Studio, you can execute this command from the Package Manager Console. Otherwise you can use the Terminal in VS Code. In both cases, a node_modules folder will be created. Visual Studio will create the folder in the solution folder, i.e. one level up from the project. VS Code will create in the node_modules folder in the location that the command is executed
- usually the root of the project. Either way, you will want to copy the knockout-latest.js file from the download (node_modules/knockout/build/output) into your wwwroot folder. I create a folder structure that follows those for existing
third party libraries: wwwroot/lib/knockout/dist/ and place the knockout file there, renaming it to knockout.js.
code. You might get some code completion suggestions based on previous usage in
the same file if you are lucky. Because you can opt in to static typing with
TypeScript, you can define the data types of variables etc. And if you do that.
IntelliSense is a lot more helpful and the compiler can catch errors as you
type. That's OK for the basic types supported by TypeScript such as
and so on, but what about types defined in third party libraries?
Declaration files have a
.d.ts extension and "describe
in TypeScript by enabling various advanced IDE features that you
usually rely on when working with statically typed languages such as
IntelliSense. Declaration files are a design-time tool. They are not included as
part of the published output.
Declaration files are available from https://github.com/DefinitelyTyped/DefinitelyTyped where there are thousands of them for all sorts of libraries. That makes it kind of difficult to locate the one that you are interested in to get its installation command, but Microsoft have developed a search engine for declaration files here: https://microsoft.github.io/TypeSearch/.
The command for installing Knockout is
npm install --save @types/knockout. This will create a new folder in
Inside that, you will find a folder for Knockout containing an index.d.ts file. It doesn't matter whether this is located in the project folder or the soution folder. The TypeScript compiler will find it. You should avoid any temptation to create a copy of this file anywhere else in the project. If you do, the compiler will generate a number of "duplicate identifier" errors, not knowing which version of the declarations to use.
Module Loading and Dependency Resolution
When you create your Knockout ViewModel code in TypeScript, you will create a module. It will have
a dependency on Knockout. At runtime, something needs to be able to resolve and
load the dependency - a Module Loader. The recommended module loader for
use in web applications is RequireJS. You
can install this using the npm command:
npm install requirejs. Once installed, you may need to move a copy of the require.js file to a location that's accessible to browsing. To keep things consistent in relation to third party script libraries, I copy it to wwwroot/lib/requirejs/dist/.
Some Code, Finally
Now that the configuration and setup is complete, it's time to code. The end result of code will take you to the third stage in the Single Page Application tutorial on the Knockout site. At this point, you will have a web mail application that displays a lsit of mail by folder, and enables you to view the details of individual emails.
Mail Data Service, Entities and an API
The mail will come from a service. So to begin with, you need some data. I borrowed the data from the Knockout tutorial site. You can get it here. Create a folder named Data in the application and store the content of the Gist as webmail.json in it.
The code for the model follows:
These classes represent the entities that the application will work with. You may need to change the namespace to suit your application. I place this file in a folder named Models in the root of the application. Then I create a folder named Services and add a new C# class file with the following content:
And then register the service with the Dependendency Injection system (line 11):
Finally, I add a Web API controller named MailController.cs with the the following content:
The UI is a Razor Page named WebMail. It includes the following markup in the content page:
The page references a CSS style sheet named webmail.css (also acquired from the Knockout tutorial):
The style sheet is included in a
RenderSection, so a complementary method call is required in the
@RenderSection("Styles", required: false)
This line is placed just before the closing
Another line of code is placed just before the
RenderSection call for
scripts section just before the closing
</body> tag at the end of the file
to bring Requirejs into play:
I won't go into too much detail about how Knockout works. That isn't the
focus of this article. But the code declares a ViewModel with some properties
representing the mailbox folders, the currently selected folder and the
currently selected email message. The
goToFolder function enables the user to
move from one folder to another, and the contents of the folder are obtained
using the jQuery AJAX
get method from a server-side service. The
function uses the same technology to obtain the selected message from the mail service. The firing of these methods is controlled by declarative bindings to click events
in the UI. Data is bound to the UI using similar bindings.
Here is the typescript version which is added to the wwwroot/js folder as webmail.ts. When the TypeScript compiler runs, a file named webmail.js is generated in the same location:
The functionality of the code is identical. The
import statement at the top
of the file resolves Knockout as a dependency. The TypeScript code is based on
EcmaScript 2015, which introduced classes, modules, arrow functions, string interpolation and much
ViewModel is defined as a class, and its members are declared along with their
data types. The
KnockoutObservable<T> type is made available to the code via the
declaration file. The code also makes use of the
Fetch API rather than the standard XmlHttpRequest.
I have also declared a couple of entity classes at the end of the file, adding the bare minimum members to support IntelliSense:
And here is the default view when the application is run:
The full code for the application is available at this github repo.
In truth, the UI used for this demonstration could have been a plain HTML file. There is no real use of Razor in it. However, I could have made the arry of mail folders part of a Razor PageModel, along with the content of the inbox which forms the default view and rendered that on the server, which would speed the initial page load time.
Ultimately however, I am hoping that the Blazor experiment becomes an official thing from Microsoft. Then I will be able to just use C# for client-side development.