TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #2540 2.46K
День 2099. #ЗаметкиНаПолях
Реализация Идемпотентных POST-запросов в
ASP.NET Core API
Идемпотентность — важнейшая концепция REST API, которая обеспечивает надёжность и согласованность системы, особенно в распределённых системах, где сетевые проблемы могут привести к повторным запросам. Реализуя идемпотентность, вы предотвращаете дублирование операций, которое может возникнуть из-за повторных попыток клиента.

POST-запросы изначально не являются идемпотентными, но мы можем реализовать идемпотентность для них. Например, проверка существования ресурсов перед созданием гарантирует, что повторные запросы не приведут к дублированию действий или ресурсов.

Мы будем использовать стратегию с использованием ключей идемпотентности:
1. Клиент генерирует уникальный ключ для каждой операции и отправляет его в пользовательском заголовке.
2. Сервер проверяет, видел ли он этот ключ раньше:
- ключ новый - обработать запрос и сохранить результат,
- ключ не новый - вернуть сохраненный результат без повторной обработки.
Это гарантирует, что повторные запросы (например, из-за проблем с сетью) будут обработаны на сервере только один раз.

Примечание: если запрос не удается (возвращает 4xx/5xx), мы не кэшируем ответ. Это позволяет клиентам повторять попытки с тем же ключом идемпотентности. Однако это означает, что неудавшийся запрос, за которым следует успешный запрос с тем же ключом, будет успешным — убедитесь, что это соответствует вашим бизнес-требованиям.

Мы можем реализовать идемпотентность для контроллеров с помощью атрибута фильтра действия, реализовав IAsyncActionFilter:
[AttributeUsage(AttributeTargets.Method)]
internal sealed class IdempotentAttribute :
Attribute, IAsyncActionFilter
{
private readonly TimeSpan _cacheMins;

public IdempotentAttribute(int cacheMins = 60)
{
_cacheMins = TimeSpan.FromMinutes(cacheMins);
}

public async Task OnActionExecutionAsync(
ActionExecutingContext ctx,
ActionExecutionDelegate next)
{
// проверяем, передал ли клиент ключ
if (!ctx.HttpContext.Request
.Headers.TryGetValue(
"Idempotence-Key",
out var keyValue) ||
!Guid.TryParse(keyValue, out Guid key))
{
ctx.Result = new BadRequestObjectResult(
"Отсутствует заголовок Idempotence-Key");
return;
}

var cache = ctx.HttpContext
.RequestServices
.GetRequiredService<IDistributedCache>();

// проверяем, обработан ли ключ
var cacheKey = $"Idempotent_{key}";
var cachedResult = await
cache.GetStringAsync(cacheKey);
if (cachedResult is not null)
{
var response = JsonSerializer
.Deserialize<IdempotentResponse>(cachedResult);

var result = new ObjectResult(response.Value) {
StatusCode = response.StatusCode };

ctx.Result = result;

return;
}

// выполняем действие и кэшируем результат
var executedContext = await next();

if (executedContext.Result is
ObjectResult { StatusCode: >= 200 and < 300 } objRes)
{
int status = objRes.StatusCode
?? StatusCodes.Status200OK;
IdempotentResponse response =
new(status, objRes.Value);

await cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(response),
new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = _cacheMins }
);
}
}
}

internal sealed class IdempotentResponse
{
[JsonConstructor]
public IdempotentResponse(
int status, object? value)
{
StatusCode = status;
Value = value;
}

public int StatusCode { get; }
public object? Value { get; }
}

Теперь можно применить атрибут к методу контроллера:
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
[Idempotent(cacheMins: 60)]
public IActionResult Create(
[FromBody] CreateOrderRequest request)
{
// обработка заказа…

return CreatedAtAction(nameof(GetOrder),
new { id = orderDto.Id }, orderDto);
}
}


Окончание следует…

Источник
  • 👍 28
More from @netdeveloperdiary
  1. Oct 7, 2026День 2807. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Окончание Нач…
  2. Oct 6, 2026🦈 Открытое собеседование на Middle C# | 6 октября, 19:00 МСК Приглашаем на открытое собес…
  3. Oct 6, 2026День 2806. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Начало Больши…
  4. Oct 5, 2026День 2805. #ЧтоНовенького #NET11 Аргументы в Выражениях Коллекций в C#15 В C#15 реализован…
  5. Oct 4, 2026День 2804. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как тех…
  6. Oct 3, 2026Post #3360
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →