Как изменить формат данных JSON на Snake Case в ASP.NET Core Web API

от автора

Стандартный способ отображения данных в ASP.NET Web API — это Camel Case. Но иногда возникают задачи, когда нужно изменить формат данных на нечто другое. Например, на фронтенде у вас может быть SPA, которое как раз работает с данными в формате snake case. В этой статье я покажу, как изменить формат сериализации в ASP.NET Core Web API.

Camel case vs Snake case
Camel case vs Snake case

В статье приведены примеры кода, которые необходимо будет перенести в свой проект. В конце поста — ссылка на Github репозиторий, где я уже настроил приложение на сериализацию в snake case. Все примеры кода и проект в репозитории написаны на ASP.NET Core версии .net5.

Меняем формат сериализации запросов и ответов сервера

Все, что нам нужно сделать для изменения сериализации, это установить Naming Policy в настройках приложения. Стандартный полиси — это Camel Case. Установка полиси на Snake Case — задача несложная.

Сначала добавим утилитарные методы для трансформации строк к snake case:

using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Utils.Helpers;  namespace YourNamespace {     public static class JsonSerializationExtensions     {         private static readonly SnakeCaseNamingStrategy _snakeCaseNamingStrategy             = new SnakeCaseNamingStrategy();          private static readonly JsonSerializerSettings _snakeCaseSettings = new JsonSerializerSettings         {             ContractResolver = new DefaultContractResolver             {                 NamingStrategy = _snakeCaseNamingStrategy             }         };          public static string ToSnakeCase(this T instance)         {             if (instance == null)               {                    throw new ArgumentNullException(paramName: nameof(instance));                }              return JsonConvert.SerializeObject(instance, _snakeCaseSettings);         }          public static string ToSnakeCase(this string @string)         {             if (@string == null)               {                    throw new ArgumentNullException(paramName: nameof(@string));                }              return _snakeCaseNamingStrategy.GetPropertyName(@string, false);         }     } } 

Здесь мы добавляем пару полезных методов: первая перегрузка метода нам пригодится для применения на объектах, вторая — для строкового значения. Мы используем тут класс SnakeCaseNamingStrategy для трансформации строк. Эти методы понадобятся нам в реализации нашего Naming Policy: 

using System.Text.Json; using Utils.Serialization;  namespace YourNamespace {     public class SnakeCaseNamingPolicy : JsonNamingPolicy     {         public override string ConvertName(string name) => name.ToSnakeCase();     } } 

Здесь мы как раз используем метод-экстеншн ToSnakeCase() для трансформации. Инстанс класса SnakeCaseNamingPolicy мы будем использовать в Startup.cs в методе ConfigureServices:

public class Startup {   public void ConfigureServices(IServiceCollection services)   {     // ...     services         .AddMvc()         .AddJsonOptions(x =>         {             x.JsonSerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();         });     // ...   } } 

Несмотря на то, что у нас Web API, мы используем метод .AddMvc() вместо .AddControllers(), так как у последнего нет возможности переопределять способ сериализации. Работать наш Web API будет и в рамках MVC.

После добавления этой настройки наше приложение принимает и отдает данные в JSON в формате Snake Case:

Данные в Snake Case формате
Данные в Snake Case формате

Но когда у нас возникнет ошибка валидация входящих данных, мы обнаружим, что…

Формат выдачи ошибок валидации до сих пор в Camel Case
Формат выдачи ошибок валидации до сих пор в Camel Case

Скрин выше — это то, как будет отображена ошибка валидации. Как мы видим, она до сих пор в формате Camel Case, даже с примененными нами настройками. Более того, поля класса FirstName и LastName написаны уже в формате Pascal Case, а мы принимаем и отдаем их в Snake Case. Нам такое поведение не подходит, будем исправлять.

Меняем формат выдачи ошибок валидации

Чтобы это сделать, нам нужно заменить стандартную «фабрику ответов» в ASP на свою собственную. Сначала создадим класс, который и будет формировать нам структуру ошибок:

using System; using System.Collections.Generic; using System.Net; using Microsoft.AspNetCore.Mvc;  namespace YourNamespace {     public class ValidationProblemDetails : ProblemDetails     {         // 400 status ccode is usually used for input validation errors         public const int ValidationStatusCode = (int)HttpStatusCode.BadRequest;          public ValidationProblemDetails(ICollection validationErrors)         {             ValidationErrors = validationErrors;             Status = ValidationStatusCode;             Title = "Request Validation Error";         }          public ICollection ValidationErrors { get; }          public string RequestId => Guid.NewGuid().ToString();     } } 

Этот класс принимает список ошибок, который и будет отображен в JSON. Класс наследуется от ProblemDetails из неймспейса Microsoft.AspNetCore.Mvc. Поле RequestId нам поможет найти конкретную запись ошибки в логах при просмотре через наш UI мониторинга.

Затем нам нужно создать класс для замены стандартного InvalidModelStateResponseFactory:

using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Utils.Serialization;  namespace YourNamespace {     public class ValidationProblemDetailsResult : IActionResult     {         public async Task ExecuteResultAsync(ActionContext context)         {             var modelStateEntries = context.ModelState                 .Where(e => e.Value.Errors.Count > 0)                 .ToArray();              var errors = new List();              if (modelStateEntries.Any())             {                 foreach (var (key, value) in modelStateEntries)                 {                     errors.AddRange(value.Errors                         .Select(modelStateError => new ValidationError(                             name: key.ToSnakeCase(),                             description: modelStateError.ErrorMessage)));                 }             }              await new JsonErrorResponse(                 context: context.HttpContext,                 error: new ValidationProblemDetails(errors),                 statusCode: ValidationProblemDetails.ValidationStatusCode).WriteAsync();         }     } } 

Напоследок добавим замену механизма формирования ошибок в классе Startup.cs:

public class Startup {    // ...   public void ConfigureServices(IServiceCollection services)   {     // ...     services         .Configure(x =>         {             x.InvalidModelStateResponseFactory = ctx => new ValidationProblemDetailsResult();         });     // ...   } } 

И теперь наши ошибки сериализуются в Snake Case тоже:

Структура ошибки в формате Snake Case
Структура ошибки в формате Snake Case

После всех изменений наше приложение теперь не только отдает и принимает JSON данные в формате Snake Case, но и показывает валидационные ошибки тоже в том виде, в котором нам нужно. Здесь по ссылке вы можете открыть Github репозиторий, где есть пример настроенного приложения. По описанным шагам вы можете применить не только Snake Case, но и любой другой формат сериализации данных, который вам по душе.

ссылка на оригинал статьи https://habr.com/ru/post/543370/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *