How to pass parameters to a custom ActionFilter in ASP.NET MVC 2?

quakkels picture quakkels · Dec 3, 2010 · Viewed 55.9k times · Source

I'm trying to create a custom ActionFilter which operates on a set of parameters that would be passed to it from the controller.

So far, my customer ActionFilter looks like this:

public class CheckLoggedIn : ActionFilterAttribute
{
    public IGenesisRepository gr { get; set; }
    public Guid memberGuid { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Member thisMember = gr.GetActiveMember(memberGuid);
        Member bottomMember = gr.GetMemberOnBottom();

        if (thisMember.Role.Tier <= bottomMember.Role.Tier)
        {
            filterContext
                .HttpContext
                .Response
                .RedirectToRoute(new { controller = "Member", action = "Login" });
        }

        base.OnActionExecuting(filterContext);
    }
}

I know I still need to check for nulls, etc. but I can't figure out why gr and memberGuid aren't successfully being passed. I'm calling this Filter like this:

    [CheckLoggedIn(gr = genesisRepository, memberGuid = md.memberGUID)]
    public ActionResult Home(MemberData md)
    {
        return View(md);
    }

genesisRepository and md are being set in the controller's constructor.

I'm not able to get this to compile. The error I get is:

Error   1   'gr' is not a valid named attribute argument because it is not a valid attribute parameter type
Error   2   'memberGuid' is not a valid named attribute argument because it is not a valid attribute parameter type

I double checked that gr and memberGuid were the same types as genesisRepority and md.memberGUID, What is causing these errors?

Solution

Thanks to jfar for offering a solution.

Here's the Filter I ended up using:

public class CheckLoggedIn : ActionFilterAttribute
{

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var thisController = ((MemberController)filterContext.Controller);

        IGenesisRepository gr = thisController.GenesisRepository;
        Guid memberGuid = ((MemberData)filterContext.HttpContext.Session[thisController.MemberKey]).MemberGUID;

        Member thisMember = gr.GetActiveMember(memberGuid);
        Member bottomMember = gr.GetMemberOnBottom();

        if (thisMember.Role.Tier >= bottomMember.Role.Tier)
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary(
                    new { 
                        controller = "Member", 
                        action = "Login" 
                    }));
        }

        base.OnActionExecuting(filterContext);
    }
}

Answer

John Farrell picture John Farrell · Dec 3, 2010

This is a way to make this work. You have access to the ControllerContext and therefore Controller from the ActionFilter object. All you need to do is cast your controller to the type and you can access any public members.

Given this controller:

public GenesisController : Controller
{
    [CheckLoggedIn()]
    public ActionResult Home(MemberData md)
    {
        return View(md);
    }
}

ActionFilter looks something like

public class CheckLoggedIn : ActionFilterAttribute
{
    public IGenesisRepository gr { get; set; }
    public Guid memberGuid { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        /* how to get the controller*/
        var controllerUsingThisAttribute = ((GenesisController)filterContext.Controller);

        /* now you can use the public properties from the controller */
        gr = controllerUsingThisAttribute .genesisRepository;
        memberGuid = (controllerUsingThisAttribute .memberGuid;

        Member thisMember = gr.GetActiveMember(memberGuid);
        Member bottomMember = gr.GetMemberOnBottom();

        if (thisMember.Role.Tier <= bottomMember.Role.Tier)
        {
            filterContext
                .HttpContext
                .Response
                .RedirectToRoute(new { controller = "Member", action = "Login" });
        }

        base.OnActionExecuting(filterContext);
    }
}

Of course this is assuming the ActionFilter isn't used across multiple controllers and you're ok with the coupling. Another Option is to make a ICheckedLoggedInController interface with the shared properties and simply cast to that instead.