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

В статье приведены примеры кода, которые необходимо будет перенести в свой проект. В конце поста - ссылка на 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
.AddControllers()
.AddJsonOptions(x =>
{
x.JsonSerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
});
// ...
}
}
После добавления этой настройки наше приложение принимает и отдает данные в JSON в формате Snake 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 тоже:

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