I am working on a fairly large MVC 3 application, and I'm running into an issue that doesn't smell quite right to me. This question takes a little set up to understand, so here are the premises that I'm currently operating on:
- Views should be strongly typed (ViewBag/ViewData should be avoided) 开发者_如何转开发
- Because the views are strongly typed, view model objects should be created that encapsulate all the data the view needs to display
- When we have a need for drop down menus we should have two properties:
- A property in the model that stores the selected value of the drop down
- A
SelectList
property in the view model that represents the items in the drop down
- The view itself always uses the
@Html.DropDownListFor()
helper method - We are using Entity Framework 4, and letting it generate entity classes from our already designed database
- To avoid duplication and take advantage of LINQ, we are not creating our own separate business/model classes but adding to the partial classes generated by the entity framework
- These partial classes that we write are located in the business layer to make everything compile correctly
- Most model classes have a shared editor template that can be used in multiple views
Here's where the trouble comes in. The shared editor template's model type is set to the model class. This means that the partial view that makes up the editor template does not have access to the containing view model object where the list of drop down items is stored.
I was able to "solve" this by adding a SelectList
property directly to the model class in the business layer instead of keeping it in the view model. But the SelectList
class is specific to MVC, which in turn means that my business layer has a dependcy on MVC. That doesn't seem right to me because the BL should be agnostic to the UI.
Has anyone else run into this issue? How should I solve this? It's also possible that one of my premises are wrong.
Everything seems very nice and good design up until this point (which is not surprising as it is that point that is causing you headaches :-)):
Most model classes have a shared editor template that can be used in multiple views
It's view models that should have editor templates and not EF models. And because view models are specific to the requirements of the view you are free to put whatever information you need into them, like in this case the SelectList
. So don't simply define a root view model that has your EF models as properties (that's not a view model). Define a view model that is designed to meet the requirements of the particular view. Don't put a single EF class in your view model hierarchy and you will see how much simpler your life will be :-)
And don't worry if you have duplicate properties in your view models. That's what those classes are designed for. Also AutoMapper could greatly simplify the mapping between your models and view models.
You have 2 main solution IMHO:
1. Generate DTOs / Models for your business logic entities
You can use AutoMapper to minimize the copying code. You will achieve a nice separation between your view and business logic. This however may be time-consuming for your large application.
2. Use extension methods
Instead of declaring a SelectList property in EF entity partial class and polluting your business logic with view-related code create an extention methods for your EF entites in Web project. You can than move your view-related code to web project from BL and keep type-safety.
Example:
Business Logic assembly
// This is EF entity
public partial class FooTable
{
public long Id { get; set; }
public string Name { get; set; }
}
Web assembly
public static class FooTableExtensions
{
public static SelectList GetSelectList(this IEnumerable<FooTable> fooTables)
{
return new SelectList(); // Create your select list from FooTables here.
}
}
Well, we ended up taking the simple way out. In the business layer we just changed the type to plain old Object
. I figured that, regardless of the presentation layer, it will need some sort of list to contain the options available.
I know this isn't super clean as per @Darin and @Jakub, but I don't see our end result being any different this way except that we've avoided having to write and/or set up a whole bunch of mappings between objects.
精彩评论