From 92f68d8555f2a60a807d324cf6a12d56a8b1b5fa Mon Sep 17 00:00:00 2001 From: MahanCh Date: Wed, 25 Jun 2025 11:00:35 +0330 Subject: [PATCH] fix enum convertor for api and razor --- ServiceHost/MiddleWare/ApiJsonEnumFilter.cs | 31 ++++++++++ .../ConditionalJsonOutputFormatter.cs | 29 +++++++++ .../MiddleWare/CustomJsonResultExecutor.cs | 42 +++++++++++++ .../RazorJsonEnumOverrideMiddleware.cs | 61 +++++++++++++++++++ ServiceHost/Program.cs | 25 +++++--- 5 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 ServiceHost/MiddleWare/ApiJsonEnumFilter.cs create mode 100644 ServiceHost/MiddleWare/ConditionalJsonOutputFormatter.cs create mode 100644 ServiceHost/MiddleWare/CustomJsonResultExecutor.cs create mode 100644 ServiceHost/MiddleWare/RazorJsonEnumOverrideMiddleware.cs diff --git a/ServiceHost/MiddleWare/ApiJsonEnumFilter.cs b/ServiceHost/MiddleWare/ApiJsonEnumFilter.cs new file mode 100644 index 00000000..f577cd61 --- /dev/null +++ b/ServiceHost/MiddleWare/ApiJsonEnumFilter.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json.Serialization; +using System.Text.Json; + +namespace ServiceHost.MiddleWare; + +public class ApiJsonEnumFilter : IActionFilter +{ + public void OnActionExecuting(ActionExecutingContext context) + { + // قبل از اکشن نیازی نیست کاری کنیم + } + + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Result is ObjectResult objectResult) + { + var enumConverter = new JsonStringEnumConverter(); + var options = new JsonSerializerOptions(JsonSerializerDefaults.Web); + options.Converters.Add(enumConverter); + var json = JsonSerializer.Serialize(objectResult.Value, options); + context.Result = new ContentResult + { + Content = json, + ContentType = "application/json", + StatusCode = objectResult.StatusCode + }; + } + } +} \ No newline at end of file diff --git a/ServiceHost/MiddleWare/ConditionalJsonOutputFormatter.cs b/ServiceHost/MiddleWare/ConditionalJsonOutputFormatter.cs new file mode 100644 index 00000000..9a507d0e --- /dev/null +++ b/ServiceHost/MiddleWare/ConditionalJsonOutputFormatter.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc.Formatters; +using System.Text.Json; +using System.Text; + +namespace ServiceHost.MiddleWare; + +public class ConditionalJsonOutputFormatter : TextOutputFormatter +{ + private readonly JsonSerializerOptions _options; + + public ConditionalJsonOutputFormatter(JsonSerializerOptions options) + { + _options = options; + SupportedMediaTypes.Add("application/json"); + SupportedEncodings.Add(System.Text.Encoding.UTF8); + } + + public override bool CanWriteResult(OutputFormatterCanWriteContext context) + { + var path = context.HttpContext.Request.Path; + return path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase); + } + + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + var response = context.HttpContext.Response; + await JsonSerializer.SerializeAsync(response.Body, context.Object, context.ObjectType ?? context.Object?.GetType() ?? typeof(object), _options); + } +} \ No newline at end of file diff --git a/ServiceHost/MiddleWare/CustomJsonResultExecutor.cs b/ServiceHost/MiddleWare/CustomJsonResultExecutor.cs new file mode 100644 index 00000000..37c71417 --- /dev/null +++ b/ServiceHost/MiddleWare/CustomJsonResultExecutor.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text; + +public class CustomJsonResultExecutor : IActionResultExecutor +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + public CustomJsonResultExecutor(IHttpContextAccessor httpContextAccessor, ILogger logger) + { + _httpContextAccessor = httpContextAccessor; + _logger = logger; + } + + public async Task ExecuteAsync(ActionContext context, JsonResult result) + { + var response = context.HttpContext.Response; + response.ContentType = "application/json; charset=utf-8"; + + var requestPath = context.HttpContext.Request.Path; + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false + }; + + if (requestPath.HasValue && requestPath.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)) + { + // API → enum به صورت string + options.Converters.Add(new JsonStringEnumConverter()); + } + // Else: Razor → enum بدون converter (یعنی عدد) + + var json = JsonSerializer.Serialize(result.Value, options); + + await response.WriteAsync(json, Encoding.UTF8); + } +} \ No newline at end of file diff --git a/ServiceHost/MiddleWare/RazorJsonEnumOverrideMiddleware.cs b/ServiceHost/MiddleWare/RazorJsonEnumOverrideMiddleware.cs new file mode 100644 index 00000000..f0d3cd23 --- /dev/null +++ b/ServiceHost/MiddleWare/RazorJsonEnumOverrideMiddleware.cs @@ -0,0 +1,61 @@ +using System.Text.Json; +using System.Text; + +namespace ServiceHost.MiddleWare; + +public class RazorJsonEnumOverrideMiddleware +{ + private readonly RequestDelegate _next; + + public RazorJsonEnumOverrideMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // نگه‌دار خروجی + var originalBody = context.Response.Body; + using var newBody = new MemoryStream(); + context.Response.Body = newBody; + + await _next(context); // اجرای مرحله بعدی + + // فقط برای مسیرهای غیر /api و وقتی Content-Type = application/json + if (!context.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase) && + context.Response.ContentType?.Contains("application/json", StringComparison.OrdinalIgnoreCase) == true) + { + newBody.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(newBody); + var originalJson = await reader.ReadToEndAsync(); + + // JSON رو deserialize و دوباره serialize می‌کنیم بدون EnumConverter + try + { + var obj = JsonSerializer.Deserialize(originalJson); + var newJson = JsonSerializer.Serialize(obj, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + context.Response.Body = originalBody; + context.Response.ContentLength = Encoding.UTF8.GetByteCount(newJson); + await context.Response.WriteAsync(newJson); + return; + } + catch + { + // fallback if deserialization fails + context.Response.Body = originalBody; + newBody.Seek(0, SeekOrigin.Begin); + await newBody.CopyToAsync(originalBody); + return; + } + } + + // اگر شرط نداشت، همون خروجی قبلی رو منتقل کن + context.Response.Body = originalBody; + newBody.Seek(0, SeekOrigin.Begin); + await newBody.CopyToAsync(originalBody); + } +} \ No newline at end of file diff --git a/ServiceHost/Program.cs b/ServiceHost/Program.cs index ec893e05..dd904273 100644 --- a/ServiceHost/Program.cs +++ b/ServiceHost/Program.cs @@ -21,6 +21,10 @@ using _0_Framework.Application.UID; using _0_Framework.Exceptions.Handler; using Microsoft.OpenApi.Models; using ServiceHost.Test; +using System.Text.Json.Serialization; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(args); @@ -51,6 +55,7 @@ var connectionStringTestDb = builder.Configuration.GetConnectionString("TestDb") //builder.Services.AddSingleton(mongoDatabase); #endregion +builder.Services.AddSingleton, CustomJsonResultExecutor>(); PersonalBootstrapper.Configure(builder.Services, connectionString); TestDbBootStrapper.Configure(builder.Services, connectionStringTestDb); AccountManagementBootstrapper.Configure(builder.Services, connectionString); @@ -142,11 +147,15 @@ builder.Services.AddAuthorization(options => // }); -builder.Services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); - }); +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); +}); + +//builder.Services.AddControllers( +//options=> { +// options.Filters.Add(new ApiJsonEnumFilter()); +//}); builder.Services.AddRazorPages(options => @@ -181,8 +190,9 @@ builder.Services.AddSignalR(); #region Swagger builder.Services.AddSwaggerGen(options => { - - var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.UseInlineDefinitionsForEnums(); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); @@ -321,6 +331,7 @@ app.UseAuthorization(); //app.UseLoginHandlerMiddleware(); //app.UseCheckTaskMiddleware(); +app.UseMiddleware(); #endregion