Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
    }
}
like image 934
quakkels Avatar asked Dec 03 '10 17:12

quakkels


3 Answers

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.

like image 81
John Farrell Avatar answered Oct 23 '22 14:10

John Farrell


You can only use constant values for attribute properties; see a this page for a full explanation.

like image 35
DanP Avatar answered Oct 23 '22 15:10

DanP


Attributes are essentially metadata added to a type. They can only use const values, instead of instance variables. In your case you are tying to pass in your instance variables of genisisRepository, etc. This will fail to compile as they are not compile time constants.

You should look into Dependency Injection for Action Filters to achieve this, typically using an IoC container.

Also, if your ActionFilter is performing a post ActionResult action, such as OnActionExecuted, you could probably get away with storing something in the route data:

public ActionResult Index()
{
  ControllerContext.RouteData.DataTokens.Add("name", "value");
  return View();
}
like image 4
Matthew Abbott Avatar answered Oct 23 '22 15:10

Matthew Abbott