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:

class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
class PersonViewModel
{
...
public long Id { get; set; }
public string Name { get; set; }
}
view raw Person.cs hosted with ❤ by GitHub

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:

class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
class PersonViewModel
{
...
public long Id { get; set; }
public string Name { get; set; }
public PersonViewModel (Person person) { ... }
}
view raw PersonSpec5.cs hosted with ❤ by GitHub

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:

class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
class PersonViewModel
{
...
public long Id { get; set; }
[Display(Name="The user's name")]
public string Name { get; set; }
public string GetCapitalizedName() { ... }
}
view raw PersonSpec2.cs hosted with ❤ by GitHub

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.

class Person
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsPayingCustomer(Object someArgument)
{
//code
}
}
class PersonViewModel
{
...
public long Id { get; set; }
[Display(Name="The user's name")]
public string Name { get; set; }
public bool IsPayingCustomer(Object someArgument)
{
...
}
}
view raw PersonSpec3.cs hosted with ❤ by GitHub

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:

class Person
{
public long Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
}
class PersonViewModel
{
...
public long Id { get; set; }
/*
[MaxLength(50)] is applied implicitly or explicitly,
statically or dynamically
*/
public string Name { get; set; }
public string GetCapitalizedName() { ... }
}
view raw PersonSpec4.cs hosted with ❤ by GitHub

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:

class Person
{
public long Id { get; set; }
public string Name { get; set; }
}
class PersonViewModel
{
...
public long Id { get; set; }
public string Name { get; set; }
public PersonViewModel (Person person) { ... }
public Person DomainModel
{
get
{
// RETURN a copy of the inner DomainModel
...
}
}
}
var person = new Person { Id= 2149, Name="John Johnson" };
var personViewModel = new PersonViewModel(person);
personViewModel.Name = "Jack Jackson";
person = personViewModel.DomainModel;
// person.Name should now contains "Jack Jackson"
personViewModel.DomainModel.Name = "James Jameson";
// person.Name should not be mutated to "James Jameson"
// since personViewModel.DomainModel is a copy of the internal
// domain model.
view raw PersonSpec6.cs hosted with ❤ by GitHub

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:

class Person
{
public long Id { get; set; }
public string Name { get; set; }
public Car OwnedCar { get;set;}
public List<Skill> Skills { get; set;}
}
class Car
{
public string Name { get;set; }
}
class Skill
{
public string Name { get;set; }
}
// View Models
class CarViewModel { ... }
class SkillViewModel { ... }
view raw PersonSpec7.cs hosted with ❤ by GitHub

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:

class PersonViewModel
{
...
public CarViewModel OwnedCarVM { get;set; }
public List<SkillViewModel> SkillsVM { get;set; }
}

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:

var personViewModel = PersonViewMode(person);
//ERROR
//OwnedCar is ideally undefined on PersonViewModel
personViewModel.OwnedCar;
view raw PersonSpec8.cs hosted with ❤ by GitHub