Best approach for 404 error handling in MVC - asp.net-mvc

I am trying to handle 404 page not found exception in mvc and i just want to know the right approach to handle page not found. this is how i handle the 404 page not found exception
protected void Application_Error()
{
ShowCustomErrorPage(Server.GetLastError());
}
private void ShowCustomErrorPage(Exception exception)
{
var httpException = exception as HttpException;
if (httpException.GetHttpCode() == 404)
{
Response.Redirect("PagenotFound");
}
}
is this the right approach !!!

Related

How to redirect to a controller action from app.UseExceptionHandler in ASP.NET MVC 4.5

From reading UseExceptionHandler vs UseStatusCodePagesWithRedirects I've come to understand UseExceptionHandler is used to gracefully handle thrown exceptions in a dotnet core application while the latter handles non-successful status code responses.
The requirements I have to implement are:
If a thrown exception is FileNotFoundException return the custom 404 page.
If a thrown exception is anything else return the custom 505 page.
I have an ErrorController with an Error action method that is responsible for returning the appropriate error page. It looks like this:
public IActionResult Error(int statusCode = 500)
{
if(statusCode == 404)
{
// return 404 page
}
// default return 505
}
In my Startup.cs Configure method I'm using both UseExceptionHandler and UseStatusCodePagesWithReExecute:
app.UseExceptionHandler( new ExceptionHandlerOptions {
ExceptionHandler = async context => {
var exceptionHandlerPathFeature = context.Features.Get <IExceptionHandlerPathFeature>();
var statusCode = exceptionHandlerPathFeature?.Error is FileNotFoundException ? 404 : 500;
}
});
app.UseStatusCodePagesWithReExecute("/Error/Error", "?statusCode={0}");
I need to somehow trigger the /Error/Error method with the statusCode from inside the ExceptionHandler. I thought I might be able to simply set the response status code:
context.Response.StatusCode = exceptionHandlerPathFeature?.Error is FileNotFoundException ? 404 : 500;
But this didn't work. How can I solve this problem?

Automatically throw 404 exception from action method if entity object is null

For example, we have simple action method to show any book with proper id:
public ActionResult GetBook(int id) // id = 123456789
{
var book = dataManager.Books.GetBookById(id); // == null
logger.Info("Getting book with id " + book.Id);
return View(book);
}
If id parameter is not valid we get 500 error because there is no book with that id.
We have to handle this situation manually if we need to throw 404 error, like this:
if (book == null)
{
return HttpNotFound();
}
Is it possible to switch 500-error to 404-error for all action methods somewhere in web-application (custom filter, request pipeline)?
Technically you could, by installing an error handler, rewrite any 500 Internal Server Error to a 404 Not Found in Application_Error() as explained in ASP.NET MVC 5 error handling.
However, you seem to want to let your code throw a NullReferenceException on a null book in book.Id, and turn that exception into a 404.
You really shouldn't be doing that in this case, because this will hide programming errors, because you will miss the exceptions where this happened unintentionally.
So: just do this explicitly where you do expect a null. The code you showed is exactly what you need:
if (book == null)
{
return HttpNotFound();
}
I found this helpful Bulk 301 Redirect
I test then throw a 302
try
{
------
}
catch
{
throw new HttpException(302, "not found");
}
Catch the exception in the Global.asax and look up a redirect csv file
protected void Application_Error()
{
Exception exception = Server.GetLastError();
Response.Clear();
Server.ClearError();
HttpException ex = exception as HttpException;
if (ex.GetHttpCode() == 404 || ex.GetHttpCode() == 302 || ex.GetHttpCode() == 500)
{
Redirect code in the link!
}
}

Handle Session Timeout in .Net MVC Razor

I am working on a website which is API based, client side is being developed in .Net MVC. For exception handling, I am using
public void Application_Error(object sender, EventArgs e)
{
string action = "Index";
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null)
{
switch (httpException.GetHttpCode())
{
case 404:
// page not found
action = "Error404";
break;
default:
action = "Index";
break;
}
// clear error on server
Server.ClearError();
}
Response.Redirect(String.Format("/error/{0}", action));
}
so for any exception thrown by try catch from Controller, the page redirects to error page.
Now I want that when session is expired it should redirect to Login page, How can I do that?
Right now what is happening is, after session expires, when I try to access the session value, it throws exception "object reference not set to an instance of object." then it redirects to the default error page.
I don't think you're going to be able to do this from inside a generic exception handler because - as you said - missing session variables simply throw a NullReferenceException. Perform a null check on the session variable from your controller:
Public ActionResult MyAction ()
{
if (Session["myVariable"] == null)
{
RedirectToAction("SessionTimeOut", "Error");
}
...
}
If you have session variables that should always exist unless the session has expired, you could try overriding the OnActionExecuting method for your controller and performing your null check in there. To do this for multiple controllers, define a BaseController, override its OnActionExecuting method and then inherit this in your other controllers.

Can the ASP.NET MVC Yellow Screen of Death (YSOD) be generated on demand

As asked here.
I want to know if it is possible to get the YSOD's HTML Rendering for exceptions to be sent by mail WITHOUT the use of ELMAH? I am handling the errors and showing a custom error page to the user. I am also sending the exception general information throught mail, however I really would like to know if I can wrap them into the real built-in YSOD engine of ASP.NET and keep the HTML formatting.
UPDATE1:
I have my custom exceptions (DupplicatedArbsException) that returns a view with the message which i consider "Managed Exceptions". However, if it is a real error that I did not catch, it will return the Error view.
[HandleError(ExceptionType = typeof(Exception), View = "Error")]
[HandleError(ExceptionType = typeof(DuplicatedArbsException), View = "ErrorViewArbs")]
public ActionResult Create(string id, int? version)
{
//...
}
The HandleError Raises which does nothing currently.
protected override void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
base.OnException(filterContext);
}
..
<customErrors mode="On" defaultRedirect="Error"/>
The exception raised in customErrors mode="off" is the YSOD from asp.net. However, when I turn customErrors mode="on" those exceptions are not wrapped in it's html equivalent but only the exception messages (no html at all).
You could handle the Application_Error event in global.asax which is triggered by the ASP.NET engine every-time an exception is not handled:
protected void Application_Error(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var context = app.Context;
// get the exception that was unhandled
Exception ex = context.Server.GetLastError();
// TODO: log, send the exception by mail
}

Error Handling in ASP.NET MVC

How can I correctly handle exceptions thrown from controllers in ASP.NET MVC? The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
Using this web.config
<customErrors mode="On">
<error statusCode="401" redirect="/Errors/Http401" />
</customErrors>
with the following code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
// Force a 401 exception for testing
throw new HttpException(401, "Unauthorized");
}
}
}
doesn't result in what I was hoping for. Instead I get the generic ASP.NET error page telling me to modify my web.config to see the actual error information. However, if instead of throwing an exception I return an invalid View, I get the /Shared/Views/Error.aspx page:
return View("DoesNotExist");
Throwing exceptions within a controller like I've done above seems to bypass all of the HandleError functionality, so what's the right way to create error pages and how do I play nice with the MVC infrastructure?
Controller.OnException(ExceptionContext context). Override it.
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("ErrorPage", data);
}
Thanks to kazimanzurrashaid, here is what I wound up doing in Global.asax.cs:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
HttpException httpException = unhandledException as HttpException;
if (httpException == null)
{
Exception innerException = unhandledException.InnerException;
httpException = innerException as HttpException;
}
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
switch (httpCode)
{
case (int) HttpStatusCode.Unauthorized:
Response.Redirect("/Http/Error401");
break;
}
}
}
I'll be able to add more pages to the HttpContoller based on any additional HTTP error codes I need to support.
The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
That is just wrong. Indeed, HandleError will only "process" exceptions either thrown in your own code or in code called by your own code. In other words, only exceptions where your action is in the call stack.
The real explanation for the behavior you're seeing is the specific exception you're throwing. HandleError behaves differently with an HttpException. From the source code:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
I don't think you will be able to show specific ErrorPage based upon the HttpCode with the HandleError Attribute and I would prefer to use an HttpModule for this purpose. Assuming that I have folder "ErrorPages" where different page exists for each specific error and the mapping is specifed in the web.config same as the regular web form application. And the following is the code which is used to show the error page:
public class ErrorHandler : BaseHttpModule{
public override void OnError(HttpContextBase context)
{
Exception e = context.Server.GetLastError().GetBaseException();
HttpException httpException = e as HttpException;
int statusCode = (int) HttpStatusCode.InternalServerError;
// Skip Page Not Found and Service not unavailable from logging
if (httpException != null)
{
statusCode = httpException.GetHttpCode();
if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
{
Log.Exception(e);
}
}
string redirectUrl = null;
if (context.IsCustomErrorEnabled)
{
CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
if (section != null)
{
redirectUrl = section.DefaultRedirect;
if (httpException != null)
{
if (section.Errors.Count > 0)
{
CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
}
}
context.Response.Clear();
context.Response.StatusCode = statusCode;
context.Response.TrySkipIisCustomErrors = true;
context.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
context.Server.Transfer(redirectUrl);
}
}
}
One other possibility (not true in your case) that others reading this may be experiencing is that your error page is throwing an error itself, or is not implementing :
System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
If this is the case then you will get the default error page (otherwise you'd get an infinite loop because it would keep trying to send itself to your custom error page). This wasn't immediately obvious to me.
This model is the model sent to the error page. If your error page uses the same master page as the rest of your site and requires any other model information then you will need to either create your own [HandleError] type of attribute or override OnException or something.
protected override void OnException (ExceptionContext filterContext )
{
if (filterContext != null && filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
this.View("Error").ExecuteResult(this.ControllerContext);
}
}
I chose the Controller.OnException() approach, which to me is the logical choice - since I've chosen ASP.NET MVC, I prefer to stay at the framework-level, and avoid messing with the underlying mechanics, if possible.
I ran into the following problem:
If the exception occurs within the view, the partial output from that view will appear on screen, together with the error-message.
I fixed this by clearing the response, before setting filterContext.Result - like this:
filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
filterContext.Result = View("ErrorPage", data);
Jeff Atwood's User Friendly Exception Handling module works great for MVC. You can configure it entirely in your web.config, with no MVC project source code changes at all. However, it needs a small modification to return the original HTTP status rather than a 200 status. See this related forum post.
Basically, in Handler.vb, you can add something like:
' In the header...
Private _exHttpEx As HttpException = Nothing
' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
_exHttpEx = CType(ex, HttpException)
HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If

Resources