I am using the following code to redirect users to the Error Page in my asp.net mvc4 application and also using ELMAH to send error as E-mails:
protected void Application_Error()
{
Exception lastException = Server.GetLastError();
//add user specific details
lastException.Data["MachineName"] = Environment.MachineName;
lastException.Data["ClientIP"] = Request.UserHostAddress.ToString();
if (Request.ServerVariables["REMOTE_HOST"] != null)
{
System.Net.IPHostEntry host = new System.Net.IPHostEntry();
host = System.Net.Dns.GetHostEntry(Request.ServerVariables["REMOTE_HOST"]);
lastException.Data["ClientMachineName"] = host.HostName;
}
IController errorController;
HttpException httpException = lastException as HttpException;
RouteData routeData = new RouteData();
//Try catch the error, as NLog throws error in Release mode
System.Diagnostics.EventLog _eventLog = new System.Diagnostics.EventLog("Application", Environment.MachineName, ConfigurationManager.AppSettings[Constants.EventLogSource]);
_eventLog.WriteEntry(lastException.ToString(), System.Diagnostics.EventLogEntryType.Error);
//Possible reason for null is the the server error may not be a proper http exception
if (httpException == null)
{
//Call target Controller
errorController = new ErrorController();
routeData.Values.Add("controller","Error");
routeData.Values.Add("action", "Index");
Response.Clear();
}
else
//It's an Http Exception, Let's handle it.
{
switch (httpException.GetHttpCode())
{
case 404:
//Page not found.
//Call target Controller
errorController = new UserController();
routeData.Values.Add("controller", "User");
routeData.Values.Add("action", "PageNotFound");
break;
case 302:
//Page Temporarily Moved
//Call target Controller
errorController = new UserController();
routeData.Values.Add("controller", "User");
routeData.Values.Add("action", "PageNotFound");
break;
case 500:
//Server error.
//Call target Controller
errorController = new ErrorController();
routeData.Values.Add("controller","Error");
routeData.Values.Add("action", "Index");
break;
//Here you can handle Views to other error codes.
//I choose to redirect to Signin
default:
//Call target Controller
errorController = new ErrorController();
routeData.Values.Add("controller","Error");
routeData.Values.Add("action", "Index");
break;
}
}
//Pass exception details to the target error View.
routeData.Values.Add("error", lastException);
//Clear the error on server.
Server.ClearError();
//Call target Controller and pass the routeData.
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
It returns the error perfectly in LOCAL, but when from the Server (Windows Server 2008 R2, IIS 7.5), it returns page not found error in the E-mail sent by ELMAH.
Local Error:(which is perfect)
System.Web.HttpCompileException: c:\Projects\MVC4\Views\Home\Demo.cshtml(15): error CS0103: The name 'ResolveUrl' does not exist in the current context
Generated: Wed, 18 Apr 2012 07:21:05 GMT
Server Error:(This is incorrect)
System.InvalidOperationException: The view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Views/Home/Error.aspx ~/Views/Home/Error.ascx ~/Views/Shared/Error.aspx ~/Views/Shared/Error.ascx ~/Views/Home/Error.cshtml ~/Views/Home/Error.vbhtml ~/Views/Shared/Error.cshtml ~/Views/Shared/Error.vbhtml
Generated: Wed, 18 Apr 2012 07:19:11 GMT
System.InvalidOperationException: The view 'Error' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Error.aspx
~/Views/Home/Error.ascx
~/Views/Shared/Error.aspx
~/Views/Shared/Error.ascx
~/Views/Home/Error.cshtml
~/Views/Home/Error.vbhtml
~/Views/Shared/Error.cshtml
~/Views/Shared/Error.vbhtml
at System.Web.Mvc.ViewResult.FindView(ControllerContext context)
at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
Any suggestions on whats wrong with this implementation?
Related
I have a problem when combining ASP.NET custom errors and use DataTables in a view.
If DataTables tries to get data from the server and an error occurs, first, the error is caught by ASP.NET MVC in Application_Error and from there it is passed to the Index action in the ErrorController with the exception information.
However, the error page is not displayed, instead the browser jumps to the DataTables error handling in javascript.
I can force the page to be displayed, as in the code below (window.location), but I would like the error page with the original exception to be displayed. If I omit the window.location part, no error page is displayed, even though the Index action in ErrorController is triggered.
Here is my code:
Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Server.ClearError();
// Redirect to error page
var routeData = new RouteData();
routeData.Values.Add("controller", "Error");
if (exception.GetType() == typeof(HttpException))
{
// Save error log
Log.WriteLog(exception, "", Session["userName"] as string);
var code = ((HttpException)exception).GetHttpCode();
if (code == 404)
{
routeData.Values.Add("action", "NotFound");
}
else if (code == 403 || code == 401)
{
routeData.Values.Add("action", "NotAuthorized");
}
else
{
routeData.Values.Add("action", "BadRequest");
routeData.Values.Add("statusCode", 400);
}
}
else
{
routeData.Values.Add("action", "Index");
routeData.Values.Add("statusCode", 500);
}
routeData.Values.Add("exception", exception);
// Redirect to error pages
Response.TrySkipIisCustomErrors = true;
IController controller = new ErrorController();
controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
Response.End();
}
ErrorController:
public ActionResult Index(dynamic exception, int? statusCode = 500)
{
Response.StatusCode = statusCode.Value;
Response.TrySkipIisCustomErrors = true;
ViewBag.Message = (exception is String[]) ? exception[0] : exception.Message;
return View("Error500");
}
DataTables error handling:
$.fn.dataTable.ext.errMode = 'throw';
$("#tblMSLs").on('error.dt', function (e, settings, techNote, message) {
console.log('Error in DataTables tblMSLs: ', message);
window.location.href = `${webOrigin}Error?exception=${message}&statusCode=500`;
});
I have defined my Custom Error pages in my global.asax.cs and can get them working locally in IIS Express with VS 2013, but when I publish to the server running IIS 7 the 401 and 403 errors do not display the custom error page but rather return a 500 and not the custom 500 page either. I've tried many solutions found here on SO but none have worked. Anyone have any thoughts?
Web.Config portions:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
</customErrors>
</system.web>
<system.webServer>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
Global.asax portions:
protected void Application_Error()
{
string descriptor = null;
string errMsg = null;
string additionalInfo = null;
string title = null;
Exception lastError = null;
Exception ex = null;
if (HttpContext.Current.Server.GetLastError() != null)
{
ex = HttpContext.Current.Server.GetLastError().GetBaseException();
if (ex.GetType() == typeof (HttpException))
{
HttpException httpException = (HttpException) ex;
switch (httpException.GetHttpCode())
{
case 404:
title = "Not Found";
descriptor = "Page Not Found";
errMsg =
"The page you requested could not be found, either contact your webmaster or try again. Use your browsers Back button to navigate to the page you have prevously come from.";
additionalInfo =
"We are working hard to correct this issue. Please wait a few moments and try your search again.";
lastError = new Exception(errMsg);
break;
case 500:
title = "Server Error";
descriptor = "Oooops, Something went wrong!";
errMsg = "You have experienced a technical error. We apologize.";
additionalInfo =
"We are working hard to correct this issue. Please wait a few moments and try your search again.";
lastError = new Exception(errMsg);
break;
}
CallErrorController(descriptor, additionalInfo, title, lastError, httpException.GetHttpCode());
}
}
}
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Response.StatusCode == 401 || Response.StatusCode == 403)
{
Response.ClearContent();
string additionalInfo = null;
string title = null;
const string errMsg =
#"You have attempted to access a resource for which you do not have the proper authorization or which is not available from your location.
If you received this message by clicking on a link on this website, please report it to the webmaster. Use your browsers Back
button to navigate to the page you have previously come from.";
const string descriptor = "Access is Denied";
title = descriptor;
Exception lastError = new Exception(errMsg);
CallErrorController(descriptor, additionalInfo, title, lastError, Response.StatusCode);
}
}
private void CallErrorController(string descriptor, string additionalInfo, string title, Exception lastError, int statusCode)
{
Server.ClearError();
Response.TrySkipIisCustomErrors = true;
HttpContextWrapper contextWrapper = new HttpContextWrapper(this.Context);
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("statusCode", statusCode);
routeData.Values.Add("exception", lastError);
routeData.Values.Add("descriptor", descriptor);
routeData.Values.Add("additionalInfo", additionalInfo);
routeData.Values.Add("title", title);
routeData.Values.Add("isAjaxRequet", contextWrapper.Request.IsAjaxRequest());
IController controller = new ErrorController();
RequestContext requestContext = new RequestContext(contextWrapper, routeData);
controller.Execute(requestContext);
Response.End();
}
ErrorController:
public class ErrorController : Controller
{
public ActionResult Index(int statusCode, Exception exception, string descriptor, string additionalInfo, string title, bool isAjaxRequet)
{
if (!isAjaxRequet)
{
ErrorModel model = new ErrorModel { HttpStatusCode = statusCode, Exception = exception, Descriptor = descriptor, AdditionalInfo = additionalInfo, Title = title};
return View("Error", model);
}
else
{
// Otherwise, if it was an AJAX request, return an anon type with the message from the exception
var errorObjet = new { message = exception.Message };
return Json(errorObjet, JsonRequestBehavior.AllowGet);
}
}
}
Running Locally:
Running From the Server:
Try adding this to your web.config
<configuration>
... //all the other gubbins that goes in your web config
<system.webServer>
<httpErrors errorMode="Detailed" />
</system.webServer>
</configuration>
Or turning on detailed errors via the iis adamin module
In my mvc application I handle 404 and 505 errors by checking response code in Application_EndRequest() method in global.asax.cs and returning custom 404/error page like below
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 404)
{
Response.Clear();
var rd = new RouteData();
rd.Values["controller"] = "Errors";
rd.Values["action"] = "PageNotFound";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
else if (Context.Response.StatusCode == 500)
{
Response.Clear();
var rd = new RouteData();
rd.Values["controller"] = "Errors";
rd.Values["action"] = "ServerError";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
}
}
And I have Errors controller like below
public ActionResult PageNotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("Error");
}
Now when I type the url in addressbar like http://mydomain/errors/servererror, It displays my current custom error page
So I want to prevent user by accessing this page, so how can I accomplish that?
P.S.
So far I have tried to solve this by configuring route named "Errors/ServerError" and redirecting it to somewhere else, but I think there may be another good way out there..
I'm testing example on this link: http://msdn.microsoft.com/en-us/vs11trainingcourse_aspnetmvc4_topic5#_Toc319061802 but I have 500 error calling another controller using WebClient.
When I access to "http://localhost:2323/photo/gallery directly is running, but I'm trying from action using WebClient it return 500 error? Why?"
public ActionResult Index()
{
WebClient client = new WebClient();
var response = client.DownloadString(Url.Action("gallery", "photo", null, Request.Url.Scheme));
var jss = new JavaScriptSerializer();
var result = jss.Deserialize<List<Photo>>(response);
return View(result);
}
500 error created by below exception:
[ArgumentNullException: Value cannot be null.
Parameter name: input]
System.Text.RegularExpressions.Regex.Match(String input) +6411438
Microsoft.VisualStudio.Web.Runtime.Tracing.UserAgentUtilities.GetEurekaVersion(String userAgent) +79
Microsoft.VisualStudio.Web.Runtime.Tracing.UserAgentUtilities.IsRequestFromEureka(String userAgent) +36
Microsoft.VisualStudio.Web.Runtime.Tracing.SelectionMappingExecutionListenerModule.OnBeginRequest(Object sender, EventArgs e) +181
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
It's hard to tell. Maybe the controller action that you are calling requires authorization? Or uses session? When you send your WebClient request it doesn't delegate any of the client cookies that were sent by the client to the Index action.
Here's how you can debug your code and see the exact response returned by the server:
WebClient client = new WebClient();
try
{
var response = client.DownloadString(Url.Action("gallery", "photo", null, Request.Url.Scheme));
}
catch (WebException ex)
{
using (var reader = new StreamReader(ex.Response.GetResponseStream()))
{
string responseText = reader.ReadToEnd(); // <-- Look here to get more details about the error
}
}
And if it turns out that the problem is related to the ASP.NET Session that your target controller action depends upon, here's how you could delegate the client cookies with the request:
WebClient client = new WebClient();
client.Headers[HttpRequestHeader.Cookie] = Request.Headers["Cookie"];
error occurred because of the User-Agent Header
Resolution is:
public ActionResult Index()
{
WebClient client = new WebClient();
client.Headers[HttpRequestHeader.UserAgent] = Request.Headers["User-Agent"];
var response = client.DownloadString(Url.Action("gallery", "photo", null, Request.Url.Scheme));
var jss = new JavaScriptSerializer();
var result = jss.Deserialize<List<Photo>>(response);
return View(result);
}
When an Action is called and throws a specific exception, I use an ExceptionFilterAttribute that translate the error into a different response as HttpStatusCode.BadRequest. This has been working locally, but we pushed it to a server and now when I get the BadRequest I do not get any information in the reponse. What am I missing?
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
MyException ex = actionExecutedContext.Exception as MyException;
if (ex == null)
{
base.OnException(actionExecutedContext);
return;
}
IEnumerable<InfoItem> items = ex.Items.Select(i => new InfoItem
{
Property = i.PropertyName,
Message = i.ToString()
});
actionExecutedContext.Result = new HttpResponseMessage<IEnumerable<InfoItem>>(items, HttpStatusCode.BadRequest);
}
Edit: When I hit the service locally the body is included. It seems the problem is when hitting the service from a remote machine.
Try this:
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always