In a previous post, we talked about the MVVM pattern which aims to build a wrapper around a domain model class adding view specific behavior.
It is usually considered good practice to create separate input and output view models:
- Input view models are typically used for form pages; they can contain form specific metadata (for data validation and model binding) and will be populated with the data present in the form when it gets submitted.
- Output view models are used to display data, they can contain, for example, helper methods that takes care of proper data formatting.
In this post, we are going to define a specification for a general purpose view model class which can be used for both input and output. I don’t have enough experience with special purpose view models yet, so maybe we will elaborate on them in a future post.
The view model class should fulfill the following requirements:
- Expose the public properties of the domain model to client code
- Provides a constructor that takes a domain model as its argument
- Contain view specific code(data and behavior)
- Provide access to domain model methods
- Automatically apply attributes on properties from the domain model on the properties of the view model
- Provide a way to get a domain model instance populated with the data from the view model
- Provide access to the view model version of nested entities and collections
- Hides domain model versions of nested properties
Let’s elaborate on these points.
exposing public properties of the domain model
The view model class should expose the public and simply typed properties of the domain model class, consider the following:
Person class defines two simple properties
Name, according to the spec
PersonViewModel is expected to provide access to these properties. Very straightforward.
providing a constructor that accepts a domain model
The view model class should provide a constructor that accepts an instance of the corresponding domain model. The provided instance is used to initialize the corresponding properties and will be typically used as a back-end for the domain model method calls.
containing view specific code
Obviously, the responsibility of the view model class is to address any view specific concern by keeping it outside the domain model that it wraps, consider the following classes:
[Display] attribute is slapped on the
PersonViewModel.Name property, this attribute provides to the view engine the label that should be displayed for the
Name field. This view specific metadata attribute is kept out of the
Person domain model class.
PersonViewModel.GetCapitalizedName is a helper method that is going to be used by the view engine; encapsulated behavior for the view engine should be placed in the view model class.
providing access to domain model methods
Domain model methods should be available on the view model and should behave as expected when called, the view model class as to implement some mechanism to ensure this.
IsPayingCustomer method in the previous example, when called from a
PersonViewModel instance should behave as if it was called from the corresponding
applying attributes from the domain model
Attributes slapped on the properties of the domain model should be slapped as well on the equivalent properties on the view model.
Attributes are sometimes used to indicate data constraints and some data constraints are domain specific and should consequently be factored in the domain layer.
These attributes need to be taken into account in the view model class, consider the following:
[MaxLength] attribute is slapped on the
Person.Name property to limit the length of the person’s name to 50 characters.
PersonViewModel class should make sure to apply this attribute to the corresponding property, it can statically duplicate the attribute on top of the property or preferably implement a mechanism that applies dynamically the attribute from a domain model instance.
providing a way to get a populated domain model instance
The view model class should provide a readable property or a method that returns a domain model instance that is populated with the data present in the view model instance, example:
If you take a look at the usage at the end of the listing, you can see that mutations on the view model should be available in the domain model instance retrieved from the view model.
providing access to view model incarnations of nested complex types and collections
Entity classes from the domain model can have properties that are themselves other entities or collection of other entities, consider the following example:
Person class has an
OwnedCar property of type
Skills property of type
List<Skill>. For the
Skill entities we have defined corresponding view model classes
PersonViewModel class should provide access to the view model version of the
Skills properties, as in the following example:
The view model class as to make sure that these properties are correctly initialized from the provided domain model instance.
hiding domain model versions of nested entities
Finally, the domain model versions of the nested entities as we saw in the previous section should be hidden in the view model class.
Using the previous
PersonViewModel definition it should not be possible to perform the following: