public async Task Load_Invalid(string sort, string order, string search, int limit, int offset, string extraParam)
        {
            // Arrange
            var data = new HashesDataTableLoadInput
            {
                Sort       = sort,
                Order      = order,
                Search     = search,
                Limit      = limit,
                Offset     = offset,
                ExtraParam = extraParam,
            }.ToDictionary();

            using (var content = new FormUrlEncodedContent(data))
            {
                var queryString = await content.ReadAsStringAsync();

                // Act
                using (HttpResponseMessage response =
                           await _client.GetAsync($"{_fixture.AppRootPath}{VirtualScrollController.ASPX}/{nameof(HashesDataTableController.Load)}?{queryString}",
                                                  HttpCompletionOption.ResponseContentRead))
                {
                    // Assert
                    Assert.NotNull(response);
                    Assert.False(response.IsSuccessStatusCode);
                    Assert.NotEqual(HttpStatusCode.OK, response.StatusCode);
                    Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
                }
            }
        }
        public async Task <IActionResult> Load(HashesDataTableLoadInput input)
        {
            if (!ModelState.IsValid)
            {
#if DEBUG
                //_logger.LogWarning("!!!!!!!validation error" + Environment.NewLine +
                //	ModelState.Values.Where(m => m.ValidationState != Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
                //	.SelectMany(m => m.Errors)
                //	.Select(m => m.ErrorMessage + Environment.NewLine)
                //	.Aggregate((me, me1) => me1 + " " + me));
#endif
                return(BadRequest(ModelState));
            }

            CancellationToken token = HttpContext.RequestAborted;
            try
            {
                //await Task.Delay(2_000, token);

                var found = await _repo.PagedSearchAsync(input.Sort, input.Order, input.Search, input.Offset, input.Limit, token);

                var result = new
                {
                    total = found.Count,
                    rows  = found.Itemz                   //.Select(x => new string[] { x.Key, x.HashMD5, x.HashSHA256 })
                };

                if (input.ExtraParam == "cached" && found.Itemz.Count() > 0)
                {
                    HttpContext.Response.GetTypedHeaders().CacheControl =
                        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
                    {
                        Public = true,
                        MaxAge = HashesRepository.HashesInfoExpirationInMinutes
                    };
                }

                return(Json(result, _serializationSettings));
            }
            catch (OperationCanceledException ex)
            {
                _logger.LogWarning(ex, $"!!!!!!!!!!!!!!!Cancelled {nameof(Load)}::{nameof(_repo.SearchAsync)}" +
                                   $"({input.Sort}, {input.Order}, {input.Search}, {input.Offset}, {input.Limit}, {token})");
                return(Ok());
            }
            catch (Exception)
            {
                throw;
            }
        }
        public async Task <IActionResult> Stream(HashesDataTableLoadInput input)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            CancellationToken token = HttpContext.RequestAborted;

            try
            {
                //await Task.Delay(2_000, token);

                var found = await _repo.PagedSearchAsync(input.Sort, input.Order, input.Search, input.Offset, input.Limit, token);

                var sb = new StringBuilder(found.Count * 113).AppendFormat("{{ \"total\": {0} }}", found.Count);
                //string separator = string.Empty;
                foreach (var item in found.Itemz)
                {
                    sb.AppendFormat("{{ \"arr\": [Key:\"{0}\",HashMD5:\"{1}\",HashSHA256:\"{2}\"] }}", item.Key, item.HashMD5, item.HashSHA256 /*, separator*/);
                    //separator = ";";
                }
                string result = sb.ToString();

                if (input.ExtraParam == "cached" && found.Itemz.Count() > 0)
                {
                    HttpContext.Response.GetTypedHeaders().CacheControl =
                        new Microsoft.Net.Http.Headers.CacheControlHeaderValue
                    {
                        Public = true,
                        MaxAge = HashesRepository.HashesInfoExpirationInMinutes
                    };
                }

                return(Content(result, "text/plain"));
            }
            catch (OperationCanceledException ex)
            {
                _logger.LogWarning(ex, $"!!!!!!!!!!!!!!!Cancelled {nameof(Load)}::{nameof(_repo.SearchAsync)}" +
                                   $"({input.Sort}, {input.Order}, {input.Search}, {input.Offset}, {input.Limit}, {token})");
                return(Ok());
            }
            catch (Exception)
            {
                throw;
            }
        }
        public async Task <IActionResult> Load(HashesDataTableLoadInput input)
        {
            if (!ModelState.IsValid)
            {
                return(BadRequest(ModelState));
            }

            CancellationToken token = HttpContext.RequestAborted;

            try
            {
                //await Task.Delay(2_000, token);

                var found = await _repo.PagedSearchAsync(input.Sort, input.Order, input.Search, input.Offset, input.Limit, token);

                var result = new
                {
                    total = found.Count,
                    rows  = found.Itemz                   //.Select(x => new string[] { x.Key, x.HashMD5, x.HashSHA256 })
                };

                if (input.ExtraParam == "cached" && found.Itemz.Count() > 0)
                {
                    HttpContext.Response.GetTypedHeaders().CacheControl =
                        new Microsoft.Net.Http.Headers.CacheControlHeaderValue
                    {
                        Public = true,
                        MaxAge = HashesRepository.HashesInfoExpirationInMinutes
                    };
                }

                return(Json(result /*, _serializationSettings*/));
            }
            catch (OperationCanceledException ex)
            {
                _logger.LogWarning(ex, $"!!!!!!!!!!!!!!!Cancelled {nameof(Load)}::{nameof(_repo.SearchAsync)}" +
                                   $"({input.Sort}, {input.Order}, {input.Search}, {input.Offset}, {input.Limit}, {token})");
                return(Ok());
            }
            catch (Exception)
            {
                throw;
            }
        }
        public async Task <int> Load_Valid(string sort, string order, string search, int limit, int offset, string extraParam)
        {
            if (_fixture.DOTNET_RUNNING_IN_CONTAINER)
            {
                return(0);                                                 //pass on fake DB with no data
            }
            // Arrange
            var query_input = new HashesDataTableLoadInput
            {
                Sort       = sort,
                Order      = order,
                Search     = search,
                Limit      = limit,
                Offset     = offset,
                ExtraParam = extraParam,
            }.ToDictionary();

            using (var content = new FormUrlEncodedContent(query_input))
            {
                var queryString = await content.ReadAsStringAsync();

                // Act
                using (HttpResponseMessage response = await _client.GetAsync($"{_fixture.AppRootPath}{VirtualScrollController.ASPX}/{nameof(HashesDataTableController.Load)}?{queryString}", HttpCompletionOption.ResponseContentRead))
                {
                    // Assert
                    Assert.NotNull(response);
                    response.EnsureSuccessStatusCode();
                    Assert.Equal(HttpStatusCode.OK, response.StatusCode);

                    var jsonString = await response.Content.ReadAsStringAsync();

                    var typed_result = new TypedResult
                    {
                        total = 1,
                        rows  = new ThinHashes[] { }
                    };

                    // Deserialize JSON String into concrete class
                    var data = JsonSerializer.Deserialize <TypedResult>(jsonString, new JsonSerializerOptions {
                        PropertyNameCaseInsensitive = true
                    });
                    Assert.IsType(typed_result.GetType(), data);
                    Assert.IsAssignableFrom <IEnumerable <ThinHashes> >(data.rows);

                    Assert.True(data.rows.Length == 5 || data.rows.Length == 0);
                    Assert.True(data.total >= 0);

                    if (data.rows.Length > 0)
                    {
                        Assert.StartsWith(search, data.rows[0].Key);

                        if (query_input.TryGetValue("ExtraParam", out string value) && value == "cached")
                        {
                            Assert.True(response.Headers.CacheControl.Public &&
                                        response.Headers.CacheControl.MaxAge == DotnetPlayground.Repositories.HashesRepository.HashesInfoExpirationInMinutes);
                        }
                        else
                        {
                            Assert.Null(response.Headers.CacheControl?.Public);
                        }
                    }
                    else
                    {
                        Assert.Null(response.Headers.CacheControl?.Public);
                    }

                    return(data.total);
                }
            }
        }