From 6ecfd8c38adedc642ba37188e1077a30ad7826db Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Sat, 3 Oct 2026 19:38:56 +0330 Subject: [PATCH] people tags #387 --- .../Areas/Admin/Pages/ReviewEdits.cshtml.cs | 16 +++++++++++- .../Services/Implementation/GanjoorService.cs | 16 ++++++++++++ RMuseum/Startup.cs | 26 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs b/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs index c500898b..546a48b3 100644 --- a/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs +++ b/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs @@ -635,7 +635,21 @@ namespace GanjooRazor.Areas.Admin.Pages } else { - err = JsonConvert.DeserializeObject(err); + try + { + // normal case: the API returns the error as a JSON-encoded string (see + // OnPostDeletePoemCorrectionsAsync in Editor.cshtml.cs for the same + // pattern). Guard against it not being one - e.g. an HTML error page + // from a proxy/host in front of the API - so that doesn't throw an + // unhandled exception here and surface as GanjooRazor's own generic HTML + // error page, which is unreadable and leaves the real error only in the + // Windows Event Log. + err = JsonConvert.DeserializeObject(err); + } + catch (JsonException) + { + err = "خطایی در سرور رخ داد. لطفاً بعداً دوباره تلاش کنید."; + } } return new BadRequestObjectResult(err); } diff --git a/RMuseum/Services/Implementation/GanjoorService.cs b/RMuseum/Services/Implementation/GanjoorService.cs index 7fe283e3..a9aa3117 100644 --- a/RMuseum/Services/Implementation/GanjoorService.cs +++ b/RMuseum/Services/Implementation/GanjoorService.cs @@ -2076,6 +2076,17 @@ namespace RMuseum.Services.Implementation /// public async Task> SuggestPoemCorrection(GanjoorPoemCorrectionViewModel correction) { + // wrapped in try/catch (matching ModeratePoemCorrection's own pattern) so an unexpected + // exception - e.g. the EF Core "same key value is already being tracked" conflict this + // method used to throw on a second save - comes back as a normal RServiceResult.ExceptionString + // instead of an unhandled exception. Left uncaught, it would bypass GanjoorController's own + // "if (!string.IsNullOrEmpty(res.ExceptionString)) return BadRequest(...)" check entirely + // (that line is never reached because this method never returns), fall through to the + // framework's generic HTML error page, and show up to the user as unreadable HTML source + // instead of a readable message - with the real exception visible only in the Windows Event + // Log. + try + { if (!string.IsNullOrEmpty(correction.Rhythm3) || !string.IsNullOrEmpty(correction.Rhythm4)) return new RServiceResult(null, "انتساب وزن سوم و چهارم هنوز پیاده‌سازی نشده است."); @@ -2263,6 +2274,11 @@ namespace RMuseum.Services.Implementation } return new RServiceResult(correction); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } } /// diff --git a/RMuseum/Startup.cs b/RMuseum/Startup.cs index 8987f8d9..f2f79d4f 100644 --- a/RMuseum/Startup.cs +++ b/RMuseum/Startup.cs @@ -3,6 +3,7 @@ using Betalgo.Ranul.OpenAI.Extensions; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -16,6 +17,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi; +using Newtonsoft.Json; using RMuseum.DbContext; using RMuseum.Models.Auth.Memory; using RMuseum.Services; @@ -358,7 +360,29 @@ namespace RMuseum } else { - app.UseExceptionHandler("/Error"); + // RMuseum is API-only (see UseEndpoints below - MapControllers only), so there is no + // Razor "/Error" page or MVC "Error" action for the old app.UseExceptionHandler("/Error") + // to redirect to - that redirect just 404s, and (especially when hosted behind IIS/ANCM, + // as in production here) a 404 with no body of its own can get replaced by IIS's own + // generic HTML error page instead. Either way, callers - including GanjooRazor's own + // server-side page handlers, which otherwise assume every error body is a JSON-encoded + // string - got back unreadable HTML instead of the real exception, visible only by + // digging through the Windows Event Log. Handling the exception directly here instead + // of redirecting anywhere guarantees a small JSON-string body with the real exception + // message, in the exact same shape a normal RServiceResult.ExceptionString error already + // comes back as (see e.g. GanjoorController's "return BadRequest(res.ExceptionString)"), + // so every existing client-side error handler keeps working unchanged. + app.UseExceptionHandler(errApp => + { + errApp.Run(async context => + { + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + context.Response.ContentType = "application/json; charset=utf-8"; + var exceptionFeature = context.Features.Get(); + var message = exceptionFeature?.Error?.ToString() ?? "خطای غیرمنتظره‌ای در سرور رخ داد."; + await context.Response.WriteAsync(JsonConvert.SerializeObject(message)); + }); + }); }