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.
Related
When an action filter is called, is there any way to determine whether this call is originated from an attribute applied at controller level or action level?
I need myAttribute to be run for all of my action methods. The Delete action method, however, is specifically annotated with the filter so myAttribute is called twice. I need the call originated from Controller to do nothing or potentially do different things in that case.
Is there anyway to do this without actually removing the [myAttribute] from controller?
[myAttribute]
public class HomeController
{
public ViewResult Index()
{
}
public ViewResult View()
{
}
public ViewResult Edit()
{
}
[myAttribute]
public ViewResult Delete()
{
}
}
I am using these action filters to authorize a user. A user could have access to a controller but if a method specifically demands permission (by having [myAttribute] above it), then controller level access is not enough and that action should be explicitly mentioned in user permissions.
Not a solution,
What you need to understand is Attributes is designed to use for Annotation only not to define Behavior.
Moreover, When you apply Filter attribute on a Controller, It actually meant to apply for all the ActionMethods within the controller.
What I suggest is, Create one more FilterAttribute that have specific work related to your Delete method and decorate your method with it.
I'm new to MVC and trying to get my head around Controllers. Please could you comment on the following scenario?:
It is a business requirement of our application that users must accept a "privacy policy" screen when using the application. I do this by setting a cookie when the user clicks a link in the initial Home controller index view.
But, I need to make sure that any entry point in our application (i.e not just from the index page but anywhere in the application) checks for the existence of the cookie. It makes sense to me that the value could be checked by each controller, but I don't want to duplicate the same "If cookie exists do something" code on each controller. So is there a next level up I can write my method?
Hoping this makes sense!
Use Custom ActionFilterAttribute.
Create a class CheckCookie:
public class CheckCookie : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!CookieExists)// Check the Cookie exists
{
//Redirect if the cookie does not exists
filterContext.Result = new RedirectResult("~/Redirect/NoCookie");
return;
}
}
}
Controller:
[CheckCookie]//Check the Cookie exists
public ActionResult Index()
{
return View();
}
Write a custom base controller (that itself inherits the standard ASP.NET MVC controller) that contains your cookie verification code, then have your other controllers derive from that.
For instance, to make sure all my actions by default require a user to be authenticated, I use something similar to this as a base controller:
[Authorize]
public class CoreController : Controller
{
/* ... */
}
Then my home controller looks like:
public class HomeController : CoreController
{
/* ... */
}
This is what I'm trying to achieve:
I want to extend the HandleErrorAttribute so I can redirect to an appropriate error page. I had a look at the implementation of HandleErrorAttribute and noticed that essentially it just returns a ViewResult, that points to a view, that can be defined per Exception type.
I'd like to use the same system as HandleErrorAttribute, except:
I'd like to define a Controller for Error pages
I want to maintain the Exception as model (HandleErrorInfo)
I could obviously use a RedirectToRouteResult, but I can't pass through the Exception as model, since it's an actual Response.Redirect. And I'd like to avoid having to cache the model server side.
For now I'm just overwriting the ViewResult and manually setting controller. But that still just returns the view and doesn't actually execute the controller.
public class ErrorViewResult : ViewResult
{
public ControllerBase Controller { get; set; }
public string ControllerName { get; set; }
protected override ViewEngineResult FindView(ControllerContext context)
{
context.Controller = Controller;
context.RouteData.Values["controller"] = ControllerName;
return base.FindView(context);
}
}
I have to somehow return a result, that restarts the whole pipeline starting with the Controller.
Any ideas?
Thanks!
Be careful with TempData functionality, it will store your values only till the next request, and if in between these requests you will do any others or if you use mvc to handle client resources (like dynamically combined css and js files) then you will loose your data.
If you want to start controller manually (with all nested actions) then look at that:
RouteData data = new RouteData();
data.Values.Add("controller", "error");
data.Values.Add("action", "Handle500");
data.Values.Add("area", "");
data.Values.Add("exception", sb.ToString());
var controller = new MTool.BusinessLogic.Controllers.ErrorController();
controller.ControllerContext = new ControllerContext([HttpContextInstance], data, controller);
controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Handle500");
Why not use TempData?
TempData allows you to store data that can be read on the very next request. If they refresh the page after being redirected it will be gone.
TempData["exception"] = exception;
return RedirectToAction("Index");
Now in the get you have access to TempData["exception"] for the first GET only, then it is gone. Sounds like what you need.
Looks like I'm about to do something weird again...
The situation:
public ExperimentAttribute
{
public override void OnActionExecuted (ActionExecutingContext filterContext)
{
filterContext.Result =
new RedirectToRouteResult (
new RouteValueDictionary (new { Action = "Action2", Controller = "Experiment" }));
}
}
public ExperimentController
{
[Experiment]
public ActionResult ExperimentEntryPoint ()
{
RedirectToAction ("Action1", "Experiment");
}
public ActionResult Action1 ()
{
/* ... */
}
public ActionResult Action2 ()
{
/* ... */
}
}
So, which redirect will take place? The one in controller action, the one in ActionFilter or maybe both?
So I've just conducted an experiment that confirmed what I expected.
ExperimentEntryPoint returns an ActionResult, namely redirect to Action1.
OnActionExecuted overwrites the result before the rest of framework gets to process it. When it comes to it, it sees the redirect to Action2 command.
Nice and clear.
I can't think of a reason (YET! -- I can think of a few reasons you might want to do it in OnActionExecuting(), before your Action method code executes) for changing the ActionResult using filterContext.Result in the OnActionExecuting() method, but it is definitely allowed.
The order of execution is:
OnActionExecuting()
Your Action method code in your Controller
OnActionExecuted()
OnResultExecuting()
Whatever the assigned ActionResult.ExecuteResult() returns (renders a view, json, content, etc)
OnResultExecuted()
As your experiment showed, you're allowed to change filterContext.Result in both OnActionExecuted() and also OnActionExecuting(). Since OnActionExecuted() is called after your Controller code (example: return RedirectToAction()), whatever it sets as the ActionResult takes precedence.
I found decent coverage of ActionFilters in Steven Sanderson's book 'Pro ASP.NET MVC framework'. [1] [2]
[1] http://books.google.com/books?id=Xb3a1xTSfZgC (The content on ActionFilters is not available in the Google Book Preview)
[2] http://blog.codeville.net/2009/04/29/now-published-pro-aspnet-mvc-framework-apress/
They can't both happen as they both return a HTTP 302 redirect.
The attribute will execute the redirect since it's the last thing to happen before the result is sent to the client. Both OnActionExecuting and OnActionExecuted will happen over top of the controller's action result.
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()