Cannot catch exception with HandleErrorAttribute in ASP.NET MVC - c#

I created a custom exception handling method as shown below and I can catch the database constraint exception with it and return the error to error method of AJAX call. However, when trying to create an exception using throw new Exception()" or throw new ArgumentNullException("instance") I encounter an error as displayed on the image below. Is there any mistake in the custom method? Or how can I test properly test it if it works for AJAX request and Normal request? Any help would be appreciated...
public class CustomErrorHandler : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
//If the request is AJAX return JSON, else return View
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
{
// Log exception first
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
message = "Error occured",
type = filterContext.Exception.GetType().Name,
exception = filterContext.Exception.ToString(),
number = ((System.Data.SqlClient.SqlException)filterContext.Exception.InnerException.InnerException).Number
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
// Let the system know that the exception has been handled
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
}
else
{
// Normal Exception. So, let it handle by its default ways
base.OnException(filterContext);
}
}
}

Related

Handling 'A potentially dangerous Request.Form value was detected from the client' and informing user of this error

i'm trying to handle the following issue
'A potentially dangerous Request.Form value was detected from the
client'
However, I can't find a good tutorial which explains how to catch this error and handle it. Just to be clear I want to stop users from entering any sort of markup when filling out my form. Most of the articles dealing with this issue suggest turning off request validation. This is not something I want to do. I want to catch the error and playback the error to the user. Any help would be greatly appreciated.
You can catch the error with an exception filter. Something like:
public class RequestValidationExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if(filterContext.Exception is HttpRequestValidationException)
{
filterContext.Result = new RedirectResult("/Error");
filterContext.ExceptionHandled = true;
}
}
}
You can get some more info in the Message of the exception, e.g.:
A potentially dangerous Request.QueryString value was detected from the client (FilterName="<script>alert("!!")<...").
But that is not really something to be shown to the user. So best thing to do would be a redirect to some generic error page. Or you can send them back to the current page.
I was doing some quick testing just now, and for some reason action filters and exception filters were not getting executed for me when the validation exception was happening - so I whipped up something quick using Application_Error(). (you can create this method in Global.asax.cs if it does not already exist)
protected void Application_Error()
{
var lastError = Server.GetLastError() as HttpRequestValidationException;
if (lastError == null)
return;
MvcHandler mvcHandler = Context.CurrentHandler as MvcHandler;
if (mvcHandler == null)
return;
RequestContext requestContext = mvcHandler.RequestContext;
if (requestContext == null)
return;
Server.ClearError();
Response.Clear();
Response.TrySkipIisCustomErrors = true;
// pick one of the following two options, or maybe more?
RedirectToUrl(requestContext);
ExecuteActionResult(requestContext, ...);
}
void ExecuteActionResult(RequestContext requestContext, ActionResult result)
{
string controllerName = requestContext.RouteData.GetRequiredString("controller");
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(requestContext, controllerName);
ControllerContext controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);
result.ExecuteResult(controllerContext);
}
void RedirectToUrl(RequestContext requestContext)
{
requestContext.HttpContext.Server.TransferRequest($"~/Error/Something", false);
}
I included an example of how to redirect to an arbitrary url, and also an example of how to execute a new ActionResult against the same controller that the original request was executed on.

MVC 5 Custom error pages when using Autofac

I'm trying to implement custom error handling in my asp.net mvc 5 application, using the same technique I have always used :
protected void Application_Error(object sender, EventArgs e)
{
try
{
HandleApplicationErrors();
Response.TrySkipIisCustomErrors = true;
}
catch(Exception ex)
{
Response.Write(ex.ToString());
Response.StatusCode = 500;
}
}
private void HandleApplicationErrors(int? statusCode = null)
{
try
{
Exception ex = Server.GetLastError();
Response.Clear();
HttpException httpEx = ex as HttpException;
if (statusCode == null && httpEx != null) statusCode = httpEx.GetHttpCode();
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
switch (statusCode)
{
case 404:
routeData.Values.Add("action", "NotFound");
break;
case 403:
routeData.Values.Add("action", "Forbidden");
break;
default:
routeData.Values.Add("action", "ServerError");
break;
}
routeData.Values.Add("exception", ex);
Server.ClearError();
this.Server.ClearError();
this.Response.TrySkipIisCustomErrors = true;
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(this.Context), routeData));
}
catch(Exception ex)
{
Response.Write(ex.ToString());
Response.StatusCode = 500;
}
}
This has worked just fine in the past, however in this application, I'm using Autofac, and it's throwing "Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed." on the controller.Execute line. I've tried excluding the ErrorController from autofac registration (it doesn't have and constructor parameters anyway) and constructing it directly.
The controller code is pretty simple :
private CustomErrorModel GetModel(Exception ex)
{
var model = new CustomErrorModel();
model.RequestedUrl = Request.Url.OriginalString;
model.ReferrerUrl = (Request.UrlReferrer == null || model.RequestedUrl == Request.UrlReferrer.OriginalString) ? null : Request.UrlReferrer.OriginalString;
model.Exception = ex;
return model;
}
public ActionResult ServerError(Exception ex)
{
var model = GetModel(ex);
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
Response.Status = "500 Internal Server Error";
return View(model);
}
Anyone have any ideas on making this work?
I am doing a very similar thing in my application, including using Autofac for dependency injection, and have not seen the same issues as you.
Looking closely at your HandleApplicationErrors method, I notice that your route data does not match the parameter that your ServerError action is expecting.
In HandleApplicationErrors, you are passing in a parameter named exception
In ServerError, you are expecting a parameter named ex
Updating the setting of the route data to match like so:
routeData.Values.Add("ex", ex);
will resolve the issue.
Ok, answering my own question here. I had a look at stack trace, and it seemed the exception was caused by mvc trying to use the container to find the model binder.. so I made my action methods parameterless and that solved it!
Posting code for reference here :
protected void Application_Error(object sender, EventArgs e)
{
try
{
HandleApplicationErrors();
Response.TrySkipIisCustomErrors = true;
}
catch(Exception ex)
{
Response.Write(ex.ToString());
Response.StatusCode = 500;
}
}
private void HandleApplicationErrors(int? statusCode = null)
{
try
{
Exception ex = Server.GetLastError();
Response.Clear();
HttpException httpEx = ex as HttpException;
if (statusCode == null && httpEx != null) statusCode = httpEx.GetHttpCode();
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
switch (statusCode)
{
case 404:
routeData.Values.Add("action", "NotFound");
break;
case 403:
routeData.Values.Add("action", "Forbidden");
break;
default:
routeData.Values.Add("action", "ServerError");
break;
}
//routeData.Values.Add("exception", ex);
Server.ClearError();
this.Server.ClearError();
this.Response.TrySkipIisCustomErrors = true;
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(this.Context), routeData));
}
catch(Exception ex)
{
Response.Write(ex.ToString());
Response.StatusCode = 500;
}
}
and my controller method :
public ActionResult ServerError()
{
HttpException ex = Server.GetLastError() as HttpException;
var model = GetModel(ex);
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
Response.Status = "500 Internal Server Error";
return View(model);
}
"The request lifetime itself is disposed in the EndRequest event. You
can see this in the RequestLifetimeHttpModule which gets added
automatically to your pipeline when you reference the MVC integration.
Most likely there is a race condition where the Autofac event handler
is firing before your own EndRequest event handler. This is most
likely cropping up now simply because more recent Autofac integration
tries to make the registration of the module, etc. more seamless to
the developer so we register the module on pre-application-start. We
subscribe to the event first, so we get called first.
Unfortunately, there's really not much to be done about it from the
Autofac side - EndRequest is the last event in the pipeline and we
have to dispose of the lifetime scope, so that's where it happens.
If you are handling something at EndRequest that needs to be resolved,
it may be too late. Even in previous integrations, EndRequest would
have been a risk. For example, if you resolve an object that
implements IDisposable, the release of the lifetime scope on
EndRequest would dispose of the object and you'd be working with an
object in a bad state.
I recommend trying to move the execution of your action to some time
before EndRequest. Alternatively, if you are 100% sure that the
objects in your chain aren't IDisposable, you could resolve the object
in an earlier event, store it in HttpContext.Items, and retrieve it
from there to use in your EndRequest handler."
font: https://github.com/autofac/Autofac/issues/570

MVC routing to Index if no action found in controller?

I have a multi-page form in my application, and as such, each method posts to the next. This works fine unless you try to visit the URL of one the methods decorated with [HttpPost].
Is there any way I can route all 404 requests within this specific controller to the Index method?
I will post this as an answer because I am not able to add it as comment
have a look to this link, it might help you
The idea you can catch the error in the OnActionExecuting and there you can make redirect
also as mentioned in this page in the answer, you can handle the Controller.OnException
public class BaseController: Controller
{
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("Index", data); // to redirect to the index page
}
}
after this you can let all your controller to inhert from BaseController

How to return error message from Catch block. Right now empty is returned

My sample code of ApiKey validation is given below (I am using MVC4 web api RC):
public class ApiKeyFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
//read api key from query string
string querystring = context.Request.RequestUri.Query;
string apikey = HttpUtility.ParseQueryString(querystring).Get("apikey");
//if no api key supplied, send out validation message
if (string.IsNullOrWhiteSpace(apikey))
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "You can't use the API without the key." });
throw new HttpResponseException(response);
}
else
{
try
{
GetUser(decodedString); //error occurred here
}
catch (Exception)
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "User with api key is not valid" });
throw new HttpResponseException(response);
}
}
}
}
Here problem is with Catch block statement. I Just wanted to send custom error message to user. But nothing is sent. It displays a blank screen
However, the statement below is working well and sends out the validation error message correctly:
if (string.IsNullOrWhiteSpace(apikey))
{
var response = context.Request.CreateResponse(HttpStatusCode.Unauthorized, new Error { Message = "You can't use the API without the key." });
throw new HttpResponseException(response);
}
Is there anything that i am doing wrong.
I was having the same issue in the exact same scenario. However, in this scenario, you need to return some content in your response to be shown and not really throw the exception. So based on this, I would change your code to the following:
catch (Exception)
{
var response = context.Request.CreateResponse(httpStatusCode.Unauthorized);
response.Content = new StringContent("User with api key is not valid");
context.Response = response;
}
So with this change you are now returning your response, with content that will displayed in place of the blank screen.

Elmah not logging exceptions

In my MVC web project. I am trying to show custom error pages to my visitors without using "custromerrors" element in web.config.
I can catch exceptions like below
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
bool success = RaiseErrorSignal(exception);
Response.Clear();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
if (httpException == null)
{
routeData.Values.Add("action", "Index");
}
else //It's an Http Exception, Let's handle it.
{
switch (httpException.GetHttpCode())
{
case 404:
// Page not found.
routeData.Values.Add("action", "Error404");
break;
case 500:
// Server error.
routeData.Values.Add("action", "Error500");
break;
// Here you can handle Views to other error codes.
// I choose a General error template
default:
routeData.Values.Add("action", "Index");
break;
}
}
// Pass exception details to the target error View.
routeData.Values.Add("error", exception);
// Clear the error on server.
Server.ClearError();
// Call target Controller and pass the routeData.
IController errorController = new ProjectName.WebSite.Controllers.ErrorController();
errorController.Execute(new RequestContext(
new HttpContextWrapper(Context), routeData));
}
private static bool RaiseErrorSignal(Exception e)
{
var context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
signal.Raise(e, context);
return true;
}
But Elmah cant log errors also i am raising error signal.
I found the problem, I missed a web.config section. I added "ErrorLog" module to <system.webserver><modules>.
I also need to add it to <system.web><httpModules>.
After adding, Elmah start to log errors.
Also I don't need to call ErrorSignal.Raise() method, Elmah can detect errors without signalling.

Categories