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
Related
I've some "bad boys" users which call my website using controller that doesn't exist.
Such as www.myapp.com/.svn/, or www.myapp.com/wp-admin/.
The app basically throw a System.Web.HttpException, which this ExceptionMessage: The controller for path '...' was not found or does not implement IController.
Is there a way to exactly catch this "sub" System.Web.HttpException in a robust way? (not comparing the string of ExceptionMessage, of course).
Any sub InnerException type and/or some "code" which indicate that exact exception?
You might create a custom IControllerFactory, e.g. by deriving from DefaultControllerFactory.
Its GetControllerInstance method will be called with a null value for the controllerType argument, when no matching controller could be resolved.
At this point, you are ahead of the Exception.
IController GetControllerInstance(RequestContext requestContext, Type controllerType)
Here you decide how to handle the request, like e.g. logging and/or handling the request by a specific controller.
The example below shows how the ErrorController is being used as fallback, executing its Index action method.
class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
var routeData = requestContext.RouteData;
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Index";
controllerType = typeof(ErrorController);
}
return base.GetControllerInstance(requestContext, controllerType);
}
}
A custom controller factory gets installed from within e.g. Application_Start in Global.asax.cs using:
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
Note that this doesn't guard you from a related exception in case the contoller does exist, but the action doesn't.
E.g. given a HomeController without an Ufo action method, the url www.myapp.com/home/ufo results in an Exception "A public action method 'ufo' was not found on controller '...HomeController".
A simple solution to handle such scenario is by overriding HandleUnknownAction in a custom base controller of which all your controllers inherit.
The example below shows how a shared Error view (Shared\Error.cshtml) gets executed.
public abstract class BaseController : Controller
{
protected override void HandleUnknownAction(string actionName)
{
View("Error").ExecuteResult(ControllerContext);
}
}
The problem as you have stated seems like a routing problem.
Have you tried a catch all route?... That is a route that is executed when no other route matches the request's path. The catch all route is defined last and seems like:
// Specific routes here...
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Catch-All",
"{*controller}",
new { controller = "Home", action = "Index" }
);
I tested with .NET Core MVC
// This is how it is configured in .NET Core MMVC
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "catch-all",
pattern: "{*controller}",
defaults: new { controller="Home", action="Index" });
});
I think that managing the excepion, as sugested in previous answers, is a good practice, but does not solve the routing problem you are facing.
You might want to take a look to this discussion regarding similiar problem with ASP.NET MVC:
ASP.NET MVC - Catch All Route And Default Route
Like #Amirhossein mentioned, since you are using .net 4.6 framework you can have a global exception catch inside the
Global.asax file something like:
protected void Application_Error(object sender, EventArgs e)
{
var ex = Server.GetLastError();
Debug.WriteLine(ex);
var httpEx = ex as HttpException;
var statusCode = httpEx?.GetHttpCode() ?? 500;
Server.ClearError();
...
}
or you can address this issue via IIS (since I presume you host your application on IIS) by adding a mapping rule (which in turn can be done in the web.config) or by adding a filter, or even a firewall rule (since by my account those kind of probing scans are not worth to be logged in your application - I believe they should be handled on another level)
I try to redirect from function BeginExecuteCore to another controller
all My controller inheritance the function BeginExecuteCore and I want do some logic if something happen so redirect to "XController"
How to do it?
EDIT:
Balde:
I use function BeginExecuteCore I can't use Controller.RedirectToAction
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
//logic if true Redirect to Home else .......
return base.BeginExecuteCore(callback, state);
}
The Balde's solution is working but is not optimal.
Let's take an example :
public class HomeController : Controller
{
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
Response.Redirect("http://www.google.com");
return base.BeginExecuteCore(callback, state);
}
// GET: Test
public ActionResult Index()
{
// Put a breakpoint under this line
return View();
}
}
If you run this project you will obviously get the Google main page. But if you look at your IDE you'll note that the code is waiting for you due to the breakpoint.
Why ? Because you redirected the response but didn't stop the flow of ASP.NET MVC and so, it continues the process (by calling the action).
It's not a big issue for a small website but if you forecast to have a lot of visitors, this can become a serious performance issue : potentially thousand of requests per second that run for nothing because the response is already gone.
How can you avoid that ? I have a solution (not a pretty one but it does the job) :
public class HomeController : Controller
{
public ActionResult BeginExecuteCoreActionResult { get; set; }
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
this.BeginExecuteCoreActionResult = this.Redirect("http://www.google.com");
// or : this.BeginExecuteCoreActionResult = new RedirectResult("http://www.google.com");
return base.BeginExecuteCore(callback, state);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = this.BeginExecuteCoreActionResult;
base.OnActionExecuting(filterContext);
}
// GET: Test
public ActionResult Index()
{
// Put a breakpoint under this line
return View();
}
}
You store your redirect results inside a controller member and you execute it when the OnActionExecuting is running !
Redirect from Response:
Response.Redirect(Url.RouteUrl(new{ controller="controller", action="action"}));
Im try write this and success:
Response.RedirectToRoute(new { controller = "Account", action = "Login", Params= true });
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.
I want to override the HandleErrorAttribute with a new version called something like HandleErrorsWithLogging. Essentially, I want it to log the unhandled exception to my log file and then proceed with the typical 'Redirect to ~/Shared/Error' functionality you get with HandleError.
I am already adding HandleError to all my actions in the global.asax
Is this the best approach or is there some easier way to gain access to that unhandled exception for logging purposes? I had also though about an Ajax call on the Error view itself.
You can create a custom filter that inherits from FilterAttribute and implements IExceptionFilter. Then register it in global.asax.cs. Also you must enable custom errors handling in the web.config:
<customErrors mode="On"/>
public class HandleErrorAndLogExceptionAttribute : FilterAttribute, IExceptionFilter
{
/// <summary>
/// The method called when an exception happens
/// </summary>
/// <param name="filterContext">The exception context</param>
public void OnException(ExceptionContext filterContext)
{
if (filterContext != null && filterContext.HttpContext != null)
{
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
// Log and email the exception. This is using Log4net as logging tool
Logger.LogError("There was an error", filterContext.Exception);
string controllerName = (string)filterContext.RouteData.Values["controller"];
string actionName = (string)filterContext.RouteData.Values["action"];
HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
// Set the error view to be shown
ViewResult result = new ViewResult
{
ViewName = "Error",
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
result.ViewData["Description"] = filterContext.Controller.ViewBag.Description;
filterContext.Result = result;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
}
}
Overriding the HandleError attribute is indeed one approach into handling this. There are other approaches as well. Another approach is to use ELMAH which will take care of this so that you shouldn't worry about logging. Yet another approach consists in removing any HandleError global filters from Global.asax and subscribe for the Application_Error which is more general than HandleError as it will intercept also exceptions that happen outside of the MVC pipeline. Here's an example of such handling.
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());
}