When a ajax call is made by my application I throw an error and wish to capture it at my client side. Which approach will be the best.
My server side code is:
try
{
...
}
catch (Exception ex)
{
throw new HttpException("This is my error", ex);
}
My client side code is:
var url = $(this).attr('href');
var dialog = $('<div style="display:none"></div>').appendTo('body');
dialog.load(url, {},
function (responseText, status, XMLHttpRequest) {
if (status == "error") {
alert("Sorry but there was an error: " + XMLHttpRequest.status + " " + XMLHttpRequest.statusText);
return false;
}
....
At runtime, when debugging, I don't get my error details as you can see on the screenshot below:
I get a generic error:
status: 500
statusText: Internal Server Error
How can I get the detail I sent : "This is my error" ?
Finally I use this method:
Web.config:
<system.web>
<customErrors mode="On" defaultRedirect="~/error/Global">
<error statusCode="404" redirect="~/error/FileNotFound"/>
</customErrors>
</system.web>
ErrorController:
public class ErrorController : Controller
{
public ActionResult Global()
{
return View("Global", ViewData.Model);
}
public ActionResult FileNotFound()
{
return View("FileNotFound", ViewData.Model);
}
}
Don't forget to create 2 specific views.
Finally when I need to throw specific error code & description, I proceed like this:
public ActionResult MyAction()
{
try
{
...
}
catch
{
ControllerContext.RequestContext.HttpContext.Response.StatusCode = 500;
ControllerContext.RequestContext.HttpContext.Response.StatusDescription = "My error message here";
return null;
}
}
Then client side I receive such error information.
Do something like this
Server side:
try{
//to try
}catch(Exception ex)
{
return this.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Error :"+ ex.Message);
}
Then the request will return an error 500 to javascript but with your exception message.
You can control the detailed error messages being sent to the clients. By default, the detailed error messages can be viewed only by browsing the site from the server itself.
To display custom error in this way from the server side, you need add the rule in your IIS configuration.
You can read this thread, maybe can help you:
HOW TO enable the detailed error messages for the website while browsed from for the client browsers?
Related
I am developing an API using C# and .net 4.5.2; The API methods can return a handled BadRequest error or OK with an object response as per the below example:
[Authorize]
[RoutePrefix("api/Test")]
public class TestController : ApiController
{
[HttpGet]
[Route("TestMethod")]
public IHttpActionResult TestMethod()
{
MyProvider op = new MyProvider();
var lstResults = new List<Result>();
try
{
lstResults = op.TestMethod();
}
catch (Exception ex)
{
return BadRequest(ParseErrorMessage(ex.Message.ToString()));
}
return Ok(lstResults);
}
}
All errors are returned in a message object as below JSON:
{
Message: "Username or password is incorrect!"
}
The above was working perfectly until we added the below new configuration to redirect all 404 errors to a custom page for security issues. Now anybody (more specifically a hacker) who tries to call the API randomly, will be redirected to a custom 404 error page instead of the original .NET 404 page.
Web.config:
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" path="404.html" responseMode="File"/>
</httpErrors>
The problem is:
BadRequest errors are not handled anymore as it was mentioned in the beginning, the custom JSON structure is not returned anymore, custom messages like "Username or password is incorrect!" are not taken into consideration, just the same simple text is always returned: Bad Request as per below screenshot:
The solution should be running on windows server 2016 IIS version 10.
How to solve the issue by keeping both working?
Update:
If I remove existingResponse="Replace", the badrequest message is returned, but the 404 custom error is not working anymore as per below screenshot
If I set errorMode="Detailed" the 404 custom error won't work anymore, and HTML description is returned for a bad request as you can see here:
I ended up using the below to solve the issue; Thus I won't mark it as the perfect answer since it didn't solve the above issue and I didn't know yet why the configuration did not work properly as excepted. So any answer that can solve the issue is more than welcomed, below is what worked as excepted for me:
Remove the httpErrors configuration from web.config
Use Global.asax file and add the below method to handle any error not handled in the API solution:
protected void Application_Error(object sender, EventArgs e)
{
try
{
try
{
Response.Filter = null;
}
catch { }
Exception serverException = Server.GetLastError();
//WebErrorHandler errorHandler = null;
//Try to log the inner Exception since that's what
//contains the 'real' error.
if (serverException.InnerException != null)
serverException = serverException.InnerException;
// Custom logging and notification for this application
//AppUtils.LogAndNotify(new WebErrorHandler(serverException));
Response.TrySkipIisCustomErrors = true;
string StockMessage =
"The Server Administrator has been notified and the error logged.<p>" +
"Please continue on by either clicking the back button or by returning to the home page.<p>";
// Handle some stock errors that may require special error pages
var HEx = serverException as HttpException;
if (HEx != null)
{
int HttpCode = HEx.GetHttpCode();
Server.ClearError();
if (HttpCode == 404) // Page Not Found
{
Response.StatusCode = 404;
//Response.Write("Page not found; You've accessed an invalid page on this Web server. " + StockMessage);
Response.Redirect("404.html");
return;
}
}
Server.ClearError();
Response.StatusCode = 500;
// generate a custom error page
Response.Write("Application Error; We're sorry, but an unhandled error occurred on the server." + StockMessage);
return;
}
catch (Exception ex)
{
Server.ClearError();
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 500;
Response.Write("Application Error Handler Failed; The application Error Handler failed with an exception.");
}
}
It worked like a charm:
user is redirected to 404.html custom page
Any 400 error is being thrown properly with the JSON format and proper message
I have some rest API written in C# and the API is called from Angular (I am using version Angular 8). The call is fine and it is working fine. However, in case of any exception, I cannot display the customized error message in angular. For example, suppose I have a server side validation in C# which validates if the value of a field matches with the string "abc". If it does not match, it will throw an error and in UI (developed in Angular), I want to display the message
"Invalid String Specified".
My server side code is as below -
if (headerValues.Equals("abc")) {
throw new InvalidStringException("Invalid String specified", 999);
}
The Invalid InvalidStringException class is as below -
public class InvalidStringException : System.Exception
{
int status { get; set; }
public InvalidStringException() { }
public InvalidStringException(string message, int status) : base(message) {
this.status = status;
}
}
When that exception is thrown and caught in server side, it is available as 500 exception but could not print the custom message.
I am trying following code in Angular -
} catch (error) {
console.log("Error Status: ", error.status);
console.log("Error Status: ", error.message);
}
Please suggest how to handle that scenario.
The error object that your Angular app receives should be an instance of HttpErrorResponse
You could do something like this to handle http errors:
if (error instanceof HttpErrorResponse) {
if (!error.status) {
console.log(error.message || error.toString());
} else {
console.log(`error status : ${error.status} ${error.statusText}`);
switch (error.status) {
case 401:
this.router.navigateByUrl("/login");
break;
case 500:
this.router.navigateByUrl("/login");
console.log(`redirect to login`);
break;
}
}
} else {
console.error("Other Errors");
}
You are throwing an exception which is handled by C# exception handler and it will only return the custom error message specified in that handler.
To return a custom message, you need to return with http code like 4xx or 5xx.
new HttpResponseException(Request.CreateErrorResponse(System.Net.HttpStatusCode.Conflict, "Custom Message"));
Or you can return with 2xx and you have to parse this subscribe or then method e.g.
new System.Web.Http.Results.ResponseMessageResult(
Request.CreateResponse((HttpStatusCode)227, "Custom Error Message")
);
this.http.get().toPromise().then((response: any) => {
if (response.status == 227) {
return error;
} else {
return data;
}
return apiResponse;
}).catch(error => {
//nothing here
});
If throwing a exception is not really necessary, you can return status code 400 and a message using BadRequest:
if (headerValues.Equals("abc")) {
return BadRequest("Invalid String specified");
}
Are you explicitly catching the InvalidStringException in your .NET API controller and returning the custom message? If not, the response will be a generic HTTP 500 'Internal Server Error' response. I'd suggest explicitly catching the InvalidStringException in your .NET API controller and returning a 400 response with your custom message e.g.
try {
...
}
catch (InvalidStringException iex) {
return BadRequest(iex.message); // iex.message == Invalid String specified
}
When the InvalidStringException scenario occurs, This will return a HTTP 400 response with "Invalid String specified" as the response body. You should be able to log the error on Angular side as you're currently doing...
As other people have mentioned, you need to catch the exception and convert it to an appropriate HTTP response in your own code.
The reason for that is because if otherwise your exception is handled by ASP.NET Core using exception handling configuration you have, and it may vary:
With developer exception page
Usually in development, you will have code:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
What it does is when your environment is Development, it turns on a special page for developers to see information of unhandled exceptions. It is only in this case, you get the exception stacktrace as well as the exception message in the response body.
Without developer exception page
Conversely, if the exception page is turned off (usually off for Production environment), you will see nothing in the response body.
How to fix
Given exception handling in ASP.NET Core is a cross-cutting concern, I wouldn't use try...catch around everywhere InvalidStringException needs to be converted to HttpResponse.
I would instead use either an IActionFilter or use UseExceptionHandler which is the exception handling middleware:
Here is an example of using UseExceptionHandler in Configure method in Startup.cs:
app.UseExceptionHandler(opt => opt.Run(
async ctx =>
{
var feature = ctx.Features.Get<IExceptionHandlerFeature>();
if (feature.Error is InvalidStringException ex)
{
await ctx.Response.WriteAsync(ex.Message);
}
}));
In this way, your InvalidStringException is handled globally in your application, without explicit try...catch. And you could throw the exception anywhere in your code, the above code would catch the exception and properly convert it to an HTTP response with your own message as the body.
Also note, because you are calling the API from an Angular app, so chances are you might need to set CORS up in your API application if the two applications run from different origins.
Without CORS, your HTTP request from the Angular app may fail before it can reach your API. In this case, the status of the HTTP response in your Angular app may be undefined. And in your console, you could see CORS errors.
you can use http interceptor to create general error handler for all http error in angular app,this way you can use alert ,redirect to login page in case token expired ,overwrite the error object and more but you can still access to the error object at the component level by add a callback for observable error.
Error Handler Service
#Injectable()
export class ErrorHandlerService implements HttpInterceptor {
constructor(private msgServ: MessageService) {}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err: HttpErrorResponse) => {
switch (err.status) {
case 500: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: "Server is gone..💀"
});
break;
}
case 400: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: "custome error message..."
});
break;
}
case 401: {
if (err.message == "invalid_token") {
// router 👉 navigate to login
}
break;
}
default: {
this.msgServ.add({
severity: "error",
summary: "Error ",
detail: err.message
});
}
}
return throwError(err);
})
);
}
}
add the Interceptor to Providers in app module
#NgModule({
....
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerService, multi: true },
MessageService
],
....
})
export class AppModule {}
demo 🚀
MessageService is related to primeng component library ,you can use your own alert structure
I need to return the server error from azure functions.
Now I implement the same using InternalServerErrorResult(). It only sends the error code and no response/message can be sent with this function.
How to implement an exception handler where the error code and message can be sent together using actionresult in azure functions
current implementation
catch (Exception ex)
{
log.LogInformation("An error occured {0}" + ex);
//json = new Response(ex.StackTrace, AppConstants.ErrorCodes.SystemException).SerializeToString();
return (ActionResult)new InternalServerErrorResult();
}
this returns with an empty response in postman with error 500
Note that this is from Microsoft.AspNetCore.Mvc namespace:
var result = new ObjectResult(new { error = "your error message here" })
{
StatusCode = 500
};
Based on configured formatters it will return serialized object to client.
For JSON (it's default) it will return following:
{ "error" : "your error message here" }
To send a message with the status code you can use return StatusCode(httpCode, message), which is an ObjectResult.
For example:
return StatusCode(500, "An error occurred");
You can also pass an object (example using HttpStatusCode enum):
return StatusCode((int)HttpStatusCode.InternalServerError, json);
I've developed an MVC 4 site which runs correctly on my local Win 7 workstation. My workstation has MVC 4 installed as part of the Visual Studio 2010 bolt-on.
I've deployed the app to my DEV server, which is Windows Server 2008 R2. Note that MVC 4 is NOT installed onto the DEV server, instead the app uses the MVC bin deployables. In some cases my error controller gets called but I'm not sure why. Here's the exception:
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/Customer/Error.cshtml
~/Views/Customer/Error.vbhtml
~/Views/Shared/Error.cshtml
~/Views/Shared/Error.vbhtml
The stack trace does not show any line numbers as to where the exception originates. By the exception it appears MVC is expecting to find view associated with the Customer controller first then checks shared. However, there is no view in either path and there SHOULDN'T be.
The site uses a global error handler via Application_Error in global.asax.cs:
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError().GetBaseException();
ILogger httplog = new HttpLogIt(new HttpContextWrapper(Context));
if (new HttpRequestWrapper(Request).IsAjaxRequest())
{
httplog.Error(1, Enums.ErrorCode.INTERNAL_SERVER_ERROR, "An application error occurred during an AJAX request. See exception for details.", ex, false);
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
Response.ContentType = "application/json";
Response.Write(new JavaScriptSerializer().Serialize(new
{
errorMessage = "We apologize, the website has experienced an error. Please try again."
}));
return;
}
else
{
httplog.Error(1, Enums.ErrorCode.INTERNAL_SERVER_ERROR, "An application error occurred. See exception for details.", ex, false);
}
Response.Clear();
// Clear the error on server.
Server.ClearError();
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
// try to send error info to Error controller
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
// maintain current url, even if invalid, when displaying error page
routeData.Values.Add("url", Context.Request.Url.OriginalString);
if (ex is HttpException)
{
HttpException httpException = ex as HttpException;
switch (httpException.GetHttpCode())
{
case 404:
// Page not found.
routeData.Values.Add("action", "Http404");
break;
default:
routeData.Values.Add("action", "Unavailable");
break;
}
}
else
{
routeData.Values.Add("action", "Unavailable");
}
// Pass exception details to the target error View.
var model = new HandleErrorInfo(ex, routeData.Values["controller"].ToString(), routeData.Values["action"].ToString());
routeData.Values.Add("errorinfo", model);
// Call target Controller and pass the routeData.
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
The re-route from the global handler to the Error controller lands in the the Error controller where it explicitly sets the views to use:
[SessionState(SessionStateBehavior.Disabled)]
public class ErrorController : Controller
{
private ILogger _httplog;
public ErrorController()
{
_httplog = new HttpLogIt(this.HttpContext, this.RouteData);
}
public ActionResult Http404()
{
if (this.ControllerContext.RouteData.Values["errorinfo"] != null)
{
HandleErrorInfo errorinfo = null;
errorinfo = (HandleErrorInfo)this.ControllerContext.RouteData.Values["errorinfo"];
_httplog.Warn(1, Enums.WarningCode.PAGE_NOT_FOUND, "A global application exception was handled and the end user was redirected to the Error Controller with Http404 Action.");
}
Response.StatusCode = (int)HttpStatusCode.NotFound;
// explicitly set the View below
return View("Error404");
}
public ActionResult Http500()
{
if (this.ControllerContext.RouteData.Values["errorinfo"] != null)
{
HandleErrorInfo errorinfo = null;
errorinfo = (HandleErrorInfo)this.ControllerContext.RouteData.Values["errorinfo"];
_httplog.Error(1, Enums.ErrorCode.INTERNAL_SERVER_ERROR, "A global application exception was handled and the end user was redirected to the Error Controller with Http500 Action.", errorinfo.Exception, true);
}
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// explicitly set the View below
return View("Unavailable");
}
public ActionResult Unavailable()
{
if (this.ControllerContext.RouteData.Values["errorinfo"] != null)
{
HandleErrorInfo errorinfo = null;
errorinfo = (HandleErrorInfo)this.ControllerContext.RouteData.Values["errorinfo"];
_httplog.Error(1, Enums.ErrorCode.INTERNAL_SERVER_ERROR, "A global application exception was handled and the end user was redirected to the Error Controller with Unavailable Action.", errorinfo.Exception, true);
}
Response.StatusCode = (int)HttpStatusCode.OK;
// explicitly set the View below
return View("Unavailable");
}
}
I added add'l logging in each method to try to find the culprit but to no avail. What's odd is that, thru adding extra logging, the global error handler is NOT firing when this exception is thrown and logged!
Has anyone encountered something like this?
Try to add this line in your web.config
<customErrors mode="On" />
Added some more logging and found the culprit. In the Error controller, Http404 action, I added the following logging block
_httplog.Info(String.Format("Controller is {0}, Action is {1} and URL is {2}.",
RouteData.Values["controller"].ToString(),
RouteData.Values["action"].ToString(),
RouteData.Values["url"].ToString()));
The resulting log showed:
Message: Controller is Error, Action is Http404 and URL is __utm.gif.
This is a Google Analytics image file and I had seen this gif call in my server dumps during performance monitoring, however, didn't correlate it with my Error controller firing. Since my site is a subsite and the parent site uses GA, the urchin script was enabled in the DEV env.
Here's why it fired - in the RouteConfig.cs, the very last entry handles all bad URLs and routes them to the Error controller, Http404 action, like so:
routes.MapRoute("BadRoute", "{*url}", new { controller = "Error", action = "Http404" });
This is to handle any bad URL path and keep the current URL in place - instead of showing an IIS 404 or a customerror html page.
This answers the question as to why my Error controller got called outside of the code, however does not answer why the MVC framework went looking for a different error controller with a matching default view. The fix for that entailed disabling the following line in my FilterConfig.cs
filters.Add(new HandleErrorAttribute());
This was firing despite the app handling errors in the global.asax. Hope this helps!
I'm currently working on a change of password exception handling on server side. So, if the change of password throws an exception on server side (i.e. new password does not fulfill the security requirements) there is an error handled like this:
catch (Exception ex)
{
context.Response.Write(ex.Message);
context.Response.StatusCode = 500;
context.Response.End();
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
Then, the failure is read in AJAX handler, in javascript:
failure: function(response, options) {
loadMask.hide();
if (response.status != -1) {
if (response.responseText.indexOf("title") == -1) {
Ext.MessageBox.alert('Error', response.responseText);
}
else {
var idx1 = response.responseText.indexOf("title") + 6;
var idx2 = response.responseText.indexOf("/title") - 1;
if (idx1 > 0) {
Ext.MessageBox.alert('Error', response.responseText.substring(idx1, idx2));
}
else {
Ext.MessageBox.alert('Error', 'No change of password');
}
}
}
And the problem is:
Everything works fine locally, the detailed exception is written correctly and saved in the response (thus, the responseText displays proper ex.Message).
However, when the application is imported on client computer, there is always '500 - Internal server error.'
I'm almost 99% sure that the Web.config files are identical on both workstations.
Can the problem be within the IIS settings?
Best regards,
loopdster