! Design View Model
Notes:
1. we could design the viewmodel carefully by decorating the property names, which make it possible that we don't need to config the mapping, just rely on the naming convension. the property name could rely on the rule like binding, e.g. domain model is order, the viewmodel name is orderDTO. the property of orderDTO could be CustomerName, it will map automatically either CustomerName in order class(in this case, no such property in order class), or Customer Property, and Name property(in this case, it map);
class Order
{
public Customer {get; set}
}
class Customer
{
public Name{get; set}
}
http://www.codeplex.com/wikipage?ProjectName=AutoMapper&title=Flattening&referringTitle=Home for more information about flatten.
Reference
WHY YOU NEVER EXPOSE YOUR DOMAIN MODEL AS YOUR MVC MODEL
Having helped a lot of programmers to take their first steps with ASP.NET MVC, I can safely say that the most common rookie error is to re-use your domain model in your view model.
I can see why people do it - it seems like the right thing to do when you have DRY beaten into you day-in, day-out - but there are some really good reasons to avoid re-using your domain model.
INDECENT EXPOSURE
The main reason to avoid this common mistake is that it accidentally exposes data for editing that you might not want changed. It's practical example time.
Here's a really basic domain object for a User. I have left off all the methods and just dumped common properties in the example.
public class User {
public int UserId { get; set; }
public string EmailAddress { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public int UserRoleId { get; set; }
}
So imagine you allow users to update their name and email address on some kind of profile editing page - tempting to just re-use this domain object right? So you use a User object as your view model and the job is done. On the view you spit out inputs for first name, last name and email address and live happily ever after. Except you don't.
One of the handy things ASP.NET MVC does for you is automatically map the HTTP POST to the paramater on the action in your controller, which so far looks like this...
public ActionResult Edit(User model) {
//...
So in this happy-path example you get the following automatic mapping...
| POST | User |
|---|---|
| EmailAddress=abc@def.hij | EmailAddress=abc@def.hij |
| FirstName=John | FirstName=John |
| LastName=Smith | LastName=Smith |
| UserId=0 | |
| Password="" | |
| UserRoleId=0 |
Of course, this relies on the user not doing anything funky. Remember the first rule of web security? Never trust user input. What happens if the user starts experimenting by adding additional data to the POST (either by adding inputs to the HTML form, or by sending amended data in a raw POST?)
What happens is you end up with that additional data mapped to your domain object! So if they sent "UserId=1" they could overwrite another user's data. If they sent "UserRoleId=1" they could elevate their permissions in the program.
One common screw-up is that to make the mapping easier, hidden inputs are added to the page to supply the supposedly non-editable fields - but this makes it even easier to hack things as nobody needs to guess the likely field names.
This is why it is actually rather sensible to create yourself a UserEditModel that looks like this:
public class UserEditModel {
public string EmailAddress { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And we accept this in our controller...
public ActionResult Edit(UserEditModel model) {
if (!ModelState.IsValid) {
return View(model);
}
var user = MapUserEditModelToUser(model);
if (!user.IsValidForPersistence()) {
// Add model error
return View(model);
}
this.userRepository.Persist(user);
return RedirectToAction("Details", new { id = user.UserId });
}
We protect ourselves by getting the user from the logged in user details and only mapping across the three fields we want them to be able to update. Great stuff.
As a bonus, having view models that only include the specific data that should be displayed / updated means you can take advantage of scaffolding to generate views without needing to manually remove stuff.
GETTING CROWDED
Another consideration is that your domain object will almost certainly be extended in the future. If you are using your domain objects as view models, anything you add starts getting included. Nobody should have to find and check all the user interfaces in case adding something to the domain model affects one of them. That's just insane.
ATTRIBUTES
Unless you are hand-cranking all your validation, it is likely that you are using attributes to provide validation rules to the properties on your view model.
[Required]
[StringLength(50)]
public string FirstName { get; set; }
Needless to say, the validation you impose on your UI is not the same as the validation you may impose elsewhere and these attributes in particular do not belong on your domain object. Indeed, the validation on the FirstName property on the user registration page may be entirely different from the validation on the edit user profile page.
SOURCES
Unless you have a limited application (or limited vision) you should have an eye on different sources of data for the application. I recently created a mobile application using ASP.NET MVC that sat over a national database of services. One good reason not to use the domain model in this instance is that the application could very easily sit on top of different services with just a simple mapping from the set of view models back to the API for the various services it could front.
People often talk about keeping the UI easy to replace, which is commendable - but why not make it just as easy to replace everything except the UI.
DUPLICATION
But what about duplication? That's a bad thing right? Well yes, but duplication is having the same thing in two places. Your view model should be very different to your domain object. They are for different purposes and should contain different things. If the mapping feels too much like boilerplate, Automapper was invented for just such a purpose - although I challenge anyone to show me a project where mapping from domain objects to view models or data transfer objects was the part that took up time.
SUMMARY
Your view model is a slave to your view. It might need to change to support a slight change in that view. Your domain object shouldn't have to change because you added something to your UI. Equally, your domain object is a slave to your business domain and it should be able to keep up with changes to that domain without causing a change to the UI. If your changes are creeping up and down your stack, that is a bad level of amplification and it indicates low cohesion.
===============================================================================================
A while back, I went over a few of the patterns and opinions we’ve gravitated towards on our current large-ish ASP.NET MVC project, or, how we do MVC. Many of these opinions were forged the hard way, by doing the wrong thing many times until we found the “right” opinion. Of course, many of these opinions are only really valid in the constraints of our project. While the domain of this project isn’t important, here are some key aspects to consider:
- AJAX is used very, very sparingly. Section 508 compliance is required
- XHTML compliance is also required
- XHTML DTD validation is also required
- All (well, 99%) operations revolve a single uber-entity. Think customer relationship management, where everything you do deals with exactly one customer
- Snippets of information repeated across many screens
- Screens are either edit, or view, but never both. (99% never)
Given these constraints, these opinions may or may not apply to the project you work on. Again, patterns are all about tradeoffs, benefits and liabilities. But, opinionated software is like building a bullet train. It goes extremely fast, but only in the direction you build it.
That said, I’m going to go over some of the main aspects of our MVC usage in a series of posts – starting with ViewModels.
ViewModel design
For our application, the ViewModel is a central aspect of our MVC architecture. One of the first dilemmas facing MVC developers is to decide what the “M” in MVC means in ASP.NET MVC. In Rails, this is fairly clear, the M is ActiveRecord (by default). But in ASP.NET MVC, the “M” is silent! Its out-of-the-box architecture offers no guidelines nor advice on what the M should be. Should it be an entity? Data access object? DTO? Something else?
Sidenote – the term DTO is far overused. DTO is a Data Transfer Object. The pattern describes the usage, not the shape of a type. Just because an object is all properties and no methods does NOT mean it’s a DTO.
For us, the ViewModel is inextricably linked to Views, which leads us to our first rule:
Rule #1 – All Views are strongly-typed
I think I’ve gone over this one enough, as I really can’t stand magic strings and loose contracts. A dictionary as ViewModel is a very loose contract between the Controller and View. While on rare occasions we still need to pass information in the dictionary part, we’ve limited this to supporting information to help render some of the lower-level pieces of the View, and are used for some “plumbing” pieces, these pieces do not show up in our Controller action nor are they visible when you design the view.
Rule #2 – For each ViewModel type, there is defined exactly one strongly typed View
We’ll get into how we do this soon, but this rule has a lot of implications:
- ViewModel types are distinct from our Domain Model types
- The choice of what View to show can be decided strictly on the shape of your ViewModel
- Re-used pieces in a View (through Partials) can be decided through re-using ViewModel types
On the first point, we never pass an Domain Model entity straight into the view. Most of the time, we only show a slice of information from a single entity. And many other times, the same snippet is shown in many places.
Rule #3 – The View dictates the design of the ViewModel. Only what is required to render a View is passed in with the ViewModel.
If a Customer object has fifty properties, but one component only shows their name, then we create a custom ViewModel type with only those two properties. Because we only have one ViewModel type per View, we shape our ViewModel around only what is displayed (or used) in that View. Why is this a Good Thing?
For one, just having a ViewModel points us to the right View. We need any other information other than your ViewModel type to pick the correct View. This also means we no longer need to concern ourselves with locating views by some arbitrary name, as the ViewModel is the View. Things like RenderPartial, which I have to select a name, become rather pointless at that point.
Rule #4 – The ViewModel contains only data and behavior related to the View
For the most part, our ViewModel contains only data. Most, if not all aggregations/calculations or shaping is done through our Domain Model. But occasionally, we have View-specific behavior or information, and that rightly belongs on our ViewModel.
We’ve looked at how we design our ViewModel and what it looks like, but how does it get there? If we create all these distinct ViewModel types separate from our Domain Model, didn’t we just create a bunch of work for ourselves? We thought so too, which is why we developed AutoMapper on this project.
Building the ViewModel
When we introduced AutoMapper into our MVC pipeline, we had a real problem. Do Controllers need to do the mapping between Domain Model and ViewModel in each action method? That becomes rather annoying for unit testing, as the mapping operation could warp things to a state that it becomes difficult to pull things back out. For example, or EditModels (ViewModels for forms) are very string-y, where DateTimes, Ints, Decimals etc are represented as strings. This comes from us using Castle Validators (future post, I promise) for validation.
So more moving parts, a dependency across all controllers? No, mapping in our Controller action just won’t do. Instead, we’ll use an Action Filter to do the work for us:
A request comes in, handled by an Action. The Action does its thing, ultimately deciding how to respond to the request. In many cases, this means rendering a view (ViewResult). From there, our Action Filter comes into play. On our Action method, we decorate it with an AutoMap attribute to configure the source/destination type pair to be mapped:
[AutoMap(typeof(Product), typeof(ShowProduct))]
public ActionResult Details(int id)
{
var product = _productRepository.GetById(id);
return View(product);
}
Very trivial, yes, but here we see that we still use the strongly-typed version of the View method, so that means that our model on the Action side, which I call the Presentation Model (feel free to pick a better name), is the strongly-typed ViewModel for the moment. The Presentation Model, which the Action creates, can be an entity, an aggregate root, or some other custom aggregate component that we build up.
From there, we decorated our action with a filter that specified we need to map from Product to ShowProduct. Why do we have to specify the source type? Well, many ORMs, including NHibernate, rely on proxy types for things like lazy loading. Instead of relying on the runtime type, we’ll explicitly specify our source type directly. This also helps us later in testing, as we can whip through all of our controller actions using reflection, and test to make sure the source/destination type specified is actually configured.
The filter attribute is very simple:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AutoMapAttribute : ActionFilterAttribute
{
private readonly Type _sourceType;
private readonly Type _destType;
public AutoMapAttribute(Type sourceType, Type destType)
{
_sourceType = sourceType;
_destType = destType;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var filter = new AutoMapFilter(SourceType, DestType);
filter.OnActionExecuted(filterContext);
}
public Type SourceType
{
get { return _sourceType; }
}
public Type DestType
{
get { return _destType; }
}
}
We simply capture the types and delegate to the real action filter for the work. This is again because I believe in separating metadata in attributes from the behavior they perform. Attributes just don’t work well for behavior. Instead, I’ll create a separate action filter:
public class AutoMapFilter : BaseActionFilter
{
private readonly Type _sourceType;
private readonly Type _destType;
public AutoMapFilter(Type sourceType, Type destType)
{
_sourceType = sourceType;
_destType = destType;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var model = filterContext.Controller.ViewData.Model;
object viewModel = Mapper.Map(model, _sourceType, _destType);
filterContext.Controller.ViewData.Model = viewModel;
}
}
The BaseActionFilter is just a class that implements the various filter methods as virtual members, so I can override just the ones I need to use. The AutoMapFilter pulls the original PresentationModel out of ViewData, performs the mapping operation, and puts the mapped ViewModel into the ViewData.Model property. From there, the strongly-typed view for our specified ViewModel type is rendered.
Because AutoMapper can flatten source types, we often find our ViewModel to simply follow a property chain for various pieces of information. Again, we let our View shape that piece. If we decide not to flatten, it’s usually because we’re creating a partial that re-uses a ViewModel type across other parent ViewModel types.
Wrapping it up
Designing ViewModels is quite ambiguous with MVC, as the shipped platform doesn’t offer any guidance or opinions in that area. But by forming rules around our ViewModel, we can create a path and direction for our innovation. Our rules are designed to strengthen the relationship between the View and the Model, with a concept of ViewModel – a Model designed exclusively for exactly one View.
In the next post, we’ll look at designing our views – for both viewing and editing data, and how we’ve crafted opinionated HTML builders to eliminate a lot of duplication and enforce standardization.

浙公网安备 33010602011771号