Reputation: 2664
I am developing a ASP.Net Core MVC application with Razor view. The application consists of many forms where user should fill and submit. I have a particular situation in which to log all exception that raises in the application to log. I know ASP.Net MVC Core comes with a global exception handler middleware where we can catch all exception happens in the application and log the same there. But at the same time I have to show a popup to the user that an error happened while saving the data on submitting the forms. If its success then show a success popup. If I put a try-catch block in Controller Action I can handle this, but I have to log the same from the action itself. Is there any way in which I can handle all exception in one place and show error popup to user instead of redirecting the user to another error page.
Upvotes: 3
Views: 6743
Reputation: 5293
The bit that comes in a default CORE app Startup > Configure
method:
app.UseExceptionHandler("/Home/Error");
Bounces any error directly to the default controller / page:
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
Which can use:
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
Which has exception
, message
, endpoint
and path
properties, so you can then log everything alongside that user-visible RequestId
for easy bug hunting in logs:
public IActionResult Error()
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
StringBuilder sb = new StringBuilder();
sb.Append($"RequestId={requestId};\r\n");
sb.Append($"Message={exceptionHandlerPathFeature.Error.Message};\r\n");
sb.Append($"Endpoint={exceptionHandlerPathFeature.Endpoint};\r\n");
sb.Append($"Path={exceptionHandlerPathFeature.Path};\r\n");
_logger.LogError(sb.ToString());
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
You could easily extend this to check the Exception
type and redirect.
Upvotes: 0
Reputation: 551
Globally Handel Exception In Asp.Net Core Web Api 3.1.5 I implement those code in asp.net core Web Api 3.1.5 it's Working For Me
public class ProblemDetails
{
public ProblemDetails();
[JsonPropertyName("detail")]
public string Detail { get; set; }
[JsonExtensionData]
public IDictionary<string, object> Extensions { get; }
[JsonPropertyName("instance")]
public string Instance { get; set; }
[JsonPropertyName("status")]
public int? Status { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//Data Base Configuration
services.AddDbContext<Context>(option => option.UseSqlServer(Configuration.GetConnectionString("XYZ")));
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// 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();
}
//else
//{
// app.UseExceptionHandler("/Error");
// // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
// app.UseHsts();
//}
app.ConfigureExceptionHandler();//This The Main Method For Handel Exception
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = errorFeature.Error;
// the IsTrusted() extension method doesn't exist and
// you should implement your own as you may want to interpret it differently
// i.e. based on the current principal
var problemDetails = new ProblemDetails
{
Instance = $"urn:myorganization:error:{Guid.NewGuid()}"
};
if (exception is BadHttpRequestException badHttpRequestException)
{
problemDetails.Title = "Invalid request";
problemDetails.Status = (int)typeof(BadHttpRequestException).GetProperty("StatusCode",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(badHttpRequestException);
problemDetails.Detail = badHttpRequestException.Message;
}
else
{
problemDetails.Title = "An unexpected error occurred!";
problemDetails.Status = 500;
problemDetails.Detail = exception.Demystify() .ToString();//Error 1
}
// log the exception etc..
context.Response.StatusCode = problemDetails.Status.Value;
context.Response.WriteJson(problemDetails, "application/problem+json");//(Error 2)
});
});
}
}
public static class ExceptionExtentions
{
private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
private static void SetStackTracesString(this Exception exception, string value)
=> stackTraceString.SetValue(exception, value);
/// <summary>
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
/// </summary>
public static T Demystify<T>(this T exception) where T : Exception
{
try
{
var stackTrace = new EnhancedStackTrace(exception);
if (stackTrace.FrameCount > 0)
{
exception.SetStackTracesString(stackTrace.ToString());
}
if (exception is AggregateException aggEx)
{
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
{
ex.Demystify();
}
}
exception.InnerException?.Demystify();
}
catch
{
// Processing exceptions shouldn't throw exceptions; if it fails
}
return exception;
}
/// <summary>
/// Gets demystified string representation of the <paramref name="exception"/>.
/// </summary>
/// <remarks>
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
/// issues if a system relies on the stack trace be in the specific form.
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
/// computes a demystified string representation and then restores the original state of the exception back.
/// </remarks>
[Pure]
public static string ToStringDemystified(this Exception exception)
=> new StringBuilder().AppendDemystified(exception).ToString();
}
public static class HttpExtensions
{
private static readonly JsonSerializer Serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore };
public static void WriteJson<T>(this HttpResponse response, T obj, string contentType = null)
{
response.ContentType = contentType ?? "application/json";
using (var writer = new HttpResponseStreamWriter(response.Body, Encoding.UTF8))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.CloseOutput = false;
jsonWriter.AutoCompleteOnClose = false;
Serializer.Serialize(jsonWriter, obj);
}
}
}
}
Upvotes: 0
Reputation: 1307
It's a long story (I used jquery for API call). First of all, I add an exception handling like this:
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; // 500 if unexpected
var result = new BaseResponseDTO<string>()
{
ErrorCode = (int)HttpStatusCode.InternalServerError,
ErrorMessage = ex.Message,
Succeed = false,
};
var jsonResult = JsonConvert.SerializeObject(result);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(jsonResult);
}
}
And then register it(It must be registered before app.UseMvc()):
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
Ok, After that, call your API. I always return DTO class like this:
public class BaseResponseDTO<T>
{
public bool Succeed { get; set; }
public string ErrorMessage { get; set; }
public T Result { get; set; }
public int? ErrorCode { get; set; }
}
And now my web API: Sometimes it returns a value and sometimes throws an exception.
public BaseResponseDTO<string> TestApi()
{
var r = new Random();
var random = r.Next(0, 2);
if (random == 0)
throw new Exception("My Exception");
else
return new BaseResponseDTO<string>() { Succeed = true, Result = "Some result..." };
}
In the end, Call it by jquery:
function callApi() {
$.ajax({
type: 'GET',
url: 'https://localhost:5001/Home/TestApi',
data: null,
dataType: 'json',
success: function (data) {
if (data.succeed) {
alert(data.result);
}
else {
alert(data.errorMessage);
}
},
error: function (error) {
debugger;
alert(error.responseJSON.ErrorMessage);
}
});
}
If Api returns an exception:
If Api returns a result:
Upvotes: 3