Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/asp.net/37.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# ASP.NET Web api核心:处理客户端连接问题并查找冲突 背景_C#_Asp.net_Asp.net Web Api_Asp.net Core - Fatal编程技术网

C# ASP.NET Web api核心:处理客户端连接问题并查找冲突 背景

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

我有一个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 the database
    // 3) Return the new entity in response.
}
得到这样的回应:

Status 200

{
    "id": "123456" // The newly created entity id
}
问题 当发送数千个这样的请求时,由于网络连接,在某个点上它将失败。当连接失败时,它会导致我们进入两种不同的情况:

  • 网络呼叫在到服务器的途中结束了-在本例中,服务器不知道此请求。因此,没有创建实体。用户只需再次发送相同的消息
  • 网络调用从服务器发送回客户机,但从未到达目的地——在这种情况下,请求已完全完成,但客户机对此一无所知。预期的解决方案是再次发送相同的请求。在这种情况下,它将创建相同的实体两次,这就是问题所在
请求的解决方案 我想为web api创建一个通用解决方案,该解决方案“remmeber”命令它已经完成了。如果他两次收到相同的请求,则返回HTTP状态码
冲突

到目前为止我在哪里 我想向客户端添加一个选项,向请求中添加一个唯一的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作为缓存服务。如果只有一台服务器,则可以使用
    字典

    此筛选器可执行以下操作:

  • 在处理请求之前-向Redis添加一个新的空值键
  • 服务器处理请求后,将服务器响应存储在Redis中。当用户再次请求相同的请求时,将使用此数据

    公共类冲突筛选器:ActionFilterAttribute { 常量字符串冲突\u KEY\u NAME=“冲突检查器”; 静态只读TimeSpan EXPIRE\u AFTER=TimeSpan.FromMinutes(30)

    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);
            }
        }
    }