Server Side Caching
This commit is contained in:
parent
dcc6396b4d
commit
f6735a38fe
@ -24,6 +24,7 @@ namespace API.Controllers
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CachedAttributes(600)]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IReadOnlyList<Pagination<ProductToReturnDto>>>> GetProducts([FromQuery]ProductSpecParams productParams)
|
public async Task<ActionResult<IReadOnlyList<Pagination<ProductToReturnDto>>>> GetProducts([FromQuery]ProductSpecParams productParams)
|
||||||
{
|
{
|
||||||
@ -35,6 +36,7 @@ namespace API.Controllers
|
|||||||
return Ok(new Pagination<ProductToReturnDto>(productParams.PageIndex, productParams.PageSize, totalItems, data));
|
return Ok(new Pagination<ProductToReturnDto>(productParams.PageIndex, productParams.PageSize, totalItems, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CachedAttributes(600)]
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
|
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
|
||||||
@ -46,12 +48,14 @@ namespace API.Controllers
|
|||||||
return _mapper.Map<Product, ProductToReturnDto>(product);
|
return _mapper.Map<Product, ProductToReturnDto>(product);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CachedAttributes(600)]
|
||||||
[HttpGet("brands")]
|
[HttpGet("brands")]
|
||||||
public async Task<ActionResult<IReadOnlyList<ProductBrand>>> GetProductBrands()
|
public async Task<ActionResult<IReadOnlyList<ProductBrand>>> GetProductBrands()
|
||||||
{
|
{
|
||||||
return Ok(await _productBrandRepo.ListAllAsync());
|
return Ok(await _productBrandRepo.ListAllAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CachedAttributes(600)]
|
||||||
[HttpGet("types")]
|
[HttpGet("types")]
|
||||||
public async Task<ActionResult<IReadOnlyList<ProductType>>> GetProductTypes()
|
public async Task<ActionResult<IReadOnlyList<ProductType>>> GetProductTypes()
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,7 @@ namespace API.Extensions
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
services.AddSingleton<IResponseCacheService, ResponseCacheService>();
|
||||||
services.AddScoped<ITokenService, TokenService>();
|
services.AddScoped<ITokenService, TokenService>();
|
||||||
services.AddScoped<iProductRepository, ProductRepository>();
|
services.AddScoped<iProductRepository, ProductRepository>();
|
||||||
services.AddScoped<IBasketRepository, BasketRepository>();
|
services.AddScoped<IBasketRepository, BasketRepository>();
|
||||||
|
53
API/Helpers/CachedAttributes.cs
Normal file
53
API/Helpers/CachedAttributes.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Core.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
namespace API.Helpers
|
||||||
|
{
|
||||||
|
public class CachedAttributes : Attribute, IAsyncActionFilter
|
||||||
|
{
|
||||||
|
private readonly int _timeToLiveSeconds;
|
||||||
|
public CachedAttributes(int timeToLiveSeconds)
|
||||||
|
{
|
||||||
|
_timeToLiveSeconds = timeToLiveSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||||
|
{
|
||||||
|
var cacheService = context.HttpContext.RequestServices.GetRequiredService<IResponseCacheService>();
|
||||||
|
var cacheKey = GenerateCacheKeyFromRequest(context.HttpContext.Request);
|
||||||
|
var cachedResponse = await cacheService.GetCachedResponseAsync(cacheKey);
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(cachedResponse))
|
||||||
|
{
|
||||||
|
var contentResult = new ContentResult
|
||||||
|
{
|
||||||
|
Content = cachedResponse,
|
||||||
|
ContentType = "application/json",
|
||||||
|
StatusCode = 200
|
||||||
|
};
|
||||||
|
context.Result = contentResult;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var executedContext = await next();
|
||||||
|
if(executedContext.Result is OkObjectResult okObjectResult)
|
||||||
|
{
|
||||||
|
await cacheService.CacheResponseAsync(cacheKey, okObjectResult.Value, TimeSpan.FromSeconds(_timeToLiveSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateCacheKeyFromRequest(HttpRequest request)
|
||||||
|
{
|
||||||
|
var keyBuilder = new StringBuilder();
|
||||||
|
keyBuilder.Append($"{request.Path}");
|
||||||
|
foreach (var (key, value) in request.Query.OrderBy(x => x.Key))
|
||||||
|
{
|
||||||
|
keyBuilder.Append($"|{key}-{value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBuilder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Core/Interfaces/IResponseCacheService.cs
Normal file
8
Core/Interfaces/IResponseCacheService.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Core.Interfaces
|
||||||
|
{
|
||||||
|
public interface IResponseCacheService
|
||||||
|
{
|
||||||
|
Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive);
|
||||||
|
Task<string> GetCachedResponseAsync(string cacheKey);
|
||||||
|
}
|
||||||
|
}
|
45
Infrastructure/Services/ResponseCacheService.cs
Normal file
45
Infrastructure/Services/ResponseCacheService.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Core.Interfaces;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class ResponseCacheService : IResponseCacheService
|
||||||
|
{
|
||||||
|
private readonly IDatabase _database;
|
||||||
|
public ResponseCacheService(IConnectionMultiplexer redis)
|
||||||
|
{
|
||||||
|
_database = redis.GetDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive)
|
||||||
|
{
|
||||||
|
if(response == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
var serializedResponse = JsonSerializer.Serialize(response, options);
|
||||||
|
await _database.StringSetAsync(cacheKey, serializedResponse, timeToLive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetCachedResponseAsync(string cacheKey)
|
||||||
|
{
|
||||||
|
var cachedResponse = await _database.StringGetAsync(cacheKey);
|
||||||
|
if(cachedResponse.IsNullOrEmpty){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Loading…
Reference in New Issue
Block a user