Archive

Archive for March, 2008

Unit Testing MonoRail Controllers with PrincipalPermissions

March 19, 2008 Neal 3 comments

When you apply a CodeAccessSecurityAttribute that returns a PrincipalPermission to an action on a controller, all your existing tests for that action will break (to find out more about applying principal permissions, take a look at this blog entry about creating clean principal permission attributes). After a bit of digging and a lot of googling, I came across this post by Phil Haack that demonstrates how to set up your unit tests so they run correctly again.

Phil’s approach uses Rhino Mocks to mock out the IPrincipal and IIdentity interfaces and then attaches the mocked IPrincipal to Thread.CurrentPrincipal (which is where the PrincipalPermission retrieves the principal from to check permissions against). This approach is great, however I rapidly got tired of having to write out all of the code that this approach required, and I was getting annoyed with the clutter of try / finally blocks inside my tests (as well as forgetting to put the MockRepository into replay mode numerous times), so I stubbed out the principal and identity to create a much simpler alternative to the mocked versions. You can see these classes below:

    public class PrincipalStub : IPrincipal
{
    private readonly string _name;
    private readonly string _authenticationType;
    private readonly bool _isAuthenticated;
    private readonly bool? _isInRoleResponse;
    private readonly List<string> _roles;

    public PrincipalStub( string name, string authenticationType, bool isAuthenticated, bool isInRoleResponse )
    {
        _name = name;
        _isAuthenticated = isAuthenticated;
        _authenticationType = authenticationType;
        _isInRoleResponse = isInRoleResponse;
    }

    public PrincipalStub( string name, string authenticationType, bool isAuthenticated, params string[] roles )
    {
        _name = name;
        _isAuthenticated = isAuthenticated;
        _authenticationType = authenticationType;
        _roles = new List<string>( roles );
    }

    public IIdentity Identity
    {
        get
        {
            return new IdentityStub( _name, _authenticationType, _isAuthenticated );
        }
    }

    public bool IsInRole( string role )
    {
        if ( _isInRoleResponse.HasValue )
        {
            return _isInRoleResponse.Value;
        }

        return _roles.Contains( role );
    }
}

public class IdentityStub : IIdentity
{
    private readonly string _name;
    private readonly string _authenticationType;
    private readonly bool _isAuthenticated;

    public IdentityStub( string name, string authenticationType, bool isAuthenticated )
    {
        _name = name;
        _isAuthenticated = isAuthenticated;
        _authenticationType = authenticationType;
    }

    public string Name
    {
        get
        {
            return _name;
        }
    }

    public string AuthenticationType
    {
        get
        {
            return _authenticationType;
        }
    }

    public bool IsAuthenticated
    {
        get
        {
            return _isAuthenticated;
        }
    }
}

This means I can now replace the mocks and all the associated setup with a single line of code:

IPrincipal testPrincipal = new PrincipalStub("Test", "Test", true, true);

which will create a new principal that is always in any role asked of it (remember we want to test the action, not the code access security). This still left me with the setup and teardown code that I could have put into appropriate methods in the test class, but that lacked the flexibility to specify the exact parameters of the PrincipalStub for each test, and meant that all of my tests would be running under a “false” principal – less than ideal.What I needed was a mechanism for performing a two-phase operation with a chunk of indeterminate code in the middle, and the guarantee that the second phase would always run. Delegates and anonymous methods could have worked, but I got my inspiration from an approach Oren Eini (Ayende) uses to provide a more explicit mocking syntax. Taking this and applying it to the scenario I had resulted in the following TestPrincipalManger:

public class TestPrincipalManager
{
    private PrincipalStub _principal;

    public TestPrincipalManager(string username, string authenticationType, bool isAuthenticated, params string[] roles)
    {
        _principal = new PrincipalStub(username, authenticationType, isAuthenticated, roles);
    }

    public TestPrincipalManager(string username, string authenticationType, bool isAuthenticated, bool isInRoleResponse)
    {
        _principal = new PrincipalStub(username, authenticationType, isAuthenticated, isInRoleResponse);
    }

    public IDisposable Attach()
    {
        IPrincipal originalPrincipal = Thread.CurrentPrincipal;
        Thread.CurrentPrincipal = _principal;
        return new TestPrincipalCleanup( originalPrincipal );
    }

    public class TestPrincipalCleanup : IDisposable
    {
        private readonly IPrincipal _principal;

        public TestPrincipalCleanup( IPrincipal principal )
        {
            _principal = principal;
        }

        public void Dispose()
        {
            Thread.CurrentPrincipal = _principal;
            GC.SuppressFinalize( this );
        }
    }
}

That can be used as follows:

[Test]
public void AdministratorPermissionAttribute_Permission_Succeeds_If_Principal_Does_Have_Administrator_Permission()
{
    TestPrincipalManager authorisedPrincipal = new TestPrincipalManager("A.User", "Test Auth", true, "Administrator");
    AdministratorPermissionAttribute permissionsAttribute = new AdministratorPermissionAttribute( SecurityAction.Demand );
    IPermission permission = permissionsAttribute.CreatePermission();

    using ( authorisedPrincipal.Attach() )
    {
            Assert.IsTrue(Thread.CurrentPrincipal.IsInRole("Administrator"), "Pre-condition failed: Current user does not have the Administrator permission");
            permission.Demand();
    }
}

While at first glance, this appears to be a perversion of the using syntax / IDisposable interface, if you take a second look it makes perfect sense. Using is essentially just a compiler macro that replaces the using with a try / finally, and the object that we are disposing of is following the semantics of the dispose operation – it is cleaning up after itself. Extracting the class doing the disposal out means that the Attach() method can be called repeatedly with no nasty side-effects (I hope!).

Categories: MonoRail, Unit Testing

Creating Clean Principal Permission Attributes

March 18, 2008 Neal 1 comment

MonoRail allows you to use the PrincipalPermissionAttribute to secure your code in a reasonably unobtrusive manner as shown in the MonoRail documentation here. This is great as it allows a simple way to express the security requirements of your actions without cluttering the action with security checks. This problem I have with this is it adds a rather ugly attribute to the action that looks like:

[PrincipalPermission(SecurityAction.Demand, Role='Administrator')]
public void DoSomeAdminStuff()
{
    // ...
}

I also have to remember exactly how to type it out if I want to reuse it, so I set out to create a “nice” attribute that would communicate the same information with a lot less clutter – something like:

[RequireAdministratorPermission]
public void DoSomeAdminStuff()
{
    // ...
}

A quick bit of digging shows that the PrincipalPermissionAttribute is sealed so I can’t apply my usual trick of extending it and providing the default values I want, so I set about figuring out exactly what the PrincipalPermissionAttribute does. Turns out it is very simple, just extend the CodeAccessSecurityAttribute and implement IPermission CreatePermission(). Poking around in the PrincipalPermissionAttribute just to make sure I was doing things correctly, showed that I need to check if the underlying SecurityAttribute had it’s Unrestricted flag set and return an unrestricted permission if it was, so I extracted this into a superclass to simplify creating additional permission attributes later. The resulting classes are very simple and do not do much at all (except make my code look a lot nicer!).

public abstract class BasePrincipalPermissionAttribute : CodeAccessSecurityAttribute
{
    protected BasePrincipalPermissionAttribute( SecurityAction action ) : base( action )
    {
    }

    public override IPermission CreatePermission()
    {
        if (Unrestricted)
        {
            return new PrincipalPermission(PermissionState.Unrestricted);
        }

        return CreatePrincipalPermission();
    }

    protected abstract PrincipalPermission CreatePrincipalPermission();
}

public class AdministratorPermissionAttribute : BasePrincipalPermissionAttribute
{
    public ManageUsersPermissionAttribute( SecurityAction action ) : base( action )
    {
    }

    protected override PrincipalPermission CreatePrincipalPermission()
    {
        return new PrincipalPermission( null, "Administrator", true );
    }
}

And they can be used like:

[AdministratorPermission(SecurityAction.Demand)]
public void DoSomeAdminStuff()
{
    // ...
}

Unfortunately you still have to specify the security action, this is due to something that occurs during the compile process to serialise the permissions and store them with the assembly requiring the SecurityAction to be specified in the constructor of the attribute.

Categories: MonoRail, c#

Using a Domain Model Simplifies Your Unit Testing

March 18, 2008 Neal Leave a comment

I’ve just had one of those AHA! moments – you know, when you are staring at code and you have a bunch of concepts running around the back of your mind and something just falls into place.

The code I happened to be staring at was a set of unit tests for my UserController, and the tests simply ensured the basic user management operations worked as they should (user is created, user is updated etc etc). For some reason while I was staring at this I was thinking about testing that the newly created users could log in successfully, but what that would really be testing was are the users created correctly?

This kind of thinking comes from my days before unit testing, domain objects and DRY, back then the code that created the user would be quite separate from the code that logged the user in and both of these actions would have a significant “plumbing” code in them meaning that if we changed something about the way the user was created, we could conceivably break the login process without knowing it (a lot of the applications I used to build were highly procedural / transaction script oriented), so I would have something like (warning: aircode):

public bool Login(string username, string password)
{
    DataRow userRow = UserDAO.GetUser( username );
    return userRow["Password"] == password;
}

… somewhere else in the app …

public void CreateUser(string username, string password, ... )
{
    UserDAO.CreateUser(username, password, ... );
}

So what would happen if the boss suddenly decreed that all passwords should be stored with hashes? I’d definately change the CreateUser code, but what if I didn’t write the Login code, would I know about it? Without unit tests, definitely not; with unit tests, maybe, depending on how much was mocked out (as ideally I wouldn’t be testing the data access code so my mock would return what I was expecting). Compare that to my current implementation – a Credentials class that contains all of the username / password / login attempts related details (and nothing about persistence):

    public class Credentials
    {
        public const int MaximumLoginAttempts = 3;

        private string _username;
        private readonly HashedPassword _password;
        private int _loginAttemptsRemaining;

        public string Username
        {
            get
            {
                return _username;
            }
        }

        public int LoginAttemptsRemaining
        {
            get
            {
                return _loginAttemptsRemaining;
            }
        }

        public bool IsLockedOut
        {
            get
            {
                return _loginAttemptsRemaining  MaximumLoginAttempts)
            {
                loginAttemptsRemaining = MaximumLoginAttempts;
            }

            _loginAttemptsRemaining = loginAttemptsRemaining;
        }

        public bool ValidatePassword(string password)
        {
            if (_loginAttemptsRemaining <= 0)
            {
                return false;
            }

            if (_password.IsSameAs(password))
            {
                _loginAttemptsRemaining = MaximumLoginAttempts;
                return true;
            }

            _loginAttemptsRemaining = _loginAttemptsRemaining - 1;
            return false;
        }
    }

This implementation already stores the data as a hashed password, but the original version didn’t (only for the first version where I was concentrating on getting the basic behaviours). Adding the ability to hash the password didn’t change the external interface in any way and therefore any existing tests I had didn’t require any changes – they just worked.

It is not so much that using a domain model directly simplifies your unit testing, it’s more that a domain model encourages you to implement separation of concerns and the single responsibility principle. SoC and SRP combine to significantly increase the testability of your application (as well as decreasing the fragility of the unit tests).

In my case, the true realisation was that because the responsibility for the persistence of the object was separate from the user object and the controller, I couldn’t break the login code by changing the persistence code or the user creation code (assuming I created a valid user object), and because I had previously tested the login code, I had already tested the login code under all of the scenarios that were applicable.

Building a domain model is definitely worth the effort!

Troubleshooting IIS7 : 3 Things I’ve Learnt

March 17, 2008 Neal 2 comments

1.  Stop!  Take a step back and think hard about the fault.

- if you are not familiar with IIS7 (and you’ve tried all those “tricks” you used to use with IIS6), more than likely your issue is IIS7 installation / configuration related.

2.  Remember IIS7 installs nothing by default

- are you sure you have installed all of the handlers you need for the site?

3.  Remember IIS7 installs nothing by default

- not even error handlers and error page generators

When something that “should” work doesn’t, frustration and “thrashing” often ensues – you refuse to accept the obvious and keep trying the same thing different ways yet the result is always the same.  This is why stopping and taking a step back often results in seeing the true nature of the problem and allowing you to solve it.  Every issue I have experienced with IIS7 so far has been because of installation (or lack of) or configuration issues.  This is because I keep thinking about IIS7 like I am used to thinking about IIS6.

Most of the issues I have had with IIS7 are due to the correct handlers not being installed.  This one has caused me problems a couple of times, the most classic example being wondering why a site looked different under IIS7 when the same site worked fine under IIS6.  I spent an hour checking and trying a lot of different things before I finally stopped and thought about what was happening – the css and images for the site were not being served, a bit of research showed that IIS7 has a separate static content handler that is not installed by default.  Installed the correct handler and the site worked fine.

If you are sure everything else is working and installed as it should be, then more than likely you have a configuration issue.  This problem is a definite probability if you are setting up an ASP.NET web application that runs fine under IIS6 but seems to fail under IIS7.  Today I spent yet another hour troubleshooting the deployment of something to IIS7, the biggest problem I had was I had no information to diagnose the issue.  Turns out that IIS7 does not even install any kind of error handlers to generate those charming yellow pages of death most ASP.NET developers will be familiar with.  Once I figured out that I had to install yet another set of handlers, I could review the error details, resolve the issue and get the site up and working quite quickly.

If you’ve got everything installed and nice error messages and fault tracing, and you still can’t figure out what the problem is, I’m sorry I can’t help, you exhausted my current knowledge on IIS7.  Google is your friend or you could try The Official Microsoft IIS Site and forums as there are plenty of people more knowledgeable than I lurking around there.

Categories: IIS7

A Simple IComparer Decorator to Add Sort Ordering

March 14, 2008 Neal Leave a comment

Today I needed to implement some sorting over domain objects based on the following rules:

  • Surveys must be sorted by their age (effective date – generally the date the survey was created)
  • Priority surveys must appear at the top of the list

So I set out an encapsulated these rules into a nice class that implemented IComparer<T>. The single method of this interface int Compare(T x, T y) should return an integer according to the following rules:

  • If x is less than y, return an int less than 0
  • If x is equal to y, return 0
  • If x is greater than y, return an int greater than 0

Writing this up, we end up with a pretty simple encapsulation of the comparison rules than make pretty good sense:

// Priority > Normal
if (left.IsPriority != right.IsPriority)
{
    if (left.IsPriority)
        return 1;
    else
        return -1;
}

// Older Effective Date > Newer Effective Date
if(left.EffectiveDate > right.EffectiveDate)
{
    return -1;
}

if(left.EffectiveDate < right.EffectiveDate)
{
    return 1;
}

return 0;

The problem is, when I view the sorted result, my priority surveys are at the bottom of the list, a bit of digging and I find out that by default List<T>.Sort(IComparer<T>) returns the list sorted in descending order.

I could correct this by simply changing the returned values to return the inverse of the value (i.e. 1 becomes -1 and vice versa), however that would distort the meaning of the comparison (priority survey > normal survey and older survey > newer survey). A better approach is to add the sort ordering after the comparison, even better, we can completely separate the ordering from the comparison by decorating the comparer with another class that implements the sort ordering logic. This leads to the SortOrderComparer<T> shown below:

public enum CompareSortOrder
{
    Descending,
    Ascending
}

public class SortOrderComparer<TEntity> : IComparer<TEntity>
{
    private readonly IComparer<TEntity> _innerComparer;
    private readonly CompareSortOrder _sortOrder;

    public SortOrderComparer( IComparer<TEntity> innerComparer, CompareSortOrder sortOrder )
    {
        _innerComparer = innerComparer;
        _sortOrder = sortOrder;
    }

    public int Compare( TEntity x, TEntity y )
    {
        int result = _innerComparer.Compare( x, y );
        if(_sortOrder == CompareSortOrder.Descending)
        {
            return result;
        }

        return result * -1;
    }
}

Now we can apply sort ordering to any comparer quickly and simply:

IComparer<MyClass> sortingComparer = new SortOrderComparer( new MyComparer(), CompareSortOrder.Ascending );
list.Sort( sortingComparer );

I wouldn’t be surprised if this has been done somewhere else as it is reasonably obvious (only took me a few days to see it!) :) .

Categories: c#

TDD and NCover

March 12, 2008 Neal 1 comment

I am learning TDD. It’s hard and I often fall back into old habits of writing code before writing tests – particularly when I can “see” the solution (I tend to code from within existing classes out, meaning I still write the code the way I’d like to use it but not from the tests). This leads to patches in the code that are quite often not tested, and not always required. I found the trick to identifying these patches of code (and quite often refactoring them away or simply deleting them), is to use a code coverage tool while running the tests so I can see what isn’t tested.

I use VS2005 Pro and NUnit for my testing needs. I don’t have any budget to go and buy tools so the commercial code coverage tools were out, however I found that you can still get the earlier versions of NCover and NCoverExplorer for free, and right now they do just what I need. Setting them up is easy:

  1. Make sure you have NUnit installed, setup and running from the console – I am using 2.4.6 for .NET 2.0
  2. Get the last free version of NCover and install it – I am using 1.5.8 beta
  3. Add the locations of nunit-console.exe and ncover.console.exe to your PATH environment variable
  4. Grab a copy of NCover Explorer 1.4 from here
  5. Run NCover Explorer, hit Ctrl+N to Run NCover, configure NCover via the NCover tab and the application to profile via the Application to Profile tab (the Application Arguments will be the name of the assembly containing the NUnit tests) and if you want to limit what is profiled, use the options tab to customise your profiling.

Once you have the profile results up, you can navigate the coverage and see where your tests missed things and look at what you need to do to test what is missed. Critically analyse code that has not been tested, particularly where a majority of the code around it has been covered. I found myself refining the untested code based on one of the following questions quite often:

  • Do I really need that guard condition?
  • Is the scenario I have allowed for likely to happen?
  • Should I be handling that case differently from the rest?

Although it is nice to get code coverage up to 95%, I am realistic about what I am testing. It is a (relatively) small system I have previously talked about, and that means a lot of simple property get/set type code and some “untestable” code that integrates it into the underlying web application (primarily in the global.asax and container / component registration). My primary goal for using code coverage was to identify complex and / or significant parts of the system I had written while in a code first groove and get them under test as soon as possible. Now I have NCover up and running, I am trying to make it a habit to run it every couple of hours to catch those places where I have fallen back into old (bad?) habits.

Categories: Code Coverage, NCover, TDD

Clean controllers with custom attributes

March 3, 2008 Neal 1 comment

It might be an obvious thing to do, but sometimes you miss the most obvious approaches…

Instead of repeatedly typing (copy and pasting!) [Filter(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter)] for every controller you want to apply (in my case) the AuthenticationFilter to, spend 5 minutes building a custom attribute to do the work for you (I place these attribute classes in the same file as the filter):

public class ApplyAuthenticationFilterAttribute : FilterAttribute
{
    public ApplyAuthenticationFilterAttribute() : base ( ExecuteEnum.BeforeAction, typeof(AuthenticationFilter)
    {
    }
}

Now you leverage the IDE to do all the work for you (intellisense and code completion are the smart programmers copy / paste), and even if you only use it on one base controller, you still get the benefit of a nice attribute that you don’t even have to think about to understand…

[ApplyAuthenticationFilter]
public class MyController : Controller
{
    ...
}

vs

[Filter(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter)]
public class MyController : Controller
{
    ...
}

The same approach can be used with layouts and rescues making your controllers “read” nicely

[UseDefaultLayout, UseGeneralError, ApplyAuthenticationFilter]
public class MyController : Controller
{
    ...
}
Categories: MonoRail

Adding Action Filters to MonoRail Controllers

March 3, 2008 Neal 5 comments

Update: See this post for making the cancel buttons work with client side validation

MonoRail has a rather nifty technique for adding cross-cutting concerns to controllers, it allows you to specify one or more filters to be run before or after the action, after rendering or always. Filters are commonly used for tasks like authentication and localisation, however by default they run for all actions in the controller and you must specify which actions to exclude using the [SkipFilter] attribute. In a majority of cases this works well as the filter is designed to handle cross-cutting concerns (which by definition affect most if not all actions), however there are times when you want to apply the filter to a minority of the actions in a controller, or add action specific metadata that may vary the way the filter executes depending on the action. Recently I came across just such a scenario…

In the surveying application that I am building, I needed to add a cancel button to a number of pages and be able to execute some cleanup logic or simply redirect the user to a different page. The buttons were embedded in the html as:

 <button name="cancel" type="submit">Cancel</button>

This meant that if the user pressed the cancel button, “cancel” will appear in the request parameters. Handling the cancel in the action specified in the form’s target is reasonably simple but I ended up with a lot of duplicated code at the top of my actions that wasn’t directly related to the goal of the action:

 bool isRequestCancelled = IsRequestCancelled( Request.Form );

if( isRequestCancelled )
{
  // do some cleanup here if required and redirect to cancel page
  return;
}

// do the actual work for the action

So I decided to split out the handling of the cancel button and put it into a filter. Sounds simple and after about half an hour of testing and coding I had something that resembled what I wanted, BUT, when I applied the filter directly to my actions it didn’t work. Back to the documentation and a bit of digging in the source code and I figure out that filter attributes are not parsed when the action metadata for a controller is created, meaning filters can be applied at the controller level only. While this is great for a majority of the cases where you use filters, I needed to be able to control the application of the filter at a much finer level of detail for each method (and the cancel button was an exception rather than a rule so adding [SkipFilter] everywhere would have led to some ugly controllers).Taking a step back, I realised the problem was essentially in 2 parts,

  1. processing the canceled request, and
  2. indicating how the canceled request was to be handled for each action.

This led me to create a filter to enable the cancellation of an action and a set of attributes that provided the additional metadata to the filter indicating how the cancellation should be handled.

The code for the filter then became relatively simple, check for cancel in the request, check for the attribute on the current action, redirect according to the attribute (note the attribute below the filter shows my preferred method for attaching filters to controllers):

public class ExecuteRedirectOnCancelFilter : IFilter
    {
        public bool Perform( ExecuteWhen exec, IEngineContext context, IController controller, IControllerContext controllerContext )
        {
            bool isRequestCancelled = FormContainsKey( context.Request.Form, "cancel" );
            if(!isRequestCancelled)
            {
                return true;
            }

            OnCancelRedirectToAttribute onCancel = GetCancellationDetails(controller);
            if(onCancel == null)
            {
                return true;
            }

            onCancel.Redirect( context.Response, controllerContext.Name);
            return false;
        }

        private bool FormContainsKey(NameValueCollection form, string key)
        {
            foreach (string formKey in form.AllKeys)
            {
                if (formKey.Equals(key))
                {
                    return true;
                }
            }

            return false;
        }

        private OnCancelRedirectToAttribute GetCancellationDetails(IController controller)
        {
            Controller mrController = controller as Controller;
            if (mrController == null)
            {
                throw new InvalidOperationException("The type of controller that the ExecuteRedirectOnCancelFilter has been applied to does not derive from Castle.Monorail.Framework.Controller.  The filter is unable to access the information necessary to execute.");
            }

            string currentActionName = mrController.Action;
            MethodInfo currentAction = mrController.MetaDescriptor.Actions[currentActionName] as MethodInfo;
            if (currentAction == null)
            {
                throw new InvalidOperationException("The method info for the currently executing action could not be retrieved.");
            }

            object[] redirectInfoAttributes =
                currentAction.GetCustomAttributes(typeof(OnCancelRedirectToAttribute), true);

            if (redirectInfoAttributes.Length == 0)
            {
                return null;
            }

            OnCancelRedirectToAttribute redirectInfoAttribute = (OnCancelRedirectToAttribute)redirectInfoAttributes[0];
            return redirectInfoAttribute;
        }
    }

    public class EnableRedirectOnCancelAttribute : FilterAttribute
    {
        public EnableRedirectOnCancelAttribute() : base( ExecuteWhen.BeforeAction, typeof(ExecuteRedirectOnCancelFilter) )
        {
        }
    }

The attribute simply allows us to specify a controller and action:

[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
    public class OnCancelRedirectToAttribute : Attribute
    {
        private string _controller;
        private readonly string _action;

        public string Controller
        {
            get
            {
                return _controller;
            }
        }

        public string Action
        {
            get
            {
                return _action;
            }
        }

        public bool IsControllerSpecified
        {
            get
            {
                return !String.IsNullOrEmpty( _controller );
            }
        }

        public bool IsActionSpecified
        {
            get
            {
                return !String.IsNullOrEmpty( _action );
            }
        }

        public OnCancelRedirectToAttribute( string redirectToController, string redirectToAction )
        {
            _controller = redirectToController;
            _action = redirectToAction;
        }

        public virtual void Redirect(IRedirectSupport redirector, string currentController)
        {
            if(!IsControllerSpecified)
            {
                _controller = currentController;
            }

            if(!IsControllerSpecified || !IsActionSpecified)
            {
                throw new InvalidOperationException( "Unable to redirect the user after request was cancelled as the controller or action was not specified");
            }

            redirector.Redirect( _controller, _action );
        }
    }

    public class OnCancelRedirectToActionAttribute : OnCancelRedirectToAttribute
    {
        public OnCancelRedirectToActionAttribute( string redirectToAction ) : base( null, redirectToAction )
        {
        }
    }

    public class OnCancelRedirectToIndexAttribute : OnCancelRedirectToAttribute
    {
        public OnCancelRedirectToIndexAttribute( ) : base( null, "Index" )
        {
        }
    }

I have not performance tested this yet, however caching the attributes against the actions or parsing all of the controllers to generate a list of actions with the cancel attributes should be relatively easy to add at a later date if it is required.The technique could also be easily extended to a simple EnableActionFiltersFilter that simply determined if the current action had an ActionFilter attribute attached and delegated execution of the filter directly to the attribute, as demonstrated by the following aircode:

 public class EnableActionFiltersFilter : IFilter
    {
        public bool Perform( ExecuteWhen exec, IEngineContext context, IController controller, IControllerContext controllerContext )
        {
            ActionFilterAttribute actionFilter = GetActionFilter(controller);

            if(actionFilter == null)
            {
                return true;
            }

            return actionFilter.Perform( ExecuteWhen exec, IEngineContext context, IController controller, IControllerContext controllerContext );
        }

        private ActionFilterAttribute GetActionFilter(IController controller)
        {
            Controller mrController = controller as Controller;

            if (mrController == null)
            {
                throw new InvalidOperationException("The type of controller that the ActionFilter has been applied to does not derive from Castle.Monorail.Framework.Controller.  The filter is unable to access the information necessary to execute.");
            }

            string currentActionName = mrController.Action;
            MethodInfo currentAction = mrController.MetaDescriptor.Actions[currentActionName] as MethodInfo;
            if (currentAction == null)
            {
                throw new InvalidOperationException("The method info for the currently executing action could not be retrieved.");
            }

            object[] actionFilterAttributes = currentAction.GetCustomAttributes(typeof(ActionFilterAttribute), true);
            if (actionFilterAttributes .Length == 0)
            {
                return null;
            }

            ActionFilterAttribute actionFilter = (ActionFilterAttribute )actionFilterAttributes[0];
            return actionFilter;
        }
    }

    public class EnableActionFiltersAttribute : FilterAttribute
    {
        public EnableActionFiltersAttribute () : base( ExecuteWhen.Always, typeof(EnableActionFiltersFilter) )
        {
        }
    }

And the ActionFilterAttribute to perform the filter action:

[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
    public abstract class ActionFilterAttribute : Attribute
    {
        public abstract bool Perform( ExecuteWhen exec, IEngineContext context, IController controller, IControllerContext controllerContext );
    }

Now you can simply extend the ActionFilterAttribute, override the Perform method and you have action level filters in your controller (you would also have to enable the action level filters on your controller using the EnableActionFilters attribute).

Categories: MonoRail