Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions E-Commerce.Domain/Contracts/ICacheRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E_Commerce.Domain.Contracts
{
public interface ICacheRepository
{
Task<string?> GetAsync(string Cachekey);
Task SetAsync(string Cachekey, string Cachevalue, TimeSpan TimeToLive);

}
}
30 changes: 30 additions & 0 deletions E-Commerce.Persistance/Repositories/CacheRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using E_Commerce.Domain.Contracts;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E_Commerce.Persistance.Repositories
{
public class CacheRepository : ICacheRepository
{
private readonly IDatabase _database;
public CacheRepository(IConnectionMultiplexer connection)
{
_database = connection.GetDatabase();
}

public async Task<string?> GetAsync(string Cachekey)
{
var CacheValue = await _database.StringGetAsync(Cachekey);
return CacheValue.IsNullOrEmpty ? null : CacheValue.ToString();
}

public async Task SetAsync(string Cachekey, string Cachevalue, TimeSpan TimeToLive)
{
await _database.StringSetAsync(Cachekey, Cachevalue, TimeToLive);
}
}
}
72 changes: 72 additions & 0 deletions E-Commerce.Presentaion/Attributes/RedisCacheAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using E_Commerce.Services.Abstraction;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E_Commerce.Presentaion.Attributes
{
internal class RedisCacheAttribute : ActionFilterAttribute
{
private readonly int _durationInMinutes;

public RedisCacheAttribute(int DurationInMinutes = 5)
{
_durationInMinutes = DurationInMinutes;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 1.Get Cache Service From Dependancy Ingection Container
var CacheService = context.HttpContext.RequestServices.GetRequiredService<ICacheService>();

// 2.Create Cache Key Based On Request Path And Query String
var CacheKey = CreateCacheKey(context.HttpContext.Request);

// 3.Check If Cached Data Exists
var CahceValue = await CacheService.GetAsync(CacheKey);

// 4.If Exists , Return Cached Data and Skip Executing Of Endpoint
if (CahceValue is not null)
{
context.Result = new ContentResult()
{
Content = CahceValue,
ContentType = "application/Json",
StatusCode = StatusCodes.Status200OK
};
return;
}

// 5.If Not Exists , Excute Endpoint And Store The Data In Cache if 200 OK Response
var ExcutedContext = await next.Invoke();
if(ExcutedContext.Result is OkObjectResult result)
{
await CacheService.SetAsync(CacheKey, result.Value!,TimeSpan.FromMinutes(_durationInMinutes));
}

}

// /api/Products
// /api/Products?brandId=2&typeId=1
// /api/Products?typeId=1
// /api/Products?typeId=1&brandId=2



private string CreateCacheKey(HttpRequest request)
{
StringBuilder key = new StringBuilder();
key.Append(request.Path); // /api/Products
foreach (var item in request.Query.OrderBy(x => x.Key))
key.Append($"| {item.Key}-{item.Value}");
return key.ToString();
}


}
}
7 changes: 6 additions & 1 deletion E-Commerce.Presentaion/Controllers/ProductController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using E_Commerce.Services.Abstraction;
using E_Commerce.Presentaion.Attributes;
using E_Commerce.Services.Abstraction;
using E_Commerce.Shared;
using E_Commerce.Shared.DTOs.ProductDTOs;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
Expand All @@ -24,6 +26,7 @@ public ProductController(IProductService productService)
//GetAllProducts
//Get : BaseUrl/api/Products
[HttpGet]
[RedisCache]
public async Task<ActionResult<PaginatedResult<ProductDTO>>> GetAllProducts([FromQuery] ProductQueryParams queryParams)
{
var Products = await _productService.GetAllProductAsync(queryParams);
Expand All @@ -35,7 +38,9 @@ public async Task<ActionResult<PaginatedResult<ProductDTO>>> GetAllProducts([Fro
[HttpGet("{id}")]
public async Task<ActionResult<ProductDTO>> GetProduct(int id)
{
//throw new Exception();
var Product = await _productService.GetProductByIdAsync(id);

return Ok(Product);
}

Expand Down
4 changes: 4 additions & 0 deletions E-Commerce.Presentaion/E-Commerce.Presentaion.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@


</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Runtime" Version="4.3.0" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions E-Commerce.Services.Abstraction/ICacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E_Commerce.Services.Abstraction
{
public interface ICacheService
{
Task<string?> GetAsync(string Cachekey);
Task SetAsync(string Cachekey, object Cachevalue, TimeSpan TimeToLive);

}
}
6 changes: 5 additions & 1 deletion E-Commerce.Services/BasketService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using E_Commerce.Domain.Contracts;
using E_Commerce.Domain.Entities.BasketModule;
using E_Commerce.Services.Abstraction;
using E_Commerce.Services.Exceptions;
using E_Commerce.Shared.DTOs.BasketDTOs;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -35,7 +36,10 @@ public async Task<BasketDTO> CreateOrUpdateBasketAsync(BasketDTO basketDTO)
public async Task<BasketDTO> GetBasketAsync(string id)
{
var Basket = await _basketRepository.GetBasketAsync(id);
return _mapper.Map<CustomerBasket, BasketDTO>(Basket!);

if (Basket == null)
throw new BasketNotFoundException(id);
return _mapper.Map<CustomerBasket, BasketDTO>(Basket);
}
}
}
36 changes: 36 additions & 0 deletions E-Commerce.Services/CacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using E_Commerce.Domain.Contracts;
using E_Commerce.Services.Abstraction;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace E_Commerce.Services
{
public class CacheService : ICacheService
{
private readonly ICacheRepository _cacheRepository;

public CacheService(ICacheRepository cacheRepository)
{
this._cacheRepository = cacheRepository;
}

public async Task<string?> GetAsync(string Cachekey)
{
return await _cacheRepository.GetAsync(Cachekey);
}

public async Task SetAsync(string Cachekey, object Cachevalue, TimeSpan TimeToLive)
{
var Value = JsonSerializer.Serialize(Cachevalue,new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
await _cacheRepository.SetAsync(Cachekey, Value, TimeToLive);

}
}
}
23 changes: 23 additions & 0 deletions E-Commerce.Services/Exceptions/NotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace E_Commerce.Services.Exceptions
{
public abstract class NotFoundException(string Message) : Exception(Message)
{

}

public sealed class ProductNotFoundException(int id) : NotFoundException($"Product With Id {id} is Not Found")
{

}

public sealed class BasketNotFoundException(string id) : NotFoundException($"Basket With Id {id} is Not Found")
{

}
}
5 changes: 5 additions & 0 deletions E-Commerce.Services/ProductService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using E_Commerce.Domain.Contracts;
using E_Commerce.Domain.Entities.ProductModule;
using E_Commerce.Services.Abstraction;
using E_Commerce.Services.Exceptions;
using E_Commerce.Services.Specifications;
using E_Commerce.Shared;
using E_Commerce.Shared.DTOs.ProductDTOs;
Expand Down Expand Up @@ -58,6 +59,10 @@ public async Task<ProductDTO> GetProductByIdAsync(int id)
{
var spec = new ProductWithTypeAndBrandSpecification(id);
var Product = await _unitOfWork.GetRepository<Product,int>().GetByIdAsync(spec);

if (Product == null)
throw new ProductNotFoundException(id);

return _mapper.Map<ProductDTO>(Product);
}
}
Expand Down
64 changes: 64 additions & 0 deletions E-CommerceProject/CustomMiddleWares/ExceptionHandlerMiddleWare.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using E_Commerce.Services.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace E_CommerceProject.CustomMiddleWares
{
public class ExceptionHandlerMiddleWare
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlerMiddleWare> _logger;

public ExceptionHandlerMiddleWare(RequestDelegate Next,ILogger<ExceptionHandlerMiddleWare> logger)
{
_next = Next;
this._logger = logger;
}

public async Task Invoke(HttpContext httpcontext)
{
try
{
await _next.Invoke(httpcontext);
await HandleNotFoundEndPointAsync(httpcontext);
}
catch (Exception ex)
{
//1.Logging

_logger.LogError(ex, "Something Went Wrong !");

//2.Return Custom Error Respone

var Problem = new ProblemDetails()
{
Title = "An Unexpected Error Occurred !",
Detail = ex.Message,
Instance = httpcontext.Request.Path,
Status = ex switch
{
NotFoundException => StatusCodes.Status404NotFound,
_ => StatusCodes.Status500InternalServerError
}
};
httpcontext.Response.StatusCode = Problem.Status.Value;
await httpcontext.Response.WriteAsJsonAsync(Problem);
}

}

private static async Task HandleNotFoundEndPointAsync(HttpContext httpcontext)
{
if (httpcontext.Response.StatusCode == StatusCodes.Status404NotFound)
{
var Problem = new ProblemDetails()
{
Title = "Error Will Processing The Http Request - EndPoint Not Found !",
Status = StatusCodes.Status404NotFound,
Detail = $"EndPoint {httpcontext.Request.Path} Not Found",
Instance = httpcontext.Request.Path
};
await httpcontext.Response.WriteAsJsonAsync(Problem);
}
}
}
}
28 changes: 28 additions & 0 deletions E-CommerceProject/Factories/ApiResponseFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace E_CommerceProject.Factories
{
public static class ApiResponseFactory
{
public static IActionResult GenerateApiValidationResponse(ActionContext actionContext)
{
var Errors = actionContext.ModelState.Where(x => x.Value.Errors.Count() > 0)
.ToDictionary(x => x.Key,
x => x.Value.Errors.Select(x => x.ErrorMessage.ToArray()));
var Problem = new ProblemDetails
{
Title = "Validation Errors",
Detail = "One or More Validation Errors Occurred.",
Status = StatusCodes.Status400BadRequest,
Extensions =
{
{"Errors",Errors }
}
};
return new BadRequestObjectResult(Problem);

}

}
}
Loading