Markup controls with code-behind
Sometimes you need to pass some parameters in the markup control.
For example, you don't need to display the Phone field in the billing address. Also, you want the control from the sample on the markup control registration page to generate the fieldset
and legend
tags itself. Thus, it will need to know the text you need to display in the legend
element.
Create markup control with code-behind
You can create a markup control using the Add > New Item in the Solution Explorer context menu, and after you choose the name of the control, click on the checkbox which generates the code behind file for you.
The code behind file AddressEditor.cs
will be created. Notice that it derives from DotvvmMarkupControl
which is required for all markup controls.
public class AddressEditor : DotvvmMarkupControl
{
}
Add code-behind file to a previously created markup control
If you've already created the control without the code-behind file, you can add it later.
Create the code-behind class and make it inherit from
DotvvmMarkupControl
.Then, reference this class using the
@baseType
directive:
@viewModel DotvvmDemo.Model.IAddress, DotvvmDemo
@baseType DotvvmDemo.Controls.AddressEditor, DotvvmDemo
...
Declare control properties
In our example, we want to add two properties into the control.
The first property is called Title
- it will be printed out inside the legend
tag.
The second property is DisplayPhoneNumber
property which will show or hide the Phone field.
The properties in a DotVVM control cannot be simple C# properties with default getter and setter - in order to support data-binding expressions, they need to be exposed as DotvvmProperty
objects which contain metadata about the property and can store binding expressions.
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 theDotvvmProperty
class 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 to Title
, the type to string
, the containing class to AddressEditor
and the default value to "Address"
:
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 second property called DisplayPhoneNumber
of type bool
with default value true
will look like this:
public bool DisplayPhoneNumber
{
get { return (bool)GetValue(DisplayPhoneNumberProperty); }
set { SetValue(DisplayPhoneNumberProperty, value); }
}
public static readonly DotvvmProperty DisplayPhoneNumberProperty
= DotvvmProperty.Register<bool, AddressEditor>(c => c.DisplayPhoneNumber, true);
See the Control properties chapter for more information.
Access the properties from the control
Now, you can access the value of these properties using the {value: _control.Title}
binding in the markup.
Notice that the markup control must declare the @baseType
directive that specifies the code behind class.
The .dotcontrol
file will look like this:
@viewModel DotvvmDemo.Model.IAddress, DotvvmDemo
@baseType DotvvmDemo.Controls.AddressEditor, DotvvmDemo
<fieldset><legend>{{value: _control.Title}}</legend>
<table>
<tr>
<td>Street: </td>
<td><dot:TextBox Text="{value: Street}" /></td>
</tr>
...
<tr Visible="{value: _control.DisplayPhoneNumber}">
<td>Phone: </td>
<td><dot:TextBox Text="{value: Phone}" /></td>
</tr>
</table>
</fieldset>
In the page, we can now use the control like this:
<cc:AddressEditor DataContext="{value: BillingAddress}"
Title="Billing Address"
DisplayPhoneNumber="false" />
<cc:AddressEditor DataContext="{value: DeliveryAddress}"
Title="Delivery Address" />
Note that you can also put a data-binding as a value of the Title
and DisplayPhoneNumber
properties.
In previous versions of DotVVM, we suggested using the
{controlProperty: Title}
binding expression. It still works, but we recommend using{value: _control.Title}
. ThecontrolProperty
binding will be deprecated in the future versions of DotVVM.
Important fact about control properties
The properties of markup controls do not store the value. They are only references to the value or the data-binding specified on the place where the control is used.
If you set a value of the Title
property from the code-behind, there are 3 situations that can happen:
- In the page, the
Title
property is not set:<cc:AddressEditor DataContext="{value: DeliveryAddress}" />
.
The value will not be stored anywhere, and the Title
will have its default value on the next postback.
- In the page, the
Title
property is set to a static value:<cc:AddressEditor DataContext="{value: DeliveryAddress}" Title="Delivery Address" />
.
The value will not be stored anywhere, and the Title
will have the value "Delivery Address"
on the next postback.
- In the page, the
Title
property is bound to some property in the viewmodel:
<cc:AddressEditor DataContext="{value: DeliveryAddress}" Title="{value: DeliveryAddressTitle}" />
Only in the third case, the value will be persisted. If you set the Title
property from the code-behind, the value will be written into the DeliveryAddressTitle
property in the viewmodel, and you will find it there on the next postback.
If you need to persist any state information in the markup control, it must be done by data-binding to some viewmodel property.
Call methods in controls
If you need to add custom logic in the markup control, you can declare a method (e.g. ClearAddress
) in the code behind file, and invoke it like this:
<dot:Button Text="Clear address" Click="{command: _control.ClearAddress()}" />
In this case, the ClearAddress
can be declared in the code behind file because it does the same thing in all of the implementations. The implementation in the code-behind class will look like this:
public void ClearAddress()
{
var target = (IAddress)DataContext;
target.Street = "";
target.City = "";
...
}
Notice that you can access the binding context using the DataContext
property. We can safely cast it to IAddress
because the @viewModel
directive of the control specifies that the binding context must implement this interface.
Logic in controls vs viewmodel
We recommend to put only simple logic in the markup controls. If you need to do something more sophisticated, place the logic in the viewmodel.
In the previous example, you can declare the ClearAddress
method in the viewmodel, and call just {command: ClearAddress()}
. In such case, the IAddress
interface would have to declare this method, and all classes that implement the interface would have to implement also the ClearAddress
method.
It is common for complex controls to ship with their own viewmodels - something like AddressEditorViewModel
. Such viewmodel can be embedded in the page viewmodel, and it is easy to inject dependencies in these viewmodels (for example, to validate the ZIP codes or load the address from user profiles).
Updating the viewmodel properties
Data-binding in DotVVM can do one more thing - update the source property.
Imagine we have a NumericUpDown
control which has one textbox and two buttons. The buttons increase or decrease the value of a number inside the textbox.
In the page, the control can be used like this:
<cc:NumericUpDown Value="{value: MyNumber}" />
The buttons in the control are using command
bindings to call the _control.Up
and _control.Down
methods.
The Up
method looks like this:
public void Up()
{
// breaking change in DotVVM 4.0 - you need to call SetValueToSource to update the original property
// previously, calling Value++; was enough
SetValueToSource(ValueProperty, Value + 1);
}
The SetValueToSource
will look up the property in the viewmodel (MyNumber
) to which the Value
property is bound, and will update its value accordingly.
Prior to DotVVM 4.0, the value in the viewmodel was updated just by calling the setter (which contains call to
SetValue
). However, this was not reliable in some cases, and when the property was not bound to anything, calling this had no effect. That's why DotVVM 4.0 added the explicitSetValueToSource
method.