Handling unhandled exceptions in .Net Core 2.2 - c#

I am doing a application level exception handling in .NetCore 2.2. But facing some issue
So in my startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseExceptionHandler("/GlobalException");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
else
{
app.UseExceptionHandler("/GlobalException");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
//app.UseAuthentication();
app.UseSession();
//app.UseCookiePolicy();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Account}/{action=Index}/{id?}");
});
}
And ExceptionController.cs is
public class ExceptionController : Controller
{
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var statusCodeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
break;
default:
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
break;
}
return View();
}
[AllowAnonymous]
[Route("GlobalException")]
public IActionResult GlobalException()
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var exceptionPath = exceptionHandlerPathFeature.Path;
var exceptionMessage = exceptionHandlerPathFeature.Error.Message;
var exceptionStackTrace = exceptionHandlerPathFeature.Error.StackTrace;
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
//return await HttpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject("he"));
//return View("GlobalException");//"~/Views/Shared/HttpStatusCodeHandler.cshtml"
return View("~/Views/Exception/GlobalException.cshtml");
}
}
AuthenticationMiddleware.cs
public async Task Invoke(HttpContext context)
{
bool isAjaxCall = context.Request.Headers["x-requested-with"] == "XMLHttpRequest";
if(isAjaxCall)
{
// someAjax related functionality.
}
else if (!context.Request.Path.Value.Contains("/account/Index"))
{
if (context.Session.Keys.Contains("UserName"))
{
await _next.Invoke(context);
}
else
{
context.Response.Redirect("/account/Index");
}
}
else if (context.Request.Path.Value.Contains("/GlobalException"))
{
await _next.Invoke(context);
}
await _next.Invoke(context);
}
I am able to navigate to all the exception page when I receive 404,and other errors.
But when any run time exception is arised in the application. GlobalException() is being executed but in UI, It not navigating that View.
Trying to raise an exception for testing purpose
public ActionResult RaiseException()
{
throw new Exception();
}
Could anyone please help me with this as I am struggling since two days.
Please provide me a better approach if possible. Thank you.

Related

Error routing in Startup.cs wont redirect to controller upon 404 or 500 error ASP.NET Core MVC

I want my startup.cs class to redirect to my Error controller when a 404 or 500 error occurs.
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpContextAccessor accessor)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseExceptionHandler("/ErrorPages/500");
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseIPRestrictionService();
app.Use(async (content, next) =>
{
await next();
if (content.Response.StatusCode == 404 && !content.Response.HasStarted)
{
content.Request.Path = "/ErrorPages/404";
await next();
}
if (content.Response.StatusCode == 500)
{
content.Request.Path = "/500";
await next();
}
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapContent();
endpoints.MapControllers();
});
ContentExtensions.SetHttpContextAccessor(accessor);
VisitorGroupManager.SetHttpContextAccessor(accessor);
PageExtensions.SetHttpContextAccessor(accessor);
//IsAuthenticatedCriterion.SetHttpContextAccessor(accessor);
}
But when the content.Request.Path is set when a 404 or 500 status code is detected, the path does not change in the URL. How to I get this to redirect to my controller so I can then apply my logic.
ErrorController.cs
[Route("ErrorPages")]
class ErrorController : Controller
{
[Route("500")]
public IActionResult AppError()
{
return View();
}
[Route("404")]
public IActionResult PageNotFound()
{
return View("~/Views/404.cshtml");
}
}
For that you need an error controller
in your ErrorController.cs file
public class ErrorController : Controller
{
private readonly ILogger<ErrorController> logger;
public ErrorController(ILogger<ErrorController> logger)
{
this.logger = logger;
}
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var viewToReturn = string.empty;
var statusCodeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
ViewBag.ErrorMessage = "Sorry the resource you requested could not be found";
logger.LogWarning($"404 Error Occured. Path = {statusCodeResult.OriginalPath}" + $"and QueryString = {statusCodeResult.OriginalQueryString}");
viewToReturn = nameof(Notfound);
break;
}
return View(viewToReturn ?? "defaultUnInterceptedErrorView");
}
[Route("Error")]
[AllowAnonymous]
public IActionResult Error()
{
var exceptionDetails = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
logger.LogError($"The Path {exceptionDetails.Path} threw an exception" + $"{exceptionDetails.Error}");
return View("Error");
}
}
In your startup.cs file
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
you can also have your NotFound.cshtml view, which you can be listening for the value of ViewBag.ErrorMessage ( Note: ASP.NET Core always searches for the Notfound action in AccountController.cs but you can change that in your startup.cs )
and then you can also continue the case switch statement to suit all the status code errors you're planning to intercept

How do I redirect to a View instead of UseDeveloperExceptionPage on Specific Http Error Status

I have a .NET Core MVC environnement. I want to manage a login routine when server give 403 error.
I currently use this configuration in my Startup.cs file :
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error/500");
app.UseHsts();
}
But I would like something like :
if (env.IsDevelopment())
{
if ( error === 403 )
app.UseExceptionHandler("/Home/MyCustomError");
else
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error/500");
app.UseHsts();
}
How can I deal with it ?
I tried to make what Microsoft explained with app.UseExceptionHandler in both case. In this way, I want to do what I want and show error if error is not 403 and return login View if it is.
The problem with this solution is that the displayed error is not a nice detailed page for debugging like app.UseDeveloperExceptionPage render.
You can try to use UseStatusCodePagesWithReExecute.Here is a demo(I test with 404,you can also add other status code error page to Error folder in the demo):
Startup:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/StatusCode", "?code={0}");
StatusCodeController:
public class StatusCodeController : Controller
{
public IActionResult Index(string code)
{
if (string.IsNullOrEmpty(code))
{
code = "Unknown.cshtml";
}
return View($"/Views/Shared/Error/{code}.cshtml");
}
}
Error folder:
404.cshtml:
<h1>404</h1>
result:
You can also try UseStatusCodePagesWithRedirects.
Try this
if (env.IsDevelopment())
{
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 403 && !context.Response.HasStarted)
{
context.Response.Redirect("/Home/MyCustomError");
}
});
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error/500");
app.UseHsts();
}

Return json from .net core api when using NotFound()

I'm trying to make my web api core return application/json, but it strangely always returns this html page breaking the error convention established by the team.
Here's the code i'm trying to execute but with no success at all so far:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" });
});
services.AddMvcCore().AddRazorViewEngine().AddRazorRuntimeCompilation().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errorList = (from item in actionContext.ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
return new BadRequestObjectResult(new
{
ErrorType = "bad_request",
HasError = true,
StatusCode = (int)HttpStatusCode.BadRequest,
Message = "Formato do request inválido",
Result = new
{
errors = errorList
}
});
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseCors(
options => options.AllowAnyOrigin().SetIsOriginAllowed(x => _ = true).AllowAnyMethod().AllowAnyHeader()
);
app.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
AuthController.cs
[HttpPost("refreshtoken")]
public IActionResult PostRefreshToken(Guid token)
{
if (!_authTokenService.IsValid(token))
{
return NotFound(new JsonResponse
{
HasError = true,
ErrorType = "not_found",
StatusCode = (int)HttpStatusCode.NotFound,
Title = "Token não encontrado",
Message = "refresh is not valid because it was not found or does not comply",
});
}
var savedToken = _authTokenService.Get(token);
...
return Ok(new JsonResponse
{
StatusCode = (int)HttpStatusCode.OK,
Title = "Token atualizado",
Message = "jwt access token refreshed with success, please update your keys for subsequent requests",
Result = new
{
Expiration = accessToken.Expiration.ToString("dd/MM/yyyy HH:mm:ss"),
AccessToken = accessToken.Token,
RefreshToken = refreshToken.Token,
}
});
}
when this code is executed i was expecting a json result when NotFound() block is reached, but instead it returns this text/html page
ErrorHandlingMiddleware.cs
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new
{
HasError = true,
StatusCode = (int)code,
Message = ex.Message
}, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
}
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
In my case it ended up being the visual studio extension Conveyor by Keyoti being the culprit of the errors aforementioned.
When i disabled the extension, the code was revealed to be ok and returning the right code, a json object body sent by the server.

ModelState error of JSON serialization behaves differently in ASP.NET Core 2.2 WebApi

I'm developing an application with ASP.NET Core 2.2. The solution contains two API projects. One is for private APIs and another one is for public APIs.
Both projects have code for model validation through a filter.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errorKey = context.ModelState.Select(x => x.Key).FirstOrDefault();
var errorMessage = context.ModelState.SelectMany(x => x.Value.Errors).First().ErrorMessage;
var globalError = new GlobalErrorObject
{
Error = string.IsNullOrEmpty(errorMessage) ? $"The request contained at least one invalid parameter: {errorKey}" : errorMessage,
ErrorCode = HttpStatusCode.UnprocessableEntity
};
context.Result = new CustomActionResult(JsonConvert.SerializeObject(globalError), HttpStatusCode.UnprocessableEntity);
}
}
}
Now when I pass invalid JSON request data to my first API project, it gives me an invalid model state with errors in exception.
But passing the same invalid JSON to the second project behaves differently.
Startup.cs
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Not sure what could be the issue and why the second API project is not throwing an exception or I have missed any configuration.
Edit-1 I have error handling middleware in both projects
public class RequestMiddleware
{
private readonly RequestDelegate _next;
public RequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
//preparing errorObject
await context.Response.WriteAsync(JsonConvert.SerializeObject(errorObject));
}
}
Edit-2: Configure method of both projects
Project 1:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
env.ConfigureNLog(_configuration);
var rateLimitingOptions = _configuration.GetSection("IpRateLimiting");
if (rateLimitingOptions != null && Convert.ToBoolean(rateLimitingOptions["EnableRateLimiting"]))
{
app.UseAppIpRateLimiting();
}
app.UseSecurityHeaders();
app.UseHttpsRedirection();
app.UseMultitenancy<Tenants>();
app.UseResponseCompression();
app.UseMiddleware(typeof(RequestMiddleware));
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
options.RoutePrefix = string.Empty;
});
//other services
}
Project 2:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
env.ConfigureNLog(_configuration);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMultitenancy<Tenants>();
app.UseResponseCompression();
app.UseMiddleware(typeof(RequestMiddleware));
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
c.RoutePrefix = string.Empty;
});
}

How to configure multiple exception handlers

I am trying to configure my middleware pipeline to use 2 different exception handlers to handle the same exception. For example, I'm trying to have both my custom handler and in-built DeveloperExceptionPageMiddleware as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.ConfigureCustomExceptionHandler();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.ConfigureCustomExceptionHandler();
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
My objective is to have the custom handler do its own thing (logging, telemetry, etc), and then pass on (next()) to the other in-built handler which displays a page. My custom handler looks like this:
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureCustomExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Use(async (context, next) =>
{
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
//log error / do custom stuff
await next();
}
});
});
}
}
I cannot get CustomExceptionHandler to pass on processing to the next middleware. I get the following page instead:
404 error:
I tried switching around the order, but then the developer exception page takes over and the custom exception handler is not called.
Is what I'm trying to do possible at all?
Update:
The solution was to take Simonare's original suggestion and re-throw the exception in the Invoke method. I also had to remove any type of response-meddling by replacing the following in HandleExceptionAsync method:
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
with:
return Task.CompletedTask;
Instead of calling two different Exception Handling Middleware, you may consider to add logging under your Home/Error
[AllowAnonymous]
public IActionResult Error()
{
//log your error here
return View(new ErrorViewModel
{ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
alternatively, you can use custom Expception Handling Middleware
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IHostingEnvironment env)
{
try
{
await _next(context);
}
catch (Exception ex)
{
if (!context.Response.HasStarted)
await HandleExceptionAsync(context, ex, env);
throw;
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception, IHostingEnvironment env)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
var message = exception.Message;
switch (exception)
{
case NotImplementedException _:
code = HttpStatusCode.NotImplemented;
break;
//other custom exception types can be used here
case CustomApplicationException cae: //example
code = HttpStatusCode.BadRequest;
break;
}
Log.Write(code == HttpStatusCode.InternalServerError ? LogEventLevel.Error : LogEventLevel.Warning, exception, "Exception Occured. HttpStatusCode={0}", code);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return Task.Completed;
}
}
and Simply Register it inside IApplicationBuilder Method
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<ErrorHandlingMiddleware>();
}
Here's a very simple version of how to use custom exception handling logic WITH the built-in ASP.NET Core error page at the same time:
app.UseExceptionHandler("/Error"); //use standard error page
app.Use(async (context, next) => //simple one-line middleware
{
try
{
await next.Invoke(); //attempt to run further application code
}
catch (Exception ex) //something went wrong
{
//log exception, notify the webmaster, etc.
Log_Exception_And_Send_Email_or_Whatever(ex);
//re-throw the exception so it's caught by the outer "UseExceptionHandler"
throw;
}
});
P.S. Uhmm, I added an explicit language: c# hint to my answer but syntax highlighting still does not see catch as a keyword... Interesting.

Categories