outline
specification for an input/output view model class
UPDATE, 15 Nov2017 — Thanks to Magick’s comment we have been able to make the specification more consistent, please checkout the updated “6.Provide a way to get a domain model instance populated with the data from the view model” point
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 specification
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:
The Person
class defines two simple properties Id
and 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.
Example:
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:
The [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.
The 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.
The IsPayingCustomer
method in the previous example, when called from a PersonViewModel
instance should behave as if it was called from the corresponding Person
instance.
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:
The [MaxLength]
attribute is slapped on the Person.Name
property to limit the length of the person’s name to 50 characters.
The 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 near 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.
The DomainModel
read-only property can (perhaps should) return a copy of the DomainModel
object encapsulated in the view model and not a reference to it in order to prevent outside mutations of the encapsulated DomainModel
object. Checkout the usage at the end of the listing.
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:
The Person
class has an OwnedCar
property of type Car
and Skills
property of type List<Skill>
. For the Car
and Skill
entities we have defined corresponding view model classes CarViewModel
and SkillViewModel
.
The PersonViewModel
class should provide access to the view model version of the OwnedCar
and 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: