DotVVM 5.0 was a long time in the making and is the most significant upgrade since DotVVM 2.0. We replaced the Newtonsoft.Json view model serializer with System.Text.Json. The serializer is the core of the framework, so this was a substantial task. We also added support for resource bindings in DataContext and DataSource properties, redesigned GridViewDataSet for better extensibility, added support for static commands in GridView and DataPager, introduced localizable routes, added support for ASP.NET Core static assets, and many other improvements.
Breaking Changes
Upgrading to DotVVM 5.0 will likely require changes in your application, most likely because of the serializer switch. We are therefore extending security support for version 4.3 to two years after the 5.0 release, instead of the standard one year. DotVVM 4.3 is supported until July 2028.
-
View model serialization is based on System.Text.Json instead of Newtonsoft.Json (#1799). Serializer extension points now use
JsonDocument,JsonElement,JsonSerializerOptions, UTF-8 readers/writers, and byte buffers instead ofJObject,JToken,JsonSerializerSettings, and strings. Custom serializers, converters, code usingIViewModelSerializer,IViewModelServerCache, and code readingIDotvvmRequestContext.ReceivedViewModelJsonmay need migration. -
Newtonsoft.Json converters are no longer supported by DotVVM view model serialization (#1799). Use
System.Text.Json.Serializationattributes and converters for new code. Some Newtonsoft compatibility remains (i.e. for property-name attributes), but custom Newtonsoft converters are ignored. -
Parser reads
<script>and<style>verbatim (#1900). This aligns DotHTML with HTML, but may break existing code which used HTML escape sequences or resource bindings inside<script>or<style>tags. Behavior may be reverted by clearing theDotvvmConfiguraion.Markup.RawTextElementscollection. -
GridViewDataSetand pager contracts were redesigned (#1548, #2004). Old dataset interfaces such asIBaseGridViewDataSet, non-genericIPageableGridViewDataSet,ISortableGridViewDataSet,IRowEditGridViewDataSet,IRowInsertGridViewDataSet, andIRefreshableGridViewDataSetare removed. Custom datasets must expose the new typed filtering, sorting, paging, row insert, and row edit option contracts. -
Static command arguments now always use DotVVM serialization (#1898).
ExperimentalFeatures.UseDotvvmSerializationForStaticCommandArgumentsis removed. To opt out, annotate the argument class with[DotvvmSerialization(DisableDotvvmConverter = true)]. -
DotVVM internal endpoints moved under
/_dotvvm/(#1895, #1956). File upload, returned file, CSRF token, and resource URLs changed. Update reverse proxy rules, CSP allowlists, custom middleware, or hard-coded DotVVM endpoint URLs. -
File uploads are stricter. Generated upload requests now include CSRF and protected upload metadata headers. Custom upload clients must send the expected headers, and
MaxFileSizeis enforced while storing the upload. -
Legacy diagnostic window services were removed (#1993).
AddDiagnosticServices, diagnostic sender/model/renderer types, and related tracers are gone. -
IDotvvmRequestContextchanged (#1937).ReceivedViewModelJsonis nowJsonDocument?,ViewModelJsonwas removed,IsPostBackis read-only, and request cancellation is exposed asIDotvvmRequestContext.RequestAborted. -
Stricter routing APIs (#1881, #1824, #1922).
DotvvmRouteconstructors now require a route name, route-tableAdd*methods returnRouteBase,AddGroupreturnsRouteTableGroup, either presenter or virtual path is required in each route. -
Binding parser behavior is closer to C# (#1947, #1909). C# keywords are reserved in bindings and must be escaped with
@; assignment to init-only properties is only allowed client-side. -
Property directives are validated more strictly (#1639).
@propertyis now reported as an error outside markup controls. Markup controls are detected fromconfig.Markup.Controls, not only by.dotcontrolfile extension. -
Resource/configuration serialization APIs changed (#1799, #1958, #1960). Resource and configuration JSON serialization now use
System.Text.Json. Some resource API signatures also changed, includingILinkResource.GetLocations(). -
DotvvmPresenteris no longer registered as a generalIDotvvmPresenterservice by default (#2059). -
ExplicitAssemblyLoadingoption moved (#1952). UseDotvvmConfiguration.Runtime.ExplicitAssemblyLoading. -
Target frameworks changed (#1838, #1917, #1994). ASP.NET Core hosting now targets .NET 10 instead of
netstandard2.1/net6.0. Shared DotVVM libraries still keepnetstandard2.1and .NET Framework targets where applicable. -
staticCommandautomatically receive the request CancellationToken. PreviouslyCancellationToken ct = defaultreceived the default (inactive) token, now it will get assigned fromcontext.RequestAborted.
Major Improvements
View Model Serialization
DotVVM 5 rewrites view model serialization around UTF-8 System.Text.Json APIs (#1799). The switch dramatically lowers allocations and serialization time for both large and small responses. DotVVM works around some System.Text.Json limitations, including object and collection population (#1957). DotVVM 5 serializer supports the floating-point values NaN and Infinity (#1954). View models can opt out of DotVVM's converter when native System.Text.Json behavior is preferred by adding [DotvvmSerialization(DisableDotvvmConverter = true)] to the type (#1916).
DotVVM doesn't yet support the [JsonPolymorphic] attribute.
System.Text.Json serialization follows the static property type, while Newtonsoft.Json always inspected the runtime object type (performed "dynamic dispatch"). In the following example, Newtonsoft.Json will serialize both P1 and P2 properties, while System.Text.Json only serializes P1. Only when the property is of type object, System.Text.Json will also perform dynamic dispatch and select converter based on the runtime type.
class A { public string P1 { get; set; } }
class B: A { public string P2 { get; set; } }
class ViewModel {
// statically resolved type serializer
public A MyProperty { get; set; } = new B();
// dynamically dispatched type serializer
public object AnotherProperty { get; } = new B();
}DotVVM 5 follows the System.Text.Json semantics, but also does dynamic dispatch for all interfaces and abstract types. Dynamic dispatch can also be explicitly enabled or disabled for any property using the [Bind(AllowDynamicDispatch = true)] attribute.
GridViewDataSet, Paging, and Static Loading
GenericGridViewDataSet<T, ...> separates filtering, sorting, paging, row insert, and row edit into composable option types (#1548, #2004). New options include multi-criteria sorting, token-based paging, token-history paging, and no-op option types. DotVVM 5 includesLoadFromQueryableAsync, which provides asynchronous loading compatible with EF6, EF Core, and Marten. It is not available on .NET Framework.
public GridViewDataSet<Customer> Customers { get; set; } = new() {
PagingOptions = { PageSize = 20 }
};
public override async Task PreRender()
{
if (Customers.IsRefreshRequired)
{
await Customers.LoadFromQueryableAsync(db.Customers, Context.RequestAborted);
}
}DataPager and GridView can load data through static commands, and the new AppendableDataPager supports "load more" and infinite-scroll patterns (#1837). DataPager also supports custom page-number templates, HTML capabilities for list items and links, and configurable CSS classes for active and disabled items.
[AllowStaticCommand]
public static async Task<GridViewDataSetResult<Customer, NoFilteringOptions, SortingOptions, PagingOptions>>
LoadCustomers(GridViewDataSetOptions options)
{
var dataSet = new GridViewDataSet<Customer>();
dataSet.ApplyOptions(options);
await dataSet.LoadFromQueryableAsync(GetCustomers());
return new(dataSet.Items.ToList(), dataSet.GetOptions());
}<dot:GridView DataSource={value: Customers}
LoadData={staticCommand: RootViewModel.LoadCustomers}>
<dot:GridViewTextColumn HeaderText="Name" ValueBinding={value: Name} AllowSorting />
</dot:GridView>
<dot:DataPager DataSet={value: Customers}
LoadData={staticCommand: RootViewModel.LoadCustomers} />
<dot:AppendableDataPager DataSet={value: Customers}
LoadData={staticCommand: RootViewModel.LoadCustomers}>
<LoadTemplate>
<dot:Button Text="Load more" Click={staticCommand: _dataPager.Load()} />
</LoadTemplate>
</dot:AppendableDataPager>
Resource Data Contexts
DataContext={resource: ...} renders server-side data without serializing it into the client view model (#1392). It works across key item controls such as Repeater, HierarchyRepeater, GridView, EmptyData, AutoUI, and command bindings in resource-rendered templates where applicable.
<dot:Repeater DataSource={resource: Customers.Items}>
<span data-id={resource: Id}>{{resource: Name}}</span>
<dot:Button Text="Select" Click={command: _root.SelectCustomer(Id)} />
</dot:Repeater>
Server Values Inside Value Bindings
Value and static command bindings can now contain server-evaluated fragments using _page.Resource(...) (#2043). This is useful when part of an expression must remain interactive on the client but a subexpression can only be evaluated during server rendering. The resource part is evaluated during initial page render; it is not reactive and is not re-evaluated by commands, unless PostBack.Update is used. It also works inside resource-backed data contexts, allowing a server-rendered item value to be combined with reactive data from its client-side parent.
By default, untranslated static property accesses are wrapped in _page.Resource(...) instead of being evaluated once at compile time. This implies that .NET resources can now be used in value bindings.
<dot:Literal Text={value: 'Hello ' + _page.Resource(UserDisplayName) + ' and ' + FriendName} />
<dot:Button Text="Open"
Click={staticCommand: OpenItem(MyResource.ItemLabel, SelectedAction)} />
<dot:Repeater DataSource={resource: Customers.Items}>
{{value: _parent.Prefix + _page.Resource(Name)}}
</dot:Repeater>
IncludeInPage no longer flashes uninitialized content
IncludeInPage={value: ...} now renders controls as Knockout template resources when it evaluates to false on the server (#2056). This eliminates the flash of uninitialized content before JavaScript hides those controls.
ContentPlaceHolder used in templates
ContentPlaceHolder can be used in template-generated control trees, including composite controls and selected template scenarios (#2017). Master page composition is deferred until the placeholder is instantiated.
<dot:AuthenticatedView>
<NotAuthenticatedTemplate>
<dot:ContentPlaceHolder ID="Body" />
</NotAuthenticatedTemplate>
</dot:AuthenticatedView>
Routing, Localization, and Sitemap
Routes and route groups can define localized URL variants (#1824, #1881, #1922). Route groups now carry richer metadata, redirection routes work consistently inside groups, and ASP.NET Core integration includes DotvvmRoutingRequestCultureProvider. The new DotVVM.Sitemap package generates sitemap.xml from the route table, with options on individual routes, route groups, and the entire route table (#1953, #1983).
config.RouteTable.AddGroup(
"Products",
"products",
"Views/Products",
routes =>
{
routes.Add("Detail", "{Id}", "Detail.dothtml")
.WithSitemapOptions(o => o.Priority = 0.8);
},
localizedUrls: new[] { new LocalizedRouteUrl("cs-CZ", "produkty") });
services.AddSitemap();Resources and Static Assets
Local resource URLs have a new format: /_dotvvm/resource-{name}/{name} with version-hash moved to query string (#1956) Link resources can render fetchpriority (#1958), and embedded resources can expose debug source maps (#1960).
On .NET 10, DotVVM integrates ASP.NET Core static assets: href and src attributes are rewritten to hashed static asset URLs,
and script and style assets are available dotvvm resources named asset:{filepath}(#1974).
<!-- will reference a script -->
<dot:RequiredResource Name="asset:Scripts/app.js" />
<!-- will be rewitten to src="Content/logo.thu2ae9i.svg" -->
<img src="~/Content/logo.svg" alt="Logo" />
Components and Controls
DotVVM 5 also adds the dot:Timer control (#1902) and dot:ValidationErrorsCount (#1969). ButtonBase.ClickArguments lets controls pass arguments to command bindings (#1548).
<dot:Timer Interval="5000"
Enabled={value: IsLive}
Command={staticCommand: Positions = _service.LoadPositions()} />
<dot:ValidationErrorsCount Validation.Target={value: Form}
InvalidCssClass="is-invalid"
HideWhenValid="true" />
The new dotvvm-jscomponent-svelte package adds dot:JsComponent support for Svelte 5 components (#1951), similarly to how React components could be defined.
Returned files can now be included without interrupting request execution (#2062). This allows a command or static command to finish normally and modify viewModel while also triggering one or more client-side downloads.
public async Task ExportAndContinue()
{
await Context.IncludeReturnedFileAsync(
Encoding.UTF8.GetBytes("export"),
"export.txt",
"text/plain");
Status = "File exported";
}Developer Experience
Error pages format more runtime objects, DataContext mismatch messages are clearer, and command-blocking errors from IEventValidationHandler are less confusing (#1892, #1901, #1933, #1946).
<script> and <style> elements are parsed in HTML-compliant way (#1900).
Binding and JavaScript translation improvements C# predefined type handling, escaped identifiers, and translations for special floating-point helpers (#1947, #1959).
The new DotVVM06 analyzer warns when a catch block could swallow the DotvvmInterruptRequestExecutionException used by redirects and returned files (#1965).
Fixes
- Fixed resource URL caching when the same app runs under multiple
PathBasevalues (#2045). - Fixed encrypted value serialization when custom DotVVM serializers are involved (#2050).
- Fixed
string.IsNullOrEmptytranslation in DataContext value bindings (#2014). - Fixed
FindControl*<T>(throwIfNotFound: false)(#2037). - Fixed several
ValueOrBindingedge cases and data-source access throughIGridViewDataSet<T>(#2069, #2078). - Fixed
TemplateHostdata-context validation for root template controls with explicitDataContext(#2071). - Allowed
Sec-Fetch-Dest: emptyin page GET requests (#2003).