In MVC an OutputCacheAttribute is capable to prevent the execution of the action it is decorating (if the relative cache exists)
How to implement the same mechanism with a custom attribute?
In other works I would like to be able to decorate an action, and based on a logic internal to the attribute it will decide whether the action should carry on executing or not.
Additions
I've implemented a mechanism by which if a request to an action arrives with a querystring like flushaction=flush_silent, the custom attribute (which is extending the OutputCacheAttribute) invalidates the cache.
What I would also like to do, is not to execute the Action:
[JHOutputCache(CacheProfile = "ContentPageController.Index")]
public ActionResult Index(string url)
{
//custom code that should not execute when flushing the cache
}
As JHOutputCache extends OutputCacheAttribute, which derives from ActionFilterAttribute, halting execution of the underlying action is quite simple:
public class JHOutputCacheAttribute : OutputCacheAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (condition)
filterContext.Result = new EmptyResult();
else
base.OnActionExecuting(filterContext);
}
}
You can return any valid ActionResult here, including any custom ActionResult you may have derived.
Related
Situation:
I have a controller (with its associated views), that after it was developed, it was decided that it will be excluded from the current phase, and instead, it will be included in some phase in the future.
I cannot simply exclude or delete the controller and the views from the solution because of what I mentioned that will be using the controller in the future. Something that came to my mind was to force each action on the controller to redirect to the main page if they are accessed, but I think this is not too "elegant".
Question:
What other method can I use to block the execution of an action in a controller when they are accessed through the URL?
Keeping unused code in your codebase like that is generally a bad idea. Assuming you have some sort of version control in place, keep a branch around with that controller, but delete it from your master branch. Merge that branch back in when you're bringing the feature back.
If, however, removing the code really isn't possible, I'd make a custom filter to redirect any requests to that controller to some other URL.
i.e.
[RedirectTo("Index", "Home")]
public class MyFutureController : Controller {
...
where you have
public class RedirectToFilter : ActionFilterAttribute {
public string RedirectAction { get; set;}
public string RedirectController { get; set; }
public RedirectToFilter(string redirectAction, string redirectController) {
RedirectAction = redirectAction;
RedirectController = redirectController;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = RedirectToAction(RedirectAction, RedirectController);
}
}
Any requests to FutureController will just get redirected to your Home/Index route. In the future, you just have to remove the attribute from the controller and you're good to go.
You can add a custom route, so that when request comes for this controller it actually redirects to some other controller, e.g. HomeController
Use an OnActionExecuting action filter on the controller.
public class FooController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Detail()
{
return View();
}
protected override void OnActionExecuting( ActionExecutingContext filterContext )
{
filterContext.Result = RedirectToAction( "Index", "Home" );
}
When you're ready to start using the controller just delete the OnActionExecuting method and you're all set.
Just decorate the action method with the [NonAction] attribute. More Here
You could simply write a view to display to the user who directs themselves there that it is still under construction or something of the like. Excluding or commenting out the controller is certainly an option, or you could rewrite all associated methods to reroute to another page.
I think the best solution is probably to just exclude it altogether, having it in your application doesn't benefit you or the user if you're not using it currently. The code should remain so you're free to tweak and refine it before you actually get a release version going.
some of my controller actions require a user to be authenticated. Those actions are flagged with a custom [Authorize] attribute. Behind the scene, a custom membership provider does some magic, among which setting some temporary data into the common-thread.
At the end of each action that required an authentication, a call to the OnActionExecuted() filter is required to cleanup the thread. This is done via another custom attribute called [CleanupContext].
So my actions look like this:
[Authorize]
[CleanupContext]
public ViewResult Action()
{
...
}
Since those two are always used together, since I am lazy and since I fear that someday one dev might forget to put one or the other and we end up with some weird behavior: is there a way to combine them into one attribute?
[AuthorizeAndCleanup]
public ViewResult Action()
{
// Aaah, if only it could look like this :D
}
Thanks a lot!
You could derive from AuthorizeAttribute in order to do your custom authorization stuff and implement IActionFilter in order to have access to the OnActionExecuting and OnActionExecuted events (to do your custom cleanup code):
public class AuthorizeAndCleanupAttribute : AuthorizeAttribute, IActionFilter
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// TODO: your custom authorization logic
return base.AuthorizeCore(httpContext);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// TODO: your custom cleanup code
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
Obviously you should be aware that neither the OnActionExecuting or the OnActionExecuted events will ever be executed if the authorization fails (a.k.a. the AuthorizeCore method returns false) so make sure you do your cleanup in this method if you are about to return false.
A quick. dirty and (possibly) slow solution that I can think of is to skip the cleanup attribute and check for the presence of the custom Authorize attribute in the OnActionExecuted() and execute any cleanup code if you find it (since you stated that they are always present together).
You should implement your own filter provider (http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html) which will automatically add Cleanup attribute to any action marked by Authorize.
I would like to implement caching at the action level in MVC in a certain way.
I am aware of the OutputCache attribute, but I can't cache the entire page.
I would like to cache the model returned by the action.
So basically, I want to create a filter that will prevent the action method from being invoked, but have MVC behave as if it was invoked.
Assume that I plan on ignoring any "return View("viewName")" assuming all will be "return View()".
You can create a filter that inherits from ActionFilterAttribute
This is what I use
public class CacheControlAttribute : ActionFilterAttribute
{
public CacheControlAttribute(HttpCacheability cacheability)
{
_cacheability = cacheability;
}
private readonly HttpCacheability _cacheability;
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
cache.SetCacheability(_cacheability);
cache.SetExpires(DateTime.Now);
cache.SetAllowResponseInBrowserHistory(false);
cache.SetNoServerCaching();
cache.SetNoStore();
}
}
You can do partial caching.
For example, you can make an action method which IS NOT invoked as a regular action, but rather renders a partial view (an HTML snippet eventually) by calling Html.RenderPartial(). That way, you don't cache the whole page, but only those fragments which change less frequently.
I started off using the default project's AccountController, but I've extended/changed it beyond recognition. However, in common with the original I have a LogOn and LogOff action.
Clearly, the LogOn action must be accessible to everyone. However, since I've added lots of other actions to this controller (to create & edit users), I want 99% of the actions to require administrator role membership.
I could decorate all my actions with [Authorize Roles="Administrators"] but there's a risk I'll forget one. I'd rather make it secure by default, by decorating the controller class itself with that attribute, and then relax the requirement on my LogOn method. Can I do that?
(As in, can I do that out-of-the-box without creating custom classes, etc. I don't want to complicate things more than necessary.)
To override an controller Attribute at the Action level you have to create a custom Attribute and then set the Order property of your custom attribute to a higher value than the controller AuthorizeAttribute. I believe both attributes are then still executed unless your custom attribute generates a result with immediate effect such as redirecting.
See Overriding controller AuthorizeAttribute for just one action for more information.
So I believe in your case you will just have to add the AuthorizeAttribute on the Actions and not at the controller level. You could however create a unit test to ensure that all Actions (apart from LogOn) have an AuthorizeAttribute
You can use AuthorizeAttribute on your class
http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx
For relaxing you can implement for example a custom action filter attribute like this (I didn' test if it works).
public class GetRidOfAutorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// you can for example do nothing
filterContext.Result = new EmptyResult();
}
}
After way too much time, I came up with a solution.
public class OverridableAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if(action.IsDefined(typeof(IgnoreAuthorization), true)) return;
var controller = action.ControllerDescriptor;
if(controller.IsDefined(typeof(IgnoreAuthorization), true)) return;
base.OnAuthorization(filterContext);
}
}
Which can be paired with IgnoreAuthorization on an Action
public class IgnoreAuthorization : Attribute
{
}
I would like to do this:
[RequiresAuthentication(CompanyType.Client)]
public class FooController
{
public ActionResult OnlyClientUsersCanDoThis()
public ActionResult OnlyClientUsersCanDoThisToo()
[RequiresAuthentication]
public ActionResult AnyTypeOfUserCanDoThis()
You can see why this won't work. On the third action the controller-level filter will block non-clients. I would like instead to "resolve" conflicting filters. I would like for the more specific filter (action filter) to always win. This seems natural and intuitive.
Once upon a time filterContext exposed MethodInfo for the executing action. That would have made this pretty easy. I considered doing some reflection myself using route info. That won't work because the action it might be overloaded and I cannot tell which one is the current executing one.
The alternative is to scope filters either at the controller level or the action level, but no mix, which will create a lot of extra attribute noise.
We're looking into a way to expose other filters, but no promises.
Applying a filter to the controller isn't really a "scope", it's merely a short-hand for applying it to all filters. Unfortunately, that means you can't include all but one action. One simple way you could do this is to put that one method in another controller. You could even add a custom route just for that one case so the URL doesn't have to change.
you can put authorisation logic into the OnActionExecuting(..) method of the Controller, i.e.
public override void OnActionExecuting(ActionExecutingContext filterContext) {
base.OnActionExecuting(filterContext);
new RequiresAuthentication()
{ /* initialization */ }.OnActionExecuting(filterContext);
}
Hope this helps,
Thomas
You could change filter order with the general auth filter on the controller and the specific auth filters on the actions. Somehow like this:
[RequiresAuthentication]
public class FooController
{
[RequiresAuthentication(CompanyType.Client)]
public ActionResult OnlyClientUsersCanDoThis()
[RequiresAuthentication(CompanyType.Client)]
public ActionResult OnlyClientUsersCanDoThisToo()
public ActionResult AnyTypeOfUserCanDoThis()