views:

552

answers:

3

In computer science we've been taught that each method should do one thing and one thing only. I'm a little confused then that we see MVC actions like the following given as examples of good practice:

    [AcceptVerbs(HttpVerbs.Post), Authorize]
    public ActionResult Edit(int id, FormCollection collection) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (!dinner.IsHostedBy(User.Identity.Name))
            return View("InvalidOwner");

        try {
            UpdateModel(dinner);

            dinnerRepository.Save();

            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }
        catch {
            ModelState.AddModelErrors(dinner.GetRuleViolations());

            return View(new DinnerFormViewModel(dinner));
        }
    }

Basically this piece of code provides a lot of functionality:

  1. Defines how to access the Action - Post only
  2. Defines who can access the Action - Authorize
  3. Accesses persistence mechanism - dinnerRepository
  4. Accesses state information - (User.Identity.Name)
  5. Converts NameValueCollection to strongly typed object - UpdateModel()
  6. Specifies 3 possible ActionResults and content for each - InvalidOwner/Details/Edit views

To me this seems like too many responsibilities for one method. It is also a fairly simple action ie it doesn't deal with common scenarios like:

  1. Checking business rules - "Never trust user input"
  2. Navigation paths - On successful save always returns to "Details"
  3. Different return types - Someone wants to call "Edit" from a grid and needs a JsonResult?
  4. Better error handling - YSOD if the database is not accessible during GetDinner(id)
  5. Constructing additional view data - SelectLists for Drop down lists

Not too mention the amount of testing required around this single method i.e. mocking/faking for FormCollection/UserIdentity/Authorization Provider/Repository/etc.

My question is how do we avoid cramming so much stuff into our controller actions?

I tend to think "opinions" are a great concept, especially the "Thunderdome Principle". While I have great respect for the guys involved with building FubuMVC and their reasons behind doing so, I need something I can use right now.

Edit - Well it seems I was after something like this - Opinionated Controller. I'll need to examine it further as it's for MVC Preview 5 so i may need to update it myself.

A: 

I am a little confused by your post. First you complain that this action is doing to much, and then you complain that is not doing more.

Edited to add:

Honestly this is not a very complicated controller action. Whether it is a best practice example or not is debatable, but, you probably are not going to get a whole lot simpler than that. I suppose you could break some of it up into separate routines, but at some point you have to decide where to draw that line. Ultimately we, as programmers have to write software. Design principals are awesome, but if we are too rigid with them nothing will ever get built.

Chrisb
@Chrisb - Not complaining as such, I'm simply stating that in the real world things will get much more complex than this simple example. I want to know what will be the best way to deal with such additional complexity.
Neil
Actually, this is probably a pretty normal example of the complexity you will find in such an action. You did mention some other possible scenarios that this does not address. If you need to address those other situations, you would rewrite the action to handle them.I suppose what you want to think about is, what should this action accomplish? It is responsible for getting the data from the view to the model, and displaying some kind of a response to the user. If what you are looking to do is part of that, then great. If it isn't, then it should probably go somewhere else.
Chrisb
Now, if you have common things happening in a bunch of your actions, such as the adding of the ModelErrors in the above example, you can seperate that out into a seperate method. The example you posted does just that actually.In the sample app there is a file called ControllerHelpers that contains code for that purpose.
Chrisb
@Chrisb Edit - I know this is a trivial example that won't get much simpler. Quite the opposite, most Actions I write are far more complex and I feel as though I'm intertwining far too many concerns into one method. I understand I can push things down into subroutines/BaseClasses/ControllerHelpers etc but as a whole my Actions seem "polluted" with too many concerns. Hence testing an Action is becoming much more arduous.
Neil
Maybe you can post an example of that to illustrate your point. One thing I miss from when I built stuff in the old Jakarta Struts were the built in methods of the actions. You had a setup, validate, and update method for each action, so you could seperate some of this stuff a bit better if need be. Most of the time however only one of the methods was used.
Chrisb
+2  A: 

To me, this method does only one thing: update the model with the edited values received from the web form.

It's clear that doing this requires a couple of things to happen, but they are atomic and well defined. If you ever need to modify how this part of the model needs to be updated, this is the controller action to look for and update.

You could argue that one of the Thunderdome principles, "controllers should be light", is not met since a business rule is checked here. But NerdDinner is a very trivial app that it makes no sense to place this in an extra layer.

If you find that this method does too much, perhaps you should find a language in which it's forbidden to place more than one statement in a method.

Dave Van den Eynde
@Dave- I disagree, this trivial example does at least 2 things - update the model AND specifies program flow. If either of these requirements change you need to update this method. I'd much rather some other method determine program flow based on the outcome of a model update.BTW- let's not get too pedantic about 1 statement = perform 1 thing :)
Neil
A: 

I think it is still doing on the minimum actions required..for this "Action" may not fit the Absolute single responsbility - but it is single action

  1. The the attributes are saying to ASP.NET that this method will only work with HTTP.Post, and identity that is trying to use it must be authorised. - Good Security. So at this point nothing is actually being done. These are just telling the server what to check.

    ie if not HTTP.post method will not work, and if your not the list, wont work.

  2. There is validation to check if the user Identity matchs those of the dinner. - Sanity Check.

  3. This is based on Strong Type Checking - do UpdateModel(dinner) - is just making sure that the current objects in the model have been updated with the new data, then the repository is called to Save(). - This is still in the one unit of action - updating the model so that we can call save and persist.

  4. Validation checks are handled in the Catch which is adding RuleViolations to the Model, and returning the user back to the offending view - ie edit/create partial view which is passing responisbility to that to deal with.

  5. If the save works - its is just moving the "workflow" of the user to the Details - i.e clearing the form from memory and moving the work back to the details. IMHO great as - we are in a POST scenario and stuff not lying around in memory - Good.

  6. It would be easier not to move the flow to back the details and just leave on the partial edit view.

I agree especially with number six. Of course most apps are not going to display an entire table of data in a details format like NerdDinner does, so just returning the user to the edit view would be appropriate.This method would be handy for cases where lots of data is entered rapidly though.
Chrisb
@littlegeek - Your answer provides no help at all for the question I asked. I appreciate you going to all the effort to attempt to enlighten me on how/why this Action works. Problem is I still feel it mixes far too many concerns for such a trivial example, regardless of further complexity. I guess I just don't agree with the concept that a "Single Action" is supposed to handle so many concerns between a request/response. I'd much rather offload these concerns to specific handlers.
Neil