Skip to content

DotVVM 4.1

Compare
Choose a tag to compare
@exyi exyi released this 06 Feb 15:45
· 53 commits to release/4.1 since this release

Potentially breaking changes

While we try to keep "minor" releases free of major breaking changes, it's sometimes impossible to avoid. In general, you should not have problems updating to 4.1. If you encounter a breakage not on this list, please submit a bug report to this repository, or contact us on gitter.

  • In general, we aim to be source-compatible, not binary-compatible. If you are using other DotVVM packages, make sure all are on 4.1 version; otherwise MissingMethodExceptions (or similar) might occur.
  • 4.0 had a bug that allowed using private members from binding. This is fixed in 4.1, so you will now get an error when a private member is used in binding expressions.
  • 4.0 had a bug which treated { get; init; } properties as if they had normal setter. DotVVM 4.1 will always clone the object before setting an init-only property.
  • We made the ReflectionUtils.PrimitiveTypes collection private. Please use the IsPrimitiveType method. In case it doesn't fulfill your requirement, please contact us. We plan to add support for custom primitive types in the next release.

We recommend using the view compilation page at _dotvvm/diagnostics/compilation after updating to check that all views still compile.

Record serialization (#1246 #1525)

DotVVM can (finally) correctly serialize and deserialize value types and records with constructor properties. F# records and single-case unions are also supported.

record UserInfo(string Name,
                string Id,
                string Email);

In general classes, the non-default constructor has to be marked with the constructor [JsonConstructor] attribute. Without it, DotVVM will not automatically deserialize properties into constructor arguments, even when no other constructor is available (to avoid breaking existing code)

class C {
    public string Name { get; }

    [JsonConstructor]
    public C(string name) {
        this.Name = name;
    }
}

HierarchyRepeater (#1206)

DotVVM now has a generic control for displaying hierarchies that support both client-side and server-side rendering. The following code will create a hierarchy of ul / li tags from a collection of Roots. The ItemWrapperTag (li) only contains a template for a specific item, while LevelWrapperTag (ul) also includes all child items.

<dot:HierarchyRepeater DataSource={value: Roots}
                       ItemChildrenBinding={value: Children}
                       LevelWrapperTagName=ul
                       ItemWrapperTagName=li >
     {{value: Name}}
</dot:HierarchyRepeater>

Note that you can set any other attributes or classes on the wrapper tags using the Level or Item prefix, for example LevelClass-highlighted={value: Name == _root.HighlightedName}.

AutoUI (#1293)

DotVVM Dynamic Data is a library that generates forms and grids automatically from your model classes. We have rewritten it using precompiled CompositeControls and server-side styles. This makes it much easier to extend and customize, and also eliminates performance issues the old DynamicData library had.

We renamed this new implementation as DotVVM AutoUI, as it's... an automatic UI generator, not data and not dynamic :)
The old DynamicData library still works as before, although we don't plan to develop it further.

Since there is a lot to cover, please refer to the dedicated page for more information. In short, this how the usage looks like:

<!-- create from with all the fields -->
<auto:Form DataContext="{value: EditedEmployee}" />

<!-- a table with automatic columns (we support inline editing too) -->
<dot:GridView DataSource="{value: Employees}">
    <auto:GridViewColumns />
</dot:GridView>

<!-- basic form component based on data type and attributes. Includes validation -->
<auto:Editor Property="{value: Age}" />

Markup Declared Properties (#1231)

Properties of markup controls can now be defined directly in the control .dothtml file:

@property string Name

<div class="name">
   {{value: _control.Name}}
</div>

For each property, you can specify:

  • A default value, for example
    • @property string Width = "100%"
    • @property string[] Classes = [ "something" ]
    • @property Func<int, int> GetNextId = (int i) => i + 1
  • Markup options, for example @property string Name, MarkupOptionsAttribute.Required=true

Note that @baseType and @property directives cannot be combined, as it could lead to ambiguities where the property is defined.

CompositeControl precompilation (#1261)

DotVVM 4.0 introduced Composite controls and made Server-Side Styles much more powerful.

DotVVM 4.1 builds on this foundation and introduces Composite control which is only evaluated once - when the page is being compiled.
This is very useful when the component needs to create and compile new bindings (like Auto UI) since that is a bit too slow to do on each request.
It also allows other server-side styles to match the generated content and customize it.

The usage is fairly simple:

[ControlMarkupOptions(Precompile = ControlPrecompilationMode.IfPossible)]
public class MyControl : CompositeControl
{
    public DotvvmControl GetContents(
        ValueOrBinding<string> myProperty,
        string otherProperty
    )
    {
       return ...;
    }
}

This will try to create the control during page compilation, invoke the GetContents method and replace the component with the generated control tree. The component instance won't even be created at runtime.

However, if otherProperty contains a resource binding, it's not possible to call GetContents before the binding is evaluated. This is fine, the control will be evaluated at runtime normally. You might decide that for certain combinations of arguments, the precompilation is not possible and throw a SkipPrecompilationException. Please try not to depend on cases where DotVVM automatically decides precompilation isn't possible, since we might want to add support for them in future versions. If you want to make sure the control never falls back on runtime evaluation, you can use the ControlPrecompilationMode.Always setting.

A special mode is ControlPrecompilationMode.InServerSideStyles which is used by AutoUI. This instructs DotVVM to build the control while server-side styles are running which allows other styles to modify the children. Normal precompiled controls are evaluated after styles are finished, in order to behave similarly to runtime controls which are also unaffected by styles.

JsComponent is stable now

DotVVM 4.0 included experimental support for integrating components written in JavaScript; as of DotVVM 4.1 we consider the React integration stable. To include a React component into a DotVVM view:

  • Install the dotvvm-jscomponent-react npm package
  • Setup your favorite JS bundler (and optionally a typescript compiler) to produce ES module bundle (let's say it's called scripts/mycomponents.js)
  • Register the script for DotVVM: config.Resources.RegisterScriptModuleFile("mycomponents-js", "script/mycomponents.js")

The module should export the components in the $controls field

import { registerReactControl } from 'dotvvm-jscomponent-react';
import * as React from 'react';

function SampleComponent(props) {
   return <span>{props.text}</span>
}

export default (context) => ({
    $controls: {
        SampleComponent: registerReactControl(SampleComponent),
    }
})

Then you can import it in any DotVVM view or markup control using the @js directive and use it with the <js: tag prefix.

@js mycomponents-js

<js:SampleComponent text={value: Text} />

For more information, see the documentation page, our sample project or the sample we use as UI test.

We'd like to thank @lorencjan for thoroughly testing the dotvvm-jscomponent-react package, many improvements have been made based on his suggestions.

We also have working Svelte integration. However, it's not published as a npm package at the moment - if you want to try it out, you can copy the integration function from our repository (yes the link correct, all the code really fits on one screen). If you'd like to try any other JS web framework, it should be quite easy to wire it into the DotVVM API. We'll definitely welcome any pull requests, but feel free to also submit an issue requesting it. If you can "only" help with testing, that's also very valuable.

Other smaller changes

  • DotVVM now implements `IHeathCheck on ASP.NET Core. It checks whether all pages can be compiled. #1209
  • control.AddCssClass and control.AddCssStyle extension methods now also support ValueOrBinding as input. It will now also work when it's called multiple times with a binding. (#1274, #1354)
  • Control property can now be IEnumerable<DotvvmControl> or IEnumerable<IMyControl>, previously it had to be List<DotvvmControl> (#1325, #1355)
  • BindingCompilationService.Cache.CreateValueBinding(string, DataContextStack): Helper method for creating value and staticCommand bindings (#1340)
  • Serialized configuration (dotvvm_serialized_config.json.tmp) contains more information, to make our VS Code extension work well. (#1352)
  • control.GetDotvvmUniqueId and control.CreateClientId allow specifying prefix and suffix (#1361)
  • Runtime warning for large viewmodels and long requests (#1371)
  • New JavaScript API for modifying the client-side error collection (#1375)
    • dotvvm.validation.addErrors([{ errorMessage: "Test", propertyPath: "/X/Y/Z" }])
    • dotvvm.validation.removeErrors("/FirstName", "/LastName")
  • Bitwise operators are supported in value bindings (for integers, booleans and enums) (#1378, #1404)
  • Validator.Value now supports more complex expressions, for example MyDateProperty.ToBrowserLocalTime().ToString("yyyy") will validate MyDateProperty (#1385)
  • Controls can have a PrimaryName and AlternativeNames, allowing for backwards compatible renames (#1388)
  • Server-side styles allow removing controls, for example styles.Register<Button>().Remove() removes all buttons from all pages (#1397)
  • Property groups can be used in MarkupControls (#1441)
  • Compile time warning for uppercase attributes (it's probably a mistyped property) (#1443)
  • React JsComponents get setProps function to allow writing values back (#1466)
  • DateOnly and TimeOnly types are supported
  • <dot:CheckBox is by default set into the intermediate mode when Value is null. It can now be disabled using DisableIndeterminate, the checkbox will be unchecked when value is null (#1492)
  • We have added Security.md file, please follow the instructions if you have found a (suspected) vulnerability in DotVVM or related products
  • New JS translations - methods supported in value and staticCommand bindings

Notable bugfixes

  • Fixed packaging of DotVVM.CommandLine, fixed the CLI when project path contains whitespace. (#1350, #1351)
  • Fixed parsing of expressions like A=!A (#1374)
  • Encrypted values are serialized using the same settings as they are deserialized, allowing usage of more complex objects (#1391)
  • Fixed number of bugs on .NET Framework, so we now run unit tests on both dotnet 6 and net471 to avoid them.
  • Fixed parsing of <![CDATA[, easy way to escape bindings and html characters for large text
  • Fix resetting of half written inputs bound to DateTime or number (#1451)
  • Reworked client-side date parsing function (few times 🙂, #1450, #1503)
  • Page compilation now fails when hardcoded value is written into property of type IBinding (instead of crashing at runtime, #1464)
  • Hot reload is only used in debug mode (#1468)
  • Fixed issue with not attaching validation errors when propertyPaths could not be resolved (#1500)
  • Fixed building URL when query parameters contain null or empty string (#1502)
  • Fixed refreshing triggers in REST API bindings (#1513)
  • Fixed REST API bindings in SPA apps (#1523)
  • Fixed evaluation of async commands with multiple calls when exception occurs (#1520)
  • Calling private members from bindings is not allowed (#1522, the bug was introduced in 4.0, sorry if this breaks your code)
  • On command, don't reset value of properties which aren't sent from server. This fixes Direction.ClientToServerInPostbackPath | Direction.ServerToClientFirstRequest (#1536)