Hi I have my mvc app and this code snippet:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
ActionInvoker.InvokeAction(filterContext, "ErrorMessage");
}
public ActionResult ErrorMessage(ExceptionContext filterContext)
{
ViewModel<Exception> viewModel = ViewModelFactory.CreateFor(filterContext.Exception);
return View(viewModel);
}
The problem is that I can't pass arguments to method. I thought it would be this filterContext but in ErrorMessage method it has all default values.
So my question is - How to pass some values to method that I invoke?
I don't know if it was clear from my question but what I wanted achive was that I didn't wanted to catch any exception in my controllers actions and still get good-looking message about error. So my solution which satisfies me is:
protected override void OnException(ExceptionContext filterContext)
{
base.OnException(filterContext);
TempData.Add(MyCommons.Exception, filterContext.Exception);
var controllerName = filterContext.RequestContext.RouteData.Values["Controller"];
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary(new {controller = controllerName, action = "Error"}));
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
You dont pass parameters to the invoked action, use model binding instead.
If you want to pass some custom parameters out of default model binding sources (query, form, route..), wrap them in some class and implement your custom model binder for it (or valueprovider, but i think modelbinder is more appropriate here).
Related
Situation is this:
I can't find a way of getting the viewModel that was passed to the POST action method.
[HttpPost]
public ActionResult Edit(SomeCoolModel viewModel)
{
// Some Exception happens here during the action execution...
}
Inside the overridable OnException for the controller:
protected override void OnException(ExceptionContext filterContext)
{
...
filterContext.Result = new ViewResult
{
ViewName = filterContext.RouteData.Values["action"].ToString(),
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData
};
}
When debugging the code filterContext.Controller.ViewData is null since the exception occurred while the code was executing and no view was returned.
Anyways I see that filterContext.Controller.ViewData.ModelState is filled and has all the values that I need but I don't have the full ViewData => viewModel object available. :(
I want to return the same View with the posted data/ViewModel back to the user in a central point. Hope you get my drift.
Is there any other path I can follow to achieve the objective?
You could create a custom model binder that inherits from DefaultModelBinder and assign the model to TempData:
public class MyCustomerBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
controllerContext.Controller.TempData["model"] = bindingContext.Model;
}
}
and register it in Global.asax:
ModelBinders.Binders.DefaultBinder = new MyCustomerBinder();
then access it:
protected override void OnException(ExceptionContext filterContext)
{
var model = filterContext.Controller.TempData["model"];
...
}
Is it possible to skip the whole action method execution and return a specific ActionResult when a certain condition is met in OnActionExecuting?
You can use filterContext.Result for this. It should look like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Check your condition here
if (true)
{
//Create your result
filterContext.Result = new EmptyResult();
}
else
base.OnActionExecuting(filterContext);
}
See my download sample and MSDN article Filtering in ASP.NET MVC.
You can cancel filter execution in the OnActionExecuting and OnResultExecuting methods by setting the Result property to a non-null value.
Any pending OnActionExecuted and OnActionExecuting filters will not be invoked and the invoker will not call the OnActionExecuted method for the cancelled filter or for pending filters.
The OnActionExecuted filter for previously run filters will run. All of the OnResultExecuting and OnResultExecuted filters will run.
The following code from the sample shows how to return a specific ActionResult when a certain condition is met in OnActionExecuting:
if (filterContext.RouteData.Values.ContainsValue("Cancel"))
{
filterContext.Result = new RedirectResult("~/Home/Index");
Trace.WriteLine(" Redirecting from Simple filter to /Home/Index");
}
If anyone is extending ActionFilterAttribute in MVC 5 API, then you must be getting HttpActionContext instead of ActionExecutingContext as the type of parameter. In that case, simply set httpActionContext.Response to new HttpResponseMessage and you are good to go.
I was making a validation filter and here is how it looks like:
/// <summary>
/// Occurs before the action method is invoked.
/// This will validate the request
/// </summary>
/// <param name="actionContext">The http action context.</param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
ApiController ctrl = (actionContext.ControllerContext.Controller as ApiController);
if (!ctrl.ModelState.IsValid)
{
var s = ctrl.ModelState.Select(t => new { Field = t.Key, Errors = t.Value.Errors.Select(e => e.ErrorMessage) });
actionContext.Response = new System.Net.Http.HttpResponseMessage()
{
Content = new StringContent(JsonConvert.SerializeObject(s)),
ReasonPhrase = "Validation error",
StatusCode = (System.Net.HttpStatusCode)422
};
}
}
You can use the following code here.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
...
if (needToRedirect) //your condition here
{
...
filterContext.Result = new RedirectToAction(string action, string controller)
return;
}
...
}
RedirectToAction will redirect you the specific action based on the condition.
I archived this as follows:
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!condition)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{ { "controller", "YourController" },
{ "action", "YourAction" }
});
}
}
This will redirect you to the given action. Worked with MVC 4.
I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I'm looking for a way to enforce a controller's action to be accessed only via an AJAX request.
What is the best way to do this before the action method is called? I want to refactor the following from my action methods:
if(Request.IsAjaxRequest())
// Do something
else
// return an error of some sort
What I'm envisioning is an ActionMethodSelectorAttribute that can be used like the [AcceptVerbs] attribute. I have no experience crating such a custom attribute though.
Create an ActionFilter that fires OnActionExecuting
public class AjaxActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new RedirectResult(//path to error message);
}
}
Setting the filter's Result property will prevent execution of the ActionMethod.
You can then apply it as an attribute to your ActionMethods.
Its as simple as this:
public class AjaxOnly : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
return controllerContext.HttpContext.IsAjaxRequest();
}
}
I just forget where IsAjaxRequest() comes from, I'm pasting from code I have but "lost" that method. ;)
whats the best way to do a redirect (preferably a redirect to action) from within an ActionFilterAttribute?
I want to be able to pass data into the controller action from within the ActionFilterAttribute as well.
To redirect, override OnActionExecuting and assign a new RedirectToRouteResult to filterContext.Result:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "action", "newActionName" },
{ "actionArgument", someData } });
}
To assign data when redirecting, put it into the route, as shown above.