Skip to main content
A newer version of this page is available. .
All docs
V21.1

View Models Generated at Compile Time

  • 12 minutes to read

The DevExpress MVVM Framework includes a source generator that produces boilerplate code for your View Models at compile time. You need to define a stub View Model class that specifies the required logic. Our MVVM Framework analyzes your implementation and applied attributes to generate the final View Model class with all required boilerplate code. Consider the following example.

View Example: View Models Generated at Compile Time

Base View Model

Create a partial class. Add attributes to the class and its fields/methods:

using DevExpress.Mvvm.CodeGenerators;

[GenerateViewModel]
partial class ViewModel {
    [GenerateProperty]
    string username;
    [GenerateProperty]
    string status;

    [GenerateCommand]
    void Login() => Status = "User: " + Username;
    bool CanLogin() => !string.IsNullOrEmpty(Username);
}
Generated View Model

The generator inspects the base View Model and produces a partial class that complements your implementation with the following boilerplate code:

You can view and debug the generated View Model:

partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    public string? Status {
        get => status;
        set {
            if(EqualityComparer<string?>.Default.Equals(status, value)) return;
            status = value;
            RaisePropertyChanged(StatusChangedEventArgs);
        }
    }

    DelegateCommand? loginCommand;
    public DelegateCommand LoginCommand {
        get => loginCommand ??= new DelegateCommand(Login, CanLogin, true);
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangedEventArgs StatusChangedEventArgs = new PropertyChangedEventArgs(nameof(Status));
}

Prerequisites

Your project should meet the following requirements:

  • C# 9+ (VB is not supported)
  • .NET Framework v4.6.1+ or .NET Core v3.0+
  • Visual Studio v16.9.0+

Otherwise, use Runtime-generated POCO View Models instead.

Prepare Your Project

Prepare your project as outlined below to enable support for View Models generated at compile time:

  1. Add a reference to the DevExpress.Mvvm.v21.1+ or install the DevExpress.Mvvm NuGet package.
  2. Install the DevExpress.Mvvm.CodeGenerators NuGet package in your project.
  3. Set the language version to 9 in the .csproject file:

    <PropertyGroup>
        <LangVersion>9</LangVersion>
    </PropertyGroup>
    

    For .NET Core projects, set the IncludePackageReferencesDuringMarkupCompilation property to true:

    <PropertyGroup>
        <IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
    </PropertyGroup>
    

Review Generated View Model Class

You can access the generated code only from Visual Studio. Use the Peek Definition command (F12) or search the generated file under Dependencies in Solution Explorer.

Apply Attributes to the Base View Model Class

Declare a namespace as follows to access attributes:

using DevExpress.Mvvm.CodeGenerators;

GenerateViewModel

Applies to a class. Indicates that the source generator should process this class and produce View Model boilerplate code.

Property Type Description
ImplementINotifyPropertyChanging bool Implements INotifyPropertyChanging.
ImplementIDataErrorInfo bool Implements IDataErrorInfo that allows you to validate data.
ImplementISupportServices bool Implements ISupportServices that allows you to include the Services mechanism to your View Model.
ISupportParentViewModel bool Implements ISupportParentViewModel that allows you to establish a parent-child relationship between View Models.
GenerateProperty

Applies to a field. The source generator produces boilerplate code for the property getter and setter based on the field declaration.

Property Type Description
IsVirtual bool Assigns a virtual modifier to the property.
OnChangedMethod string Specifies the name of the method invoked after the property value is changed. If the property is not specified, the method’s name should follow the On[PropertyName]Changed pattern.
OnChangingMethod string Specifies the name of the method invoked when the property value is changing. If the property is not specified, the method’s name should follow the On[PropertyName]Changing pattern.
SetterAccessModifier AccessModifier Specifies an access modifier for a set accessor. The default value is the same as a property’s modifier. Available values: Public, Private, Protected, Internal, ProtectedInternal.
GenerateCommand

Applies to a method. The source generator produces boilerplate code for a Command based on this method.

Property Type Description
AllowMultipleExecution bool Specifies the allowMultipleExecution parameter in the AsyncCommand constructor. The default value is false.
UseCommandManager bool Specifies the useCommandManager parameter in the Command constructor. The default value is true.
CanExecuteMethod string Specifies a custom CanExecute method name. If the property is not specified, the method’s name should follow the Can[ActionName] pattern.
Name string Specifies a custom Command name. The default value is [ActionName]Command.

Implement Interfaces

All View Models generated at compile time implement the INotifyPropertyChanged interface:

[GenerateViewModel]
public partial class ViewModel {
    [GenerateProperty]
    string username;
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

If you implement an interface in a base View Model class, add the interface’s members to this class.

In the code sample below, the base View Model class implements INotifyPropertyChanged. The generator analyzes the implementation and searches the PropertyChanged event to raise it from the generated View Model. If the base View Model class does not include the PropertyChanged event, the generated class is empty.

[GenerateViewModel]
public partial class ViewModel : INotifyPropertyChanged {
    [GenerateProperty]
    string username;
    public event PropertyChangedEventHandler PropertyChanged;
}
partial class ViewModel { 
    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? UserName {
        get => userName;
        set {
            if(EqualityComparer<string?>.Default.Equals(userName, value)) return;
            userName = value;
            RaisePropertyChanged(UserNameChangedEventArgs);
        }
    }
}

If you want to implement INotifyPropertyChanging, IDataErrorInfo, or ISupportServices, use the GenerateViewModel attribute’s properties.

Implement INotifyPropertyChanging

Set the ImplementINotifyPropertyChanging property to true:

[GenerateViewModel(ImplementINotifyPropertyChanging = true)]
public partial class ViewModel {
    [GenerateProperty]
    string username;
}
partial class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler? PropertyChanged;
    public event PropertyChangingEventHandler? PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(UsernameChangingEventArgs);
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangingEventArgs UsernameChangingEventArgs = new PropertyChangingEventArgs(nameof(Username));
}

Implement IDataErrorInfo

Set the ImplementIDataErrorInfo property to true:

[GenerateViewModel(ImplementIDataErrorInfo = true)]
public partial class ViewModel {    
} 

Generated View Models implement the IDataErrorInfo interface as follows:

partial class ViewModel : INotifyPropertyChanged, IDataErrorInfo {
    public event PropertyChangedEventHandler? PropertyChanged;
    string IDataErrorInfo.Error { get => string.Empty; }
    string IDataErrorInfo.this[string columnName] { get => IDataErrorInfoHelper.GetErrorText(this, columnName); }

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
}

The IDataErrorInfoHelper class allows you to get an error based on specified DataAnnotation attributes.

Implement ISupportServices

Set the ImplementISupportServices property to true:

[GenerateViewModel(ImplementISupportServices = true)]
    public partial class ViewModel {                 
} 

Refer to the Services in Custom ViewModels topic for more information on how to use the Service mechanism in a View Model.

partial class ViewModel : INotifyPropertyChanged, ISupportServices {
    public event PropertyChangedEventHandler? PropertyChanged;
    IServiceContainer? serviceContainer;
    protected IServiceContainer ServiceContainer { get => serviceContainer ??= new ServiceContainer(this); }
    IServiceContainer ISupportServices.ServiceContainer { get => ServiceContainer; }
    protected T? GetService<T>() where T : class => ServiceContainer.GetService<T>();
    protected T GetRequiredService<T>() where T : class => ServiceContainer.GetRequiredService<T>();

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    ...
}

Interface Implementation Notes for Inherited View Model Classes

Your View Model can inherit from another class. In this case, implementations declared in a parent class might affect a generated View Model.

If the parent class implements INotifyPropertyChanged or INotifyPropertyChanging, the child class cannot raise the PropertyChanged nor PropertyChanging events. Ensure that the parent class includes the RaisePropertyChanged or RaisePropertyChanging methods that allow the child class to raise the events:

public class DataObjectBase : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler PropertyChanged;
    public event PropertyChangingEventHandler PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    // or protected void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    protected void RaisePropertyChanging(string propertyName) => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
    // or protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);
}

[GenerateViewModel]
partial class ViewModel : DataObjectBase {
    [GenerateProperty]
    string username;
} 
partial class ViewModel {
    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(nameof(Username));
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

View Model Parent-Child Relationships

Set the ImplementISupportParentViewModel property to true to establish a parent-child relationship between View Models. The ISupportParentViewModel interface allows you to access Services registered in the main View Model from the child View Models. You can override the OnParentViewModelChanged event in a base View Model. If a base View Model class is inherited from another class, you can override the OnParentViewModelChanged event in the ancestor.

Refer to the following topic for more information on the parent-child relationship: ViewModel relationships (ISupportParentViewModel).

Declare Fields to Generate Properties

To generate a property in your View Model, create a field in a base View Model and add the GenerateProperty attribute to the field. The field name should not start with a capital letter:

[GenerateViewModel]
public partial class ViewModel {       
    [GenerateProperty]
    string username;
}

As a result, the generated View Model includes a property, a RaisePropertyChanged method, and PropertyChangedEventArgs for the generated property:

partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}  

If your field includes additional attributes, the generated property copies them:

[GenerateViewModel]
partial class ViewModel {
    [GenerateProperty]
    [StringLength(100, MinimumLength = 5)]
    string username;
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    [System.ComponentModel.DataAnnotations.StringLengthAttribute(100, MinimumLength = 5)]
    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

Property Change Notifications

You can define methods invoked when properties are changed. These methods should meet the following requirements:

  • Their names follow the On[PropertyName]Changed and On[PropertyName]Changing pattern.
  • They return void.
  • They have no parameters or a parameter of the same type as the property.
[GenerateViewModel(ImplementINotifyPropertyChanging = true)]
partial class ViewModel {
    [GenerateProperty]
    string username;

    // void OnUsernameChanged() { }
    void OnUsernameChanged(string oldUsername) {
        //...
    }
    // void OnUsernameChanging() { }
    void OnUsernameChanging(string newUsername) {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler? PropertyChanged;
    public event PropertyChangingEventHandler? PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(UsernameChangingEventArgs);
            OnUsernameChanging(value);
            var oldValue = username;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
            OnUsernameChanged(oldValue);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangingEventArgs UsernameChangingEventArgs = new PropertyChangingEventArgs(nameof(Username));
}

If you want to specify a custom name, specify the OnChangedMethod or OnChangingMethod attribute property as follows:

[GenerateViewModel]
    public partial class ViewModel {
        [GenerateProperty(OnChangedMethod = nameof(CustomName))]
        string username;

        void CustomName() { }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UserNameChangedEventArgs);
            CustomName();
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

Turn Methods into Commands

To generate a Command in your View Model, create a method in a base View Model and add the GenerateCommand attribute. The method should return void if you create DelegateCommands.

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand]
    void Save() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? saveCommand;
    public DelegateCommand SaveCommand {
        get => saveCommand ??= new DelegateCommand(Save, null, true);
    }
}

Create a CanExecute method if necessary. This method name should complete the Can[ActionName] pattern and have the same parameters as the Action method. If you require a custom name, specify the CanExecuteMethod attribute property.

If the source generator cannot find a method that matches the description, it passes null as the Command constructor’s canExecuteMethod parameter.

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand]
    void Login(string parameter) {
        //...
    }
    bool CanLogin(string parameter) {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? loginCommand;
    public DelegateCommand LoginCommand {
        get => loginCommand ??= new DelegateCommand(Login, CanLogin, true);
    }
}

Use the attribute properties to specify the Command constructor parameters. The code sample below specifies a custom name for the Command and sets the UseCommandManager parameter to false. If you disable UseCommandManager, add the RaiseCanExecuteChanged method to the base View Model as follows:

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand(UseCommandManager = false, Name = "CustomName")]
    void Show() {
        //...
    }
    bool CanShow() {
        //...
    }
    public void UpdateShowCommand() => CustomName.RaiseCanExecuteChanged();
} 
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? customName;
    public DelegateCommand CustomName {
        get => customName ??= new DelegateCommand(Show, CanShow, false);
    }
}

Asynchronous Commands

Declare a method that returns a Task to create an Asynchronous Command. Apply the GenerateCommand attribute.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand]
    async Task CalculateAsync() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    AsyncCommand? calculateAsyncCommand;
    public AsyncCommand CalculateAsyncCommand {
        get => calculateAsyncCommand ??= new AsyncCommand(CalculateAsync, null, false, true);
    }
}

Use the attribute properties to specify the Command constructor parameters. The code sample below sets the AllowMultipleExecution parameter to true.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand (AllowMultipleExecution = true)]
    async Task CalculateAsync() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    AsyncCommand? calculateAsyncCommand;
    public AsyncCommand CalculateAsyncCommand {
        get => calculateAsyncCommand ??= new AsyncCommand(CalculateAsync, null, true, true);
    }
}

Add XML Comments

You can add XML comments to a field and method. The generated property or command copies these comments. The following members are not supported:

  • PropertyChanged events
  • PropertyChanging events
  • RaisePropertyChanged methods
  • RaisePropertyChanging methods
  • ParentViewModels
  • ServiceContainers

If you want to add comments to these members, declare them in a base View Model.