Put your controllers on a diet: redux

A few years back, I put together a talk on putting your controllers on a diet using  variety of techniques and extension points inside MVC. Well, a lot has changed in four years, and with the lessons learned from the drawbacks of some of my original approaches, putting your controllers on a diet needs some revisiting.

The original goals of slim controller actions are still valid:

  • Controller is a lousy place for business logic
  • Actions should be almost declarative in nature (though I’m not using controller-less actions or the like)
  • Simplicity and consistency is paramount
  • ASP.NET MVC Controller unit tests are dumb

As before, we found that in our controller actions, there is a stark contrast between GET and POST actions. This shouldn’t be new – GET actions get data, and POST actions modify data. It’s not my opinion – it’s the spec! I already talked about what has changed in my “four years later” post, but I got quite a few questions to revisit that original talk on putting controller on a diet. Evidently some folks think I actually know what I’m doing.

Before we start looking at our changes, let’s look back on what we want to actually fix:

public class ConferenceController : Controller
{
    private readonly IConferenceRepository _repository;

    public ConferenceController(IConferenceRepository conferenceRepository)
    {
        _repository = conferenceRepository;
    }

    public ActionResult Index(int? minSessions)
    {
        minSessions = minSessions ?? 0;

        var list = (from conf in _repository.Query()
                    where conf.SessionCount >= minSessions
                    select new ConferenceListModel
                    {
                        Id = conf.Id,
                        Name = conf.Name,
                        SessionCount = conf.SessionCount,
                        AttendeeCount = conf.AttendeeCount
                    }).ToArray();

        return View(list);
    }

    public ViewResult Show(string eventName)
    {
        var conf = _repository.GetByName(eventName);

        var model = new ConferenceShowModel
        {
            Name = conf.Name,
            Sessions = conf.GetSessions()
                .Select(s => new ConferenceShowModel.SessionModel
                {
                    Title = s.Title,
                    SpeakerFirstName = s.Speaker.FirstName,
                    SpeakerLastName = s.Speaker.LastName,
                }).ToArray()
        };

        return View(model);
    }

    public ActionResult Edit(string eventName)
    {
        var conf = _repository.GetByName(eventName);

        var model = new ConferenceEditModel
        {
            Id = conf.Id,
            Name = conf.Name,
            Attendees = conf.GetAttendees()
                .Select(a => new ConferenceEditModel.AttendeeEditModel
                {
                    Id = a.Id,
                    FirstName = a.FirstName,
                    LastName = a.LastName,
                    Email = a.Email,
                }).ToArray()
        };

        return View(model);
    }


    [HttpPost]
    public ActionResult Edit(ConferenceEditModel form)
    {
        if (!ModelState.IsValid)
        {
            return View(form);
        }

        var conf = _repository.GetById(form.Id);

        conf.ChangeName(form.Name);

        foreach (var attendeeEditModel in form.Attendees)
        {
            var attendee = conf.GetAttendee(attendeeEditModel.Id);

            attendee.ChangeName(attendeeEditModel.FirstName, attendeeEditModel.LastName);
            attendee.Email = attendeeEditModel.Email;
        }

        return this.RedirectToAction(c => c.Index(null), "Default");
    }
}

There’s nothing horrible about this controller, each action isn’t too bad. However, if GETs or POSTs get any more complex than this, I’d want to start isolating that behavior outside of things like ModelState and ActionResults. In the next series of posts, we’ll cover building concepts around GETs and POSTs, moving all business logic outside of the controller into pieces isolated from the UI, without resorting to as many somewhat esoteric tricks of custom action results and inflexible model binding extensions.

Next up: mediator pattern enlightenment.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.