Предотвращение повторной отправки API ASP.NET Core Web API с помощью блокировки - PullRequest
0 голосов
/ 28 декабря 2018

Я пытаюсь заблокировать дубликат запроса от клиента на внутренней стороне.(Этот вопрос касается игнорирования действий поддержки на стороне клиента, таких как: отключение кнопок, отображение экрана загрузки, Google re-captcha, ...)

Вот моя идея:

  • Объявите ConcurrentDictionary<string, bool> как синглтон.
  • Создайте один API [GET] api/survey/token для создания уникального токена на внутреннем сайте и возврата клиенту.
  • Сохраните вновь сгенерированный уникальныйтокен для ConcurrentDictionary<string, bool>.

Когда пользователь отправляет информацию

  • Создайте API [POST] api/survey для отправки информации опроса (имя,описание и уникальный токен )
  • Проверьте, находится ли уникальный токен в словаре или нет.

    • Если токеннаходится в словаре:

      • Удалить токен из словаря.
      • Сохранить информацию в базе данных.
    • Если токен не находится всловарь.

      • Вернуть ответ об ошибке клиенту.

Вот моя реализация:

[Route("api/[controller]")]
    public class SurveyController : ControllerBase
    {
        #region Properties

        private readonly ConcurrentDictionary<string, DateTime> _concurrentDictionary;

        #endregion

        #region Constructor

        public SurveyController(ConcurrentDictionary<string, DateTime> concurrentDictionary)
        {
            _concurrentDictionary = concurrentDictionary;
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Generate token asynchronously.
        /// </summary>
        /// <returns></returns>
        [HttpGet("token")]
        public virtual IActionResult GetSubmissionTokenAsync()
        {
            var uniqueToken = Guid.NewGuid().ToString("D");
            var generateUniqueTokenModel = new GenerateSurveyTokenResultViewModel();
            generateUniqueTokenModel.UniqueToken = uniqueToken;

            var dateTime = DateTime.UtcNow.Subtract(TimeSpan.FromMilliseconds(500));

            _concurrentDictionary.TryAdd(uniqueToken, dateTime);

            return Ok(generateUniqueTokenModel);
        }

        /// <summary>
        /// Add survey asynchronously.
        /// </summary>
        /// <returns></returns>
        [HttpPost("")]
        public virtual async Task<IActionResult> AddSurveyAsync([FromBody] AddSurveyViewModel model)
        {
            // Validate model.
            if (model == null)
            {
                model = new AddSurveyViewModel();
                TryValidateModel(model);
            }

            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            var apiMessage = new ApiMessageViewModel();

            lock (_concurrentDictionary)
            {
                // Check whether token is registered before or not.
                if (!_concurrentDictionary.TryGetValue(model.UniqueToken, out var dateTime))
                {
                    apiMessage.Message = "INVALID_SUBMISSION_TOKEN";
                    return NotFound(apiMessage);
                }

                // Remove the registered unique token.
                _concurrentDictionary.TryRemove(model.UniqueToken, out var expiredTime);
            }

            await Task.Run(() =>
            {
                Thread.Sleep(2000);
            });

            return Ok();
        }

        #endregion
    }

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

Мой вопрос:

  • Должна ли блокировка влиять на производительность системы, когда тысячи запросов поступают на мой сервер API?

  • Существуют ли более эффективные способы архивации цели, которые предотвращают повторное представление формы?

**** ОБНОВЛЕНИЕ *****

У меня естьреализован атрибут с именем RequestCacheAttribute

public class RequestCacheAttribute : ActionFilterAttribute
    {
        #region Properties

        //private readonly ConcurrentDictionary<string, DateTime> _concurrentDictionary;

        #endregion

        #region Constructor

        #endregion

        #region Methods

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var availableTokens = (ConcurrentDictionary<string, DateTime>)context.HttpContext.RequestServices.GetService(typeof(ConcurrentDictionary<string, DateTime>));
            var eTag = context.HttpContext.Request.Headers[HeaderNames.ETag];
            Debug.WriteLine($"ETag = {eTag}");

            if (string.IsNullOrEmpty(eTag))
            {
                context.Result = new NotFoundResult();
                return;
            }

            lock (availableTokens)
            {
                if (!availableTokens.TryGetValue(eTag, out var dateTime))
                {
                    context.Result = new NotFoundResult();
                    return;
                }

                availableTokens.TryRemove(eTag, out var date);
            }


            base.OnActionExecuting(context);
        }

В этом атрибуте, прежде чем перейти к API [POST] api/survey для создания информации опроса, я блокирую ConcurrentDictionary, проверяю уникальный токен доступен или нет.Если доступно, разрешите пользователю войти в API и удалите уникальный токен из ConcurrentDictionary, в противном случае запретите пользователю доступ к API.

Это плохо для производительности?

Спасибо

...