Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using ViewModels for POST actions in MVC elegantly

Tags:

Currently I'm passing my domain objects to my views, and binding directly to them from POSTs. Everyone says this is bad, so I'm attempting to add in the ViewModel concept.

However, I can't find a way to do this very elegantly, and I'd like to know what other people's solutions are to not ending up with a very messy controller action.

the typical process for say some "add person" functionality looks like this:

  1. make a GET request for a view representing a blank Person viewmodel
  2. post back (in)valid data
  3. controller binds posted data onto a person viewmodel
  4. if binding fails, i need to do the same action as in (1) but with some data, not a blank object and errors
  5. if the binding suceeded, i need to map the properties from the VM onto a real model
  6. validate the model
  7. if validation passed: save the person, commit, map the users details to a display VM and return it in a view
  8. if validation failed, do the same actions as in (1) but with some data and errors

Doing all this in a controller action (ignoring the GET) certainly isnt SRP or DRY.

Im trying to think of a way of breaking this process up so that it does abide by SRP, is clean, modular and above all testable.

What are peoples solution to this?

I've been experimenting with custom controller-action-invokers to separate the concerns up into individual methods, smart modelbinders and just plain brute force but i havent yet come across a solution in happy with.

P.S. as it adds so much complexity, convince me why i even need to bother

like image 771
Andrew Bullock Avatar asked Aug 18 '09 16:08

Andrew Bullock


2 Answers

I've felt the same discomfort. My only way around it has been to do the following:

  1. Create a binder to bind and validate the view model
  2. Create a binder to get the entity from the database (or just do this in the controller)
  3. Call an inherited Save method in the superclass. This method takes the viewmodel and the entity that will be updated, and does all the work you listed in your steps.

The action method looks like this:

public ActionResult Whatever(TViewModel viewModel, TEntity entity) {     return Save(viewModel, entity); } 

The base controller has a generic definition, like so:

public abstract BaseController<TEntity, TViewModel>     where TEntity : Entity     where TViewModel : ViewModel 

The constructor has two dependencies, one for the entity repository and another for the model mapper, like so:

protected BaseController(IRepository<TEntity> repository, IMapper<TEntity, TViewModel> mapper) 

With this in place, you can then write a protected Save method that can be called from the controller actions in the subclass, like so:

protected ActionResult Save(TViewModel viewModel, TEntity entity) {     if (!ModelState.IsValid)         return View(viewModel);      _mapper.Map(viewModel, entity);     if (!entity.IsValid)     {         // add errors to model state         return View(viewModel);     }      try     {         _repository.Save(entity);         // either redirect with static url or add virtual method for defining redirect in subclass.     }     catch (Exception)     {         // do something here with the exception         return View(viewModel);     } } 

As far as testability, you can test the save method passing in valid/invalid view models and entities. You can test the implementation of the model mapper, the valid state of the view model, and the valid state of the entity separately.

By making the base controller generic, you can repeat this pattern for each entity/viewmodel combo in your domain, if you're creating many controllers to do the same thing.

I'm very interested to hear what others have to say about this. Great question.

like image 175
spot Avatar answered Oct 17 '22 20:10

spot


The MVVM (ViewModel) pattern is definitely the one to go for, I had a similar question about POSTing back to an action a few days back - here is the link: MVVM and ModelBinders in the ASP.NET MVC Framework

The result was that you can use the Bind attribute to post back the complex type you want.

like image 22
Kieron Avatar answered Oct 17 '22 20:10

Kieron