다음을 통해 공유


View Models in ASP.NET MVC

Q: Should I have a view Model in my ASP.NET MVC architecture?

A: Yes.

Well, that was a short post! J

Being more serious, this is an interesting topic that the advisors and p&p team discussed a few times while they were building the Reference Implementation for the soon-to-be-complete Web Client Guidance, and it is something I've heard discussed time and time again out in the field.

I usually describe the "M" in both MVC and MVP as "the rest of the application", i.e. the business logic and business entities, and implicitly everything below in the stack. Some people don't like this analogy, saying the "M" refers only to the classes used to pass data between Controller and View. I agree it is arguably inaccurate, but I find it a useful way to think about a design based on these patterns... not least because of some of the variations in design I've seen that reflect those described below.

Where discussion gets interesting is when people start asking what the View can depend upon (and for the rest of this post think MVC, not MVP). To dodge a lot of detail this comes down to the following options;

1. Business Entities are passed to a View as the "Model". Therefore the View has detailed knowledge of part of the business layer.

2. Business Entities are never passed to a View – instead the relevant data is extracted and placed in a new entity reserved purely for use by the UI layer... or a "View Model" as people call it today (back when I coded as a day job we called these UI Entities so don't go thinking this is something new!)

These options become particularly interesting when you consider that in many modern systems the Business Entities are actually basically Data Transfer Objects generated or consumed by a tool such as the Entity Framework, nHibernate, etc, etc. This is useful because they may have validation behaviour built in. Business Logic is usually implemented as POCOs that manipulate, create, or return these DTOs.

This means that in option 1 the UI has access to all the validation goodness that exists on these entities. What's more, you don't need to create a new set of View Models that almost exactly match your data layer, and a set of "mappers" that convert Business Entities into a View Model.

However... note that I said "... almost exactly match...". You see, in the vast majority of systems there is something other than just what is stored in your database that needs passing to the view. This could be the current user's login name, or the current date and time. Or perhaps we must pass Boolean flags to indicate whether or not various regions of the screen should be displayed based on the user's roles.

To illustrate the differences, let's see 3 different examples as to how you might pass model data to a view. I've blatantly plagiarised some of the great comments that Francis, Julian, and the advisors made here so don't credit me with all the hard work J

Variant 1: No View Model

In Variant 1 we have no View Model at all, so our controller simply retrieves data from the business logic as a business entity, and passes it to the view...

public class ModelController : Controller

{

    public ActionResult Index(int someparameter)

    {

        BusinessEntity model = BusinessLogic.GetModel(someparameter);

        return View(model);

    }

}

The code for this is simple, concise, and requires very little work on my part. However, what if I want to add a property to indicate whether or not the "login" region of the view should be rendered? What if the view needs some other model data? This approach very tightly couples not only the business logic to the UI, but potentially the database to the UI (depending on your ORM tool, and how your business entities are created).

To summarise;

1. (con) This approach is unlikely to suit real MVC applications, as there is usually additional data that must be passed across.

2. (con) It is not flexible – it is more likely to lead to additional code churn after go-live other than in the simplest of cases.

3. (con) Developers that don't know these patterns well may consider adding UI-focused fields to your business entity, which compromises your business layer.

4. (pro) MVC can take advantage of the same validation mark-up attributes (or other scheme) that your business layer uses.

5. You can use it, but be prepared to take the hit for changes later, and make sure your developers know how to evolve it into Variants 2 or 3.

The bottom line is that I would use this if I knew it was very unlikely that the view (perhaps a very focused partial view in a very simple system) would ever need more than a single business entity... but other people, including the p&p guys, rightly have strong concerns about this approach.

Variant 2: Container View Model

In Variant 1 we saw how little code was needed for the simple case, but there are warnings about the inflexibility and risk to the integrity of the business layer. Variant 2 aims to maximise the benefits of Variant 1, whilst minimising the code you must hand-crank by using a simple View Model container to pass multiple business entities (and potentially other types) to the view...

public class ModelController : Controller

{

    public ActionResult Index(int someparameter)

    {

        BusinessEntity model =

        BusinessLogic.GetModel(someparameter);

        OtherBusinessEntity othermodel =

    BusinessLogic.GetOtherModel(someparameter, model);

       ViewModelContainer container = new ViewModelContainer

        {

            ShowLogin = !User.Identity.IsAuthenticated,

            LoggedInName = User.Identity.Name ?? "",

            TheEntity = model,

            AnotherEntity = othermodel

        };

  return View(container);

    }

}

public class ViewModelContainer

{

    public bool ShowLogin { get; set; }

    public string LoggedInName { get; set; }

    public BusinessEntity TheEntity { get; set; }

    public OtherBusinessEntity AnotherEntity { get; set; }

}

Here we have the "ViewModelContainer" class, that is designed to carry our payload of a couple of business entities and some other primitives. Our Controller is responsible for fetching the business entities and then assembling this container, before passing it to the view.

So let's think about our pro's and con's;

1. (pro) Any data not contained in our business entities can easily be passed across.

2. (pro) Change is easy to handle when it consists of additive data.

3. (pro) Developers will most likely add UI fields to the View Model container, not to the business entity.

4. (pro) MVC can take advantage of the same validation mark-up attributes (or other scheme) that your business layer uses.

5. (con) But... your business entities must still match the precise requirements of your view. If you want to convert or translate internal system concepts into something easier to display, this is difficult using this model.

Overall, this is my personal favourite as it provides flexibility and future proofing for little extra effort over and above Variant 1. However, it is by no means perfect and you must be willing to consider replacing your business entities with custom View Model entities if the needs of UI and business layer diverge... i.e. convert to Variant 3.

Variant 3: View Model and Mappers

Variant 3 is what could be called the utopia practice, in that it ensures your business and UI layers are suitably separated, with "mapper" classes (often referred to as the "Entity Translation" pattern) marshalling the relationship between the two. However, it comes at a price – and that is a little coding effort.

Let's see how this looks;

public class ModelController : Controller

{

    public ActionResult Index(int someparameter)

    {

        BusinessEntity entity = BusinessLogic.GetModel(someparameter);

        MyEntityViewModel model =

            MyEntityViewModelMapper.ConvertFromBusinessEntity(entity);

       

        return View(model);

    }

}

public class MyEntityViewModel

{

    public bool ShowLogin { get; set; }

    public string LoggedInName { get; set; }

    public string Name { get; set; }

    public bool IsOver18 { get; set; }

}

public class BusinessEntity

{

    public string Name { get; set; }

    public int Age{ get; set; }

}

This time I've showed the BusinessEntity class too so that you can see how it differs from the View Model. We can see that the Controller fetches this entity and then uses a mapper class to convert it to the View Model class. This mapper might also access environmental variables (e.g. User.Identity) or I might pass in other data.

The point with this approach is that the business entities are never passed to the view - only View Model classes can be consumed by views. This leads us to some findings;

1. (pro) Any data not contained in our business entities can easily be passed across.

2. (pro) Change is easy to handle when it consists of data that should be added, removed (or not displayed), or even altered / mapped (such as our Age field).

3. (pro) Developers will add UI fields to the View Model, not to the business entity.

4. (pro) The UI's needs are kept distinct from that of the business logic.

5. (con) But... MVC cannot take advantage of the same validation mark-up attributes (or other scheme) that your business layer uses. Instead you must duplicate this.

Conclusion

The great news is that the p&p Web Client Guidance Reference Implementation includes some great examples of Variant 3. Check out the SongDetailsViewModel, and the Mapper classes. It quickly becomes clear how much freedom this separation of concerns gives us, and how easy to follow the pattern becomes when you use it as a convention.

Hopefully the Variants above help you understand why this approach was chosen, and help you pick an approach yourself. Make sure you check out the Reference Implementation to see how this can work in practice.

Comments

  • Anonymous
    January 26, 2010
    Very well put! I like how you explain this as an evolving process when you start with a simple view. I'd only suggest variant 1 if and only if the whole team is willing to make the jump to the other variants when needed. Updating the business entities because of your UI is too big a risk if this is not an identified risk.

  • Anonymous
    January 26, 2010
    I don't agree at all about DTOs and POCOs, its behavioral domain models that predominate most of the discussion you'll find. One other point I'd make, if you go for option 3 you have a definition of how to map from the domain model to the view model. If you can then access metadata that describes what rules to apply to particular properties on the domain AND if you have tool like PostSharp then you have the option to copy/map the rules from the domain onto the view model as part of the build. Its tricksy code, and gets very hard when the rules are attributes, but it is basically possible and I can provide a code sample showing it if it is useful.

  • Anonymous
    January 26, 2010
    @ Colin, interesting points. I agree that a lot of discussion is out there about domain models, but I don't think that is a true reflection of how most people write systems... but rather it's a smaller minority that happen to be vocal. I'm more than happy to be wrong here though as I don't think one or the other is a clear winner; it depends on the scenario. Also, I completely agree about copying rules to View Models - I had this tooling approach in the back of my mind when I wrote the text above but I guess that isn't clear. If you do blog any code about how you've done that I'd love to see a link so feel free to post here :-) Thanks, Simon

  • Anonymous
    January 26, 2010
    @simonince Vocal, yes. They're also the small group thats influenced key technologies including ASP.NET MVC and EF. It does depend on your scenario, but I think too many people make uninformed decisions based just on what the latest Microsoft tooling offers, and I don't think thats the way modern systems should be developed. Most people who use domain models also realize that they aren't always the best option, but many people who use DTO based business logic don't seem to know enough about the alternatives for my liking. I can definitely put together some of the code and e-mail it to you, it definitely seemed a workable option but it'd need further development. In particular it worked for code-based rules and for attributes with no arguments. Anyway I'll send you something in the next week or two if you're interested.

  • Anonymous
    January 27, 2010

  1. You can use projection instead of mapping to project EF entities onto view models.
  2. View models often have different validation rules than EF entities, since non-UI layers may provide some of the inputs after the user.
  • Anonymous
    January 27, 2010
    Hi Colin, Some good points... and some long running debates :-) I'd love to see a blog post on how you've done that! It's easy to digest that way, and time is money, as they say! Hi Craig,
  1. I agree, but fundamentally that is still mapping... just using a funky technology. It also depends on the rest of your architecture - your View Model and EF context might be many layers apart!
  2. That's a very interesting comment... it "feels right" to me, but then the more I think about it I'm not so sure... you see if I can possibly push any validation up from the database or business layer all the way to the browser client (and repeat it server side of course!) then I will. That means that if the field is displayed on the screen and it has validation, I want to "push it up the architecture stack". What I think you're probably referring to is what I meant by; "...your business entities must still match the precise requirements of your view..." ... when I was discussing Variant 2. If the EF entity doesn't match your needs for the UI, you should probably be using Variant 3. Does that make sense? Or have I misunderstood? Cheers, Simon
  • Anonymous
    January 27, 2010
    Well, I'm of the opinion that one should nearly always use "Variant 3" anyway. Regarding #2, consider the following. Imagine a system which collects timecards for employees. Employees are permitted to fill in a start time and a stop time, in which case the system will calculate elapsed time for them, or to fill in and elapsed time without a start time and a stop time. Timecards are represented at the Entity Framework by the type TimecardEntity, and at the UI level by the type TimecardPresentation. At the TimecardEntity level, it certainly makes sense to have the ElapsedTime property be required. Whether or not the user fills it in directly, it must be present before the entity can be persisted to the database. On the other hand, StartTime and StopTime are not required. The user interface's validation rules are different. The user must fill in either StartTime and StopTime (in which case, any value for ElapsedTime would be ignored and replaced with a calculated value by the system) or just the ElapsedTime. So it does not make sense to make ElapsedTime required, but it does make sense to validate that either ElapsedTime or both StartTime on TimecardPresentation are filled in. Validations, of course, are not the only thing which can vary. Other attributes you might put on your presentation model, like Description could be different if the same Entity Framework data might be displayed on two different user interfaces, via two different presentation models.

  • Anonymous
    January 27, 2010
    I would add a radical view on this topic. Lets consider Variant 4 - "No Domain Model". Or in other words "Only View Model". (pro) - maintain only one domain model (the model defined in the database). No need to maintain artificial abstraction layer of classes having exactly the same structure as the tables in the database. (pro) - higher performance and scalability. We need to show a list with 3 columns - lets extract only the data for these columns and fill the appropriate view model objects with these 3 properties, instead of pulling all data for all 20 properties/fields in our domain model and show only 3 of them. Less data reads, less internal network usage, less memory usage, faster UI response time and better scalability. (con) - duplication of the validation markup and/or validation methods (for more complex validation scenarios) Just to clarify - by "No Domain Model" I don't mean direct database access in the UI with no abstraction between. What I mean is to model in the middleware objects suitable for the consumer layer (in this case the UI) instead of recreating the database model. There are scenarios where the duplicated domain model is usefull (when an API has to be provided for integration with third party systems and the exact data usage is not specified - i.e. it is not possible to define view models in advance), but when considering a common web based application, its API is only by itself.

  • Anonymous
    January 27, 2010
    Craig; All good points; any system that has these kinds of complexities is a very strong contender for Variant 3, and that's why the p&p guys went with that approach in their guidance. Hi Nikolay, you make an interesting point. I must admit I initially thought "what?!" but then I read through your comment again and I see what you're saying. I think it's basically very similar to the age old discussion about whether or not data access classes should represent a database table or the needs of the UI. I forget the pattern names but this has been a huge discussion on and off in various forms. To be honest I think modern ORM tools mean that you can do what you describe using a framework like nHibernate or the EF, mapping just the database fields you need for the UI, and then use Variant 2 of my approaches above... this gives a very close approximation of what you describe. I also probably wouldn't recommend this for performance reasons; in reality most "average" systems have much bigger and more serious bottlenecks, and a well structured, clearly coded system is far more likely to be maintainable, and IMHO to perform well. Good spot on the forgotten variant though :-) Simon

  • Anonymous
    January 27, 2010
    The comment has been removed

  • Anonymous
    January 27, 2010
    This is a really nice walk through the ways to get a model up to the view.  I tried Variant 2 for a while, thinking views composed of business entities plus anything extra was the best way to go.  But I had properties and methods in my business entities I really didn't want the view going near. They really are separate concerns.  They may overlap a lot, but they ultimately serve different purposes. So I'm all about Variant 3 these days with AutoMapper to make the conversions easier.  I also think one view model per view will become more valuable with the new html helpers in MVC2 that spit out your entire view model for display/edit. It does seem like the last mile on Variant 3 is getting centralized validation rules from the business domain up to the view model so your controller can take advantage of the model binder validating for you. I'm just decorating my view models with the Data Annotations attributes and living with that for now.

  • Anonymous
    January 27, 2010
    Nikolay; good comments - thanks for the thoughts. I'd be interested to hear what others think to your perspective... Simon

  • Anonymous
    January 27, 2010
    Nikolay, When you project from an EF context onto a presentation model, the EF does not materialize full objects. In other words, if I do:    from e in Context.Entities    where e.Id == id    select new    {        Name = e.Name,        ChildName = e.Child.Name,        GrandchildName = e.Child.Child.Name    } ...then only three columns are returned from the DB, not every column for every entity. I realize many ORMs don't work that way, but it's a limit of that ORM, not a reason to avoid ORMs in general.

  • Anonymous
    January 27, 2010
    @ Joe, Thanks! I think your story sounds very reasonable; there are many cases where you just have to go with Variant 3, and there's no doubt it's the "grown up" solution. Simon

  • Anonymous
    January 27, 2010
    The comment has been removed

  • Anonymous
    January 27, 2010
    The comment has been removed

  • Anonymous
    January 27, 2010
    Simon Good post. I think there is a forth (or I should say fifth) variation: (a variation of) CQRS.   So if we were to take your third variation one step further, we would get View Model directly from whatever the controller is calling into (it being BusinessLogic class or application service or database through an ORM!!). That way you can share your validation logic on the server and client, because you are basically using the same class and your class is very view specific so it shares all the pros with your third variation. It also has some other added benefits; for example, there is no mapping whatsoever. View Models are stored in database as tables; so your view model is a OO representation of your table (or actually it is the other way around: your table is a database representation of your view model :o)) But is it not what Nikolay explained above? To some extent except that Nikolay thinks there should not be a domain model: only VM is required. I believe domain model is required, because VM is like a DTO with some little (mostly validation) logic, but where does all that complicated business logic go? It goes into the domain model. Here is how Udi explains it: http://www.udidahan.com/2009/12/09/clarified-cqrs/ I think some variation of what Udi explains fits easily into a lot of applications. Mehdi

  • Anonymous
    January 27, 2010
    The comment has been removed

  • Anonymous
    January 28, 2010
    The comment has been removed

  • Anonymous
    January 29, 2010
    Great discussion taking place here! I'm following it with great interest. Two problems with alternative 3 is class explosion, for every business entity you have to 2 more classes, one viewmodel and one editmodel. for a semi simple application that is 60 classes.. Another is mapping from editmodel to businessmodel, the Automapper creators says Automapper should be used for that, but don't give us any advise on how to do it correctly. Hand wire?

  • Anonymous
    January 30, 2010
    @ Carl, I completely agree Variant 3 requires additional effort, although I think you're quoting a worst case... but this is always a game of balancing trade-offs. I guess this post is intended to help people make the decision as to which is right for them. I'm not a believer in there being one correct approach. The popularity of automapper is interesting too... thanks for the comments! Simon

  • Anonymous
    February 02, 2010
    I keep hearing good things about AutoMapper and yesterday it went 1.0. Seems like we all ought to give it a serious look.

  • Anonymous
    February 02, 2010
    Just did a quick test with two classes having 10 properties each - for 1M objects the handwritten data copy took 166ms, while the automapper needed 21268ms. This is a difference of 128 times (not percentages). Would it be a bottleneck - no, at least in an average application, but the maintainability aspect is also not black & white. The automapper would handle newly added properties to both classes (if they have the same names), but if one changes a property name only in the source or the target the automapper would be unaware of the change and there will be data lost, while a handwritten mapping source code would mean either a compiler error or automatically renamed field in the mapping code using the refactoring tools in VS.

  • Anonymous
    February 04, 2010
    I'm curious, but would this be a valid viewmodel? public class ViewModelContainer<TEntity, TSecondEntity> {    public bool ShowLogin { get; set; }    public string LoggedInName { get; set; }    public TEntity TheEntity { get; set; }    public TSecondEntity AnotherEntity { get; set; } } I'm just thinking this may be naive, but it seems to keep things a bit more, well generic.

  • Anonymous
    February 05, 2010
    @ Rob, technically, yes... but if you're aiming for Variant 3 the problem with your approach is that you don't have a separate model for each View. Instead, I'd probably create a base abstract ViewModel class and inherit from it if I needed to reuse properties. Otherwise change might affect multiple screens, as developers will add UI fields to the generic View Model. All the Views' needs become entangled which would concern me. What do you think? Simon

  • Anonymous
    February 07, 2010
    @Simon Yeah, I agree, what is was driving at was not using base classes for your entities. In a more real world scenario you'd defiantly need to be more careful about what goes into a base view model container.

  • Anonymous
    June 28, 2010
    Please remove if it's a duplicate...


This thread has been dormant for several months - but it contains the best list of options, and it has one radical proposal already; so I'll throw one of my own... In vast majority of cases I don't see a reason for View Model; basing a view on domain model is just fine. Let me qualify a few things... First, I am talking specifically about "input" views (Edit or Create) - not about Display views (List or Details). Second, there are exceptions (timesheet may be a good example) - where View Model is significantly different from domain model - in my experience this amounts for 20 per cent of cases at most. Third, if in the future ASP.NET MVC will have something like UpdateModel(DomainModel m, ViewModel vm) that will automagically update matching fields - I will be happy to rethink this radical statement. AutoMapper is probably a step in that direction. @Simon, you advocate Variant 2 which is essentially a "union" of two business entities and some unrelated data. In your example it's login information that is usually orthogonal to the view, and is better presented in partial control. The example of two business (domain) entities that I can think of is Order Header (read-only on the page) and Order Details (Input). Again, having Order Details - maybe through RednerPartial() - based on domain model seems easier to develop and to maintain. So, Variant 2 becomes Variant 1 all by itself. The obvious two benefits are that you can leverage DataAnnotations from the model - both in display and validation. The downside... again - my experience that in 80 percent of the cases ("edit customer" or "create new order") there seems to be no downside. For the record, I've done MVC programming in PHP for about 5 years now; and I am managing an ASP.NET MVC project for the last year. I've read a lot of posts (including from people whose opinion I take as gospel) that advocate Variant 3 ("always use View Models") - so it took me a lot of thinking to come to this heretical conclusion...

  • Anonymous
    November 02, 2010
    This article just made MVC click for me.  I've been tinkering around with it for a while and just failed to fully realize how to make the webforms side of my brain "get it".   This article, and specifically variant 3 just made that happen! Thanks, Jon

  • Anonymous
    November 02, 2010
    @ Felix - I think you're entitled to that preference, as long as you're explicitly making that choice. I'd just say "if you need to move up to variant 2 or 3, do so rather than hacking your domain entities". If you're happy with your team all following that guidance, fair enough! I still believe in variant 2 as the starting point, moving to variant 3 if needed. I find it's the best balance of risk traded against development effort. Simon @ Jon - very pleased to hear that! Thanks for the feedback. Simon

  • Anonymous
    December 15, 2010
    The best scenario is Variant 2 with only one ViewModel (the business entities). But that rarely happens. I would go to Variant 3 but, what happens when you want to reuse a ViewModel like MenuViewModel. Would you repeat the properties for each implementation of the menu? Where would you place the translation methods? In a helper? in a class for each ViewModel? in a class just for the MenuViewModel? Already 3 months that I left web forms and still missing re-usability  in MVC

  • Anonymous
    December 15, 2010
    The comment has been removed

  • Anonymous
    December 15, 2010
    @Simonince: Good articles, man. I approached same with Variant 2 and a little bit with Variant 3. I have built ViewModelContainer and using AutoMapper for mapping between my DTOs from WCF service to ViewModel.

  • Anonymous
    December 16, 2010
    Thanks thangchung! Great to hear you're getting on well with it.

  • Anonymous
    March 24, 2011
    hello, i like your views they are very good. i like to visit your site again thanks.  

  • Anonymous
    March 25, 2011
    @ Kapil; thanks! Pleased you find it useful. Simon

  • Anonymous
    May 17, 2011
    There is a downside to variant 2 that hasn't been mentioned : the risk of exposing sensitive information in the view

  • Anonymous
    May 17, 2011
    @ Andrei, excellent point! I do call this out in conversation but you're right it isn't clear above. Simon

  • Anonymous
    June 09, 2011
    "it comes at a price – and that is a little coding effort." If you use Automapper here, its very little coding effort (and the way that I prefer) Good article, thanks for posting it.

  • Anonymous
    June 13, 2011
    The comment has been removed

  • Anonymous
    June 27, 2011
    The great news is that the p&p Web Client Guidance Reference Implementation includes some great examples of Variant 3. Check out the SongDetailsViewModel, and the Mapper classes. It quickly becomes clear how much freedom this separation of concerns gives us, and how easy to follow the pattern becomes when you use it as a convention.

  • Anonymous
    August 01, 2014
    i liked what i read, it was very clear and subjective thank you Simon