patterncsharpModerate
Separating Models and ViewModels
Viewed 0 times
andmodelsseparatingviewmodels
Problem
I have a central domain assembly which contains various rich domain models. Lots of business logic, etc. To keep this example simple, here's probably the simplest one:
```
public class Location
{
private int _id;
public int ID
{
get { return _id; }
private set
{
if (value == default(int))
throw new ArgumentNullException("ID");
_id = value;
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("Name");
_name = value;
}
}
public string Description { get; set; }
private string _address;
public string Address
{
get { return _address; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("Address");
_address = value;
GeoCoordinates = IoCContainerFactory.Current.GetInstance().ConvertAddressToCoordinates(Address);
}
}
public Coordinates GeoCoordinates { get; private set; }
private Location() { }
public Location(string name, string address)
{
Name = name;
Description = string.Empty;
Address = address;
}
public Location(int id, string name, string description, string address, Coordinates coordinates)
{
if (coordinates == null)
throw new ArgumentNullException("GeoCoordinates");
ID = id;
Name = name;
Description = description;
Address = address;
}
public class Coordinates
{
public decimal Latitude { get; private set; }
public decimal Longitude { get; private set; }
private Coordinates() { }
public Coordinates(decimal latitude, decimal longitude)
: this()
{
Latitude = latitude;
Longitude
```
public class Location
{
private int _id;
public int ID
{
get { return _id; }
private set
{
if (value == default(int))
throw new ArgumentNullException("ID");
_id = value;
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("Name");
_name = value;
}
}
public string Description { get; set; }
private string _address;
public string Address
{
get { return _address; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException("Address");
_address = value;
GeoCoordinates = IoCContainerFactory.Current.GetInstance().ConvertAddressToCoordinates(Address);
}
}
public Coordinates GeoCoordinates { get; private set; }
private Location() { }
public Location(string name, string address)
{
Name = name;
Description = string.Empty;
Address = address;
}
public Location(int id, string name, string description, string address, Coordinates coordinates)
{
if (coordinates == null)
throw new ArgumentNullException("GeoCoordinates");
ID = id;
Name = name;
Description = description;
Address = address;
}
public class Coordinates
{
public decimal Latitude { get; private set; }
public decimal Longitude { get; private set; }
private Coordinates() { }
public Coordinates(decimal latitude, decimal longitude)
: this()
{
Latitude = latitude;
Longitude
Solution
I was just playing around with this concept today. I have a User class defined in another assembly. Then I created three classes "based on" (but not derived from) that User class: CreateUser, EditUser, and DetailsUser. Each contains View-specific DataAnnotations (Required, DataType, etc.).
CreateUser has no ID, and has an extra property, VerifyPassword. My validation logic ensures that VerifyPassword==Password. There is no ID property, because it's a new User. After validation in my Create action, I can then map it to a User and add it to my data store.
For Edit User, I pull the user from the database, and map it to an EditUser object. EditUser has a read-only and hidden ID, and no Password properties. MVC's model binder prevents anyone from injecting properties on the User object that don't exist on the EditUser object.
For DetailsUser, I do something similar, again hiding the Password property.
You are right about the class explosion. However, each class is very tiny and self-contained. The nice thing about keeping all of this in the ViewModels is that I am free to use Html.EditorForModel() in my views. For me, the choice is extra code in my ViewModels, or extra code in my Views. It's up to you where to put it.
It does seem to violate DRY, having multiple User-based classes with duplicate properties. I thought perhaps they could derive from a common class, and maybe even User itself. I'm still thinking on that, and am open to thoughts and suggestions.
As for the mapping, I have been playing around with the Moo project (https://github.com/dclucas/MOO). It has a simple mapper that I find easier to use than AutoMapper.
This creates an EditUser object from an existing User object, provided the property names match.
public class CreateUser
{
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public String Email { get; set; }
[Required]
[DataType(DataType.Password)]
public String Password { get; set; }
[Required]
[DataType(DataType.Password)]
public String VerifyPassword { get; set; }
}CreateUser has no ID, and has an extra property, VerifyPassword. My validation logic ensures that VerifyPassword==Password. There is no ID property, because it's a new User. After validation in my Create action, I can then map it to a User and add it to my data store.
public class EditUser
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public String Email { get; set; }
}For Edit User, I pull the user from the database, and map it to an EditUser object. EditUser has a read-only and hidden ID, and no Password properties. MVC's model binder prevents anyone from injecting properties on the User object that don't exist on the EditUser object.
public class DetailsUser
{
[HiddenInput(DisplayValue = true)]
public int Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String Email { get; set; }
public String Password { get { return "Not Shown"; } }
}For DetailsUser, I do something similar, again hiding the Password property.
You are right about the class explosion. However, each class is very tiny and self-contained. The nice thing about keeping all of this in the ViewModels is that I am free to use Html.EditorForModel() in my views. For me, the choice is extra code in my ViewModels, or extra code in my Views. It's up to you where to put it.
It does seem to violate DRY, having multiple User-based classes with duplicate properties. I thought perhaps they could derive from a common class, and maybe even User itself. I'm still thinking on that, and am open to thoughts and suggestions.
As for the mapping, I have been playing around with the Moo project (https://github.com/dclucas/MOO). It has a simple mapper that I find easier to use than AutoMapper.
var editUser= user.MapTo();This creates an EditUser object from an existing User object, provided the property names match.
Code Snippets
public class CreateUser
{
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public String Email { get; set; }
[Required]
[DataType(DataType.Password)]
public String Password { get; set; }
[Required]
[DataType(DataType.Password)]
public String VerifyPassword { get; set; }
}public class EditUser
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public String Email { get; set; }
}public class DetailsUser
{
[HiddenInput(DisplayValue = true)]
public int Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String Email { get; set; }
public String Password { get { return "Not Shown"; } }
}var editUser= user.MapTo<EditUser>();Context
StackExchange Code Review Q#14752, answer score: 11
Revisions (0)
No revisions yet.