MonoRail peeves III—Actions

Part III in a series of… some.

Part I covers static type checking in views and Why This Would Be Good.
Part II is about making the view architecture more naturally composable.
Part III, this article, is more narrowly concerned with return types from actions.

RenderView, Redirect and Friends

In MonoRail, ‘actions’ are the units of code which respond to an incoming page request, perform some work, then cause a view to be rendered or send a ‘redirect’—or potentially an error code—back to the client.

Actions are represented by methods on controller classes and are defined as returning ‘void’. Actions invoke a view or a redirect by calling other methods on the controller, RenderView, RedirectToAction, RedirectToUrl… and others. By default (if none of these is called) the framework will act as if the view has called RenderView(name-of-action). The parameters to the view must be placed in a global variable called PropertyBag.

This all to me seems quite unnatural.

In the MonoRail scheme, results returned by actions are passed via global state in the controller. Call me old-fashioned, but I believe that if methods return a result, they should be declared with a return type, and should explicitly ‘return’ their value. This would have the following advantages:

  1. The compiler ensures that at least one result is in fact returned.
  2. The compiler ensures that at most one result is returned.
  3. The compiler potentially can check that the result returned is of the correct type. (For example, if an action is defined to return Xml, we may wish to preclude any other MIME type from being returned.)

There is a further implication. If we make views strongly typed, and callable directly by code, we can enforce that the controller passes all of the parameters to the view when invoking it. We guarantee at compile-time that the ‘PropertyBag’ (in MonoRail terms) is not missing any values. If an action returns a result like this…

return Views.EditForm.GetView(p1, p2, p3);

…the compiler will enforce the parameter list, and we eliminate a whole class of potential faults.

Result Types

What should be the proper return type of an ‘action’?

An action may have one of several outcomes:

  1. A view is rendered
  2. The client is redirected to a different action/URL.
  3. An arbitrary other HTTP response is returned to the client (e.g., 410 ‘Resource Removed’).

The first two cases are the most common. (And I don’t even know how to do the last one in MonoRail. Presusumably it involves fiddling directly with the Response object.)

Let’s say, for the sake of example, that a view produces HTML. Should the action be declared to return ‘Html’ too? What if it sometimes needs to return a redirect and sometimes render a view?

We propose a new class, ActionResult, to represent the result of an action. It would have subclasses for redirects, error codes or views. This is type-safe, and models the problem well. Actions would be declared to return a result type of ‘ActionResult’.

It does have the problem of being slightly unwieldy. For example, even a simple action, which renders a same-named view*, might end up looking like this:

return new ViewActionResult(new Views.Area.ControllerName.Index(parameters));

Not really an improvement on the equivalent code in MonoRail, which looks like this:

PropertyBag[parameter-names] = parameters;

So let’s do better.

Firstly the reference to the view could be shortened if each controller held references to its ‘own’ view factories. So let’s propose that a code generator creates a property on the controller called Views, with references to each of the view factories for the controller. We might be able to write something like Views.Index.With(parameters) to instantiate a particular view. ‘Views’ returns a generated class with a property for each view factory; ‘Index’ is the name of one of the view factories; ‘With’ seems like a nice method name for instantiating a view instance from the view factory while passing some parameters.

And we might declare some helper methods on the Controller, to create the ‘ActionResult’ object. So we could end up with this:

return Render(Views.Index.With(parameters));

That’s not bad. We could even define an implicit type conversion from a View to a ViewActionResult (think of it as a kind of automatic boxing), allowing us to write:

return Views.Index.With(parameters);

What about redirects?

Again, we represent a redirect using a class, say ‘SeeOtherRedirectActionResult’ to represent a 303 ‘See Other’ HTTP redirect. So:

return new SeeOtherRedirectActionResult(some-action);

and again we have a shortcut method on the Controller class to create the object, and code generation to give us references to the actions on the controller:

return SeeOther(Actions.ListCustomers(parameters));

Conceivably we could define the convention that ‘void’ actions always render a like-named view with no parameters… though this trivial case hardly seems worth the special-case code required.

Stronger typing

I’m not convinced how valuable this would be, but… we could also allow actions to be defined with a return type of ‘View’ (or of a particular View subclass or interface). In that case the action would not be allowed to return a redirect, and could be forced to return a view of a particular type.

Further processing

‘Actions’ are explicitly only one step in the processing chain of a client request. Other steps include the initial routing of a request URL to the particular action, user authentication, error handling… and applying boilerplate ‘layout’ to generated views. More on layouts later.

Conclusion

The current MonoRail framework for ‘actions’ requires results to be returned via global state on the controller. It’s easy to make mistakes when coding controller actions, failing to correctly pass parameters to views, or accidentally allowing control to fall through from one ‘result’ (e.g., ‘RenderView’) to another (e.g., ‘RedirectToAction’).

There’s no reason that actions could not return results through the normal C# function return mechanism, using different classes to represent different kinds of results. This would result in more direct, understandable code, with less special state being stored in the controller. Code using it could be at least as terse and readable as that using the existing mechanism.

An aside

I like to annotate all my MonoRail actions to indicate whether they are accessible via GET or POST requests. The attribute for specifying this is called ‘AccessibleThrough’. For example:

[AccessibleThrough(Verb.Post)]

This is very verbose. I guess they figure that it’s only for specialist use, but I’d like to see wider use made of these annotations. I’d prefer to see a couple of shortcut attributes:

[AcceptsPost]

and

[AcceptsGet]

(This does not in fact require a change to the framework. Application programmers may easily extend AccessibleThroughAttribute themselves to achieve this.)

* In MonoRail, each controller is typically associated with one or more views. For example a ProductController may have views called ‘list’, ‘view’ and ‘edit’. The default outcome of an action is to render the view with the same name as the action. Commonly controllers only render their ‘own’ views, though controllers are allowed to render any view in the application.

4 thoughts on “MonoRail peeves III—Actions

  1. Gauthier Segay

    A quick note about AccessibleThrough: you can do this by creating your own AcceptsPostAttribute / AcceptsGetAttribute attribute that inherit AccessibleThroughAttribute

    sample:

    public class AcceptsGetAttribute : AccessibleThroughAttribute
    {
    public AcceptsGetAttribute():base(Framework.Verb.Get)
    {
    }
    }

  2. Gauthier Segay

    Andrew, you can check the REST contrib feature here:

    http://svn.castleproject.org:8080/svn/castlecontrib/Castle.MonoRail.Rest/

    As for the ActionResult thing, there is some support for returning action, but I myself consider controller’s Actions as plain way to handle HttpRequest and I prefer keeping as readable/self explaining as possible: would prefer RedirectToAction or RenderView method call rather than returning something. On the same path, I avoid putting action parameters since it imply some vodoo magic, even with all the goodness of Binder component and such, I’m safer with “oldschool” request handling.

  3. Andrew Post author

    Gauthier: Thanks for the link. Actually, I want actions to return results because I would find it simpler and more explanatory than the side-effects-using version (i.e., the calls to “RedirectToAction” or “RenderView”)!

    I must say though I tend to avoid using the Binder too much, except for simple request parameters, especially since—as far as I can tell—there is no real way to catch parsing exceptions with the Binder.

  4. BenL

    You can pick up the binder errors using the following:

    ErrorList errors = GetDataBindErrors(boundInstance);

    In your controller.

Leave a Reply

Your email address will not be published.