可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am currently working on an ASP.NET MVC project.
Some developers on the team want to bind the auto-generated database entities directly to the Views.
Other developers want to create tailor-made ViewModel's and bind those to the Views.
Objectively, what are the pros and cons of both approaches?
(By "database entities" I am referring to the auto generated classes that an ORM framework generates, such as LINQ to SQL, Entity Framework or LLBLGen).
回答1:
Definitely use view models in your views, and use something like AutoMapper
to create view models from entities easily.
Cons:
- Sometimes it feels like you are duplicating code, specifically, when the view model and the entity have the exact same properties
Pros:
- You often need to represent an object in a simpler format (often called flattening), but you need full fidelity on the server side. This allows you to transition between the two without mucking up your domain model with presentation cruft.
- Aggregate roots often have a lots of value objects and additional entities that are irrelevant to a specific view, and omitting them in a view model makes it easier to work with.
- Your entities will have lots of two way references that are sensible in terms of an API, but create pure hell when serializing them for JSON, XML, etc. View models will eliminate these circular references.
- You may often use the same entity but in different ways for different views. Trying to balance both needs on one type can create a huge mess.
回答2:
The orthodoxy is that you should never use your raw database entities in your views. Like any rule, it is there to be broken if you know your entities well and you understand the consequences, but there are very good reasons not to break that rule, particularly when working in teams and with code that will be maintained in future by people who might not understand the rule or the entities as well as you do. The main reasons are:
ORM lazy loading. Imagine your Customer has a lazy loaded collection Orders. You pass Customer off to the View and it iterates over Orders. You get an N*1 select on the Orders table. But it also means that your database connection still needs to be open in the View. There is a pattern that people use 'Transaction per Action' which disposes of the database context in the Action_Executed event, which happens before your View is rendered. So you could be trying to access the database after it has been disposed. Even if you are not doing that now, someone in future might decide to implement that pattern because it is fashionable.
The concerns of the ViewModel are different to the db Model. For example, you typically decorate your ViewModel properties with validation attributes. These typically are different or only concern the UI not the db. If you bind to your database entities you will find all these UI concerns polluting you DB entities.
Related to 2 - the requirements of the ViewModel may demand computed or derived properties. For example, a Fullname constructed from First and Last names. These kind of things are best kept in the ViewModel.
You can unit-test your ViewModels in isolation from the database. ViewModels can end up containing quite a lot of logic which needs to be unit-tested. This is easier to test if it is not tied to your database (as with EF entities).
In general, creating and maintaining ViewModels (even without AutoMapper) is not an overhead and you will find it is a much better pattern of development overall. I would recommend it for everything but the simplest cases (lookup lists of static data, for example).
回答3:
I believe using view models is the only way to go, so no pros for ORM entities:) View models not only provide data for view, but they also define how view should look (by defining templates) or how it should validate (by adding data annotations or implementing IDataErrorInfo).
Using view models:
Pros:
- View models contain only properties required by view, nothing else.
- View models may contain specific validation rules using data annotations or IDataErrorInfo.
- View models can combine values from different database entities.
- View models document themselves and are not tied to any framework.
- View models protect you from forged POSTs, containing values, that were not provided in form, but were contained in ORM entities.
- You can specify easily display templates for view models and reuse them in many places using
DisplayFor
or EditorFor
helpers.
Using ORM entities:
Cons:
- ORM entities already contain data annotations, that can mess up your validation. Example: Password field in user may be markes as
Required
, but it is not required when you change only basic user info.
- ORM entities are strongly tied to Framework (Entity Framework) and may be not easy to implement rules in.
- ORM entities can contain properties for more than one view, but it is hard to separate validation rules for different views.
- Using ORM entities with lazy loading can lead you to executing SQL queries when views are rendered. It shouldn't happen.
- Using ORM entities can lead to using huge SQL queries instead on small ones. When you want to display dropdown with first name and last name, you should only retrieve first name and last name from database, not whole entities.
回答4:
Thanks for the answers so far - they have been a big help in understanding the pros/cons of both approaches. I have one thing to add that no one else has mentioned.
Over-posting attack
A worrying disadvantage with binding directly against DB entities is an "over-posting attack". This is where the attacker, using a tool no more advanced than FireBug, can insert form fields that are not intended to be editable by the user, but which do exist on the DB entity.
Consider an "Edit my profile" page. Your view might look like this:
@using(Html.BeginForm() {
<div>
@Html.LabelFor(x=> x.FirstName)
@Html.TextBoxFor(x=> x.FirstName)
</div>
<div>
@Html.LabelFor(x=> x.LastName)
@Html.TextBoxFor(x=> x.LastName)
</div>
<input type="Submit" value="Save" />
}
It would render the following HTML:
<form action="/profile/edit" method="post">
<div>
<label for="FirstName">FirstName</label>
<input type="text" name="FirstName" value="" />
</div>
<div>
<label for="LastName">LastName</label>
<input type="text" name="LastName" value="" />
</div>
<input type="Submit" value="Save" />
</form>
Using FireBug, an attacker merely needs to insert a chunk of HTML inside the form:
<input type="hidden" name="IsAdmin" value="true" />
...and suddenly users are able change data in very unexpected and harmful ways.
Here are some even scarier hidden form fields:
<input type="hidden" name="ShoppingCart.Items[0].Price" value="0.01" />
<input type="hidden" name="BankAccount.Balance" value="1000000" />
<input type="hidden" name="User.Administrator.Password" value="hackedPassword" />
Ouch!
Info taken from:
http://hendryluk.wordpress.com/tag/asp-net-mvc/
回答5:
I once tried to develop an app which used NHibernate entities directly in ASP.NET views. I ran into many problems with Lazy loading and deferred SQL execution running directly from the views rather than in the business logic layer or even controllers. Moving to a viewmodels and using automapper seemed to solve all these problems and make the app easier to test, debug and maintain.
I also found that view models were helpful in holding all the associated data I needed on a page. Some developers like to use the dynamic ViewBag for this, but this is bad for testing and debugging.
In particular, view models made it easy when you wanted to pick associated entities from dropdown lists.
AutoMapper was a lifesaver on this project, as it saved having to write a ton of mapping code, all I had to do was create the view models and then the controllers automapped from entities to view models.
回答6:
Do not expose backend-entities to the client.
Real world application have behaviour -not CRUD. It you databind your entities to the view it will just be a matter of time before you dig into muddy hack when behaviour is required on the clientside.
回答7:
I was just about to add exactly the same sentiment as hackedbychinese. also i'd add, with fk's to lookup lists, you just HAVE to use viewmodels as the entity model merely will hold a pointer to a single id within that table. A viewmodel allows you to pass the required populated list into the view - voila.
Also, a viewmodel can contain discreet logic where required, this would definately NOT be the case with the entity model. Also, your validations may vary depending on the use of your view, therefore different validations can be applied per 'view' requirement.
The purpose of a ViewModel is mainly separation of concerns - decoupling the View from the implementation details of the Model.
回答8:
Using DB entities in your views, especially your forms is a massive security issue. Take the following POCO object
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public bool IsAdmin { get; set; }
}
Now say you are presenting a view that allows a user to change their email. The MVC method for processing the form result when using Db Entities instead of view models would look like: (unless you don't use model binding, in which case you are making more work for yourself)
public class HomeController : Controller
{
[HttpPost]
public ActionResult ChangeEmail(User user)
{
//....
}
}
Model binding in Asp.net works by looking for GET or POST parameters matching the names of the properties in the model. Therefore, all the user has to do is add IsAdmin=true
to the POSt parameters and viola, the model passed into the ChangeEmail
function will have the IsAdmin property set to true, which could very easily be accidentally added into the database, giving users free access to change data they didn't have access to change.
This goes for user permissions, changing who owns an entity (make your question be associated with me instead of you), change original creation dates, etc...