I'm trying to handle eventual errors in my view, by using the HandleError attribute on my view:
The reason why the Action is called 'Error' is because it gets a list of logged errors from a database.
[HandleError]
public ActionResult Error(int? page)
{
var errors = errorRepository.GetErrors();
// stuff for paging
var pageSize = 10;
var pageNumber = (page ?? 1); // if there is no page, return page 1
return View("Error", errors.ToPagedList(pageNumber, pageSize));
}
This is the error page in the /Shared/ folder:
#model System.Web.Mvc.HandleErrorInfo
#{
ViewBag.Title = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
But for some reason, the error page is never being shown, even though I've forced an exception in the action method. It just goes to the default url in my RouteConfig file.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Any hint as to why it doesn't show my error page is greatly appreciated!
I am sorry I have to add this as answer, but I don't have enough points to comment.
To be able to help you I need to see the code within the HandleErrorAttribute. However what you normally want to do in these cases is:
1) Add a config setting in the web.config to say that you will handle the exceptions on your own. Something like:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="500" redirect="~/Error/InternalServer" />
<error statusCode="404" redirect="~/Error/NotFound" />
</customErrors>
</system.web>
2) Add the methods to accept those incoming calls in the ErrorController (In this case Index(), InternalServer(), NotFound())
3) Get the logs from your database and display them to the user than
Related
I have a custom Errors controller that looks like this:
public class ErrorsController : BaseController
{
public ActionResult RaiseError(string error = null)
{
string msg = error ?? "An error has been thrown (intentionally).";
throw new Exception(msg);
}
public ActionResult Error404()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult Error500()
{
Response.TrySkipIisCustomErrors = true;
var model = new Models.Errors.Error500()
{
ServerException = Server.GetLastError(),
HTTPStatusCode = Response.StatusCode
};
return View(model);
}
}
My Errors500.cshtml looks like this:
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Error500</title>
</head>
<body>
<div>
An internal error has occurred.
#if (Model != null && Model.ServerException != null && HttpContext.Current.IsDebuggingEnabled)
{
<div>
<p>
<b>Exception:</b> #Model.ServerException.Message<br />
</p>
<div style="overflow:scroll">
<pre>
#Model.ServerException.StackTrace
</pre>
</div>
</div>
}
</div>
</body>
</html>
and my web.config has my error handlers specified as such:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace" >
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" subStatusCode="-1" responseMode="ExecuteURL" path="/Errors/Error404" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" subStatusCode="-1" responseMode="ExecuteURL" path="/Errors/Error500" />
</httpErrors>
The problem is: everytime I call /errors/raiseerror to test my 500 handling; I'm redirected to errors/error500 (fine). However, the exception data isn't rendered on the page because the Server.GetLastError() call returns null instead of the exception thrown by RaiseError().
What's the best way to handle a custom 500 error page where that custom page can render out the exception details as well?
The easiest way to go about this is:
Use MVC's built-in support to handle Exceptions. By default MVC uses HandleErrorAttribute that is registered in App_Start\FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
Now make sure you have a view called Error in Views\Shared folder. The view by default has model of type HandleErrorInfo with a property named Exception. You can show the Exception message and other details if you want like this:
Error.cshtml
#model HandleErrorInfo
#if(Model != null)
{
#Model.Exception.Message
}
You can customize the Error.cshtml page the way you want...
I have an application that shows locations on a map. I have created a route so that I can have nice hackable URLs, like http://www.mydomain.com/paris. This works fine just typing in the URL, but I have a search form on the home page that sends a GET request. When the form is submitted, the URL displayed in the location bar is in the format http://www.mydomain.com/Dashboard?location=paris. Normally this wouldn't matter too much as it's hitting the correct action, but I have a backbone.js application running the show and it's particular about the URL structure.
It may be impossible to do what I need without javascript or a redirect, because the location isn't known when the form ACTION attribute is populated - but can anyone confirm?
Here are my routes.
public static void RegisterRoutes( RouteCollection routes )
{
routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
routes.MapRoute(
String.Empty,
"{location}",
new {
controller = "Dashboard",
action = "Index",
id = ""
}
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
} // Parameter defaults
);
}
Here is the controller.
public class DashboardController : Controller
{
[HttpGet]
public ViewResult Index(string location)
{
return View(new AccItemSearch { Location = location });
}
}
Here is the form.
#using (Html.BeginForm("Index", "Dashboard", FormMethod.Get)) {
<h2>Where are you going?</h2>
<input type="text" placeholder="Area, town or postcode" id="location" name="location"/>
<button>Search!</button>
</div>
}
To clarify: the problem I want help with is how to have the user submit the form then land on a page with the URL: http://www.mydomain.com/searchterm and thus match the route, rather than on a page that with the URL http://www.mydomain.com/Dashboard
You will not be able to change the action attribute of the form during HTML generation (i.e. server side) as you simply don't know what it should point to. So if you need the URL to end up being the exact search term the easiest bet is probably to change the action attribute to it with JavaScript before the form is submitted, and have a controller that catches all urls that follow the www.domain.com/searchterm pattern.
You can't really redirect to a specific action because then that would become the URL returned to the browser, and I doubt you want one action per search term.
HTML:
<form method="post" id="myform">
<input type="text" id="searchterm" />
<input type="submit" value="Search" />
</form>
jQuery:
$(function () {
$("#myform").submit(function () {
var searchVal = $("#searchterm").val();
$(this).attr("action", searchVal);
});
});
Route:
routes.MapRoute(
"",
"{searchterm}",
new { controller = "Home", action = "Search" }
);
Note that this has to be put before the default route(s).
Action:
public ActionResult Search(string searchterm)
{
//do stuff
}
Now if a visitor enters the term "Alaska" and submits the search form, they will end up on domain.com/Alaska.
That form should be a POST to submit the form data.
The search should be a submit button
<input type="submit" name="Search" value="Search" />
Otherwise it looks good, unless you have a conflicting route. Tested your route in isolation and it seems fine. It's just that location is not being sent.
I have a error page with layout that works fine in most cases but when there is an error in a controller that returns a partial view the error page and its layout is placed in the partial view. I guess thats logical but I want the error page to be loaded as full page. How do I accomplish that without changing all error handling.
web.config:
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="500" redirect="~/SystemPages/ErrorPage" />
<error statusCode="403" redirect="~/SystemPages/FileNotFound" />
<error statusCode="404" redirect="~/SystemPages/FileNotFound" />
</customErrors>
Global.asax:
Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
filters.Add(New HandleErrorAttribute())
End Sub
BaseController:
Protected Overrides Sub OnException(ByVal filterContext As ExceptionContext)
If filterContext Is Nothing Then Return
If TypeOf (filterContext.Exception) Is FaultException Then
Dim CodeName As String =
CType(filterContext.Exception, FaultException).Code.Name
Dim Message As String = CType(filterContext.Exception, FaultException).Message
TempData("ErrorMessage") = Message
Else
Logging.LogDebugData(HamtaDebugInformation(filterContext.RouteData))
Logging.WriteExceptionLog(filterContext.Exception)
TempData("ErrorMessage") = filterContext.Exception.Message
End If
Response.Redirect("/SystemPages/ErrorPage")
End Sub
SearchController:
Function GetData() As ActionResult
...
Return PartialView("_Tab", vmData)
ErrorPage:
#Code
ViewData("Title") = "ErrorPage"
Layout = "~/Views/Shared/_Layout.vbhtml"
End Code
<div id="mainContent" class="oneColumn">
<div class="panel">
<span class="panelTLC"></span>
<span class="panelTRC"></span>
<div id="inputPanel" class="panelContent">
<div class="modul">
<div class="modulHead">
<span class="TLC"></span>
<span class="TRC"></span>
</div>
<div class="modulContent">
<span class="TLC"></span><span class="TRC"></span>
<p>#ViewBag.ErrorMessage</p>
<p>#TempData("ErrorMessage")</p>
<span class="BLC"></span>
<span class="BRC"></span>
</div>
</div>
</div>
<span class="panelBLC"></span><span class="panelBRC"></span>
</div>
</div>
You could just use a try catch block and in the catch return a View() instead of PartialView().
Function GetData() As ActionResult
Try
...
Return PartialView("_Tab", vmData)
Catch ex as Exception
//Handle exception here ( send to error log, etc)
Return View("~/SystemPages/ErrorPage")
End Try
OR
web.config:
<customErrors mode="On"/>
BaseController:
Protected Overrides Sub OnException(ByVal filterContext As ExceptionContext)
If filterContext Is Nothing Then Return
Dim Message As String
If TypeOf (filterContext.Exception) Is FaultException Then
Dim CodeName As String =
CType(filterContext.Exception, FaultException).Code.Name
Message = CType(filterContext.Exception, FaultException).Message
Else
Logging.LogDebugData(HamtaDebugInformation(filterContext.RouteData))
Logging.WriteExceptionLog(filterContext.Exception)
Message = filterContext.Exception.Message
End If
Response.Redirect(String.Format("~/Error/HttpError/?message={1}", "HttpError", Message))
End Sub
ErrorController:
public class ErrorController : Controller
{
// GET: /Error/HttpError
public ActionResult HttpError(string message) {
return View("ErrorTest", message);
}
This post: ASP.NET MVC Custom Error Handling Application_Error Global.asax?
goes into how to handle each type of error separately. Keep in mind you are handling your exceptions in basecontroller instead of the global.asax file. Which if you were able to change your exception handling, that would be the better way to do it.
Given the following route:
context.MapRoute(null, "widgets",
new { controller = "Widgets", action = "Add" },
new { httpMethod = new HttpMethodConstraint("PUT") });
And the following controller:
public class WidgetsController
{
[HttpPut]
public ActionResult Add(WidgetForm model)
{
return DoStuff(); // code here doesn't matter
}
}
And a view that renders the following form (using HtmlHelper.#Html.HttpMethodOverride(HttpVerbs.Put):
<form action="/widgets" method="post">
<!-- many form elements, then -->
<input name="X-HTTP-Method-Override" type="hidden" value="PUT" />
</form>
When the form is submitted, the MVC action method selector does not choose the above action method. If I set a breakpoint on the opening brace, it is never hit. In browser, it returns 404 page (I believe this is the default ActionNotFound behavior).
However, the action method selector does choose the Add HttpPut method with the following route:
context.MapRoute(null, "widgets",
new { controller = "Widgets", action = "Add" },
new { httpMethod = new HttpMethodConstraint("PUT", "POST") });
This doesn't seem right... is it? It seems to me that I should be able to do this without a POST constraint. The action method is not decorated with HttpPost, so why should the POST constraint be necessary?
Its right. Looking a bit deeper into how this works in the MVC pipeline it's actually MVC (ActionMethodSelectorAttribute, ActionInvoker,RedirectToRoute) that handles this and not the RouteModule.
So in route module, it's still a "POST" request, not a "PUT".
I'm trying to use route constraints in an Asp.Net MVC Application.
routes.MapRoute(
"theRoute",
"MyAction/{page}",
new { controller = "TheController", action = "MyAction", page = 1 },
new { page = #"[0-9]" });
When I enter an url like ~/MyAction/aString, an YSOD is shown with an invalid operation exception. What can I do to redirect invalid url to the 404 page?
I know I can solve the issue with a string parameter in the controller action and int.TryParse, but then the route constaint is useless.
How can I choose the exceptiontype that is thrown by the route constraints?
The problem is that you do not have a route that matches the route that ends in a string.
Modify your routes similar to:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = 0 },
new { id = "[0-9]" }// Parameter defaults
);
routes.MapRoute(
"Default2", // Route name
"{controller}/{action2}/{sid}", // URL with parameters
new { controller = "Home", action = "Index2", sid = "" } // Parameter defaults
);
and modify your controller
public ActionResult Index(int id)
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC! Your id is: "+ id.ToString();
return View();
}
public ActionResult Index2(string sid)
{
ViewData["Title"] = "Home Page 2."+sid.ToString();
ViewData["Message"] = "Welcome to ASP.NET MVC! \"" + sid.ToString() +"\" is an invalid id";
return View("index");
}
now when you pass a string for the ID, Index2 will be called and you can do whatever you need to do to handle the incorrect parameter.
Just to mention a more general redirection:
You can write in the Web.config of your application:
<system.web>
...
...
<customErrors mode="On">
<error
statusCode="404"
redirect="/Home/MyCustomError" />
<!-- Is not necessary that the
view MyCustomError.aspx are inside the
Home folder, you can put that
view in the Shared folder.
-->
</customErrors>
...
...
</system.web>
Then you need to have an ActionResult called MyCustomError
public class HomeController : Controller
{
...
...
public ActionResult MyCustomError(string aspxerrorpath)
/* the var aspxerrorpath
* is that MVC generated by
* default */
{
ViewData["messageError"] = aspxerrorpath;
return View();
}
}
Then you can make a custom error page:
<%# Page Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>
<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error
</asp:Content>
<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>Shit happends</h2>
<p> <%: ViewData["messageError"]%></p>
<p>aaaaaaaaaaaaaaa!!!!!!!!!!!!!!!!!!!!</p>
</asp:Content>