How can I make a function to run on all of the controllers in ASP.NET MVC 2? - asp.net-mvc

I have a function that :
gets some information from model ( done )
gets some information from cookie ( done ), and
set the new informations on ViewData ( on views ) on every controller
Also, the function need to run on every controller when the controller is calling (I don`t know how to do this).
I have write this function on a BaseController but I get an error:
Object reference not set to an instance of an object.
And, I think this is not the right way. I'm using ASP.NET MVC 2 and .NET 3.5.
Thx for your help.

Create a custom action filter:
public class MyActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// if the ActionResult is not a ViewResult (e.g JsonResult, ContentResult),
// there is no ViewData so don't do anything.
var viewResult = filterContext.Result as ViewResult;
if (viewResult != null)
{
// call your function, do whatever you want to the result, e.g:
viewResult.ViewData["someKey"] = someData;
}
}
}
Slap that bad boy on your base controller:
[MyActionFilter]
public class BaseController : Controller
{
}
Now, after every ActionResult for every Controller is executed, your action filter logic will be executed.
You've got a few other events you can hook into, but it sounds like you want to do some stuff after the action has been executed, so i think the above should suit you fine.

It won't help you until you upgrade but in ASP.NET MVC 3 you can use a global action filter for this purpose.
http://weblogs.asp.net/gunnarpeipman/archive/2010/08/15/asp-net-mvc-3-global-action-filters.aspx

Related

OnActionExecuting fires multiple times

I'm not sure if this is the correct way to go about the problem I need to solve... however in an OnActionExecuting action filter that I have created, I set a cookie with various values. One of these values is used to determine whether the user is visiting the website for the very first time. If they are a new visitor then I set the ViewBag with some data so that I can display this within my view.
The problem I have is that in some of my controller actions I perform a RedirectToAction. The result is OnActionExecuting is fired twice, once for the original action and then a second time when it fires the new action.
<HttpGet()>
Function Index(ByVal PageID As String) As ActionResult
Dim wo As WebPage = Nothing
Try
wp = WebPages.GetWebPage(PageID)
Catch sqlex As SqlException
Throw
Catch ex As Exception
Return RedirectToAction("Index", New With {.PageID = "Home"})
End If
End Try
Return View("WebPage", wp)
End Function
This is a typical example. I have a data driven website that gets a webpage from the database based on the PageID specified. If the page cannot be found in the database I redirect the user to the home page.
Is it possible to prevent the double firing in anyway or is there a better way to set a cookie? The action filter is used on multiple controllers.
Had the same issue. Resolved by overriding property AllowMultiple:
public override bool AllowMultiple { get { return false; } }
public override void OnActionExecuting(HttpActionContext actionContext)
{
//your logic here
base.OnActionExecuting(actionContext);
}
You can save some flag value into TempData collection of controller on first executing and if this value presented, skip filter logic:
if (filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] == null)
{
filterContext.Controller.TempData["MyActionFilterAttribute_OnActionExecuting"] = true;
}
You could return the actual action instead of redirecting to the new action. That way, you dont cause an http-request, thereby not triggering the onactionexecuting (i believe)
Old question, but I just dealt with this so I thought I'd throw in my answer. After some investigating I disovered this was only happening on endpoints that returned a view (i.e. return View()). The only endpoints that had multiple OnActionExecuting fired were HTML views that were composed of partial views (i.e. return PartialView(...)), so a single request was "executing" multiple times.
I was applying my ActionFilterAttribute globally to all endpoints, which was working correctly on all other endpoints except for the view endpoints I just described. The solution was to create an additional attribute applied conditionally to the partial view endpoints.
// Used specifically to ignore the GlobalFilterAttribute filter on an endpoint
public class IgnoreGlobalFilterAttribute : Attribute { }
public class GlobalFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Does not apply to endpoints decorated with Ignore attribute
if (!filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreGlobalFilterAttribute), false).Any())
{
// ... attribute logic here
}
}
}
And then on my partial view endpoints
[HttpGet]
[AllowAnonymous]
[IgnoreGlobalFilter] //HERE this keeps the attribute from firing again
public ActionResult GetPartialView()
{
// partial view logic
return PartialView();
}

ASP.NET MVC 4, how to access/modify the view model object (and change view and action method) before it is used as action method parameter?

Is there any useful hook in ASP.NET MVC (MVC4) which can let you access the Action method parameter (View model) before the action method becomes invoked, and then also (e.g. depending on the value of something you checked in the action method parameter) let you prevent the action method from being invoked, i.e. instead either forward the view model object (action method parameter) to another action method or directly to some view (i.e. without any further processing in an action method) ?
If you do not understand the question, please see the code example below which should illustrate the kind of code I am looking for...
(though I do not know if there actually exists such kind of interface and a possibility to hook an implementation into the MVC framework)
If this is indeed possible, I would like to see an answer with code example about how to do it (and not just a response with someone claiming that e.g. "try using method 'ActionFilterAttribute.OnActionExecuting' or 'IModelBinder.BindModel' " because I have already tried those and could not make it work).
Also, please respect that I do not want this thread to become a discussion about WHY to do it, but want to see HOW to do it.
(i.e. I am not interested in getting into discussions with responses such as "What are you actually trying to achieve?" or "There are probably better things of doing what you want to do...")
The question can be split into three subquestions/code examples as my own code samples below try to illustrate:
(but would like them "refactored" into REAL code with usage of real existing types)
(obviously, every type below which includes the substring "Some" is something I have made up, and I am looking for the corresponding real thing ...)
(1) Example of how to get access to (and potentially modify) view model objects (action method parameters) in a generic place before the actual action method is invoked with the view model object parameter.
The kind of code example I am looking for would probably be similar to below but do not know what kind of interface to use and how to register it to be able to do something like below:
public class SomeClass: ISomeInterface { // How to register this kind of hook in Application_Start ?
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
string nameOfTheControllerAboutToBeInvoked = actionMethodContext.ControllerName;
string nameOfTheActionMethodAboutToBeInvoked = actionMethodContext.MethodName;
// the above strings are not used below but just used for illustrating that the "context object" contains information about the action method to become invoked by the MVC framework
if(typeof(IMyBaseInterfaceForAllMyViewModels).IsAssignableFrom(actionMethodParameterViewModel.GetType())) {
IMyBaseInterfaceForAllMyViewModels viewModel = (IMyBaseInterfaceForAllMyViewModels) actionMethodParameterViewModel;
// check something in the view model:
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// modify something in the view model before it will be passed to the target action method
viewModel.MySecondGeneralPropertyInAllViewModels = "bar";
}
}
}
}
(2) Example of how to prevent the targeted action method from being executed and instead invoke another action method.
The example might be an extension of the above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
actionMethodContext.ControllerName = "SomeOtherController";
actionMethodContext.MethodName = "SomeOtherActionMethod";
// The above is just one example of how I imagine this kind of thing could be implemented with changing properties, and below is another example of doing it with a method invocation:
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadExecuteActionMethod("SomeOtherController", "SomeOtherActionMethod", actionMethodParameterViewModel);
// Note that I do _NOT_ want to trigger a new http request with something like the method "Controller.RedirectToAction"
}
(3) Example of how to prevent the normal action method from being executed and instead forward the view model object directly to a view without any further processing.
The example would be an extension of the first above example, with something like below:
public void SomeMethodSomewhere(SomeActionMethodContext actionMethodContext, object actionMethodParameterViewModel) {
... same as the first example above ...
if(viewModel.MyFirstGeneralPropertyInAllViewModels == "foo") {
// the below used razor view must of course be implemented with a proper type for the model (e.g. interface 'IMyBaseInterfaceForAllMyViewModels' as used in first example above)
SomeHelper.PreventCurrentlyTargetedActionMethodFromBecomingExecutedAndInsteadForwardViewModelToView("SomeViewName.cshtml", actionMethodParameterViewModel);
}
You could use an action filter and override the OnActionExecuting event:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
}
}
Now let's see what useful information you could extract from this filterContext argument that is passed to this method. The property you should be looking for is called ActionParameters and represents an IDictionary<string, object>. As its name suggests this property contains all the parameters that are passed to the controller action by name and value.
So let's suppose that you have the following controller action:
[MyActionFilter]
public ActionResult Index(MyViewModel model)
{
...
}
Here's how you could retrieve the value of the view model after model binding:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyViewModel;
// do something with the model
// You could change some of its properties here
}
}
Now let's see the second part of your question. How to shortcircuit the controller action and redirect to another action?
This could be done by assigning a value to the Result property:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
... some processing here and you decide to redirect:
var routeValues = new RouteValueDictionary(new
{
controller = "somecontroller",
action = "someaction"
});
filterContext.Result = new RedirectToRouteResult(routeValues);
}
}
or for example you decide to shortcircuit the execution of the controller action and directly render a view:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewResult = new ViewResult
{
ViewName = "~/Views/FooBar/Baz.cshtml",
};
MyViewModel someModel = ... get the model you want to pass to the view
viewResult.ViewData.Model = model;
filterContext.Result = viewResult;
}
}
or you might decide to render a JSON result:
public class MyActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MyViewModel someModel = ... get the model you want to pass to the view
filterContext.Result = new JsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
So as you can see the possibilities are unlimited of what you can do.
I have experimented with the code in the answer provided by the user Darin Dimitrov, and the first and third parts of the answer are correct.
(Though, for others who might find this thread and be interested, I can clarify that in the first answer the "model" does not seem to
be a hardcoded keyword always used for the model but seems to have to correspond to the chosen name of the action method parameter.
In other words, if you instead have the method signature
public ActionResult Index(MyViewModel myViewModel)
then in your action filter you have to use
var model = filterContext.ActionParameters["myViewModel"] as MyViewModel;
)
Regarding the second answer, the usage of 'RedirectToRouteResult' will trigger a new http request (which was not desired as I mentioned in the second code example of mine).
I found another way of "changing" action method by actually invoking it explicitly:
var controller = new SomeController();
ActionResult result = controller.SomeAction(model);
filterContext.Result = result;
The above code actually seems to prevent the originally targeted action method from becoming invoked, i.e. when I put a breakpoint in the method annotated with '[MyActionFilter]' the execution never got into that method.
Typically, it is probably not desired to hardcode a controller like above, but instead reflection might be used, for example as below with the thirdpart library "fasterflect":
string nameOfController = ...
string nameOfActionMethod = ...
// both above variables might for example be derived by using some naming convention and parsing the refering url, depending on what you want to do ...
var theController = this.GetType().Assembly.CreateInstance(nameOfController);
ActionResult result = (ActionResult)theController.CallMethod(nameOfActionMethod, model);
filterContext.Result = result;
(for those who want to extract the names of the current target controller and action method, when implementing logic to determine the controller you want to invoke, you can use this code in the filter:
var routeValueDictionary = filterContext.RouteData.Values;
string nameOfTargetedController = routeValueDictionary["controller"].ToString();
string nameOfTargetedActionMethod = routeValueDictionary["action"].ToString();
)
I think it feels a bit awkward to instantiate and invoke controllers like above, and would prefer to change the target controller and action method in another way if possible ?
So, the remaining question is if there is still (in MVC 4 final version) no way of redirecting/forwarding execution "internally" (without a new http request being fired as with 'RedirectToAction') at the server ?
Basically, I think I am here just looking for something like "Server.Transfer" which was used with ASP.NET Web Forms (and also the old classic ASP I believe could use the same thing).
I have seen older question/answers on this issue with people implementing this behaviour themselves with some "TransferResult" class of their own, but it seems to tend to become broken i different MVC versions.
(for example, see here for MVC 4 beta: How to redirect MVC action without returning 301? (using MVC 4 beta) ).
Is there really still not a simple standard solution (implemented in MVC 4 final) about how to do an "internal redirect" without a new http request (as RedirectToAction does) ?

UnitOfWork in Action Filter seems to be caching

I have an MVC 3 site that uses IoC (Unity), and my model is generated w/ EF4 and POCOs. I am using an action filter to commit my UnitOfWork:
public class UseUnitOfWorkAttribute : ActionFilterAttribute, IActionFilter
{
private readonly IUnitOfWork _unitOfWork;
public UseUnitOfWorkAttribute()
{
_unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
}
void IActionFilter.OnActionExecuted(ActionExecutedContext filterContext)
{
_unitOfWork.Commit();
}
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
However, even though the Commit() seems to be getting fired, it somehow seems to be caching what it thinks is "dirty".
For example, in my controller, the following gets executed from a service class:
var user = _userRepository.Single(u => u.Id == 2);
user.DateAdded = DateTime.Now;
Whenever I do a fresh build of the solution and hit this controller action, the change is actually committed. However, successive hits to the controller doesn't do anything.
On the other hand, if I put a UnitOfWork in my controller and commit it following the service method call, it works as expected (every time I request the controller action):
public AccountController()
{
_unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
}
public ActionResult Test()
{
var user = _userRepository.Single(u => u.Id == 2);
user.DateAdded = DateTime.Now;
_unitOfWork.Commit();
}
So it definitely seems like some sort of caching is going on, but I can't figure it out what is getting cached -- the UnitOfWork, the ActionFilter, or the repository.
Any ideas what could be going on? And if not, any ideas what else I could do to troubleshoot?
Thanks in advance.
You are initializing your unit of work in the constructor of the action filter which means that it will be injected when the action filter is instantiated. Quote from the ASP.NET MVC 3 release notes:
In previous versions of ASP.NET MVC,
action filters were created per
request except in a few cases. This
behavior was never a guaranteed
behavior but merely an implementation
detail and the contract for filters
was to consider them stateless. In
ASP.NET MVC 3, filters are cached more
aggressively. Therefore, any custom
action filters which improperly store
instance state might be broken.
Make sure the dependency container returns the same instance in all places and rewrite the filter to avoid state caching:
public class UseUnitOfWorkAttribute : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuted(ActionExecutedContext filterContext)
{
var unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
unitOfWork.Commit();
}
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
I would check the lifetime on your repository. That was certainly the culprit in our implementation.

How do I unit test a controller method that has the [Authorize] attribute applied?

I've searched stackoverflow and googled four a couple of hours and still not found any solution for my "trivial" problem.
If you write unit test for your filtered [Authorize] ActionResult, how do you solve the problem to fake that user is authenticated?
I have a lot of ActionResult methods that are filtered with [Authorize] and I want to test all of my ActionResult methods regardless if they are filtered with [Authorize] or not.
A simple example of what i mean:
[TestMethod]
public void Create_Get_ReturnsView()
{
// Arrange
var controller = new UserController();
// Act
var result = controller.Create();
// Assert
Assert.IsNotNull(result as ViewResult);
}
[Authorize]
public ActionResult Create()
{
return View("Create");
}
As of now the tests don't even hit the ActionResult method because of the [Authorize] filter, exception thrown is: System.NullReferenceException: Object reference not set to an instance of an object.
You need to mock a context for your controller. Try using Moq
Your arrange would then look like:
var controller = new UserController();
var mock = new Mock<ControllerContext>();
mock.SetupGet(x => x.HttpContext.User.Identity.Name).Returns("SOMEUSER");
mock.SetupGet(x => x.HttpContext.Request.IsAuthenticated).Returns(true);
controller.ControllerContext = mock.Object;
You should be able to then do your Act & Assert.
If you haven't already, I would highly recommend looking through NerdDinner as an example MVC site.

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