Je développe une application ASP.Net Core MVC avec la vue Razor. L'application se compose de nombreux formulaires que l'utilisateur doit remplir et soumettre. J'ai une situation particulière dans laquelle je dois enregistrer toutes les exceptions qui se produisent dans l'application. Je sais qu'ASP.Net MVC Core est livré avec un middleware global de gestion des exceptions où nous pouvons attraper toutes les exceptions qui se produisent dans l'application et les enregistrer. Mais en même temps, je dois montrer un popup à l'utilisateur qu'une erreur s'est produite lors de la sauvegarde des données sur la soumission des formulaires. S'il s'agit d'un succès, alors je dois afficher une popup de succès. Si je mets un bloc try-catch dans l'action du contrôleur, je peux gérer cela, mais je dois enregistrer la même chose dans l'action elle-même. Existe-t-il un moyen de gérer toutes les exceptions en un seul endroit et d'afficher une fenêtre d'erreur à l'utilisateur au lieu de le rediriger vers une autre page d'erreur ?
Réponses
Trop de publicités?C'est une longue histoire (j'ai utilisé jquery pour l'appel API). Tout d'abord, j'ai ajouté une gestion des exceptions comme ceci :
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);
}
}
Et ensuite l'enregistrer (il doit être enregistré avant app.UseMvc()) :
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
Ok, après ça, appelez votre API. Je retourne toujours la classe DTO comme ceci :
public class BaseResponseDTO<T>
{
public bool Succeed { get; set; }
public string ErrorMessage { get; set; }
public T Result { get; set; }
public int? ErrorCode { get; set; }
}
Et maintenant mon API web : Parfois elle renvoie une valeur et parfois elle lève une 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..." };
}
En fin de compte, appelez-le par 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);
}
});
}
Si l'Api renvoie une exception :
Si l'Api renvoie un résultat :
Globalement Handel Exception In Asp.Net Core Web Api 3.1.5 J'ai implémenté ce code dans Asp.Net Core Web Api 3.1.5 et cela fonctionne pour moi.
ProblemDetail.cs
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; }
}
Ma classe Startup.cs est
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");
}
});
}
}
Et la méthode contenue dans
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)
});
});
}
}
Solution pour l'erreur 1
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();
}
Solution pour l'erreur 2
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);
}
}
}
}