当前位置:网站首页>ASP.NET Core Web API 幂等性
ASP.NET Core Web API 幂等性
2022-08-02 05:35:00 【KingCruel】
API的幂等性(Idempotent),是指调用某个方法1次或N次对资源产生的影响结果都是相同的。
GET请求默认是幂等的,因为它只是查询资源,而不会修改资源。
而POST请求默认是不幂等的,多次调用POST方法可能会产生不同的结果,并会创建多个资源。
想象一下,你在扫码支付时,输入金额后点击了2次“确定”按钮,肯定不希望扣2次款。
幂等性保证了操作只会执行一次。
1、思路
使用ASP.NET Core过滤器来处理POST请求,检查请求头【Headers】中的幂等键(IdempotencyKey)。
如果在缓存中未检查到IdempotencyKey,则真实执行操作并缓存响应数据,否则直接返回缓存的响应数据。
这样,操作只能对资源产生一次影响。
2、IdempotentAttributeFilter
创建自定义Filter,使用OnActionExecuting方法在执行操作前检查缓存,如有缓存直接返回context.Result;使用OnResultExecuted方法在执行操作后缓存响应。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace WebApi
{
/// <summary>
///
/// </summary>
public class IdempotentAttributeFilter : IActionFilter, IResultFilter
{
private readonly IDistributedCache _distributedCache;
private bool _isIdempotencyCache = false;
const string IdempotencyKeyHeaderName = "IdempotencyKey";
private string _idempotencyKey;
/// <summary>
///
/// </summary>
/// <param name="distributedCache"></param>
public IdempotentAttributeFilter(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
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;
}
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{
//已缓存
if (_isIdempotencyCache)
{
return;
}
var contextResult = context.Result;
DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions();
//相对过期时间
//cacheOptions.SlidingExpiration = TimeSpan.FromSeconds(10);
//绝对过期时间
cacheOptions.AbsoluteExpirationRelativeToNow = new TimeSpan(24, 0, 0);
//缓存:
_distributedCache.SetString(GetDistributedCacheKey(), JsonConvert.SerializeObject(contextResult), cacheOptions);
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
public void OnActionExecuted(ActionExecutedContext context)
{
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
}
private string GetDistributedCacheKey()
{
return "Idempotency:" + _idempotencyKey;
}
}
}
3、创建自定义Attribute
声明了IdempotentAttribute的Class或者Method,在运行时会创建IdempotentAttributeFilter。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace WebApi
{
/// <summary>
///
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class IdempotentAttribute : Attribute, IFilterFactory
{
/// <summary>
///
/// </summary>
public bool IsReusable => false;
/// <summary>
///
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var distributedCache = (IDistributedCache)serviceProvider.GetService(typeof(IDistributedCache));
var filter = new IdempotentAttributeFilter(distributedCache);
return filter;
}
}
}
4、新建ASP.NET Core Web API项目
创建 WeatherForecastController 控制器,为Post方法加上【Idempotent】
这里用一个静态变量模拟数据库,POST请求写入数据,GET请求读取数据
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace WebApi.Controllers
{
/// <summary>
///
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class WeatherForecastController : ControllerBase
{
private static List<WeatherForecast> _db = new List<WeatherForecast>();
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
/// <summary>
///
/// </summary>
public WeatherForecastController()
{
}
/// <summary>
///
/// </summary>
/// <param name="temperature"></param>
/// <returns></returns>
[Idempotent]
[HttpPost]
public WeatherForecast Post(int temperature)
{
var data = new WeatherForecast { TemperatureC = temperature };
_db.Add(data);
return data;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet()]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return _db.Select(p => new WeatherForecast
{
TemperatureC = p.TemperatureC,
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}
5、注册分布式缓存
必须增加分布式缓存,用于保存幂等键的值和响应数据。
管理 NuGet 程序包(N)...
Microsoft.Extensions.Caching.SqlServer
Microsoft.Extensions.Caching.Redis
Microsoft.Extensions.Caching.StackExchangeRedis
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//分布式 SQL Server 缓存
services.AddDistributedSqlServerCache(opt =>
{
opt.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
opt.SchemaName = "dbo";
opt.TableName = "sys_distributed_cache";
opt.DefaultSlidingExpiration = TimeSpan.FromMinutes(10);
opt.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5);
});
//分布式 Redis 缓存
services.AddDistributedRedisCache(cfg =>
{
cfg.Configuration = Configuration.GetConnectionString("RedisConnection");
});
//分布式 StackExchangeRedis 缓存
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
//分布式缓存
services.AddDistributedMemoryCache();
}
在数据库中新建一个名叫“CacheDB”的数据库,然后以管理员身份cmd运行下面指令,会创建一张名叫“CacheTable”表,相应的缓存信息都存在于这张表中。
dotnet sql-cache create <connection string> <schema> <table>
dotnet sql-cache create "Server=localhost;User=sa;Password=000000;Database=CacheDB" dbo CacheTable
成功后会提示【Table and index were created successfully】
表结构
CREATE TABLE [dbo].[CacheTable](
[Id] [nvarchar](449) NOT NULL,
[Value] [varbinary](max) NOT NULL,
[ExpiresAtTime] [datetimeoffset](7) NOT NULL,
[SlidingExpirationInSeconds] [bigint] NULL,
[AbsoluteExpiration] [datetimeoffset](7) NULL,
CONSTRAINT [pk_Id] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
CREATE NONCLUSTERED INDEX [Index_ExpiresAtTime] ON [dbo].[CacheTable]
(
[ExpiresAtTime] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
6、测试
运行Web API,使用不同IdempotencyKey执行POST请求,然后获取数据
code
POST /api/WeatherForecast?temperature=1000 HTTP/1.1
Host: localhost:8001
IdempotencyKey: 1000
*
*
边栏推荐
- 秒杀系统小demo
- MySQL - Multi-table query and case detailed explanation
- 蚂蚁三面:MQ 消息丢失、重复、积压问题,有哪些解决方案?
- Not annotated parameter overrides @NonNullApi parameter
- APP专项测试:流量测试
- 引领需求 为HR价值正名——“人力资源领先模型HRLM”成功首发
- How to perform concurrent calculation (stability test and stress test)?
- Kind of weird!Access the destination URL, the host can container but not
- flex布局(弹性布局)
- 代码编世界 科技创未来
猜你喜欢
Nodejs安装教程
zabbix邮件报警和微信报警
The installation of NPM, CNPM
MySQL Index Common Interview Questions (2022 Edition)
The stock price has repeatedly hit new lows, and the real estate SaaS giant is in trouble. How should Mingyuan Cloud transform and save itself?
How the Internet of Things is changing the efficiency of city operations
Shell 脚本不同玩法
node安装和配置(node-v12.20.2-x64 ) 以及node版本切换介绍
A list of 300+ learning resources compiled by senior engineers of the Tao Department (the latest version in 2021)
秒杀系统小demo
随机推荐
npm 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
Nodejs installation and global configuration (super detailed)
Node installation and configuration of environment variables
Tips for programmers to write PPT
Guarantee WIFI security in home and enterprise-with AC and AP networking experiment
聪明人的游戏提高篇:第三章第二课:“桐桐数”(number)
MySQL classic 50 practice questions and the most detailed analysis of the whole network
node安装及环境变量配置
Leading the demand and justifying the HR value - the successful launch of the "Human Resource Leading Model HRLM"
Luogu mini game Daquan (everyone who uses Luogu must know)
Different ways of shell scripting
MySQL - Multi-table query and case detailed explanation
Xgboost报错ValueError:无效的形状:标签(1650 2)
NPM 安装指定版本包的方法及版本号查看
金蝶国际:半年亏掉去年一年,疯狂烧钱的商业模式如何持续
MySQL高级SQL语句(二)
Toolbox App 1.25 New Features at a Glance | Version Update
[OpenCV from entry to practice] image processing technology [pixel] (the most detailed in the whole network)
DNS resolution process
C语言操作符详解(2)