DotVVM.com

Cheat Sheet for ASP.NET Developers

DotVVM is an open source MVVM framework for ASP.NET and ASP.NET Core.

It is inspired by ASP.NET Web Forms, but addresses the most criticized pain-points of Web Forms: viewstate, ugly markup and bad testability.

DotVVM can be used together with ASP.NET Web Forms in the same application. If you need to extend legacy Web Forms application with new pages, you can write them in DotVVM without rewriting the rest.

Learning DotVVM is a matter of days.

No <form>

ASP.NET Web Forms needs the entire page to be wrapped in the <form> element. Otherwise, the postbacks wouldn't work.

If you want to take advantage of AJAX, you need to use the UpdatePanel control.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DotVVM.WebForms.Tutorial.WebFormsSamples._01_no_form.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
        </div>
    </form>
</body>
</html>

There is no need to do that in DotVVM. Postbacks in DotVVM are using AJAX by default, and in many cases, you don't need to do full postback at all. There are other ways which are more efficient and transfer less data.

@viewModel DotVVM.WebForms.Tutorial.DotvvmSamples._01_no_form.ViewModels.DefaultViewModel, DotVVM.WebForms.Tutorial

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>


</body>
</html>

No runat="server" and control IDs

All controls in ASP.NET need to specify runat="server" to work properly, and often you need to specify their ID.

<asp:Button ID="Button1" runat="server" Text="Hello" />

DotVVM doesn't have the runat attribute and even control IDs are not necessary. DotVVM uses the MVVM pattern and data-binding. Thus, you don't need to access the controls from your code.

<dot:Button Text="Hello" />

No viewstate

ASP.NET stores the page state in a hidden field which is sent to the server on every postback. You can turn it on an off for any control, but some controls don't work when viewstate is disabled.

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="B9jhQnRecfXw4WHR9gGP2I+GO04KVMcekvzXe..." />

DotVVM keeps the page state in a viewmodel defined by your own C# class.

Thanks to that, you can precisely control what is included in the page state, and what properties will be transferred from the server to client and back.

public class MyPageViewModel
{
    public string FilterText { get; set; }

    [Bind(Direction.ServerToClient)]
    public List<CustomerDTO> Customers { get; set; }

    ...
 
}

Master Pages

ASP.NET uses ContentPlaceHolder and Content controls to define a template and a dynamic content.

Site.master

<!DOCTYPE html>

<html>
<head runat="server">
    <title></title>

    <asp:ContentPlaceHolder ID="HeadContent" runat="server">
    </asp:ContentPlaceHolder>

</head>
<body>
    <form id="form1" runat="server">
        <div>

            <asp:ContentPlaceHolder ID="MainContent" runat="server">
            </asp:ContentPlaceHolder>
            
        </div>
    </form>
</body>
</html>

Default.aspx

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

</asp:Content>

DotVVM uses the same control names. If you want to build a Single Page Application, you can just use SpaContentPlaceHolder.

Site.dotmaster

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>

    <dot:ContentPlaceHolder ID="HeadContent" />

</head>
<body>

    <dot:SpaContentPlaceHolder ID="MainContent" />

</body>
</html>

Default.dothtml

<dot:Content ContentPlaceHolderID="HeadContent">

</dot:Content>

<dot:Content ContentPlaceHolderID="MainContent">

</dot:Content>

Data-binding Everywhere

In ASP.NET, you can use Eval, Bind or Item.Property in data-bound controls like GridView. However, using Eval or Bind can lead to runtime errors, and the strongly-typed bindings cannot be used everywhere and have some limitations.

Default.aspx

<%-- You need to call DataBind() manually, or place the control in another data-bound control. --%>
<asp:TextBox ID="FirstNameTextBox" runat="server" Text="<%# FirstName %>" />

Default.aspx.cs

public string FirstName { get; set; } = "John";

protected void Page_Load(object sender, EventArgs e)
{
    this.DataBind();
}

DotVVM uses data-binding everywhere and it is always type-safe. Even complex types and collections are fully supported.

Default.dothtml

<dot:TextBox Text="{value: FirstName}" />

DefaultViewModel.cs

public string FirstName { get; set; } = "John";

Client-side Interactivity

The data-binding expressions are evaluated on the server. If you want to disable a TextBox based on CheckBox value, you need to make a postback. Alternatively, you write a piece of JavaScript and deal with client IDs.

Default.aspx

<asp:CheckBox ID="IsOtherItemCheckBox" runat="server" 
                AutoPostBack="true"
                OnCheckedChanged="IsOtherItemCheckBox_CheckedChanged"
                Text="Other" 
                CausesValidation="false" />

<asp:TextBox ID="OtherValueTextBox" runat="server" Enabled="false" />

Default.aspx.cs

protected void IsOtherItemCheckBox_CheckedChanged(object sender, EventArgs e)
{
    OtherValueTextBox.Enabled = IsOtherItemCheckBox.Checked;
}

In DotVVM, the binding expressions are translated to JavaScript and evaluated directly in the browser. You can also use expressions in value bindings.

Default.dothtml

<dot:CheckBox Checked="{value: IsOtherItem}" Text="Other" />
<dot:TextBox Enabled="{value: IsOtherItem}" Text="{value: OtherValue}" />

DefaultViewModel.cs

public bool IsOtherItem { get; set; }
public string OtherValue { get; set; }

Events

ASP.NET controls provide various events that can be handled in code-behind.

Default.aspx

<asp:Button ID="SaveButton" runat="server" Text="Save" OnClick="SaveButton_Click" />

Default.aspx.cs

protected void SaveButton_Click(object sender, EventArgs e)
{
    // ...
}

DotVVM controls can call methods in the viewmodel, which is testable.

You can pass any arguments to the methods in the viewmodel, and async methods are also supported.

Default.dothtml

<dot:Button Text="Save" Click="{command: Save()}" />

DefaultViewModel.cs

public void Save()
{
    // ...
}

Repeater

ASP.NET Repeater control can be populated using data source controls or from a code-behind. You can use HeaderTemplate and FooterTemplate to wrap the items in a custom element.

Default.aspx

<asp:Repeater ID="ItemsRepeater" runat="server">
    <HeaderTemplate><ul></HeaderTemplate>
    <ItemTemplate>
        <li><%# Eval("Text") %></li>
    </ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

Default.aspx.cs

protected void Page_PreRender(object sender, EventArgs e)
{
    ItemsRepeater.DataSource = GetCollection();
    ItemsRepeater.DataBind();
}

In DotVVM, there are no DataSource controls. Data-binding is used everywhere.

The Repeater in DotVVM is very similar to the Web Forms one. The ItemTemplate element is optional, and you can specify the wrapper tag using WrapperTagName property.

Default.dothtml

<dot:Repeater DataSource="{value: Items}" WrapperTagName="ul">
    <li>{{value: Text}}</li>
</dot:Repeater>

DefaultViewModel.cs

public List<TextDTO> Items { get; set; }
                
public override Task PreRender()
{
    Items = GetCollection();

    return base.PreRender();
}

CheckBoxList

ASP.NET CheckBoxList control can return a collection of IDs, but the collection is not strongly-typed and working with selected items is not very straight-forward.

Default.aspx

<asp:CheckBoxList ID="CountryList" runat="server"
                  DataTextField="Name" 
                  DataValueField="Id" />

Default.aspx.cs

// read selected item IDs
var selectedIds = CountryList.Items.OfType<ListItem>()
    .Where(i => i.Selected)
    .Select(i => int.Parse(i.Value))
    .ToList();

DotVVM CheckBox can be bound to a single boolean property, or to a typed collection of values. Values which are checked will appear in the collection, and vice-versa.

Default.dothtml

<dot:Repeater DataSource="{value: Countries}">
    <dot:CheckBox Text="{value: Name}" CheckedValue="{value: Id}"
                  CheckedItems="{value: _root.SelectedIds}" />
</dot:Repeater>

DefaultViewModel.cs

public List<int> SelectedIds { get; set; } = new List<int>();

TextBox and Validation

ASP.NET provides a wide range of validator controls that can display error messages. The validation rules are defined in the page markup and the syntax is quite long.

Default.aspx

<asp:TextBox ID="BeginDateTextBox" runat="server" />
-
<asp:TextBox ID="EndDateTextBox" runat="server" />

<asp:RequiredFieldValidator ID="BeginDateRequiredValidator" runat="server" 
                            ErrorMessage="BeginDate is required!" 
                            ControlToValidate="BeginDateTextBox"
                            Display="Dynamic" />
<asp:CompareValidator ID="BeginDateFormatValidator" runat="server" 
                        ErrorMessage="BeginDate format is not correct!" 
                        ControlToValidate="BeginDateTextBox" 
                        Operator="DataTypeCheck" 
                        Type="Date" 
                        Display="Dynamic" />
<asp:RequiredFieldValidator ID="EndDateRequiredValidator" runat="server" 
                            ErrorMessage="EndDate is required!" 
                            ControlToValidate="EndDateTextBox" 
                            Display="Dynamic" />
<asp:CompareValidator ID="EndDateFormatValidator" runat="server" 
                        ErrorMessage="EndDate format is not correct!" 
                        ControlToValidate="EndDateTextBox" 
                        Operator="DataTypeCheck" 
                        Type="Date" 
                        Display="Dynamic" />
<asp:CustomValidator ID="BeginEndDateValidator" runat="server" 
                        ErrorMessage="EndDate must be greater than BeginDate!"
                        ControlToValidate="BeginDateTextBox"
                        OnServerValidate="BeginEndDateValidator_OnServerValidate" 
                        Display="Dynamic" />

Default.aspx.cs

protected void BeginEndDateValidator_OnServerValidate(object source, ServerValidateEventArgs args)
{
    if (DateTime.TryParse(BeginDateTextBox.Text, out var beginDate) 
	    && DateTime.TryParse(EndDateTextBox.Text, out var endDate))
    {
        if (beginDate > endDate)
        {
            args.IsValid = false;
            return;
        }
    }

    args.IsValid = true;
}

DotVVM takes advantage of .NET Data Annotation Attributes and supports IValidatableObject for custom validation rules. The validation rules are defined in the viewmodel.

DotVVM validators can also apply CSS classes on any HTML elements and much more.

Default.dothtml

<dot:TextBox Text="{value: BeginDate}" FormatString="d" />
-
<dot:TextBox Text="{value: EndDate}" FormatString="d" />

<dot:ValidationSummary />

TimeSlot.cs

public class DefaultViewModel : DotvvmViewModelBase, IValidatableObject
{

    [Required]
	// display an error if the format is not correct 
	// (without this attribute, BeginDate would be DateTime.MinValue, or null in case of Nullable<DateTime>)
    [DotvvmEnforceClientFormat]     
    public DateTime BeginDate { get; set; }

    [Required]
    [DotvvmEnforceClientFormat]
    public DateTime EndDate { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (BeginDate > EndDate)
        {
            yield return this.CreateValidationResult("EndDate must be greater than BeginDate!", 
			    vm => vm.BeginDate);
        }
    }

}

Localization

ASP.NET provided a way to use RESX files for localization of controls.

<asp:Button ID="SaveButton" runat="server" Text="<%$ Resources: MyResources, SaveButtonText %>" />

DotVVM supports the resource binding for RESX files or other C# expressions (e.g. ASP.NET Core localization).

<dot:Button Text="{resource: MyResources.SaveButtonText}" />

Linking to Routes

ASP.NET allows to build hyperlink to routes registered in a route table. Routing is optional in ASP.NET Web Forms.

<asp:HyperLink ID="SampleLink" runat="server" 
               NavigateUrl="<%$ RouteUrl: RouteName=Sample12, Id=1 %>" 
               Text="Link" />

DotVVM ships with the RouteLink control which can do the same thing. Routing is mandatory in DotVVM - pages cannot be accessed using their file names in URL.

<dot:RouteLink RouteName="Sample12" Param-Id="1" 
               Text="Link" />

GridView

ASP.NET includes the GridView control with plenty of features. Some data sources support even server-side sorting and paging.

Default.aspx

<asp:GridView runat="server" ID="ProductsGrid" 
                AutoGenerateColumns="false"
                ItemType="DotVVM.WebForms.Tutorial.Model.ProductDTO"
                SelectMethod="GetData"
                AllowSorting="true" 
                AllowPaging="true" PageSize="20">
    <Columns>
        <asp:CheckBoxField DataField="IsSelected" />
        <asp:BoundField HeaderText="Product" DataField="Title" SortExpression="Title" />
        <asp:BoundField HeaderText="Category" DataField="Category" SortExpression="Category" />
        <asp:BoundField HeaderText="Qty/Unit" DataField="QuantityPerUnit" SortExpression="QuantityPerUnit" />
        <asp:BoundField HeaderText="Unit Price" DataField="UnitPrice" DataFormatString="c" SortExpression="UnitPrice" />
        <asp:BoundField HeaderText="In Stock" DataField="UnitsInStock" SortExpression="UnitsInStock" />
    </Columns>
</asp:GridView>

Default.aspx.cs

public IQueryable<ProductDTO> GetData()
{
    // ...
}

DotVVM contains a similar GridView control. It can be populated manually, or using IQueryable.

The control can be styled easily using CSS, and the paging is done by a stand-alone DataPager control.

Default.dothtml

<dot:GridView DataSource="{value: Products}">
    <dot:GridViewCheckBoxColumn ValueBinding="{value: IsSelected}" />
    <dot:GridViewTextColumn HeaderText="Product" ValueBinding="{value: Title}" AllowSorting="true" />
    <dot:GridViewTextColumn HeaderText="Category" ValueBinding="{value: Category}" AllowSorting="true" />
    <dot:GridViewTextColumn HeaderText="Qty/Unit" ValueBinding="{value: QuantityPerUnit}" AllowSorting="true" />
    <dot:GridViewTextColumn HeaderText="Unit Price" ValueBinding="{value: UnitPrice}" FormatString="c" AllowSorting="true" />
    <dot:GridViewTextColumn HeaderText="In Stock" ValueBinding="{value: UnitsInStock}" AllowSorting="true" />
</dot:GridView>

<dot:DataPager DataSet="{value: Products}" />

DefaultViewModel.cs

public GridViewDataSet<ProductDTO> Products { get; set; } = new GridViewDataSet<ProductDTO>()
{
    PagingOptions =
    {
        PageSize = 20
    }
};

public override Task PreRender()
{
    IQueryable<ProductDTO> queryable = GetProducts();
    Products.LoadFromQueryable(queryable);

    return base.PreRender();
}

FileUpload

In ASP.NET, files can be uploaded on a postback. It is not easy to show the upload progress to the user without a third-party control.

Default.aspx

<asp:FileUpload ID="ImageFileUpload" runat="server" />

<asp:Button ID="UploadButton" runat="server" 
            Text="Store Image"
            OnClick="UploadButton_OnClick"/>

Default.aspx.cs

protected void UploadButton_OnClick(object sender, EventArgs e)
{
    if (ImageFileUpload.HasFiles)
    {
        foreach (var file in ImageFileUpload.PostedFiles)
        {
            file.SaveAs("filename.png");
        }
    }
}

DotVVM FileUpload control can display upload progress, errors, and can be styled using CSS. It always uploads files on the background.

The files are saved in a temporary store on the server and can be retrieved by their unique IDs stored in the viewmodel.

Default.dothtml

<dot:FileUpload UploadedFiles="{value: Uploads}" 
                UploadCompleted="{command: StoreFile()}" />

DefaultViewModel.cs

// use dependency injection to request uploaded file storage
private readonly IUploadedFileStorage uploadedFileStorage;

public DefaultViewModel(IUploadedFileStorage uploadedFileStorage)
{
    this.uploadedFileStorage = uploadedFileStorage;
}

// the file IDs are kept in this collection
public UploadedFilesCollection Uploads { get; set; } = new UploadedFilesCollection();

public void StoreFile()
{
    foreach (var file in Uploads.Files)
    {
        uploadedFileStorage.SaveAs(file.FileId, "filename.png");
    }
}

Other Resources

DotVVM is open source and offers a free extension for Visual Studio 2017.

Our mission is to make DotVVM available to everyone, including schools, freelancers or open source developers. That's why the framework is open source and there is a free extension for Visual Studio.

To get even more value from DotVVM, check out our commercial controls: