Can someone clarify for me this situation:
We make request for example Home\Index;
In Global asax we have Application_AuthorizeRequest
Application_AuthorizeRequest throw exception
We have Application_Error which catch it and return new View
IController controller = new ErrorController();
//routedata is ok
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Action with Error is executed (it's OK)
But then MVC or ASP pipeline still try to execute Home\Index, how can I make pipeline forget about request?
As far as I understand mvc it's HttpHandler, how can I make sure that my action with error is a last step in all this chain?
There's a problem with this setup. If you want to prevent the Index action from being called you should write a custom Authorize attribute instead of using the Authenticate_Request event:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// perform the logic you were doing in your Authenticate_Request
// here to authorize the user. You could throw exceptions as well
throw new Exception("ok");
}
}
Authorization filters replace the Authenticate_Request method in ASP.NET MVC applications and that's what you should be using.
and then decorate your Index action with this attribute:
public class HomeController: Controller
{
[MyAuthorize]
public ActionResult Index()
{
...
}
}
Now your Application_Error will be called, the error controller executed and the Index action never triggered, exactly as it should be.
Related
I'm using MVC5 with the routing attribute decoration on my controller actions. I would like to use the Controller.Execute command inside the Application_Error function to transfer the call to mvc but i'm getting the exception "A public action method 'HttpError404' was not found on controller 'ErrorsController'.".
If i remove the RouteAttribute decoration, everything is working but i do need the RouteAttribute decoration. Why is it happening and how can i solve this problem considering that i don't want to use the HttpContext.Current.Server.TransferRequest trick?
ErrorsController
[RoutePrefix("Errors")]
public class ErrorsController : Controller
{
[Route("HttpError404")]
public ActionResult HttpError404()
{
return View();
}
}
Application_Error
protected void Application_Error(object sender, EventArgs e)
{
var routeData = new RouteData();
routeData.Values.Add("controller", "errors");
routeData.Values.Add("action", "HttpError404");
var requestContext = new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = controllerFactory.CreateController(requestContext, "errors");
try
{
controller.Execute(requestContext);
}
catch (Exception ex)
{
//A public action method 'HttpError404' was not found on controller 'ErrorsController'.
}
}
I reproduced your issue and got this error output log:
System.Web.HttpException was caught
HResult=-2147467259 (0x80004005)
Message=A public action method 'HttpError404' was not found on controller
'[ApplicationName].Controllers.ErrorsController'.
Source=System.Web.Mvc
ErrorCode=-2147467259
WebEventCode=0
StackTrace:
at System.Web.Mvc.Controller.HandleUnknownAction(String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext
requestContext)
at [ApplicationName].MvcApplication.Application_Error(Object sender, EventArgs e) in
[ProjectPath]\[ApplicationName]\[ApplicationName]\Global.asax.cs:line XX
By seeing exception details above, the exception originated from System.Web.Mvc.Controller.HandleUnknownAction method which by default looks like this (taken from aspnetwebstack reference):
protected virtual void HandleUnknownAction(string actionName)
{
// If this is a direct route we might not yet have an action name
if (String.IsNullOrEmpty(actionName))
{
throw new HttpException(404, String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UnknownAction_NoActionName, GetType().FullName));
}
else
{
throw new HttpException(404, String.Format(CultureInfo.CurrentCulture, MvcResources.Controller_UnknownAction, actionName, GetType().FullName));
}
}
As a workaround, try overriding that method by returning view from corresponding action method (using same name as action method name) as given in example below:
public class ErrorsController : Controller
{
// put error action methods here
protected override void HandleUnknownAction(string actionName)
{
// returns view from given action name
// better to use try-catch block just in case the view name is not exist
this.View(actionName).ExecuteResult(this.ControllerContext);
}
}
Note 1: Use if-condition or switch...case block if you have different view names against action method names.
Note 2: It is possible that standard routing messed up while RouteAttribute is enabled and calling HandleUnknownAction by unknown reason, I recommend to remove attribute routing if corresponding action method called with IController.Execute.
References:
MVC 5 Attribute Routing not Working with Controller Execute
Handling Unknown Actions in ASP.NET MVC
In my controller, for lets say edit user. In my controller, I check if the user has rights to edit then I'd like to throw some kind of authentication or prohibited error which would lead to an error page.
Is there some way to do this rather than creating a controller and action just for error? What is the correct way to do this?
Here's an example of a custom authorize attribute you could use:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// if the user is not authenticated render the AccessDenied view
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
and then decorate your controller action with this attribute:
[CustomAuthorizeAttribute]
public ActionResult SomeAction()
{
...
}
There's one caveat with this approach you should be aware of. If the user is not authorized the server sends 200 status code which is not very SEO friendly. It would be better to send 401 status code. The problem is that if you are using Forms Authentication there's a custom module that gets appended to the ASP.NET execution pipeline and whenever the server sends 401 status code it is intercepted and automatically redirected to the login page. This functionality is by design and is not bug in ASP.NET MVC. It has always been like this.
And as a matter of fact there is a way to workaround this unpleasant situation:
You could modify the custom authorization filter like so:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// if the user is not authenticated render the AccessDenied view
filterContext.HttpContext.Items["unauthorized"] = true;
}
}
}
and in Global.asax:
protected void Application_EndRequest()
{
if (Context.Items.Contains("unauthorized"))
{
Context.Response.Clear();
Context.Response.StatusCode = 401;
Context.Server.Transfer("~/401.htm");
}
}
Now that's better. You get 401 status code with a custom error page. Nice.
Since your authorization is based per user (I suppose the correct process is each user can only edit their own data) you can't use provided Authorize filter.
Write a custom authorization filter instead. You can provide whatever functionality you'd like. The usual is to return a 401 HTTP status code.
I would like to know how am I able to redirect the request inside the controller constructor if I need to do it?.
For example:
Inside the constructor I need to initialize an object with an dynamic value, in some cases I don't want to do it and in that case I want to redirect to some other place.
At the same way the rest of the constructor will not be executed neither the "original following action".
How can I do it?
Thank you
EDIT #1
Initially I used:
public override void OnActionExecuting(ActionExecutingContext filterContext)
There I could redirect to some other controller/action/url, but later in time, I needed to change my controller, where I initialize an variable in his constructor and have some code that really needs to redirect the request :P
I need this also because the OnActionExecuting executes AFTER the controller constructor.
And in my logic, the redirect needs to be done there.
Performing redirects inside the controller constructor is not a good practice because the context might not be initialized. The standard practice is to write a custom action attribute and override the OnActionExecuting method and perform the redirect inside. Example:
public class RedirectingActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (someConditionIsMet)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "someOther",
action = "someAction"
}));
}
}
}
and then decorate the controller which you would like to redirect with this attribute. Be extremely careful not to decorate the controller you are redirecting to with this attribute or you are going to run into an endless loop.
So you could:
[RedirectingAction]
public class HomeController : Controller
{
public ActionResult Index()
{
// This action is never going to execute if the
// redirecting condition is met
return View();
}
}
public class SomeOtherController : Controller
{
public ActionResult SomeAction()
{
return View();
}
}
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. ;)
I need to globally redirect my users if a custom error is thrown in my application. I have tried putting some logic into my global.asax file to search for my custom error and if it's thrown, perform a redirect, but my application never hits my global.asax method. It keeps giving me an error that says my exception was unhandled by user code.
here's what I have in my global.
protected void Application_Error(object sender, EventArgs e)
{
if (HttpContext.Current != null)
{
Exception ex = HttpContext.Current.Server.GetLastError();
if (ex is MyCustomException)
{
// do stuff
}
}
}
and my exception is thrown as follows:
if(false)
throw new MyCustomException("Test from here");
when I put that into a try catch from within the file throwing the exception, my Application_Error method never gets reached. Anyone have some suggestions as to how I can handle this globally (deal with my custom exception)?
thanks.
1/15/2010 edit:
Here is what is in // do stuff.
RequestContext rc = new RequestContext(filterContext.HttpContext, filterContext.RouteData);
string url = RouteTable.Routes.GetVirtualPath(rc, new RouteValueDictionary(new { Controller = "Home", action = "Index" })).VirtualPath;
filterContext.HttpContext.Response.Redirect(url, true);
You want to create a customer filter for your controllers / actions. You'll need to inherit from FilterAttribute and IExceptionFilter.
Something like this:
public class CustomExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == typeof(MyCustomException))
{
//Do stuff
//You'll probably want to change the
//value of 'filterContext.Result'
filterContext.ExceptionHandled = true;
}
}
}
Once you've created it, you can then apply the attribute to a BaseController that all your other controllers inherit from to make it site wide functionality.
These two articles could help:
Filters in ASP.NET MVC - Phil Haack
Understanding Action Filters
I found this answer (and question) to be helpful Asp.net mvc override OnException in base controller keeps propogating to Application_Error
Im your case the thing that you're missing is that you need to add your custom filter to the FilterConfig.cs in your App_Start folder:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomExceptionFilter());
filters.Add(new HandleErrorAttribute());
}