一键搞定ASP.NET Core Web API幂等性

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一键搞定ASP.NET Core Web API幂等性相关的知识,希望对你有一定的参考价值。

API的幂等性(Idempotent),是指调用某个方法1次或N次对资源产生的影响结果都是相同的。

GET请求默认是幂等的,因为它只是查询资源,而不会修改资源。

而POST请求默认是不幂等的,多次调用POST方法可能会产生不同的结果,并会创建多个资源。

想象一下,你在扫码支付时,输入金额后点击了2次“确定”按钮,肯定不希望扣2次款。

幂等性保证了操作只会执行一次。

1.思路

使用ASP.NET Core过滤器来处理POST请求,检查请求头中的幂等键(IdempotencyKey)

如果在缓存中未检查到IdempotencyKey,则真实执行操作并缓存响应数据,否则直接返回缓存的响应数据。

这样,操作只能对资源产生一次影响。

原理示意图如下:

2.实现

2.1 IdempotentAttributeFilter

创建自定义Filter。

使用OnActionExecuting方法在执行操作前检查缓存,如有缓存直接返回context.Result;使用OnResultExecuted方法在执行操作后缓存响应。

代码如下:

public class IdempotentAttributeFilter : IActionFilter, IResultFilter
{
    private readonly IDistributedCache _distributedCache;
    private bool _isIdempotencyCache= false;
    const string IdempotencyKeyHeaderName = "IdempotencyKey";
    private string _idempotencyKey;
    public IdempotentAttributeFilter(IDistributedCache distributedCache)
    {
        _distributedCache = distributedCache;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        Microsoft.Extensions.Primitives.StringValues idempotencyKeys;
        context.HttpContext.Request.Headers.TryGetValue(IdempotencyKeyHeaderName, out idempotencyKeys);
        _idempotencyKey = idempotencyKeys.ToString();

        var cacheData = _distributedCache.GetString(GetDistributedCacheKey());
        if (cacheData != null)
        {
            context.Result = JsonConvert.DeserializeObject<ObjectResult>(cacheData);
            _isIdempotencyCache = true;
            return;
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        //已缓存
        if (_isIdempotencyCache)
        {
            return;
        }

        var contextResult = context.Result;
        
        DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions();
        cacheOptions.AbsoluteExpirationRelativeToNow = new TimeSpan(24, 0, 0);

        //缓存:
        _distributedCache.SetString(GetDistributedCacheKey(), JsonConvert.SerializeObject(contextResult), cacheOptions);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
    }

    private string GetDistributedCacheKey()
    {
        return "Idempotency:" + _idempotencyKey;
    }
}

2.2 IdempotentAttribute

创建自定义Attribute。

声明了IdempotentAttribute的Class或者Method,在运行时会创建IdempotentAttributeFilter

代码如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class IdempotentAttribute : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        var distributedCache = (IDistributedCache)serviceProvider.GetService(typeof(IDistributedCache));
        
        var filter = new IdempotentAttributeFilter(distributedCache);
        return filter;
    }
}

3.使用

3.1 创建项目

新建ASP.NET Core Web API项目,实现代码如下:

private static List<WeatherForecast> _db = new List<WeatherForecast>();

[HttpPost]
public WeatherForecast Post(int temperature)
{
    var data = new WeatherForecast { TemperatureC = temperature };
    _db.Add(data);

    return data;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    return _db.Select(p => new WeatherForecast
    {
        TemperatureC = p.TemperatureC,
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
}

这里用一个静态变量模拟数据库,POST请求写入数据,GET请求读取数据。

3.2 设置幂等

为Post方法加上Idempotent Attribute:

[Idempotent]
public WeatherForecast Post(int temperature)

3.3 注册分布式缓存

从上面的原理图我们可以看到,必须增加分布式缓存,用于保存幂等键的值和响应数据。

修改Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDistributedMemoryCache();
}

3.4 测试

运行Web API,使用不同IdempotencyKey执行POST请求,然后获取数据:

可以看到,同一IdempotencyKey执行了2次,但是只写入了一条数据,成功!

结论

为了确保关键Web API的高可用性和业务连续性,实现幂等性是重要的一步。

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“,记住我

以上是关于一键搞定ASP.NET Core Web API幂等性的主要内容,如果未能解决你的问题,请参考以下文章

将文件从 ASP.NET Core Web api 发布到另一个 ASP.NET Core Web api

ASP.NET Core Web Api 自动帮助页面

ASP.NET Core Web API

Asp.Net Core 1.1 消费web api

使用 ASP.NET Core MVC 创建 Web API

如何从 ASP.NET MVC 到 ASP.NET Core Web API 的 PUT?