Released DotVVM 4.3

Published: 9/16/2024 1:45:58 AM

We are thrilled to announce the release of DotVVM 4.3. This release brings numerous bug fixes and small improvements, but we managed to add several useful features.

As we already mentioned in earlier blog posts, we've already done a significant progress in building large features of DotVVM 5.0. However, it will still take us some time until we evaluate them in real-world projects to be sure they are designed well and do not have many rough edges.

For now, let’s see the goodies you’ll find in DotVVM 4.3.

ModalDialog control

DotVVM commercial packages have their own implementations of ModalDialog. In both Bootstrap for DotVVM and DotVVM Business Pack, it is basically a <div> element cleverly styled to look like a modal dialog, with another <div> creating the “backdrop experience” (a semi-transparent background that prevent interactions with elements outside of the dialog). There are many tricks involved to make sure the control behaves properly, but you may occasionally run into various quirks, such as being able to switch focus on elements that are under the backdrop, and so on. While the dialog implementations try to prevent this behavior, there are some edge-cases where the focus escapes outside the active dialog (especially when you have multiple dialogs open).

Recently, all web browsers added supports for the standard HTML <dialog> element which gives us several advantages. It can handle more situations than custom implementation, it is more friendly to screen readers and other assistive technologies, responds automatically to pressing the Escape key, and so on. Since the modal dialog functionality has always been one of the most requested features, we've added a universal wrapper on the HTML <dialog> element to the open-source framework.

In version 4.3, our commercial packages will still use the custom <div>-based approach, however, it is likely to be changed in future releases.

<div>
    <dot:Button Text="Create folder" 
Click="{staticCommand: IsModalOpen = true}" /> </div> <dot:ModalDialog Open="{value: IsModalOpen}" CloseOnBackdropClick="true" Close="{command: OnDialogClosed()}"> <form> <label> Folder name: <dot:TextBox Text="{value: NewFolderName}" /> </label> <div> <dot:Button IsSubmitButton="true" Text="Create" Click="{command: CreateFolder()}" /> </div> </form> </dot:ModalDialog>

In this code sample, we have a button that can open the modal dialog. Notice that we use the <form> element and IsSubmitButton flag on the button to make it respond to pressing the Enter key.

The ModalDialog control has the optional Close event, which can be used to allow custom handing of the close event, for example when the Escape key is pressed or the backdrop is clicked.

To indicate whether the dialog is open, you can either bind its Open property to a boolean property in the viewmodel, or to a child viewmodel object. The dialog will appear when this child viewmodel will be set to a non-null value. If you wonder why we used the name Open instead of IsDisplayed (which we have in the commercial implementations of ModalDialog), it is because we wanted to stick to the HTML naming which uses the open attribute in the <dialog> element.

Route localization

DotVVM has the routing functionality built-in since its first version. It allows to git the pages SEO-friendly URLs that do not have to correspond to the physical structure of the application pages. You can even point multiple routes to the same DotHTML file, or you can make redirecting routes to preserve old hyperlinks, and so forth.

At the times of .NET Framework, DotVVM implemented its own Localizable presenter to detect the current language from route parameter, query string, or some other place, and set the request culture automatically. ASP.NET Core contains a similar functionality called Request localization. However, these two mechanisms do not do the actual route localization - they can merely look at some route parameter and make sure the application will use the correct culture to handle the HTTP request.

There are situations where you need more. For example, you may want the contact page to be available under /en/contact in the English version of the site and as /cs/kontakt for the Czech version. Until DotVVM 4.3, you could register a separate route for each language to achieve this effect, but it prevents you from using the RouteLink control to build the URL from route name - it is different for each language. You would have to use the resource binding (because the RouteName property does not support value binding), which would be more error-prone and make the markup harder to read.

In DotVVM 4.3, you can add alternate URLs to any route and assign culture codes to them. There is still one primary URL that works as a default (for cases where you do not specify another URL for the particular culture), but there can be as many alternate URLs as you want. They respect the hierarchy of language and region codes: you may define a URL for "en" for all English-based cultures, but have a different version for "en-US" (English in United States).

config.RouteTable.Add(
    "ContactPage",                // route name
    "en/contact",                 // default culture route URL
    "Views/Contact.dothtml",      // markup file location
    localizedUrls: [
        new("cs-CZ", "cs/kontakt"),
        new("de", "..."),
        ...
    ]);

All these URLs now belong to a single route. Thus, you can use the classic RouteLink control which will build the link based on the current request culture.

If you use some other place to store the current culture than a route parameter, such as a cookie, there is a way to redirect the user in case they have for example the German culture set up but open a link pointing to the English version of the page. This mechanism is extensible - you can define you own handler for this situation.

// if the culture in cookie is set to different language and the user navigates to a link for a different culture, 
// redirect them to the correct language version
config.RouteTable.AddPartialMatchHandler(new CanonicalRedirectPartialMatchRouteHandler());

We believe this feature will be useful, especially for public-facing applications where SEO is one of the most critical requirements.

AlternateCultureLinks control

This new control is related to the localizable routes - it is used in the <head> section and renders <link> elements to the same pages in a different localization. If you use this control on a non-localized route, it will not render any output. In pages which use route localization, this control will produce the links to all other cultures. This is great for search engines to be able to easily identify the pages that should be treated as a single page.

Feel free to use this control in the master page. You may optionally set the RouteName property to generate links for a specific route. Without that, the control will use the current route name.

AddTemplateDecorator

Speaking of new controls, there is one useful decorator that allows to append or prepend content to the decorated control. Decorators are usually used in GridView to apply attributes to <tr> elements, but with this, you can for example insert additional rows in the table (for example, group headers).

<dot:GridView DataSource="{value: Customers}">
    <Columns>
        <dot:GridViewTextColumn HeaderText="ID" ValueBinding="{value: CustomerID}" />
        <dot:GridViewTextColumn HeaderText="Name" ValueBinding="{value: ContactName}" />
        <dot:GridViewTextColumn HeaderText="City" ValueBinding="{value: City}" />
    </Columns>
    <RowDecorators>
        <dot:AddTemplateDecorator>
            <BeforeTemplate>
                <tr colspan="3"
                    IncludeInPage="{value: _index == 0 || Country != _parent.Customers[_index - 1].Country}">
                    <td>
                        <strong>{{value: Country}}</strong>
                    </td>
                </tr>
            </BeforeTemplate>
        </dot:AddTemplateDecorator>
    </RowDecorators>
</dot:GridView>

The code sample shows a simple GridView control with AddTemplateDecorator, that adds an extra “group header” row before every row. Its IncludeInPage property contains a binding that defines whether this group header shall be present – that is, before the first row (_index == 0) or when the current row Country is different from the value in the previous row. Please not that this will work only when the data set is ordered by the grouped column – Country, in our case.

GZip compression on POST requests

Since most of the payloads sent by DotVVM is a text-like content, compressing it makes a huge sense and can significantly decrease the amount of transferred data (to ~30% of the original size). This is very interesting, as it may help speed-up the application without making any changes in the code.

Most web servers have GZip compression enabled on the HTTP level for all GET requests. But when it comes to HTTP POSTs, browsers can not use the compression for the request body for they do not know the server supports it. Even thoug they made a HTTP GET request just a few seconds ago, there is no guarantee that the subsequent POST will be handled by the same server having the same software.

However, when we use DotVVM, we know that the web server will support it. Therefore, DotVVM 4.3 compresses the request body using JavaScript, saving about 70% of the size. This feature is enabled by default - you do not need to do anything.

Other improvements

Because of a security vulnerability in Newtonsoft.Json, we lifted the minimum version to 13.0.3. Although the security issue was present in a feature of Newtonsoft.Json that is not used by DotVVM, we got reported by some diagnostic tools as vulnerable because of this low dependency. DotVVM 4.3 now requires upgrading Newtonsoft.Json. We hope we will not break any of your other dependencies.

We have also added support for DotVVM components to emit warnings, and display them in the console output or in the Compilation Status Page. This allows the developers to indicate that the control usage is not ideal and provide tips how to fix the problem. DotVVM also reports warnings if you make a typo in the name of DotVVM property, when you have a HTML tag hierarchy issue in a page, and more.

You can now use several methods in value and static command bindings that were not previously supported:
* Dictionary<K, V>.GetValueOrDefault
* First / Last / Single - please note that bindings do not throw exceptions and have null-propagation by default, so these methods behave exactly as FirstOrDefault, LastOrDefault, and so on
* DateOnly/TimeOnly Year, Month, ..., Hour, Minute, ..., Now, UtcNow, Today properties and FromDateTime method

A new feature flag config.Runtime.ReloadMarkupFiles allows reloading and recompiling DotHTML files when they are changed in production environment, without the need to restart the application. This behavior was default for a long time, but we decided to disable it recently (as it may have unfortunate side effects) and quickly found out some people needed it. That's why we have added this flag.

Commercial packages

Together with DotVVM 4.3, we are releasing the version 4.3 of Bootstrap for DotVVM and DotVVM Business Pack, featuring several bug fixes and compatibility improvements.

We have also updated all DotVVM.Contrib components to the version 4.3. 

The complete list of features and changes is in the Release notes. We'll be happy to hear your feedback and thoughts!

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.