Released public preview of DotVVM 3.0

Published: 2/27/2021 11:46:05 AM

We have a great news today! After almost a year of development, we have released a public preview of DotVVM 3.0.

In January 2020, we released DotVVM 2.4 which should have been the last release before DotVVM 3.0. However, we finished quite a lot of smaller features and improvements during the year and we wanted to deliver them to the customers faster – thus, we released DotVVM 2.5 in October.

DotVVM 3.0 brings several new features and plenty of improvements in the internal code base – this version should be more stable and reliable than the previous releases.


We’ve been talking about the news in DotVVM 3.0 on our last meetup:


JS directive – easy interoperability of DotVVM and JavaScript

The most interesting feature we ship with DotVVM 3.0 is called JS directive. It enables you to write code in JavaScript (or TypeScript) that is running together with the page and can interact with DotVVM components and the viewmodel.

It is very useful in two scenarios:

  • You have a 3rd party component or library you want to integrate in DotVVM page
  • You want to make changes in the viewmodel on your own – do things just locally in the browser, or maybe call some specific API (that cannot be called directly using REST API bindings).

A frequent requirement we were seeing was using SignalR to make real-time changes in the page – with JS directive, it is very easy to do.

Declare JavaScript module

The first thing you need to do is to declare a JavaScript module that exports a factory to create instance of a “view module” that will live side by side with your page. We are using the new ECMAScript syntax – you need to export a default function that accepts one argument – context – and returns an object.

export default (context) => new App(context);

class App {
    constructor(context) {
        this.context = context;

        // initialization
    }

    $dispose() {
    // optional function - will be run when the page is being unloaded
    }
}

You can of course use TypeScript or some module bundler – the only requirement is that the transpiler or bundler must produce ES modules. Currently, we don’t have a way to use webpack for that, but we have a demo of using rollup to bundle multiple modules together.

Import the module in the page

Next, you need to import the module in your page using the @js directive. You can import modules into pages, master pages or markup controls. Please note that the modules are bound to the markup file, so if you use a module in a markup control, its code or state won’t be accessible from the page that is using the control. If there are more markup controls in the page, each control will have its own instance of the module.

@js dashboard-module

You will need to register the JavaScript resource in DotvvmStartup.cs. If you are not using a bundler, it will probably specify some dependencies that need to be loaded earlier:

config.Resources.Register("dashboard-module", new ScriptModuleResource(new UrlResourceLocation("~/app/dashboard-module.js"))
{
    Dependencies = new [] { "google-maps", "signalr" }
});

Interact with the module from DotVVM page

Now, what you can do with the module? You can define methods in it and call them from the page:

class App {
    ...

    // you can declare functions in the JS module class
    sum(a, b) {        
        return a + b;
    }

    ...
}

In DotVVM, you can call these functions from static commands:

<dot:Button Text="Calculate in JS" 
            Click="{staticCommand: Result = _js.Invoke<double>("sum", Number1, Number2)}" />

We support even async functions (although there is no support for await keyword in DotVVM bindings yet – you need to call Result):

async getData() {
    const response = await fetch("someApi/myData");
return response.json();
}
<dot:Button Text="Get data from API" 
            Click="{staticCommand: Result = _js.Invoke<Task<MyData>>("getData").Result}" />

Call DotVVM commands or static commands from JavaScript

Another common requirement was to be able to invoke DotVVM command from JavaScript. Before DotVVM 3.0, you needed to create a hidden button in the page and click on it using JS, which was not a good way, and there was even no way to determine if the action was processed or if there was an error.

From DotVVM 3.0, you can do this easily by exposing the command using the NamedCommand control, and then calling it in the JS code using the context parameter you get when the module is instantiated.

<dot:NamedCommand Name="MyCommand" Command="{command: ...}" />
async someFunctionInModule() {
   // do stuff here

   // call DotVVM command
   try {
       await this.context.namedCommands.MyCommand();
   } catch (err) {
       // there was an error
   } 
}

Thanks to the new lambda support, you can even pass parameters to the commands:

<dot:NamedCommand Name="MyCommand" 
                  Command="{command: (int result) => ReportResult(CustomerId, result)}" />
await this.context.namedCommands.MyCommand(15);


LINQ methods in data-bindings

We are extending the set of methods you can call in data-bindings. Thanks to the support of lambda expressions and extension methods in bindings we added to DotVVM 3.0, you will be able to use Where, Select, OrderBy and other functions. We don’t plan to support all LINQ functions, but we’ve selected those we think are most useful on the client-side to do filtering, sorting and so on.

The public preview doesn’t contain translations for all of these functions, the definitive set will be published in the documentation together with the stable release of DotVVM 3.0.

<dot:GridView DataSource="{value: Customers.Where((CustomerData c) => string.IsNullOrEmpty(Filter) || c.Name.Contains(Filter)}">
    ...
</dot:GridView>

Currently, the lambda expressions don’t support type inference for parameters, so you will need to specify types of arguments explicitly – this is a limitation we’ll get rid of in next releases of DotVVM – the work on it has already been started.


Client-side events and reviewed postback pipeline

One of the largest pieces of work we’ve done on DotVVM 3.0 was a total rewrite of the client-side part of DotVVM. Until 3.0, there was practically one large TypeScript file with everything. Naturally, it was difficult to maintain, so we refactored the entire TypeScript part of the framework into a nice structure of modules, taking advantage of all the newest features of TypeScript and JavaScript.

Together with this, we’ve reviewed the entire postback pipeline (which involves invoking classic commands, static commands and navigation in SPA). We’ve carefully gone through all the events, added some new ones, and made sure that the arguments passed to the events contain enough information about the request or the response, including information about an error.

We have also fixed some issues which occurred when many postbacks were triggered at the same time (which was reported by customers using SignalR and invoking commands or static commands from JavaScript). We’ve covered most of the client-side part with tests including special stress tests that detect race conditions in the asynchronous code of the postback pipeline.


Type-safe viewmodel on the client

We’ve also added type annotations to the objects in the viewmodel on the client-side. When you look at the viewmodel on the client, you’ll see that every object has the $type property that identifies a concrete type of the object. DotVVM sends the type metadata together with the viewmodel so we are able to verify that the objects have the correct properties and that the values have the right types.

If you try to assign a string value in an Int property, you’ll now get a JavaScript error.

image

There are also some situations where we can perform an automatic conversion (e.g. from string to number if the string is in a correct format) – we call this “coercion”. You should get a warning in such cases.

We believe that this feature will help you with changing the viewmodel from the client-side.

Immutable internal state and deferred notifications

The state management on the client-side has also changed – until now, the state was kept in Knockout observable objects (which can trigger notifications when the value is changed).

From DotVVM 3.0, the state is kept in a plain JS object and is treated like an immutable object. There are still Knockout observables, but they are built around this immutable object.

You can get this stat object by calling dotvvm.state and modify it using dotvvm.patchViewModel.

You can still read or write to the viewmodel using the observables, but when you change the observable, the notifications in other observables are triggered asynchronously – on a next animation frame. This makes the page much faster when you do massive changes in the viewmodel at the same time.


Stable release and roadmap

We expect the stable version of DotVVM 3.0 to be released in two to three weeks. We still have some known issues (for example, support for IE 11) that need to be fixed, but we are close.

After releasing DotVVM 3.0, we plan to work on the version 3.1 which should come quite shortly after 3.0.

We have several features that are almost complete but didn’t make it in the 3.0 version – improvements in validation, type inference for arguments in lambda expressions, more translations of .NET functions to JavaScript and so on.


We’d be happy for any feedback on DotVVM 3.0 – join us on our Gitter chat.

Tomáš Herceg

I am the CEO of RIGANTI, a small software development company located in Prague, Czech Republic.

I am Microsoft Most Valuable Professional and the founder of DotVVM project.