Returning an MVC ActionResult before the specific controller method is called - asp.net-mvc

I have a base controller class from which my other controllers are inherited
public abstract class BaseController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
...
}
}
During initialization I'm doing some setup, and there are a few cases where I'd want to short circuit the execution, jumping directly to the return of the ActionResult, skipping the execution of the actual Action method entirely. Something along these lines
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
if(specialCase)
{
ViewData[...] = specialCaseInformation;
return View("~/Shared/SpecialCase.aspx");
}
}
The intention would be to skip whatever ActionResult method was going to be called and replace it with my global special case page. But I don't think Initialize was meant for this.
What I think I need to do is create a seperate ActionFilterAttribute class, override the OnActionExecuting method, and if the specialCase comes up, construct a ViewResult object and assign it to the filterContext.Result property.
Am I going in the right direction with this, or should I be doing this differently?

Yes, an ActionFilterAttribute is exactly the right way. Look at HandleErrorAttribute.cs for an example.
Initialize is not the right way, as you say.

Related

OnActionExecuted being called twice in Web API

I am trying to do some stuff after my controller is done with the action at OnActionExecuted.
However the method is called twice.
My filter method
public class TestFilter: ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
//do stuff here
}
}
and my controller
[TestFilter]
public class BaseController : ApiController
{
public LoginResponseDTO Login(LoginRequestDTO loginRequestDTO)
{
//do login stuff
}
}
when i try this filter, the onActionExecuted Method gets called twice which causes my action in the method to be applied twice to the response. I have searched for a reason but cannot find a solution.
Any Ideas?
The answer is from #Martijn comments above:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TestFilter: ActionFilterAttribute
All credits goes to him.
(Note: I'll remove the post, if he decide to add the comment as answer)
For me the issue was I was calling /myApi/action which was redirecting to /myApi/action/ and this caused OnActionExecuted() to run twice.
I filtered out where filterContext.Result is RedirectResult within OnActionExecuted since I wasn't interested in running my code then. The HTTP status code showed as 200 on both the calls so filtering by that won't work.
You can override the AllowMultiple inside your ActionFilterAttribute, like so:
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//Your logic
}
That will stop your ActionFilter being called twice. Also check that it is not registered twice. Check out this stackoverflow answer to see more about that.
Do be aware that AttributeUsage attribute is a single-use attribute--it can't be applied more than once to the same class, as you will find in the remarks section of this.
If you have registered the custom filter in Global.asax.cs, like this:
GlobalConfiguration.Configuration.Filters.Add(new TestFilterAttribute());
Please revoke the attribute above your custom controller.
Make sure [BasicAuthenticateFilter] is not in "Controller" level as well as "Method" level. It will run twice.

At what point in Controller execution is TempData populated

I have a base class for my controllers. In the constructor of the base class I was trying to populate a ViewBag property from TempData. However it seems that TempData is not populated at that point, nor is it in the OnBeginExecute method.
I need to populate this ViewBag property in the base class, as all controllers need the same variable (it's a redirection message).
Which override of Controller in my base class can I use to do this?
TempData as well as any HttpContext related stuff is not available in the controller constructor. You can use them starting from the Initialize method. So if you need to populate them in a global manner for a controller either override this method or write a custom action filter and decorate your controller with it:
public class HomeController: Controller
{
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
// now you can access the HttpContext
}
...
}
Take a look at BeginExecuteCore:
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
// TempData is not populated here
var result = base.BeginExecuteCore(callback, state);
// TempData is populated here
return result;
}

Get instance of ActionFilterAttribute in the method

I am newbie in ASP.NET MVC platform and I faced with the following problem.
I am using ActionFilterAttribute to do some routine work before and after action method run. The problems is that I need to get instance of the attribute in action method to read some properties which was set in OnActionExecuting method. For example
public class SomeController : Controller{
public SomeController(){ }
[Some]
public ActionResult Index(){
SomeModel = someRepository.GetSomeModel();
//get instance of some attribute and read SomeProperty
return View(SomeModel);
}
}
public class SomeAttribute : ActionFilterAttribute{
public int SomeProperty { get; set; }
public SomeAttribute(){ }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var parameters = filterContext.ActionParameters;
//Here to set SomeProperty depends on parameters
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//do some work
}
}
Any ideas?
Filter attributes must be designed to be thread-safe. The framework makes no guarantees that a single instance of your filter attribute will only service one request at a time. Given this, you cannot mutate attribute instance state from within the OnActionExecuting / OnActionExecuted methods.
Consider one of these as alternatives:
Use HttpContext.Items to store the value in OnActionExecuting, then read it from the action method. You can access HttpContext via the filterContext parameter passed to OnActionExecuting.
Put the property on the controller instead of the attribute, then have the OnActionExecuting method cast the controller to SomeController and set the property directly from within that method. This will work since the framework does by default guarantee that controller instances are transient; a single controller instance will never service more than one request.
Option 1: Your ActionFilter can add information to the ViewModel, e.g.
filterContext.Controller.ViewData["YourKey"] = "Value to add";
Option 2: You can put code in your base Controller class that finds all the attributes that have been applied to the method that is executing, and you can put them in a member variable that the Action method can then use.
e.g.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var attrs = filterContext.ActionDescriptor.GetCustomAttributes(true).OfType<Some>();
...
}
Edit: And as others have noted, trying to mutate the attribute isn't going to work.
Sorry, I do not believe this is possible. Since the value of SomeProperty must be based on parameters sent into the constructor of the attribute, it must be easy to calculate. I would suggest adding some static methods to get the value from within the action.

HttpContext.Current.User is null in ControllerBase(asp.net mvc)

I have a ControllerBase class in an ASP.NET MVC Application. The other controllers inherit from ControllerBase.
I want to access HttpContext.User.Identity.Name, but HttpContext is null. What's the matter?
public ControllerBase()
{
var dataManager=new DataManager();
if (HttpContext.User.Identity.IsAuthenticated) // throws error
{
ViewData["assets"] = ud.BalanceFreeze + ud.Balance + ud.BalanceRealty;
ViewData["onaccount"] = ud.Balance;
ViewData["pending"] = ud.BalanceFreeze;
ViewData["inrealty"] = ud.BalanceRealty;
}
Try adding your code to this event in your ControllerBase:
protected override void Initialize(RequestContext requestContext){
}
Your controller gets constructed before the HttpContext has been set by ASP.NET. Like Nik says, you need to put this code into an overridden method in your class.
I would also point out that depending on HttpContext directly will make it impossible to perform unit testing on any of your controllers that extend this class. This is why many of the methods (like the Execute method) in the ControllerBase class take a RequestContext as an argument. You can say:
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
var currentUser = requestContext.HttpContext.User;
...
}
... which makes it possible to create and execute your controllers with "fake" contexts for unit testing purposes.

make sure each controller method has a ValidateAntiForgeryToken attribute?

Is there any way to centralize enforcement that every action method must have a "ValidateAntiForgeryToken" attribute? I'm thinking it would have to be done by extending one the "routing" classes.
Edit: Or maybe do some reflection at application startup?
Yes. You can do this by creating your own BaseController that inherits the Mvc Controller, and overloads the OnAuthorization(). You want to make sure it is a POST event before enforcing it:
public abstract class MyBaseController : Controller
{
protected override void OnAuthorization(AuthorizationContext filterContext)
{
//enforce anti-forgery stuff for HttpVerbs.Post
if (String.Compare(filterContext.HttpContext.Request.HttpMethod,
System.Net.WebRequestMethods.Http.Post, true) == 0)
{
var forgery = new ValidateAntiForgeryTokenAttribute();
forgery.OnAuthorization(filterContext);
}
base.OnAuthorization(filterContext);
}
}
Once you have that, make sure all of your controllers inherit from this MyBaseController (or whatever you call it). Or you can do it on each Controller if you like with the same code.
Sounds like you're trying to prevent "oops I forgot to set that" bugs. If so I think the best place to do this is with a custom ControllerActionInvoker.
Essentially what you want to do is stop MVC from even finding an action without a AntiForgery token:
public class MustHaveAntiForgeryActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var foundAction = base.FindAction(controllerContext, controllerDescriptor, actionName);
if( foundAction.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true ).Length == 0 )
throw new InvalidOperationException("Can't find a secure action method to execute");
return foundAction;
}
}
Then in your controller, preferably your base controller:
ActionInvoker = new MustHaveAntiForgeryActionInvoker();
Just wanted to add that custom Controller base classes tend to get "thick" and imo its always best practice to use MVC's brilliant extensibility points to hook in the features you need where they belong.
Here is a good guide of most of MVC's extensibility points:
http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx
Ok, I just upgraded a project to MVC v2.0 here, and eduncan911's solution doesn't work anymore if you use the AuthorizeAttribute on your controller actions. It was somewhat hard to figure out why.
So, the culprit in the story is that the MVC team added the use of the ViewContext.HttpContext.User.Identity.Name property in the value for the RequestVerificationToken.
The overridden OnAuthorization in the base controller is executed before any filters on the controller action. So, the problem is that the Authorize attribute has not yet been invoked and therefore is the ViewContext.HttpContext.User not set. So the UserName is String.Empty whereas the AntiForgeryToken used for validation includes the real user name = fail.
We solved it now with this code:
public abstract class MyBaseController : Controller
{
protected override void OnAuthorization(AuthorizationContext filterContext)
{
//enforce anti-forgery stuff for HttpVerbs.Post
if (String.Compare(filterContext.HttpContext.Request.HttpMethod, "post", true) == 0)
{
var authorize = new AuthorizeAttribute();
authorize.OnAuthorization(filterContext);
if (filterContext.Result != null) // Short circuit validation
return;
var forgery = new ValidateAntiForgeryTokenAttribute();
forgery.OnAuthorization(filterContext);
}
base.OnAuthorization(filterContext);
}
}
Some references to the MVC code base:
ControllerActionInvoker#InvokeAuthorizationFilters() line 283. Same short circuiting.
AntiForgeryData#GetUsername() line 98. New functionality.
How about this?
[ValidateAntiForgeryToken]
public class MyBaseController : Controller
{
}

Resources