views:

609

answers:

3

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

+1  A: 

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: http://stackoverflow.com/questions/1277490/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.

Kieron
+3  A: 

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.

spot
interesting ideas +1
Andrew Bullock
Definitely interesting ideas, but OpenRASTA is still a better option :)
Dve
A: 

I have many good solutions in the asp.net mvc sample application which is in the download of valueinjecter (mapper that I use to map ViewModels to/from Entities, you can also map FormCollection/Request to Entities)

here's one:

    public class TinyController :Controller
        {
            private readonly IModelBuilder<Person, PersonViewModel> modelBuilder;

            public TinyController()
            {
                modelBuilder = new PersonModelBuilder();
            }

            public ActionResult Index()
            {
                return View(modelBuilder.BuildModel(new PersonRepository().Get()));
            }

            [HttpPost]
            public ActionResult Index(PersonViewModel model)
            {
                if (!ModelState.IsValid)
                    return View(modelBuilder.RebuildModel(model));

                   var entity = modelBuilder.BuildEntity(model);
...
//save it or whatever
            }
        }
Omu