Changing filterContext.Result in OnResultExecuting - c#

I have a controller action that has an attribute applied in which if the ModelState has errors it sets them as the JsonResult in the OnResultExecuting method.
I set the value in MyAction. I change it in the attribute in OnResultExecuting but in the OnResultExecuted which is in the controller the result is the one from the controller not the one which was set in the attribute.
So my question is why does the value in OnResultExecuted remain unchanged and how do i make it stop doing that ?
public class MyController:Controller
{
[ValidateDatedObject(SkipActionExecution = true, LeaveJustModelState = true)]
public JsonResult MyAction(ViewModel viewModel)
{
return new JsonResult { Data = new { Success = false }}; // Setting the initial value
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);//filterContext.Result here is the on from the controller instead of the one from the attribute
}
}
public class ValidateDatedObject : ModelValidationFilter
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
}//filterContext.Result here is the one from the attribute
}
public abstract class ModelValidationFilter : ActionFilterAttribute
{
private JsonResult getModelStateAsJsonResult(ModelStateDictionary modelState)
{
return new JsonResult { Data = new { modelState = SerializeErrors(modelState) } };
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Result = getModelStateAsJsonResult(filterContext.Controller.ViewData.ModelState); //Setting filterContext.Result here
}
}

That's because in the OnResultExecuting you are replacing the current result with a new instance. That will modify the result in the ResultExecutingContext but will leave the overall result unchanged.
You could however modify the result instead of replacing it
public abstract class ModelValidationFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
//Modify the values in the current filterContext.Result instead of replacing it with a new instance
var jsonResult = filterContext.Result as JsonResult;
if(jsonResult == null) return;
//possibly replace Data only under certain conditions
jsonResult.Data = new { modelState = SerializeErrors(modelState) };
}
}
The reason for this is the way ResultFilters are executed by MVC. You can check the implementation of ControllerActionInvoker.InvokeActionResultFilterRecursive. This is the code calling OnResultExecuting on each filter, executing the action and then calling OnResultExecuted in reverse order.
If you look carefully, you will notice that ResultExecutedContext is created with a reference to the original actionResult object, not with a reference to ResultExecutingContext.Result. (Unless you set ResultExecutingContext.Cancel=true which will stop processing additional filters and return whatever result it has at that moment, but that also means controller OnResultExecute won't be executed)
So there is an assumption in this code in that ResultFilters may modify the values of the properties in ResultExecutingContext.Result but not entirely replace it with a new instance.

Related

Pass TempData to ActionFilter RedirectToAction

I want to set a ViewBag for second an action from the first action by using ActionFilter.
In the first Action i do the following :
TempData["Test"] = "Test";
return RedirectToAction("Action2", new { values = values });
Then in IActionFilter :
public class HelpertestActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var controller = context.Controller as Controller;
if (controller != null)
{
if (controller.TempData["Test"] != null)
{
controller.ViewBag.Notification = controller.TempData["Test"];
}
}
}
}
But in ActionFilter OnActionExecuting, TempData["Test"] is always null.
I have followed this article
After some try, there is no errors in my code, except in the startup configuration.
In Startup.Configure() the app.UseCookiePolicy() has to be after app.UseMVC() to work as expected.

How to ignore execution of method from custom attribute?

I need to do nothing when method executed from not ajax. I know how to detect ajax request, but I don't know what I should return just for ignore.
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = ???;
}
}
}
[AjaxOnly]
public async Task<ActionResult> AjaxOnlyMethod()
{
//This is method should be ignore if not ajax request
}
Try this i think you can redirect to some other action method like the one which returns an error view if the request is Ajax
like
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { action = "SomeOtherActionMethodName" }));

Check if action returned View or Redirect in ActionFitler

How do I check if an action returned View() or Redirect() in a custom ActionFilter? I need to check this as my ActionFilter populates the ViewBag with extra stuff. But if it's a redirect it is not needed.
Example
Controller Action
[MyActionFilter]
public IActionResult Index()
{
if (ModelState.IsValid())
return View();
else
return Redirect("foo");
}
Action Filter
public class MyActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (returned View)
context.Controller.ViewBag.Foo = "Bar";
else
// do other stuff
}
}
I figured it out.
Using an ResultFilter instead gave me access to the returned type. I also had to change from the after action to the before action as changing the result in the after action generally isn't allowed.
public class MyActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ViewResult)
context.Controller.ViewBag.Foo = "Bar";
else
// do other stuff
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
ActionExecutedContext has an ActionResult. You could check whether the ActionResult is a ViewResult or RedirectResult/RedirectToRouteResult in your OnActionExecuted.

Force redirect from attribute in ASP.NET MVC before reach a section from controller in ASP.NET MVC

Might be a simple question. Please let me show you what is my problem.
I have a custom attribute like
public class MyCustomAttribute: ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext filterContext )
{
if(somethingTrue) {
filterContext.Result = new RedirectToRouteResult ( ... );
}
}
}
and my controller class
[MyCustom]
public class ContactController: Controller
{
protected override void OnResultExecuting( ResultExecutingContext filterContext )
{
// so something
}
}
If I put breakpoint to OnResultExecuting method, it is reached even I put an attribute in top of controller class.
I expected that won't reach OnResultExecuting method from controller because I create a redirection result.
Or is my problem that I don't understand correctly the attribute ?
Well i guess you want to skip OnResultExecuting so i would prefer to write below code :-
public override void OnActionExecuting( ActionExecutingContext filterContext)
{
if (true)
{
//Create your result
filterContext.Result = new EmptyResult();
}
else
base.OnActionExecuting(filterContext);
}

Redirect From Action Filter Attribute

What is the best way to do a redirect in an ActionFilterAttribute. I have an ActionFilterAttribute called IsAuthenticatedAttributeFilter and that checked the value of a session variable. If the variable is false, I want the application to redirect to the login page. I would prefer to redirect using the route name SystemLogin however any redirect method at this point would be fine.
Set filterContext.Result
With the route name:
filterContext.Result = new RedirectToRouteResult("SystemLogin", routeValues);
You can also do something like:
filterContext.Result = new ViewResult
{
ViewName = SharedViews.SessionLost,
ViewData = filterContext.Controller.ViewData
};
If you want to use RedirectToAction:
You could make a public RedirectToAction method on your controller (preferably on its base controller) that simply calls the protected RedirectToAction from System.Web.Mvc.Controller. Adding this method allows for a public call to your RedirectToAction from the filter.
public new RedirectToRouteResult RedirectToAction(string action, string controller)
{
return base.RedirectToAction(action, controller);
}
Then your filter would look something like:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = (SomeControllerBase) filterContext.Controller;
filterContext.Result = controller.RedirectToAction("index", "home");
}
Alternatively to a redirect, if it is calling your own code, you could use this:
actionContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Error" })
);
actionContext.Result.ExecuteResult(actionContext.Controller.ControllerContext);
It is not a pure redirect but gives a similar result without unnecessary overhead.
I am using MVC4, I used following approach to redirect a custom html screen upon authorization breach.
Extend AuthorizeAttribute say CutomAuthorizer
override the OnAuthorization and HandleUnauthorizedRequest
Register the CustomAuthorizer in the RegisterGlobalFilters.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomAuthorizer());
}
upon identifying the unAuthorized access call HandleUnauthorizedRequestand redirect to the concerned controller action as shown below.
public class CustomAuthorizer : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = IsAuthorized(filterContext); // check authorization
base.OnAuthorization(filterContext);
if (!isAuthorized && !filterContext.ActionDescriptor.ActionName.Equals("Unauthorized", StringComparison.InvariantCultureIgnoreCase)
&& !filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.Equals("LogOn", StringComparison.InvariantCultureIgnoreCase))
{
HandleUnauthorizedRequest(filterContext);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result =
new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "LogOn" },
{ "action", "Unauthorized" }
});
}
}
It sounds like you want to re-implement, or possibly extend, AuthorizeAttribute. If so, you should make sure that you inherit that, and not ActionFilterAttribute, in order to let ASP.NET MVC do more of the work for you.
Also, you want to make sure that you authorize before you do any of the real work in the action method - otherwise, the only difference between logged in and not will be what page you see when the work is done.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Do whatever checking you need here
// If you want the base check as well (against users/roles) call
base.OnAuthorization(filterContext);
}
}
There is a good question with an answer with more details here on SO.
Try the following snippet, it should be pretty clear:
public class AuthorizeActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(FilterExecutingContext filterContext)
{
HttpSessionStateBase session = filterContext.HttpContext.Session;
Controller controller = filterContext.Controller as Controller;
if (controller != null)
{
if (session["Login"] == null)
{
filterContext.Cancel = true;
controller.HttpContext.Response.Redirect("./Login");
}
}
base.OnActionExecuting(filterContext);
}
}
Here is a solution that also takes in account if you are using Ajax requests.
using System;
using System.Web.Mvc;
using System.Web.Routing;
namespace YourNamespace{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustom : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
if (YourAuthorizationCheckGoesHere) {
string area = "";// leave empty if not using area's
string controller = "ControllerName";
string action = "ActionName";
var urlHelper = new UrlHelper(context.RequestContext);
if (context.HttpContext.Request.IsAjaxRequest()){ // Check if Ajax
if(area == string.Empty)
context.HttpContext.Response.Write($"<script>window.location.reload('{urlHelper.Content(System.IO.Path.Combine(controller, action))}');</script>");
else
context.HttpContext.Response.Write($"<script>window.location.reload('{urlHelper.Content(System.IO.Path.Combine(area, controller, action))}');</script>");
} else // Non Ajax Request
context.Result = new RedirectToRouteResult(new RouteValueDictionary( new{ area, controller, action }));
}
base.OnActionExecuting(context);
}
}
}
This works for me (asp.net core 2.1)
using JustRide.Web.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyProject.Web.Filters
{
public class IsAuthenticatedAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
context.Result = new RedirectToActionResult(nameof(AccountController.Index), "Account", null);
}
}
}
[AllowAnonymous, IsAuthenticated]
public IActionResult Index()
{
return View();
}
you could inherit your controller then use it inside your action filter
inside your ActionFilterAttribute class:
if( filterContext.Controller is MyController )
if(filterContext.HttpContext.Session["login"] == null)
(filterContext.Controller as MyController).RedirectToAction("Login");
inside your base controller:
public class MyController : Controller
{
public void RedirectToAction(string actionName) {
base.RedirectToAction(actionName);
}
}
Cons. of this is to change all controllers to inherit from "MyController" class

Categories

Resources