C# ASP.NET Web api核心:处理客户端连接问题并查找冲突 背景
我有一个web api服务器(asp.net core v2.1),它提供一些基本操作,比如管理服务器上的实体。这是接口:C# ASP.NET Web api核心:处理客户端连接问题并查找冲突 背景,c#,asp.net,asp.net-web-api,asp.net-core,C#,Asp.net,Asp.net Web Api,Asp.net Core,我有一个web api服务器(asp.net core v2.1),它提供一些基本操作,比如管理服务器上的实体。这是接口: [HttpPost] [Route("create")] public async Task<ActionResult<NewEntityResponse>> Create(CreateEntityModel model) { // 1) Validate the request. // 2) Create a new row on
[HttpPost]
[Route("create")]
public async Task<ActionResult<NewEntityResponse>> Create(CreateEntityModel model)
{
// 1) Validate the request.
// 2) Create a new row on the database
// 3) Return the new entity in response.
}
得到这样的回应:
Status 200
{
"id": "123456" // The newly created entity id
}
问题
当发送数千个这样的请求时,由于网络连接,在某个点上它将失败。当连接失败时,它会导致我们进入两种不同的情况:
- 网络呼叫在到服务器的途中结束了-在本例中,服务器不知道此请求。因此,没有创建实体。用户只需再次发送相同的消息
- 网络调用从服务器发送回客户机,但从未到达目的地——在这种情况下,请求已完全完成,但客户机对此一无所知。预期的解决方案是再次发送相同的请求。在这种情况下,它将创建相同的实体两次,这就是问题所在
冲突
到目前为止我在哪里
我想向客户端添加一个选项,向请求中添加一个唯一的id,方法如下:
POST https://example.com/create
Content-Type: application/json
{
"firstName": "Michael",
"lastName": "Jorden"
}
POST https://example.com/create?call-id=XXX
向我的服务器添加一个新的过滤器,用于检查键XXX
是否已完成。如果是,则返回冲突
。否则-继续
添加另一个服务器筛选器,用于检查方法的响应并将其标记为“已完成”,以便进一步检查
此解决方案在并发调用上的问题。如果我的方法需要5秒钟才能返回,并且客户端在1秒后再次发送相同的消息,那么它将使用相同的数据创建两个实体
问题是:
谢谢。好的,我想好了怎么做才对。所以,我自己实现了它,并与您分享 为了在不同的服务器之间同步所有请求,我使用Redis作为缓存服务。如果只有一台服务器,则可以使用
字典
此筛选器可执行以下操作:
private static bool ShouldCheck(ActionDescriptor actionDescription,IQueryCollection查询)
{
返回查询.ContainsKey(冲突密钥名称);
}
私有字符串构建密钥(字符串uid、字符串requestId)
{
返回$“{uid}{requestId}”;
}
公共重写无效OnActionExecuting(ActionExecutingContext上下文)
{
if(ShouldCheck(context.ActionDescriptor、context.HttpContext.Request.Query))
{
使用(var client=RedisConnectionPool.ConnectionPool.GetClient())
{
string key=BuildKey(context.HttpContext.User.GetId(),context.HttpContext.Request.Query[CONFLICT_key_NAME]);
字符串existing=client.Get(key);
if(现有!=null)
{
var conflict=new ContentResult();
冲突。内容=存在;
conflict.ContentType=“application/json”;
conflict.StatusCode=409;
结果=冲突;
返回;
}
其他的
{
Set(key,string.Empty,EXPIRE\u之后);
}
}
}
base.OnActionExecuting(上下文);
}
公共覆盖无效OnResultExecuted(ResultExecutedContext)
{
base.OnResultExecuted(上下文);
if(ShouldCheck(context.ActionDescriptor、context.HttpContext.Request.Query)和&context.HttpContext.Response.StatusCode==200)
{
string key=BuildKey(context.HttpContext.User.GetId(),context.HttpContext.Request.Query[CONFLICT_key_NAME]);
使用(var client=RedisConnectionPool.ConnectionPool.GetClient())
{
var responseBody=string.Empty;
if(context.Result是ObjectResult)
{
ObjectResult=context.result作为ObjectResult;
responseBody=JsonConvert.SerializeObject(result.Value);
}
if(responseBody!=string.Empty)
client.Set(key、responseBody、EXPIRE\u AFTER);
}
}
}
}?冲突检查器=XXX
时,才会执行代码
此代码是根据MIT许可证提供给您的
享受旅程:)做休息动作不是最简单的方法吗
我的意思是:调用应该检查资源是否已经存在,如果不存在,则创建一个新资源,如果存在,则返回现有资源 好问题,但是,听起来你正在发明一个新的WS-Transaction。并不是说这是一个完整或最好的解决方案。但是,您研究过dbcontext池吗?我也建议这样做,您可以轻松添加“客户端引用”,例如guid或一些哈希,当客户端重试相同的请求(使用相同的ID)时,服务器知道它已经这样做了。这些ID可以存储在redis中,以便快速访问。在某些情况下,这是一种选择,但并不总是如此。在这个策略中,用户应该知道元素的id。在我的例子中,用户在创建对象之前不知道ID…@no1lives4实际上,服务器应该检测重复项(基于资源的内容,而不是ID),并拒绝创建新的,而是返回exis
private static bool ShouldCheck(ActionDescriptor actionDescription, IQueryCollection queries)
{
return queries.ContainsKey(CONFLICT_KEY_NAME);
}
private string BuildKey(string uid, string requestId)
{
return $"{uid}_{requestId}";
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query))
{
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
string existing = client.Get<string>(key);
if (existing != null)
{
var conflict = new ContentResult();
conflict.Content = existing;
conflict.ContentType = "application/json";
conflict.StatusCode = 409;
context.Result = conflict;
return;
}
else
{
client.Set(key, string.Empty, EXPIRE_AFTER);
}
}
}
base.OnActionExecuting(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
base.OnResultExecuted(context);
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query) && context.HttpContext.Response.StatusCode == 200)
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
var responseBody = string.Empty;
if (context.Result is ObjectResult)
{
ObjectResult result = context.Result as ObjectResult;
responseBody = JsonConvert.SerializeObject(result.Value);
}
if (responseBody != string.Empty)
client.Set(key, responseBody, EXPIRE_AFTER);
}
}
}