Control properties
The properties in markup controls with code-behind and code-only controls cannot be simple C# properties with default getter and setter. Such properties could only contain values, but they couldn't store data-binding expressions.
To make the data-binding work, you have to expose those properties as DotvvmProperty objects which contain metadata about the property and which can store binding expressions. It is similar to dependency properties known from Windows Presentation Foundation.
Declare the property in markup-controls with code or code-only controls
DotVVM for Visual Studio adds an easy-to-use code snippet, which makes declaration of these properties simple.
To declare a DotVVM property, type dotprop and press Tab. The property declaration will be generated for you.
If you are using Resharper and type
dotprop, it will not see the code snippet and it will match theDotvvmPropertyclass instead. If this happens, press Escape before pressing Tab, and the snippet will work.
After you invoke the dotprop code snippet, you can change the name of the property, its type, the containing class, and the default value:
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DotvvmProperty TitleProperty
= DotvvmProperty.Register<string, AddressEditor>(c => c.Title, "Address");
The property getter and setter are technically optional, DotVVM only needs the TitleProperty field.
In controls, we recommend having the helper property for usage convenience and consistency.
On the other hand, attached properties do not have the helper getter and setter, since they are defined on a static class.
Until DotVVM 3.0, the declaration of static
DotvvmPropertyfield was optional - DotVVM inferred the field automatically from the property. However, this behavior had many limitations and we decided to drop this feature. From DotVVM 4.0, the declaration ofDotvvmPropertyis required.
Specify markup options
The properties can be decorated via the MarkupOptions attribute. It allows to specify, whether the property is required, whether it supports value binding or a hard-coded value in the markup, and whether it is mapped as an attribute or the inner element.
The attribute is applied on the property like this:
[MarkupOptions(AllowHardCodedValue = false)]
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DotvvmProperty TextProperty
= DotvvmProperty.Register<string, TextBoxWithLabel>(c => c.Text, null);
The attribute in the example above tells DotVVM to accept only the value binding as the value of this property:
<!-- only value binding is allowed -->
<cc:TextBoxWithLabel Text="{value: SomeProperty}" ... />
<!-- WRONG - this won't work -->
<cc:TextBoxWithLabel Text="Hello" ... />
<!-- WRONG - the resource binding is evaluated on the server and behaves as a hard-coded value -->
<cc:TextBoxWithLabel Text="{resource: SomeProperty}" ... />
MarkupOptions properties
Use the properties of the attribute to specify the behavior:
AllowBinding(defaulttrue) - specifies whether the value binding is allowed for this property.AllowHardCodedValue(defaulttrue) - specifies whether the hard-coded value or resource binding is allowed for this property.AllowResourceBinding(default same asAllowHardCodedValue) – specifies the resource binding is allowed.Required(defaultfalse) - specifies whether the property must be set in the markup.MappingMode(defaultMappingMode.Attribute) - specifies whether the property value is set as an attribute or inner element (e. g.ItemTemplateproperty of the Repeater control).
The attribute is commonly used in markup controls and code-only controls. The composite controls specify the support for binding or hard-coded values by using special property types described in the following section, but still allow the attribute for more fine grained control.
Declare the property in composite controls
Because declaring properties like this is uncomfortable, DotVVM 4.0 introduced the composite controls which declare the properties as parameters of the GetContents method:
public class MyControl : CompositeControl
{
public static DotvvmControl GetContents(
ValueOrBinding<string> title,
...
)
{
...
}
}
Declare properties using capabilities
Together with CompositeControl, DotVVM 4.0 introduced the Capability Property, which essentially allows you to register entire set of properties at once. Capabilities can also serve as a common interface for multiple controls, where you can easily copy the values from one control to a different type of control which has the same capability.
Capabilities are plain C# classes or records, and can be used both in composite controls and the classic code-only controls:
[DotvvmControlCapability]
public sealed record ExampleCapability
{
public string? MyProperty { get; init; }
}
Register capability in a code-only control.
public static readonly DotvvmProperty ExampleCapabilityProperty =
DotvvmCapabilityProperty.RegisterCapability<ExampleCapability, MyControl>();
Special property types
If the property accepts only the value binding, you can use the IValueBinding type to represent its value.
The same applies to ICommandBinding for command or static command properties.
The type may be generic to specify the requires binding return type (i.e. IValueBinding<string> is a binding which returns string)
In composite controls, the type ValueOrBinding<T> is often use it indicates that the property can contain either value binding or a value, and it allows to work with binding expressions in a more elegant way.
To represent templates, the ITemplate type is commonly used.
The control can also have properties of collection types. For example, a collection of strings can be mapped from the attribute with comma-separated values. A collection of DotvvmBindableObject or its descendants can be mapped as inner elements (e. g. Columns property of the GridView control).
Control markup options
By default, code-only controls can contain child elements. If you put something inside your control element, it will be placed in the control's Children collection.
Some controls may not support inner content, or want to redirect this content to another property. For example, the Repeater control doesn't support content (its inner content specified in the markup is not placed in the Children collection) - instead, it is using the ItemTemplate property to hold the inner content. Also, the property is marked as default, so it is not necessary to write the <ItemTemplate> element inside the <dot:Repeater> control.
[ControlMarkupOptions(AllowContent = false, DefaultContentProperty = nameof(Repeater.ItemTemplate))]
public class Repeater : DotvvmControl
{
...
}
<dot:Repeater ...>
<ItemTemplate> <!-- this line is optional - ItemTemplate is the DefaultContentProperty-->
My item {{value: Title}}
</ItemTemplate> <!-- this line is optional - ItemTemplate is the DefaultContentProperty-->
</dot:Repeater>
Markup controls do not support child element at all.
Composite controls can have child element, if it has a content or contentTemplate property/parameter.
Alternatively, the DefaultContentProperty markup option can be applied to composite controls.