fix(seo): proper 500 handler + safe JSON-LD escaping
CI/CD / CI · dotnet build (push) Successful in 45s
CI/CD / Deploy · drsousan (push) Successful in 25s

Add UseExceptionHandler(/error) so unhandled exceptions return a
proper HTML 500 instead of a raw response Googlebot was logging as
a server error in Search Console.

Replace manual quote-only escaping in blog JSON-LD with a J() helper
that uses JsonSerializer so newlines, backslashes, and all other
control characters are safely escaped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-19 09:01:45 +03:30
parent 82d9720e25
commit 5a1f1a8ccb
2 changed files with 25 additions and 5 deletions
+7 -5
View File
@@ -8,6 +8,8 @@
var articleType = ViewData["ArticleType"]?.ToString() ?? "MedicalWebPage"; var articleType = ViewData["ArticleType"]?.ToString() ?? "MedicalWebPage";
var pubDate = post.PublishedAt?.ToString("yyyy-MM-ddTHH:mm:ssZ") ?? ""; var pubDate = post.PublishedAt?.ToString("yyyy-MM-ddTHH:mm:ssZ") ?? "";
var updDate = post.UpdatedAt.ToString("yyyy-MM-ddTHH:mm:ssZ"); var updDate = post.UpdatedAt.ToString("yyyy-MM-ddTHH:mm:ssZ");
// Escape a string for safe embedding inside a JSON string literal.
static string J(string? s) => System.Text.Json.JsonSerializer.Serialize(s ?? "")[1..^1];
} }
@section Head { @section Head {
@@ -32,13 +34,13 @@
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@@context": "https://schema.org", "@@context": "https://schema.org",
"@@type": "@articleType", "@@type": "@J(articleType)",
"headline": "@post.Title.Replace("\"","\\\"") ", "headline": "@J(post.Title)",
"description": "@(ViewData["MetaDesc"]?.ToString()?.Replace("\"","\\\""))", "description": "@J(ViewData["MetaDesc"]?.ToString())",
"author": { "@@type": "Person", "name": "@post.Author" }, "author": { "@@type": "Person", "name": "@J(post.Author)" },
"publisher": { "publisher": {
"@@type": "Organization", "@@type": "Organization",
"name": "@ViewData["SiteName"]", "name": "@J(ViewData["SiteName"]?.ToString())",
"url": "@baseUrl" "url": "@baseUrl"
}, },
"datePublished": "@pubDate", "datePublished": "@pubDate",
+18
View File
@@ -57,6 +57,10 @@ builder.Services.ConfigureHttpJsonOptions(opts =>
// ── Build ───────────────────────────────────────────────────────────────────── // ── Build ─────────────────────────────────────────────────────────────────────
var app = builder.Build(); var app = builder.Build();
// In production return a clean 500 page rather than an unhandled exception dump
// (Googlebot seeing raw 5xx responses causes GSC "Server error" indexing failures).
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/error");
app.UseCors(); app.UseCors();
app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages) app.UseDefaultFiles(); // serves /admin/index.html for /admin/ (wwwroot/index.html deleted → no conflict with Razor Pages)
@@ -839,6 +843,20 @@ app.MapGet("/api/seo/stats", async (AppDbContext db) =>
return Results.Ok(new { total, views, topPosts, noMeta }); return Results.Ok(new { total, views, topPosts, noMeta });
}).RequireAuthorization(); }).RequireAuthorization();
// Generic error page — returns 500 with a minimal HTML body so Googlebot
// gets a proper HTTP 500 (not a connection-reset) and retries cleanly.
app.Map("/error", (HttpContext ctx) =>
{
ctx.Response.StatusCode = 500;
ctx.Response.ContentType = "text/html; charset=utf-8";
return ctx.Response.WriteAsync(
"<!DOCTYPE html><html lang='fa'><head><meta charset='utf-8'>" +
"<title>خطای سرور</title></head><body dir='rtl'>" +
"<h1>خطای موقت سرور</h1>" +
"<p>مشکلی پیش آمده. لطفاً دقایقی دیگر مجدداً تلاش کنید.</p>" +
"</body></html>");
});
app.MapRazorPages(); app.MapRazorPages();
app.Run(); app.Run();
return 0; return 0;