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:

  1. Expose the public properties of the domain model to client code
  2. Provides a constructor that takes a domain model as its argument
  3. Contain view specific code(data and behavior)
  4. Provide access to domain model methods
  5. Automatically apply attributes on properties from the domain model on the properties of the view model
  6. Provide a way to get a domain model instance populated with the data from the view model
  7. Provide access to the view model version of nested entities and collections
  8. 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: