Skip to content

Instantly share code, notes, and snippets.

@PaulStovell
Created April 13, 2012 20:42
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save PaulStovell/2380031 to your computer and use it in GitHub Desktop.
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();
}
}
@bigjump
Copy link

bigjump commented Apr 13, 2012

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.

@PaulStovell
Copy link
Author

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?

@LAGuy
Copy link

LAGuy commented Apr 27, 2012

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

@dario-l
Copy link

dario-l commented Jun 7, 2013

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment