-
-
Save PaulStovell/2380031 to your computer and use it in GitHub Desktop.
@model UserViewModel | |
@using (Html.BeginForm()) | |
{ | |
<div> | |
@Html.HiddenFor(m => m.Id) | |
@Html.TextBoxFor(m => m.FirstName) | |
@Html.TextBoxFor(m => m.LastName) | |
@Html.DropDownListFor(m => m.CountryId, Model.Countries) | |
<input type="submit" /> | |
</div> | |
} |
interface IModelBuilder<TViewModel, TEntity> | |
{ | |
TViewModel CreateFrom(TEntity entity); | |
TViewModel Rebuild(TViewModel model); | |
} |
interface IModelCommand<TInput> | |
{ | |
void Execute(TInput model); | |
} |
class UserEditCommand : IModelCommand<UserEditModel> | |
{ | |
readonly ISession session; | |
public UserEditCommand(ISession session) | |
{ | |
this.session = session; | |
} | |
public void Execute(UserEditModel model) | |
{ | |
var user = string.IsNullOrEmpty(model.Id) ? new User() : session.Find<User>(model.Id); | |
session.Store(user); | |
user.FirstName = model.FirstName; | |
user.LastName = model.LastName; | |
user.Country = session.Find<Country>(model.CountryId); | |
session.SaveChanges(); | |
// Auditing and other interesting things can happen here | |
} | |
} |
class UserController : Controller | |
{ | |
UserViewModelBuilder builder = new UserViewModelBuilder(); | |
UserEditCommand saveCommand = new UserEditCommand(); | |
public ActionResult Edit(string id) | |
{ | |
var user = session.Find<User>(id) ?? new User(); | |
return View(model, builder.CreateFrom(user)); | |
} | |
[HttpPost] | |
public ActionResult Edit(UserViewModel model) | |
{ | |
if (!ModelState.IsValid) | |
{ | |
return View(builder.Rebuild(model); | |
} | |
saveCommand.Execute(model); | |
return RedirectToAction("Index"); | |
} | |
} |
class UserEditModel | |
{ | |
public string Id { get; set; } | |
public string FirstName { get; set; } | |
public string LastName { get; set; } | |
public string CountryId { get; set; } | |
} | |
class UserViewModel : UserEditModel | |
{ | |
public ICollection<SelectListItem> Countries { get; set; } | |
} |
class UserViewModelBuilder : IModelBuilder<UserViewModel, User> | |
{ | |
readonly ISession session; | |
public UserViewModelBuilder(ISession session) | |
{ | |
this.session = session; | |
} | |
public UserViewModel CreateFrom(User user) | |
{ | |
var model = new UserViewModel(); | |
model.Id = user.FirstName; | |
model.FirstName = user.FirstName; | |
model.LastName = user.LastName; | |
model.Country = user.Country.Id; | |
model.Countries = GetCountries(); | |
return model; | |
} | |
public UserViewModel Rebuild(UserViewModel model) | |
{ | |
model.Countries = GetCountries(); | |
return model; | |
} | |
ICollection<SelectListItem> GetCountries() | |
{ | |
var countries = session.FindAll<Country>(); | |
return countries.Select(c => new SelectListItem { Value = c.Id, Text = c.Name }).ToList(); | |
} | |
} |
Thanks Jay, you're right that the naming convention could be clearer - I've renamed them.
I like the idea of being able to raise exceptions from the command. Do you try/catch within the controller, or do you have an action filter that handles it?
Hi Paul,
Thanks for this ! :)
As I'm learning MVC and EF right now, I have a question regarding the ISession interface.
class UserViewModelBuilder : IModelBuilder<UserViewModel, User>
{
readonly ISession session;
public UserViewModelBuilder(ISession session)
{
this.session = session;
}
Can you post the EF layer code and injection into the controller ?
How is the constructor fired passing the session ?
UserViewModelBuilder(ISession session)
Thanks, LA Guy
Hi Paul,
I know it is old but still active :)
When you use PRG pattern and copy ModelState between Redirect->Get then you don't needed Rebuild method anymore. :)
Regards
Darek
Looking good to me - we tend to postfix models with either "ViewModel" or "PostModel" (eg. EditCustomerViewModel and EditCustomerPostModel) which helps my brain grasp which one I'm working with. Our commands also tend to have a parameterless Execute() method - we simply set properties on the command, but passing the PostModel works well too.
Most of our GET actions follow this pattern:
[HttpGet]
public ActionResult Edit(int id) {
var builder = new EditCustomerViewModelBuilder();
var model = builder.Build(id);
return View(model);
}
Our POST actions looks pretty similar to yours - the commands can throw CommandFailureExceptions etc, which allow you to add them back into the collection of ModelState errors.