Reputation: 1317
I'm just learning MVC and I have a few questions about the design/how things are supposed to work.
I'm using MVC 3 and Razor, with Entity Framework classes (e.g. Location) and I'm going to create a buddy class with Validation annotations. In my View, I have a Partial View that renders a DevExpress TreeView (using a list of Locations) and a Form for creating/editing the Locations in the Tree. I have a LocationController, a LocationManagementView, a LocationManagementPartialView (contains the code for tree view), and a LocationModel. The LocationModel will hold the buddy class and get methods for getting children (the children are only fetched after a node is expanded). I have a service wrapper (for my service client) that will be injected using StructureMap.
Should I inject the service wrapper into the Controller's constructor or into the Model's constructor?
Also, my Model has get methods that use the service wrapper to fetch data from the database (do those methods belong here in the Model?): for example, GetChildren for the tree view.
Also, is it correct to store the Location buddy class in the Model?
I want to make sure I design this application well, as it's part of a much bigger project. Any design pointers are much appreciated. I've been reading ScottGu's blog for the MVC stuff.
Ref: http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
Upvotes: 2
Views: 1788
Reputation: 54618
Here are a couple of recommendations for your "framework".
Entity Framework
For each of your Models returned back from EF, Extract an Interface of the EF Model and use the interface as the source of data, not the implemented EF Class. The reason for this is that if you decide to use another data source for any one more or more models (or the entire Entity Framework) you can simply make sure your new Data Tier returns the same interfaces, without the need to change your entire web code. The downside is making sure your interfaces are up to date when you make model changes.
This also allows your view models to implement the interface of an EF model (with additional logic of your choice). This is advantageous if all your calls for inserts/updates to the Data Tier accept the same interface models returned. This allows you to create multiple models with different requirements that all fit what the Data Tier needs to insert/update. The downside is that in your Data Tier you'll have to [create a new EF model]/[Get the model to update] and map the fields from the interface to the model.
View Models
I highly recommend that each view model is not the actual model(s) that need to be displayed, but an class that contains the model(s). Examples:
public class Car //Not what I recommend passing to a view
{
public string Make { get; set; }
public string Model { get; set; }
}
//Pass this to the view, i'll explain why...
public class CarViewModel : IPartialViewCar {
public Car Car { get; set; }
public ColorCollection { get; set; }
}
By passing the example "CarViewModel", you can decouple partial views from views. Here's how (using the models above):
public interface IPartialViewCar
{
public Car { get; }
}
[BuildCar.cshtml]
@Model MyNameSpace.Models.Car
@Html.EditorFor(model)
[PartialViewCar.cshtml]
@Model MyNameSpace.Models.IPartialViewCar
@Html.EditorFor(model) //something like that..
Now anytime you want to use the PartialViewCar
you simply need to make a Model that implements the IPartialViewCar
Interface, basically decoupling the partialview from the view.
Validation
I would recommend creating interfaces (classes if you really want, but there really isn't a need) that have all of you validation logic.
Lets say we want to require anonymous users to type both a make and a model, but registered users only have to type a Make. How can this be done easily, here's how: (extending more on the previous code)
public interface IAnonymouseCarValidation
{
[required]
public string Make { get; set; }
[required]
public string Model { get; set; }
}
public interface IRegisteredCarValidation
{
[required]
public string Make { get; set; }
}
public interface ICar
{
public string Make { get; set;}
public string Model { get; set; }
}
[updating the Car model to abstract and use an interface now]
public abstract class Car : ICar
{
//maybe some constructor logic for all car stuff
public string Make { get; set;}
public string Model { get; set; }
//maybe some methods for all car stuff
}
//MetadataType tells MVC to use the dataannotations on the
//typeof class/interface for validation!
[MetadataType(typeof(AnonymouseCarValidation))]
public class AnonymousCar : Car
{
}
[MetadataType(typeof(AnonymouseCarValidation))]
public class RegisteredCar : Car
{
}
[Now update the ViewModel]
public class CarViewModel : IPartialViewCar
{
public ICar Car { get; set; } //this is now ICar
public ColorCollection { get; set; }
}
Now you can create either an AnonymouseCar
or RegisteredCar
, pass it into the CarViewModel, and let MVC take care of the validation. When you need to update the validation, you update a single interface. The downside to this is it feels fairly complex.
Injection & Data Requests
My preference is to try to keep the Controller Actions as simple as possible and not to include code there to retrieve data. The reason I choose not to do this is that I don't like to repeat code. For example:
public class AccountControllers
{
DataServer _Service;
public AccountControllers(DataServer Service)
{
this._Service = Service;
}
public ActionResult ShowProfiles()
{
ProfileViewModel model = new ProfileViewModel();
model.Profiles = this._Service.Profiles();
return View(model);
}
public ActionResult UpdateProfile(ProfileViewModel updatedModel)
{
service.UpdateProfile(updatedModel);
ProfileViewModel model = new ProfileViewModel();
model.Profiles = this._Service.Profiles();
return View(model);
}
}
Instead I would do something like: (not completely)
public ActionResult ShowProfile(Guid ID)
{
ProfileViewModel model = new ProfileViewModel(this._service);
return View(model);
}
public ActionResult UpdateProfile(ProfileViewModel updatedModel)
{
// pass in the service, or use a property or have SetService method
updatedModel.Update(this._service)
ProfileViewModel model = new ProfileViewModel(this._service);
return View(model);
}
public class ProfileViewModel()
{
DataServer _Service;
Profile _Profile
public ProfileViewModel()
{
}
public ProfileViewModel(DataServer Service)
{
this._Service = Service;
}
public Profile Profiles()
{
get
{
if (this._service == null)
{
throw new InvalidOperationException("Service was not set.");
}
return = Service.Profiles(ID);
}
This means that profiles is Enumerated ONLY when it's requested, I don't have to populate it myself. Tends to be less bugs if I use the code to my advantage, instead of requiring me or other programmers to manually populate models.
Upvotes: 3