Test if Attribute is on Controller or Action - asp.net-mvc

When an Attribute fires, can I test if was set on a Controller or an Action?
Behavior I want is: use Action attribute if exists, else use Controller attribute. Something like this:
public class TestAttribute : FilterAttribute, IAuthorizationFilter
{
public TestAttribute(string optionalParam = "") { /*...*/ }
public void OnAuthorization(AuthorizationContext filterContext)
{
bool isClassAttribute; // = ????
bool hasActionAttribute = filterContext.ActionDescriptor.GetCustomAttributes(typeof(TestAttribute ), false).Length > 0;
if (isClassAttribute && hasActionAttribute)
return; // handle in Action attribute
else
; // do stuff with optionalParam...
}
}
[TestAttribute]
public class TestClass
{
[TestAttribute(optionalParam:"foo"]
public ActionResult TestMethod() { return null; }
}
I could do this with the Order property, but don't want to have to set it every time (or get funky).
Edit / Solution
OK, found a solution to my issue (but not the question) - setting the Attribute base parameter AllowMultiple=false means last instance of the same filter type is allowed, and all others are discarded (and Controller attributes run first(?) so should be good to go...).
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class TestAttribute : FilterAttribute, IAuthorizationFilter
{
public TestAttribute(string optionalParam = "") { /*...*/ }
public void OnAuthorization(AuthorizationContext filterContext)
{
// this should be the Action attribute (if exists), else the Controller attribute...
}
}
Anyway I asked a slightly different question, so will still give points for answer ;)

I believe the item executes on each method call anyway but you can reference for instance:
public void OnActionExecuting(ActionExecutingContext filterContext) { }
..
..
string controllerName = filterContext.Controller.GetType().Name;
//either or:
string actionMethodName = filterContext.ActionDescriptor.ActionName;
string actionMethodName = filterContext.RouteData.Values["action"].ToString();
If your actionMethodName is null, then it's potentially from your controller - although like I said they may only get called when an action method is called as it is (not 100% sure on this one though test out the above code and that should answer your question)
Hope it helps : )

Related

Inherited custom Actionfilterattribute keeps value of the property on second call of action

I have two actionfilterattributes and an action
public class BaseAttribute : ActionFilterAttribute{
protected bool _something
public BaseAttribute(bool something){
_something = something
}
public override void OnActionExecuting(ActionExecutingContext filterContext){
_something = true;
Console.WriteLine(_something);
}
}
public class ChildAttribute : BaseAttribute{
public ChildAttribute(bool somethingChild): base(somethingChild){
}
public override void OnActionExecuting(ActionExecutingContext filterContext){
Console.WriteLine(_something);
if(!_something)
base.OnActionExecuting(filterContext);
}
}
[ChildAttribute (false)]
public ActionResult SomeMethodCalledByAngular(){
......
}
when I call the SomeMethodCalledByAngular for the first time, the _something variable gets updated as expected... it becomes true, but without refreshing the page and I hit the actionresult again, the value is still true. Is that accurate? How do I make sure that it gets reset to the original value that I passed when I decorated it on the Actionresult or false?
EDIT:
So I'm basically trying to update that variable depending on a table's field that is grabbed from the database. If for example the user did not refresh the page, but the value of that said field changed, I want to update the variable. That actionfilter becomes like a security filter for requests, since I use the actions like an API, at least for specific controllers.
EDIT2:
Just to expand more. Let's say I go to a page with a button, and that button can perform a post request to an action. But that action can only be accessed if you are SUBSCRIBED. Let's take this model
public class User{
public int Id {get;set;}
public DateTime SubscribedFrom {get;set;}
public DateTime SubscribedTo {get;set;}
}
Basically, everytime the actionfilter gets triggered, I need to check if the user is still within the subscription dates. If not, the action should not be accessed therefore returning an error.
So I'm going to have
public class BaseAttribute : ActionFilterAttribute{
protected bool _subscribed
public BaseAttribute(bool subscribed){
_subscribed= subscribed
}
public override void OnActionExecuting(ActionExecutingContext filterContext){
User user = <get the user details>
if(user.SubscribedFrom < DateTime.Now && user.SubscribedTo > DateTime.Now)
_subscribed = true;
Console.WriteLine(_something);
}
}
public class ChildAttribute : BaseAttribute{
public ChildAttribute(bool somethingChild): base(somethingChild){
}
public override void OnActionExecuting(ActionExecutingContext filterContext){
// do a check on another layer of security and see if there's an override. if there is not, _subscribed remains false, then proceeds to the base filter to validate the user subscription
if(!_subscribed)
base.OnActionExecuting(filterContext);
}
}
Ok, now I get it :) However, in my opinion, this is not a clean design. The problem is that you have this variable that is used by both classes and this logic is a bit difficult to reason about. But I don't think you need this variable for what you are doing. I redesigned this a little bit. See if this works for you:
public class BaseValidationCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
User user = < get the user details >
if (IsSubscriptionValid(user))
{
DoThings();
}
}
protected bool IsSubscriptionValid(User user)
{
return (user.SubscribedFrom < DateTime.Now && user.SubscribedTo > DateTime.Now);
}
protected void DoThings()
{
Console.WriteLine("doing something...");
}
}
public class ValidationWithOverrideCheckAttribute : BaseValidationCheckAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
User user = < get the user details >
//first checking the override and if true we don't even care about the base validation, if false, then we also check the subscription validation
if (IsSubscriptionOverride(user) || IsSubscriptionValid(user))
{
DoThings();
}
}
private bool IsSubscriptionOverride(User user)
{
//here check whatever to see if we should override the base subscription validation
return false;
}
}

Unable to get action filter to fire

Code examples are simplified to focus on the important bits.
I have three MVC applications, one of which runs at https://localhost/App/ and the other two run underneath it (https://localhost/App/App1/ and https://localhost/App/App2/). Up until now, I've been authorizing users using an attribute that derives from ActionFilterAttribute, and it's worked beautifully. The working attribute looks like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class EntitleAttribute : ActionFilterAttribute
{
public string PermissionName { get; private set; }
public EntitleAttribute(string permissionName = null)
{
PermissionName = permissionName;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool hasPermission = IsAccessibleInCurrentContext();
if (!hasPermission)
{
// If the user does not have rights on this action, return a 401.
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
However, I've recently converted over to using Ninject, and I'm trying to convert this over to an Action+Filter setup so that I can inject into the filter. So now, in order to declare the authorization a user needs to access an action, I have an attribute set up like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class EntitleAttribute : Attribute
{
public string PermissionName { get; private set; }
public EntitleAttribute(string permissionName = null)
{
PermissionName = permissionName;
}
}
And I have a filter set up like this:
public class EntitleFilter : IActionFilter
{
private readonly string _permissionName;
public EntitleFilter(string permissionName)
{
_permissionName = permissionName;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
bool hasPermission = IsAccessibleInCurrentContext();
if (!hasPermission)
{
// If the user does not have rights on this action, return a 401.
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// Nothing.
}
}
I set up the dependency injection in a NinjectModule like so:
// Bind authorization handling.
this.BindFilter<EntitleFilter>(FilterScope.Controller, 0)
.WhenControllerHas<EntitleAttribute>()
.WithConstructorArgumentFromControllerAttribute<EntitleAttribute>("permissionName", attr => attr.PermissionName);
this.BindFilter<EntitleFilter>(FilterScope.Action, 0)
.WhenActionMethodHas<EntitleAttribute>()
.WithConstructorArgumentFromActionAttribute<EntitleAttribute>("permissionName", attr => attr.PermissionName);
When I'm running App, everything works great - the OnActionExecuting method gets hit on every call. However, the method never gets called in App1 or App2, even after an iisreset. The filter setup doesn't seem to get hit on first run for App or App1, either, which seems odd.
The setup is the same for each app, all using common classes. The HomeController in each app has the EntitleAttribute on the controller and not the Index action, so that's consistent across.
I've tried using AuthorizationAttribute and IAuthorizationFilter as well, with the same results. Any direction would be greatly appreciated.

How to Disable Attribute on Action When Applied at Controller Level?

I have the following controller:
[NoCache]
public class AccountController : Controller
{
[Authorize(Roles = "admin,useradmin")]
public ActionResult GetUserEntitlementReport(int? userId = null)
{
var byteArray = GenerateUserEntitlementReportWorkbook(allResults);
return File(byteArray,
System.Net.Mime.MediaTypeNames.Application.Octet,
"UserEntitlementReport.xls");
}
}
public class NoCache : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var response = filterContext.HttpContext.Response;
response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
response.Cache.SetValidUntilExpires(false);
response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
response.Cache.SetCacheability(HttpCacheability.NoCache);
response.Cache.SetNoStore();
base.OnResultExecuting(filterContext);
}
}
As you can see, the controller is decorated with the [NoCache] attribute.
Is there any way to prevent this attribute from being applied to the GetUserEntitlementReport action?
I know I can remove the attribute at the controller level but that is not my favorite solution because the controller contains many other actions and i don't want to have to apply the attribute to each action independently.
You could create a new Attribute which can be used on individual Actions to opt out of the NoCache specified at the controller level.
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class AllowCache : Attribute { }
Mark any Actions where you want to allow caching with [AllowCache]
Then in the code for your NoCacheAttribute only disable caching if the AllowCache attribute is not present
public class NoCacheAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var actionDescriptor = filterContext.ActionDescriptor;
bool allowCaching = actionDescriptor.IsDefined(typeof(AllowCache), true) ||
actionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowCache), true);
if(!allowCaching)
{
//disable caching.
}
}
}
There is a attribute you can apply on action.
That is [OverrideActionFilters].
[NoCache]
public class AccountController : Controller
{
[Authorize(Roles = "admin,useradmin")]
[OverrideActionFilters]
public ActionResult GetUserEntitlementReport(int? userId = null)
{
var byteArray = GenerateUserEntitlementReportWorkbook(allResults);
return File(byteArray,
System.Net.Mime.MediaTypeNames.Application.Octet,
"UserEntitlementReport.xls");
}
}
This can be accomplished by implementing and registering a custom filter provider. As a starting point, I would derive from FilterAttributeFilterProvider and override GetFilters to remove the NoCache attribute when appropriate.

Help with action filters in asp.net mvc 3

I want to create an action filter that will be used by ONLY two controllers in my app... This action filter is supposed to be checked for every action inside the TWO controllers.
Heres my action filter code
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.HttpContext.Request.QueryString["ehrId"], out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Now Im just not sure how can I configure MVC framework so that the action filter gets triggered at the appropriate times.
Im using this as reference but that is applying a GLOBAL actionfilter and it doesnt specify how to limit to just some of your controllers.
Please help.
btw Im using NinjectMVC3
This depends on what the appropriate time is.
See my original blog post
http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/
Or read the other pages of the doc:
https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
(Probably I should link them)
Basically you need to configure a binding for the filter and define some condition:
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Action, 0).When...
e.g.
.WhenActionHas<AllowedToEditEHRAttribute>()
Update:
In your case simply
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Controller, 0).When(
(controllerContext, actionDescriptor) =>
controllerContext.Controller is PhysicalTestsController)
To apply the action filter to only some of your controllers, simply add the attribute to the controllers in question.
[AllowedToEditEHR]
public class YourController : Controller
{
...
For this to work, you should rename your filter to AllowedToEditEHRAttribute, i.e. replace "Filter" by "Attribute". This is a standard naming convention for attributes.
Update: To inject dependencies in the filter, just decorate it's constructor with the [Inject] attribute.
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.ActionParameters["ehrId"].ToString(), out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
And the CustomFilterProvider
public class ConfiguredFilterProvider : IFilterProvider
{
private readonly IKernel _kernel;
public ConfiguredFilterProvider(IKernel kernel)
{
_kernel = kernel;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
List<Filter> list = new List<Filter>();
if (controllerContext.Controller is PhysicalTestsController)
{
list.Add(new Filter(
_kernel.Get(typeof(AllowedToEditEHRFilter)),
FilterScope.Global, order: null
));
}
return list;
}
}
And in Ninject
kernel.Bind<IFilterProvider>()
.To<ConfiguredFilterProvider>();
It might not be the cleanest solution but its working.
You just have to decorate that two controllers with the action filter like this
[AllowedToEditEHRFilter]
public class YourController : Controller
{
...
}
However, I am not sure if it is allowed to have a complex object passed in a constructor of that filter.
Instead of implementing IActionFilter, extend ActionFilterAttribute and then assign the attribute to the two controllers you want to affect:
public class AllowedToEditEHRFilter : ActionFilterAttribute
{
// ...
}
and:
[AllowedToEditEHRFilter]
public class MyController : Controller
{
// ...
}

ASP.NET MVC: Ignore custom attribute in a base controller class

I have a number of Controllers in my project that all inherit from a controller I've named BaseController. I wrote a custom attribute that I applied to the entire BaseController class, so that each time an action runs in any of my controllers, that attribute will run first.
The problem is that I have a couple of controller actions that I'd like to ignore that attribute, but I don't know how to do it.
Can anyone help? I'm using MVC 1.
Thanks.
In your custom attribute, you can add this ShouldRun() check like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (ShouldRun(filterContext))
{
// proceed with your code
}
}
private bool ShouldRun(ActionExecutingContext filterContext)
{
var ignoreAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreMyCustomAttribute), false);
if (ignoreAttributes.Length > 0)
return false;
return true;
}
ShouldRun() simply checks whether there's a "IgnoreMyCustomAttribute" on your action. If it's there, then your custom attribute won't do anything.
You'll now want to create a simple IgnoreMyCustomAttribute, which doesn't do anything:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class IgnoreMyCustomAttribute: ActionFilterAttribute
{
}
Whenever you decorate your controller action with [IgnoreMyCustom], then MyCustomAttribute won't do anything. e.g.:
[IgnoreMyCustom]
public ViewResult MyAction() {
}
I had a similar need for something like this and found that by creating an authorization filter (implementing/deriving from FilterAttribute, IAuthorizationFilter) rather than a regular action filter (deriving from ActionFilterAttribute), and setting Inherited=true and AllowMultiple=false on the attribute, that it would only run once at the appropriate spot.
This means I am able to "cascade" my filter down from a base controller (the site-wide default), to a derived controller (for example the AdminController or whatever), or even further down to an individual action method.
For example,
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomAttribute : FilterAttribute, IAuthorizationFilter
{
private MyCustomMode _Mode;
public MyCustomAttribute(MyCustomMode mode)
{
_Mode = mode;
}
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
// run my own logic here.
// set the filterContext.Result to anything non-null (such as
// a RedirectResult?) to skip the action method's execution.
//
//
}
}
public enum MyCustomMode
{
Enforce,
Ignore
}
And then to use it, I can apply it to my super-controller,
[MyCustomAttribute(Ignore)]
public class BaseController : Controller
{
}
And I can change/override it for specific controllers, or even for specific actions!
[MyCustomAttribute(Enforce)]
public class AdministrationController : BaseController
{
public ActionResult Index()
{
}
[MyCustomAttribute(Ignore)]
public ActionResult SomeBasicPageSuchAsAHelpDocument()
{
}
}
This allowed me to "turn off" the filter for specific cases, while still being able to apply it as a default on either the whole controller or whole application.
Good luck!
I'm not sure there is an easy way to remove attributes in this situation. But I have done something similar for a project and what I did, as it was only in a few instances I didn't want my attribute to run, was to create two attributes.
My first attribute was applied to my base controller as you've done but it was aware of the existance of a second attribute and by implementing that second attribute I could disable the attribute on the base class from running.
Not sure if it was the best solution but it worked for me.
This was applied to the base controller:
/// <summary>
/// This is used to force the schema to HTTP is it is HTTPS.
/// RequireHttpsAttribute or OptionalHttpsAttribute takes precedence if used.
/// </summary>
public class RequireHttpAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.HttpContext.Request.IsSecureConnection)
{
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (!attributes.Any(a => a is RequireHttpsAttribute || a is OptionalHttpsAttribute))
{
HandleHttpsRequest(filterContext);
}
}
}
protected virtual void HandleHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET method, otherwise browser may not propogate the verb and request body correctly
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(MvcResources.RequireHttpAttribute_MustNotUseSsl);
// redirect to HTTP version
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Like so:
[RequireHttp]
public abstract class Controller : System.Web.Mvc.Controller
{
}
I could then use what is effectively a dummy attribute to disable it.
/// <summary>
/// This attribute allows the action to be server on HTTP and HTTPS but neither is enforce.
/// RequireHttpsAttribute takes precedence if used.
/// </summary>
public class OptionalHttpsAttribute : FilterAttribute
{
// This is deliberately empty, the attribute is used by RequireHttpAttribute to stop it changing schema to HTTP
}
Like so:
[OptionalHttps]
public ActionResult OptionalHttps()
{
return View();
}

Resources