DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.

30 March 2014

OTHER RELATED ARTICLES:

  1. Getting Started with DevExpress MVVM Framework. Commands and View Models.
  2. DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
  3. DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
  4. DevExpress MVVM Framework. Introduction to POCO ViewModels.
  5. DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
  6. DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
  7. DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.
  8. THIS POST: DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.
  9. DevExpress MVVM Framework. Behaviors.
  10. DevExpress MVVM Framework. TaskbarButtonService, ApplicationJumpListService and NotificationService.
  11. DevExpress MVVM Framework. Asynchronous Commands.
  12. DevExpress MVVM Framework. Converters.

000


Imagine configuring editing and validation settings from one place – eliminating the need to separately configure each data view (XAML). DataAnnotation attributes allow you to do exactly that.
Beginning with version 13.2, a consistent set of functionalities were implemented across all our controls. Originally, GridControl provided limited support for attributes and cell editors could not be configured by attributes specifying an editor data type. Bands could not be set at the data source level. Moreover, DataAnnotation attributes did not exist for PropertyGridControl. Now, PropertyGridControl, GridControl, and DataLayoutControl provide a similar functionality, and the Scaffolding Wizard also takes assigned attributes into account. We also introduced a flexible alternative in version 13.2 – the DevExpress Fluent API.

Which attributes are supported?

DevExpress PropertyGridControl, GridControl, DataLayoutControl, and Scaffolding Wizards support nearly every standard attribute in the System.ComponentModel.DataAnnotations namespace.

We also implemented a number of our own attributes for defining masks. These attributes are available from the DevExpress.Xpf.Mvvm.DataAnnotations namespace.

To learn more about the available attributes, see the following help topic: Data Annotation Attributes.

Common decoration patterns

  • In a model class

The simplest way to assign an attribute is to explicitly declare it on a property of a model class:

public class Point {
[Display(Name = "Abscissa")]
public double X { get; set; }
[Display(Name = "Ordinate")]
public double Y { get; set; }
}

Although this approach is simple and descriptive, your code may become difficult to read when there are many properties. You may also find your model class does not allow attribute decorations. For example, a WCF Data Service or the Entity Framework Database First approach may automatically generate model classes.

  • In a metadata class

Declare a metadata class and assign it to a model via the MetadataType attribute:

[MetadataType(typeof(PointMetadata))]
public class Point {
public double X { get; set; }
public double Y { get; set; }
}
public class PointMetadata {
[Display(Name = "Abscissa")]
public double X { get; set; }
[Display(Name = "Ordinate")]
public double Y { get; set; }
}

You can extend this approach to automatically generated classes using partial classes.

The obvious disadvantage is the metadata class is both difficult to support and error prone. For instance, you will not receive an error at compile time if you rename a model property and forget to do the same for its metadata class.

  • DevExpress Fluent API

Considering these disadvantages, we developed a flexible solution – the DevExpress Fluent API. The following code is written with the DevExpress Fluent API:

[MetadataType(typeof(PointMetadata))]
public class Point {
public double X { get; set; }
public double Y { get; set; }
}
public static class PointMetadata {
public static void BuildMetadata(MetadataBuilder<Point> builder) {
builder.Property(x => x.X).DisplayName("Abscissa");
builder.Property(x => x.Y).DisplayName("Ordinate");
}
}

Since the DevExpress Fluent API does not allow setting attributes on non-existent properties, renaming properties becomes easy.

It’s sometimes convenient to arrange properties in groups. The classic approach for this is to set a Display Attribute on the grouped properties. With the DevExpress Fluent API, properties can be grouped as follows:

builder
.Group("General Info")
.ContainsProperty(x => x.FirstName)
.ContainsProperty(x => x.LastName)
.EndGroup()
.Group("Contacts")
.ContainsProperty(x => x.Email)
.ContainsProperty(x => x.Phone)
.EndGroup()
.Group("Address")
.ContainsProperty(x => x.City)
.ContainsProperty(x => x.Zip)
.EndGroup();

While the Fluent API is valuable in complex scenarios, in simple cases it still makes sense to use one of the standard approaches.

Using attributes with DevExpress controls

We have a few demos that clearly demonstrate how to use attributes with our controls: “Data Grid - Smart Columns Generation”, “Layout Manager - Data Layout Control”, “Property Grid - DataAnnotation Attributes”, “Property Grid - DataAnnotation Attributes (Fluent API)”. Please refer to them to see our controls in action.

We prepared two samples. The first sample (E5179) illustrates how to use attributes with Entity Framework Code First, the second (E5180) uses the DevExpress Fluent API. The samples are very similar; below you will see a screenshot of how they look:

2014-03-31_1501 - Copy

  • GridControl

To configure the GridControl layout in for the underlying data types and attributes, set the GridControl.EnableSmartColumnsGeneration property to True. In this case, GridControl will automatically generates columns at runtime and customize them according to DataAnnotation and DevExpress Fluent API usage.

You can also generate columns at design time based on the data source objects. GridControl’s SmartTag provides the “Generate Columns” action for this purpose:

2014-03-31_1505_1

Generated columns have their IsSmart property set to True. It means that these columns are configured automatically. In the meantime, you are free to set any property manually – your settings will override the automatic settings.

You can call the TableLayout method of the Fluent API to define a grouping applicable only for GridControls:

builder.TableLayout()
.Group("Personal Data")
.ContainsProperty(x => x.FirstName)
.ContainsProperty(x => x.LastName)
.EndGroup();

The DevExpress Scaffolding Wizard also generates columns with IsSmart=True. To avoid generating columns for some properties, set the ScaffoldColumn(false) attribute for them:

[ScaffoldColumn(false)]
public string LastName { get; set; }

  • DataLayoutControl

With the Fluent API, you can configure groups by calling the Group method as we did for GridControl. If you like, it's also possible to define a grouping exclusively for DataLayoutControl using the DataFormLayout method. The following code snippet demonstrates this:

builder.DataFormLayout()
.GroupBox("General Info")
.ContainsProperty(x => x.FirstName)
.ContainsProperty(x => x.LastName)
.ContainsProperty(x => x.CreditCardNumber)
.EndGroup()
.GroupBox("Contacts")
.ContainsProperty(x => x.Email)
.ContainsProperty(x => x.Phone)
.EndGroup()
.GroupBox("Address")
.ContainsProperty(x => x.Address)
.ContainsProperty(x => x.City)
.ContainsProperty(x => x.State)
.ContainsProperty(x => x.Zip)
.EndGroup();

The Scaffolding Wizard generates a simple LayoutControl with LayoutGroup and LayoutItem objects. You can customize them in the designer as you like.

  • PropertyGridControl

PropertyGridControl automatically generates rows for the defined attributes.

PropertyGridControl provides the capability to initialize properties at runtime. In this scenario, you could opt for a custom item initializer. For this, assign the InstanceInitializer attribute to the required property.

For example:

[InstanceInitializer(typeof(Item1), "Item1")]
public object Item { get; set; }

At runtime, a user will see an additional “Item1” button in the Property Menu. When s/he presses this button, the Item property will be set to a new Item1 class instance.

Some tricks

  • It is possible to not create a metadata builder class; instead, place the BuildMetadata method directly in a data class:
public class Point {
public double X { get; set; }
public double Y { get; set; }
public static void BuildMetadata(MetadataBuilder<Point> builder) {
builder.Property(x => x.X).DisplayName("Abscissa");
builder.Property(x => x.Y).DisplayName("Ordinate");
}
}

  • If you have a library which you can’t modify, use MetadataLocator to register metadata classes on startup:
MetadataLocator.Default = MetadataLocator.Create()
.AddMetadata<Metadata>();
public class Metadata {
public static void BuildMetadata(MetadataBuilder<Employee> builder) {
builder.Property(x => x.FullName).ReadOnly();
}
public static void BuildMetadata(MetadataBuilder<Team> builder) {
builder.Property(x => x.Id).ReadOnly();
}
}

  • Metadata for generic classes:
[MetadataType(typeof(BaseGenericClassMetadata<>))]
public class BaseGenericClass<T1> {
public int BaseProperty1 { get; set; }
public int BaseProperty2 { get; set; }
public int BaseProperty3 { get; set; }
public int BaseProperty4 { get; set; }
}
public class BaseGenericClassMetadata<T1> {
public static void BuildMetadata(MetadataBuilder<BaseGenericClass<T1>> builder) {
builder.Property(x => x.BaseProperty2).ReadOnly();
}
public static void BuildBaseMetadata<T>(MetadataBuilder<T>
builder) where T : BaseGenericClass<T1> {
builder.Property(x => x.BaseProperty4).ReadOnly();
}
}

 

That’s all. Thank you for your time.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.