DotVVM 4.2
Potential breaking changes
DotVVM 4.2 should generally be source compatible with the previous 4.1 version. We did not preserve binary compatibility, so please make sure all other DotVVM packages are at 4.2 version (especially if you see a MissingMethodException
or similar error). Please let us know if we forgot to update some of our packages. We also had to change the behavior in ways which could in theory break something
- The type hashes (the client-side type metadata id) might be different. You might need to adjust if you have it hardcoded somewhere. (see #1614, should only affect generic types)
- HtmlGenericControl and RadioButton now attempts to render both the server-side value and client-side binding regardless of RenderSettings.Mode. If you relied on the value not being pre-rendered on the server, please explicitly use
_page.EvaluatingOnServer ? null : OriginalExpression
. To render value server-side only, switch to aresource
binding. - Using a non-DotvvmProperty defined on a markup control is now a compile-time error (in value bindings). It used to only fail client-side
- DotVVM does not reload dothtml files when run in
Production
environment (whenDotvvmConfiguration.Debug=false
). JsComponent
is always initialized asynchronously (see #1684)- View Modules are always initialized after dotvvm is initialized
- Operator precedence of
&&
and||
now behaves the same as in C# - Unopened closing tag used to close all currently open elements -- for example in
<a></b>
theb
tag closed thea
tag. This glitch caused all sorts of problems, so we changed it to not close any elements. RadioButton.Checked
is deprecated, please useCheckedValue
andCheckedItem
properties (the property never worked, but now it might fail compile-time if used incorrectly)
staticCommand
validation
Server-side validation now works in staticCommand
methods. The validation has to be enabled using [AllowStaticCommand(StaticCommandValidation.Manual)]
or [AllowStaticCommand(StaticCommandValidation.Automatic)]
attribute. In the Automatic
case, all arguments will be recursively validated using DataAnnotations attributes and IValidatableObject.Validate will be called. In both cases, additional validation errors can be returned using the StaticCommandModelState
class:
[AllowStaticCommand(StaticCommandValidation.Manual)]
public string MyMethod(MyModel model)
{
var modelState = new StaticCommandModelState();
modelState.AddArgumentError(() => model.Property, "Property is invalid");
modelState.FailOnInvalidModelState();
}
Enabling the validation will make the staticCommand invocation clear all validation errors currently being shown client-side. Note that client-side validation isn't currently implemented, so all invocation will round-trip to the server.
See related forum post for more examples.
Custom primitive types
In order to better support typed IDs, DotVVM now has support for custom primitive types. These types are always represented as a string client-side, which makes them allowed in places only for primitive types - notably SelectedValue
property on selector components, but also in route parameters, and html attributes.
The custom primitive type must have ToString()
and static TryParse(string, out T)
methods. It must also implement the IDotvvmPrimitiveType
interface. It's only a marker interface, since ToString
method present on all types, and TryParse
method is static and we can't use interface static methods due to support for the old framework. The type can be a class, a struct or a record.
A simple typed ID type could look like the following:
public struct OrderId: IDotvvmPrimitiveType
{
public int Value { get; }
public OrderId(int value)
{
this.Value = value;
}
public override string ToString() => Value.ToString();
public static bool TryParse(string value, out OrderId result)
{
if (int.TryParse(value, out var resultValue))
{
result = new OrderId(resultValue);
return true;
}
result = default;
return false;
}
}
WebForms adapters
We have made a new package which helps in the process of migrating Web Forms apps to the new .NET using DotVVM - DotVVM.Adapters.WebForms. It currently contains HybridRouteLink
control and RedirectToRouteHybrid
extension method.
- The
<webforms:HybridRouteLink>
control has the same functionality as theRouteLink
control, but it falls back to Web Forms routes when a requested DotVVM route doesn't exist (which means that the page hasn't been migrated yet). - The
RedirectToRouteHybrid
extension method of theIDotvvmRequestContext
also falls back to the Web Forms route in case the DotVVM route doesn't exist.
The package also works on .NET Core, where these fallbacks are disabled (because of the absence of System.Web
). This allows to keep using the API even after the migration is finished.
state
, patchState
, setState
, updateState
in JS View Modules
We have added the state property and the following methods to the global dotvvm
object, viewmodel knockout observables and JS module context (DotvvmModuleContext
)
state
- returns the current value of the view modelsetState(newValue)
- replaces the current valuepatchState(newValues)
- replaces only the specified properties, for examplepatchState({ UserName: "new-user" })
updateState(currentValue => computeNewValue(currentValue))
- applies the specified function onto the current view model
See simplified view model API in JS Modules on forum.dotvvm.com for more details and some context.
Knockout deferred updates (experimental feature)
DotVVM uses knockout.js to immediately synchronize the view model into the HTML DOM. Knockout essentially does this by subscribing to an update event on each view model variable which is used in the binding. When any value inside the view model is changed, all bindings using the value immediately get a notification and update the bound DOM element.
This normally works well, but if the binding uses very many different variables it can get equally many notification if large part of the view model gets changed. For example, you can have a binding which enumerates an array by counting number of certain elements in an array ({value: MyArray.Count(a => a.IsSpecial)}
). Each array element is a separate viewmodel value, so the binding with get up to MyArray.Length
update notifications, each time evaluating the binding and updating the DOM element. This mean we have got O(n^2) worst case complexity for updating the view model, which can be really slow (in some cases).
Knockout has got an ko.options.deferUpdates = true
option, which makes the updates asynchronous and deduplicates the notifications. Updating all array elements will then lead to just one DOM update (with a slight delay). It was always possible to enable this option, in 4.2 we made it easier to enable and fixed a few bugs in DotVVM which occurred when delayed updates were enabled. The feature is still experimental, since we didn't thoroughly test it with all our components. You can enable/disable it for certain pages, if you encounter issues.
config.ExperimentalFeatures.KnockoutDeferUpdates.Enable().ExcludeRoute("SomePageThatOtherwiseBreaks");
Metrics
We instrumented DotVVM with System.Diagnostics.Metrics. All implemented metrics are listed in the DotvvmMetrics.cs file. We'd like to point out some of the important or interesting metrics:
dotvvm_viewmodel_size_bytes
histogram - Size of the returned viewmodel JSON in bytes.- Labels
route=RouteName
andrequest_type=Navigate/SpaNavigate/Command/StaticCommand
- Labels
dotvvm_request_duration_seconds
- Time it took to stringify the resulting JSON view model.- Labels
route
,request_type
anddothtml_file
- You can use ASP.NET Core statistics about request duration, this metric makes it possible to split the measurement by DotVVM route name.
- Labels
dotvvm_viewmodel_serialization_seconds
histogram - Time it took to serialize view model to JSON objects.- Labels
route
andrequest_type
- Labels
dotvvm_control_lifecycle_seconds
histogram - Time it took to process a request on the specific route- Labels
route
andlifecycle_type=PreInit/Init/Load/PreRender/PreRenderComplete
- Labels
dotvvm_command_invocation_seconds
- Time it took to invoke a specific command method. Compared torequest_duration_seconds
this only includes the time spent in the command method, and is labeled by the executed binding (command
)- Labels
command
andresult=Ok/Exception/UnhandledException
- Labels
dotvvm_staticcommand_invocation_seconds
- Similar tocommand_invocation_seconds
, but for staticCommand invocations- Labels
command
andresult=Ok/Exception/UnhandledException
- Labels
dotvvm_viewmodel_validation_errors_total
histogram - Number view model validation errors returned to the client.- Labels
route
andrequest_type
- Labels
dotvvm_uploaded_file_bytes
- Total size of user-uploaded filesdotvvm_returned_file_bytes
- Total size of returned files. Measured when the file is returned, not when downloaded by the client
If you are using Server-side viewmodel cache you might be also interested in the related metrics in order to measure if/how is the cache helping:
dotvvm_viewmodel_cache_hit_total
counter - number of cache hitsdotvvm_viewmodel_cache_miss_total
counter - number of cache misses - on a cache miss, the request fails and client has to retry uploading the entire view model.viewmodel_cache_loaded_bytes_total
counter - number of bytes loaded from the cache, you may interpret it as an upper bound estimate on the bandwidth saved
Prometheus configuration
If you want to use the prometheus-net library to expose the metrics in prometheus format, we recommend calling the Prometheus.MeterAdapter.StartListening
method as soon as possible (at the start of Startup/DotvvmStartup is a good place), and configuring the buckets for histograms.
MeterAdapter.StartListening(new MeterAdapterOptions {
ResolveHistogramBuckets = instrument => {
// prometheus-net does not know which buckets will make sense for each histogram and System.Diagnostics.Metrics API
// does not provide a way to specify it. The ResolveHistogramBuckets function will be called for each exported histogram in to define the buckets.
return DotvvmMetrics.TryGetRecommendedBuckets(instrument) ?? MeterAdapterOptions.DefaultHistogramBuckets;
}
});
Other notable changes
- API for registering JS Method translations has been significantly upgraded - the registered method or property can be specified using a Linq.Expression. See #1608 for more details.
- It is now possible to configure
AutoUIConfiguration
using theservices.Configure<AutoUIConfiguration>(c => ...)
pattern. ( #1626) - Executing a binding in an unexpected data context should result in much clearer error message. Additionally, bindings now only collect the data contexts which are required for execution, improving performance ( #1512)
- FileUpload has
Capture
property which instructs browser to use a camera or a microphone (see HTML attribute: capture on MDN, PR #1621) - DotVVM now does not reload dothtml files in Production environment (#1542)
- DotVVM can be initialized using the WebApplicationBuilder, without the need for Asp.Net Core Startup class (#1559)
- Type hash used in client-side type metadata is computed differently for generic types. It should now exclude the assembly version and thus be more stable. (#1614)
- Feature flags have
IncludeRoute
andExcludeRoute
methods. These methods allow incrementally adding or removing routes from the feature flag, enabling/disabling the feature for specific pages. (#1646) - AutoUI GetPropertiesToDisplay method is now public, making it easier to write custom automatic forms, views or grids (#1640)
- Uploaded file storage is more resilient to temp directory deletion. It also better informs the user about missing write permissions or similar problems (#1655)
- dothtml compilation warnings are logger using Asp.Net Core ILogger - the warnings appear on terminal after application startup (#1659)
- When page fails and DotVVM error page is rendered, we also log the exception to ILogger (#1697)
- Routing now supports optional parameters with type constraints (#1672)
- JsComponent initialization and updates are run asynchronously. This speeds the the first knockout update, but more importantly avoids crashing the entire page when the component throws an exception. In such case, the exception is not caught by dotvvm nor knockout, making it easier to break the debugger on the exception ( #1684)
- RadioButton now renders the client-side expression even in RenderSettings.Mode=Server (#1667)