@ -1,33 +1,26 @@ | |||
using Microsoft.AspNetCore.Mvc.Authorization; | |||
using Microsoft.OpenApi.Models; | |||
using Swashbuckle.AspNetCore.SwaggerGen; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server | |||
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter | |||
{ | |||
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter | |||
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |||
{ | |||
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |||
{ | |||
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; | |||
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); | |||
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); | |||
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; | |||
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); | |||
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); | |||
if (isAuthorized && !allowAnonymous) | |||
{ | |||
if (operation.Parameters == null) | |||
operation.Parameters = new List<OpenApiParameter>(); | |||
if (isAuthorized && !allowAnonymous) | |||
{ | |||
if (operation.Parameters == null) | |||
operation.Parameters = new List<OpenApiParameter>(); | |||
operation.Parameters.Add(new OpenApiParameter | |||
{ | |||
Name = "Authorization", | |||
In = ParameterLocation.Header, | |||
Description = "access token", | |||
Required = true | |||
}); | |||
} | |||
operation.Parameters.Add(new OpenApiParameter | |||
{ | |||
Name = "Authorization", | |||
In = ParameterLocation.Header, | |||
Description = "access token", | |||
Required = true | |||
}); | |||
} | |||
} | |||
} |
@ -1,7 +1,7 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API; | |||
public class BasketSettings | |||
{ | |||
public class BasketSettings | |||
{ | |||
public string ConnectionString { get; set; } | |||
} | |||
public string ConnectionString { get; set; } | |||
} | |||
@ -1,103 +1,89 @@ | |||
using Basket.API.IntegrationEvents.Events; | |||
using Basket.API.Model; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Net; | |||
using System.Security.Claims; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers | |||
[Route("api/v1/[controller]")] | |||
[Authorize] | |||
[ApiController] | |||
public class BasketController : ControllerBase | |||
{ | |||
[Route("api/v1/[controller]")] | |||
[Authorize] | |||
[ApiController] | |||
public class BasketController : ControllerBase | |||
{ | |||
private readonly IBasketRepository _repository; | |||
private readonly IIdentityService _identityService; | |||
private readonly IEventBus _eventBus; | |||
private readonly ILogger<BasketController> _logger; | |||
public BasketController( | |||
ILogger<BasketController> logger, | |||
IBasketRepository repository, | |||
IIdentityService identityService, | |||
IEventBus eventBus) | |||
{ | |||
_logger = logger; | |||
_repository = repository; | |||
_identityService = identityService; | |||
_eventBus = eventBus; | |||
} | |||
private readonly IBasketRepository _repository; | |||
private readonly IIdentityService _identityService; | |||
private readonly IEventBus _eventBus; | |||
private readonly ILogger<BasketController> _logger; | |||
[HttpGet("{id}")] | |||
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] | |||
public async Task<ActionResult<CustomerBasket>> GetBasketByIdAsync(string id) | |||
{ | |||
var basket = await _repository.GetBasketAsync(id); | |||
return Ok(basket ?? new CustomerBasket(id)); | |||
} | |||
public BasketController( | |||
ILogger<BasketController> logger, | |||
IBasketRepository repository, | |||
IIdentityService identityService, | |||
IEventBus eventBus) | |||
{ | |||
_logger = logger; | |||
_repository = repository; | |||
_identityService = identityService; | |||
_eventBus = eventBus; | |||
} | |||
[HttpPost] | |||
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] | |||
public async Task<ActionResult<CustomerBasket>> UpdateBasketAsync([FromBody] CustomerBasket value) | |||
{ | |||
return Ok(await _repository.UpdateBasketAsync(value)); | |||
} | |||
[HttpGet("{id}")] | |||
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] | |||
public async Task<ActionResult<CustomerBasket>> GetBasketByIdAsync(string id) | |||
{ | |||
var basket = await _repository.GetBasketAsync(id); | |||
[Route("checkout")] | |||
[HttpPost] | |||
[ProducesResponseType((int)HttpStatusCode.Accepted)] | |||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||
public async Task<ActionResult> CheckoutAsync([FromBody] BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) | |||
{ | |||
var userId = _identityService.GetUserIdentity(); | |||
return Ok(basket ?? new CustomerBasket(id)); | |||
} | |||
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ? | |||
guid : basketCheckout.RequestId; | |||
[HttpPost] | |||
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] | |||
public async Task<ActionResult<CustomerBasket>> UpdateBasketAsync([FromBody] CustomerBasket value) | |||
{ | |||
return Ok(await _repository.UpdateBasketAsync(value)); | |||
} | |||
var basket = await _repository.GetBasketAsync(userId); | |||
[Route("checkout")] | |||
[HttpPost] | |||
[ProducesResponseType((int)HttpStatusCode.Accepted)] | |||
[ProducesResponseType((int)HttpStatusCode.BadRequest)] | |||
public async Task<ActionResult> CheckoutAsync([FromBody] BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) | |||
{ | |||
var userId = _identityService.GetUserIdentity(); | |||
if (basket == null) | |||
{ | |||
return BadRequest(); | |||
} | |||
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ? | |||
guid : basketCheckout.RequestId; | |||
var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value; | |||
var basket = await _repository.GetBasketAsync(userId); | |||
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, | |||
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName, | |||
basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket); | |||
if (basket == null) | |||
{ | |||
return BadRequest(); | |||
} | |||
// Once basket is checkout, sends an integration event to | |||
// ordering.api to convert basket to order and proceeds with | |||
// order creation process | |||
try | |||
{ | |||
_eventBus.Publish(eventMessage); | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); | |||
var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value; | |||
throw; | |||
} | |||
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, | |||
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName, | |||
basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket); | |||
return Accepted(); | |||
// Once basket is checkout, sends an integration event to | |||
// ordering.api to convert basket to order and proceeds with | |||
// order creation process | |||
try | |||
{ | |||
_eventBus.Publish(eventMessage); | |||
} | |||
// DELETE api/values/5 | |||
[HttpDelete("{id}")] | |||
[ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)] | |||
public async Task DeleteBasketByIdAsync(string id) | |||
catch (Exception ex) | |||
{ | |||
await _repository.DeleteBasketAsync(id); | |||
_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); | |||
throw; | |||
} | |||
return Accepted(); | |||
} | |||
// DELETE api/values/5 | |||
[HttpDelete("{id}")] | |||
[ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)] | |||
public async Task DeleteBasketByIdAsync(string id) | |||
{ | |||
await _repository.DeleteBasketAsync(id); | |||
} | |||
} |
@ -1,13 +1,11 @@ | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers | |||
public class HomeController : Controller | |||
{ | |||
public class HomeController : Controller | |||
// GET: /<controller>/ | |||
public IActionResult Index() | |||
{ | |||
// GET: /<controller>/ | |||
public IActionResult Index() | |||
{ | |||
return new RedirectResult("~/swagger"); | |||
} | |||
return new RedirectResult("~/swagger"); | |||
} | |||
} | |||
@ -1,102 +1,93 @@ | |||
using Grpc.Core; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.Extensions.Logging; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace GrpcBasket | |||
namespace GrpcBasket; | |||
public class BasketService : Basket.BasketBase | |||
{ | |||
public class BasketService : Basket.BasketBase | |||
private readonly IBasketRepository _repository; | |||
private readonly ILogger<BasketService> _logger; | |||
public BasketService(IBasketRepository repository, ILogger<BasketService> logger) | |||
{ | |||
_repository = repository; | |||
_logger = logger; | |||
} | |||
[AllowAnonymous] | |||
public override async Task<CustomerBasketResponse> GetBasketById(BasketRequest request, ServerCallContext context) | |||
{ | |||
private readonly IBasketRepository _repository; | |||
private readonly ILogger<BasketService> _logger; | |||
_logger.LogInformation("Begin grpc call from method {Method} for basket id {Id}", context.Method, request.Id); | |||
var data = await _repository.GetBasketAsync(request.Id); | |||
public BasketService(IBasketRepository repository, ILogger<BasketService> logger) | |||
if (data != null) | |||
{ | |||
_repository = repository; | |||
_logger = logger; | |||
} | |||
context.Status = new Status(StatusCode.OK, $"Basket with id {request.Id} do exist"); | |||
[AllowAnonymous] | |||
public override async Task<CustomerBasketResponse> GetBasketById(BasketRequest request, ServerCallContext context) | |||
return MapToCustomerBasketResponse(data); | |||
} | |||
else | |||
{ | |||
_logger.LogInformation("Begin grpc call from method {Method} for basket id {Id}", context.Method, request.Id); | |||
context.Status = new Status(StatusCode.NotFound, $"Basket with id {request.Id} do not exist"); | |||
} | |||
var data = await _repository.GetBasketAsync(request.Id); | |||
return new CustomerBasketResponse(); | |||
} | |||
if (data != null) | |||
{ | |||
context.Status = new Status(StatusCode.OK, $"Basket with id {request.Id} do exist"); | |||
public override async Task<CustomerBasketResponse> UpdateBasket(CustomerBasketRequest request, ServerCallContext context) | |||
{ | |||
_logger.LogInformation("Begin grpc call BasketService.UpdateBasketAsync for buyer id {Buyerid}", request.Buyerid); | |||
return MapToCustomerBasketResponse(data); | |||
} | |||
else | |||
{ | |||
context.Status = new Status(StatusCode.NotFound, $"Basket with id {request.Id} do not exist"); | |||
} | |||
var customerBasket = MapToCustomerBasket(request); | |||
return new CustomerBasketResponse(); | |||
} | |||
var response = await _repository.UpdateBasketAsync(customerBasket); | |||
public override async Task<CustomerBasketResponse> UpdateBasket(CustomerBasketRequest request, ServerCallContext context) | |||
if (response != null) | |||
{ | |||
_logger.LogInformation("Begin grpc call BasketService.UpdateBasketAsync for buyer id {Buyerid}", request.Buyerid); | |||
var customerBasket = MapToCustomerBasket(request); | |||
return MapToCustomerBasketResponse(response); | |||
} | |||
var response = await _repository.UpdateBasketAsync(customerBasket); | |||
context.Status = new Status(StatusCode.NotFound, $"Basket with buyer id {request.Buyerid} do not exist"); | |||
if (response != null) | |||
{ | |||
return MapToCustomerBasketResponse(response); | |||
} | |||
return null; | |||
} | |||
context.Status = new Status(StatusCode.NotFound, $"Basket with buyer id {request.Buyerid} do not exist"); | |||
private CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket) | |||
{ | |||
var response = new CustomerBasketResponse | |||
{ | |||
Buyerid = customerBasket.BuyerId | |||
}; | |||
return null; | |||
} | |||
customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse | |||
{ | |||
Id = item.Id, | |||
Oldunitprice = (double)item.OldUnitPrice, | |||
Pictureurl = item.PictureUrl, | |||
Productid = item.ProductId, | |||
Productname = item.ProductName, | |||
Quantity = item.Quantity, | |||
Unitprice = (double)item.UnitPrice | |||
})); | |||
return response; | |||
} | |||
private CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket) | |||
private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest) | |||
{ | |||
var response = new CustomerBasket | |||
{ | |||
var response = new CustomerBasketResponse | |||
{ | |||
Buyerid = customerBasket.BuyerId | |||
}; | |||
customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse | |||
{ | |||
Id = item.Id, | |||
Oldunitprice = (double)item.OldUnitPrice, | |||
Pictureurl = item.PictureUrl, | |||
Productid = item.ProductId, | |||
Productname = item.ProductName, | |||
Quantity = item.Quantity, | |||
Unitprice = (double)item.UnitPrice | |||
})); | |||
return response; | |||
} | |||
BuyerId = customerBasketRequest.Buyerid | |||
}; | |||
private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest) | |||
customerBasketRequest.Items.ToList().ForEach(item => response.Items.Add(new BasketItem | |||
{ | |||
var response = new CustomerBasket | |||
{ | |||
BuyerId = customerBasketRequest.Buyerid | |||
}; | |||
customerBasketRequest.Items.ToList().ForEach(item => response.Items.Add(new BasketItem | |||
{ | |||
Id = item.Id, | |||
OldUnitPrice = (decimal)item.Oldunitprice, | |||
PictureUrl = item.Pictureurl, | |||
ProductId = item.Productid, | |||
ProductName = item.Productname, | |||
Quantity = item.Quantity, | |||
UnitPrice = (decimal)item.Unitprice | |||
})); | |||
return response; | |||
} | |||
Id = item.Id, | |||
OldUnitPrice = (decimal)item.Oldunitprice, | |||
PictureUrl = item.Pictureurl, | |||
ProductId = item.Productid, | |||
ProductName = item.Productname, | |||
Quantity = item.Quantity, | |||
UnitPrice = (decimal)item.Unitprice | |||
})); | |||
return response; | |||
} | |||
} |
@ -1,14 +1,11 @@ | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
namespace Basket.API.Infrastructure.ActionResults; | |||
namespace Basket.API.Infrastructure.ActionResults | |||
public class InternalServerErrorObjectResult : ObjectResult | |||
{ | |||
public class InternalServerErrorObjectResult : ObjectResult | |||
public InternalServerErrorObjectResult(object error) | |||
: base(error) | |||
{ | |||
public InternalServerErrorObjectResult(object error) | |||
: base(error) | |||
{ | |||
StatusCode = StatusCodes.Status500InternalServerError; | |||
} | |||
StatusCode = StatusCodes.Status500InternalServerError; | |||
} | |||
} | |||
@ -1,21 +1,16 @@ | |||
using System; | |||
namespace Basket.API.Infrastructure.Exceptions; | |||
namespace Basket.API.Infrastructure.Exceptions | |||
public class BasketDomainException : Exception | |||
{ | |||
/// <summary> | |||
/// Exception type for app exceptions | |||
/// </summary> | |||
public class BasketDomainException : Exception | |||
{ | |||
public BasketDomainException() | |||
{ } | |||
public BasketDomainException() | |||
{ } | |||
public BasketDomainException(string message) | |||
: base(message) | |||
{ } | |||
public BasketDomainException(string message) | |||
: base(message) | |||
{ } | |||
public BasketDomainException(string message, Exception innerException) | |||
: base(message, innerException) | |||
{ } | |||
} | |||
public BasketDomainException(string message, Exception innerException) | |||
: base(message, innerException) | |||
{ } | |||
} | |||
@ -1,20 +1,17 @@ | |||
using Microsoft.AspNetCore.Builder; | |||
using System; | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
namespace Basket.API.Infrastructure.Middlewares | |||
public static class FailingMiddlewareAppBuilderExtensions | |||
{ | |||
public static class FailingMiddlewareAppBuilderExtensions | |||
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder) | |||
{ | |||
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder) | |||
{ | |||
return UseFailingMiddleware(builder, null); | |||
} | |||
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action) | |||
{ | |||
var options = new FailingOptions(); | |||
action?.Invoke(options); | |||
builder.UseMiddleware<FailingMiddleware>(options); | |||
return builder; | |||
} | |||
return UseFailingMiddleware(builder, null); | |||
} | |||
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action) | |||
{ | |||
var options = new FailingOptions(); | |||
action?.Invoke(options); | |||
builder.UseMiddleware<FailingMiddleware>(options); | |||
return builder; | |||
} | |||
} | |||
@ -1,58 +1,47 @@ | |||
using Basket.API.Infrastructure.ActionResults; | |||
using Basket.API.Infrastructure.Exceptions; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.AspNetCore.Mvc.Filters; | |||
using Microsoft.Extensions.Hosting; | |||
using Microsoft.Extensions.Logging; | |||
using System.Net; | |||
namespace Basket.API.Infrastructure.Filters; | |||
namespace Basket.API.Infrastructure.Filters | |||
public partial class HttpGlobalExceptionFilter : IExceptionFilter | |||
{ | |||
public partial class HttpGlobalExceptionFilter : IExceptionFilter | |||
private readonly IWebHostEnvironment env; | |||
private readonly ILogger<HttpGlobalExceptionFilter> logger; | |||
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | |||
{ | |||
private readonly IWebHostEnvironment env; | |||
private readonly ILogger<HttpGlobalExceptionFilter> logger; | |||
this.env = env; | |||
this.logger = logger; | |||
} | |||
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | |||
{ | |||
this.env = env; | |||
this.logger = logger; | |||
} | |||
public void OnException(ExceptionContext context) | |||
{ | |||
logger.LogError(new EventId(context.Exception.HResult), | |||
context.Exception, | |||
context.Exception.Message); | |||
public void OnException(ExceptionContext context) | |||
if (context.Exception.GetType() == typeof(BasketDomainException)) | |||
{ | |||
logger.LogError(new EventId(context.Exception.HResult), | |||
context.Exception, | |||
context.Exception.Message); | |||
if (context.Exception.GetType() == typeof(BasketDomainException)) | |||
var json = new JsonErrorResponse | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { context.Exception.Message } | |||
}; | |||
Messages = new[] { context.Exception.Message } | |||
}; | |||
context.Result = new BadRequestObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
else | |||
context.Result = new BadRequestObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
else | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { "An error occurred. Try it again." } | |||
}; | |||
Messages = new[] { "An error occurred. Try it again." } | |||
}; | |||
if (env.IsDevelopment()) | |||
{ | |||
json.DeveloperMessage = context.Exception; | |||
} | |||
context.Result = new InternalServerErrorObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | |||
if (env.IsDevelopment()) | |||
{ | |||
json.DeveloperMessage = context.Exception; | |||
} | |||
context.ExceptionHandled = true; | |||
context.Result = new InternalServerErrorObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | |||
} | |||
context.ExceptionHandled = true; | |||
} | |||
} |
@ -1,9 +1,9 @@ | |||
namespace Basket.API.Infrastructure.Filters | |||
namespace Basket.API.Infrastructure.Filters; | |||
public class JsonErrorResponse | |||
{ | |||
public class JsonErrorResponse | |||
{ | |||
public string[] Messages { get; set; } | |||
public string[] Messages { get; set; } | |||
public object DeveloperMessage { get; set; } | |||
} | |||
public object DeveloperMessage { get; set; } | |||
} | |||
@ -1,30 +1,26 @@ | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.AspNetCore.Mvc.Filters; | |||
using System.Linq; | |||
namespace Basket.API.Infrastructure.Filters; | |||
namespace Basket.API.Infrastructure.Filters | |||
public class ValidateModelStateFilter : ActionFilterAttribute | |||
{ | |||
public class ValidateModelStateFilter : ActionFilterAttribute | |||
public override void OnActionExecuting(ActionExecutingContext context) | |||
{ | |||
public override void OnActionExecuting(ActionExecutingContext context) | |||
if (context.ModelState.IsValid) | |||
{ | |||
if (context.ModelState.IsValid) | |||
{ | |||
return; | |||
} | |||
return; | |||
} | |||
var validationErrors = context.ModelState | |||
.Keys | |||
.SelectMany(k => context.ModelState[k].Errors) | |||
.Select(e => e.ErrorMessage) | |||
.ToArray(); | |||
var validationErrors = context.ModelState | |||
.Keys | |||
.SelectMany(k => context.ModelState[k].Errors) | |||
.Select(e => e.ErrorMessage) | |||
.ToArray(); | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = validationErrors | |||
}; | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = validationErrors | |||
}; | |||
context.Result = new BadRequestObjectResult(json); | |||
} | |||
context.Result = new BadRequestObjectResult(json); | |||
} | |||
} | |||
@ -1,36 +1,29 @@ | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.OpenApi.Models; | |||
using Swashbuckle.AspNetCore.SwaggerGen; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Basket.API.Infrastructure.Filters; | |||
namespace Basket.API.Infrastructure.Filters | |||
public class AuthorizeCheckOperationFilter : IOperationFilter | |||
{ | |||
public class AuthorizeCheckOperationFilter : IOperationFilter | |||
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |||
{ | |||
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |||
{ | |||
// Check for authorize attribute | |||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || | |||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); | |||
// Check for authorize attribute | |||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || | |||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); | |||
if (!hasAuthorize) return; | |||
if (!hasAuthorize) return; | |||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); | |||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); | |||
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); | |||
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); | |||
var oAuthScheme = new OpenApiSecurityScheme | |||
{ | |||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | |||
}; | |||
var oAuthScheme = new OpenApiSecurityScheme | |||
{ | |||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | |||
}; | |||
operation.Security = new List<OpenApiSecurityRequirement> | |||
operation.Security = new List<OpenApiSecurityRequirement> | |||
{ | |||
new OpenApiSecurityRequirement | |||
{ | |||
new OpenApiSecurityRequirement | |||
{ | |||
[ oAuthScheme ] = new [] { "basketapi" } | |||
} | |||
}; | |||
} | |||
[ oAuthScheme ] = new [] { "basketapi" } | |||
} | |||
}; | |||
} | |||
} | |||
} |
@ -1,95 +1,88 @@ | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
namespace Basket.API.Infrastructure.Middlewares | |||
public class FailingMiddleware | |||
{ | |||
public class FailingMiddleware | |||
private readonly RequestDelegate _next; | |||
private bool _mustFail; | |||
private readonly FailingOptions _options; | |||
private readonly Microsoft.Extensions.Logging.ILogger _logger; | |||
public FailingMiddleware(RequestDelegate next, Microsoft.Extensions.Logging.ILogger<FailingMiddleware> logger, FailingOptions options) | |||
{ | |||
private readonly RequestDelegate _next; | |||
private bool _mustFail; | |||
private readonly FailingOptions _options; | |||
private readonly ILogger _logger; | |||
_next = next; | |||
_options = options; | |||
_mustFail = false; | |||
_logger = logger; | |||
} | |||
public FailingMiddleware(RequestDelegate next, ILogger<FailingMiddleware> logger, FailingOptions options) | |||
public async Task Invoke(HttpContext context) | |||
{ | |||
var path = context.Request.Path; | |||
if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) | |||
{ | |||
_next = next; | |||
_options = options; | |||
_mustFail = false; | |||
_logger = logger; | |||
await ProcessConfigRequest(context); | |||
return; | |||
} | |||
public async Task Invoke(HttpContext context) | |||
if (MustFail(context)) | |||
{ | |||
var path = context.Request.Path; | |||
if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) | |||
{ | |||
await ProcessConfigRequest(context); | |||
return; | |||
} | |||
if (MustFail(context)) | |||
{ | |||
_logger.LogInformation("Response for path {Path} will fail.", path); | |||
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; | |||
context.Response.ContentType = "text/plain"; | |||
await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); | |||
} | |||
else | |||
{ | |||
await _next.Invoke(context); | |||
} | |||
_logger.LogInformation("Response for path {Path} will fail.", path); | |||
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; | |||
context.Response.ContentType = "text/plain"; | |||
await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); | |||
} | |||
private async Task ProcessConfigRequest(HttpContext context) | |||
else | |||
{ | |||
var enable = context.Request.Query.Keys.Any(k => k == "enable"); | |||
var disable = context.Request.Query.Keys.Any(k => k == "disable"); | |||
await _next.Invoke(context); | |||
} | |||
} | |||
if (enable && disable) | |||
{ | |||
throw new ArgumentException("Must use enable or disable querystring values, but not both"); | |||
} | |||
private async Task ProcessConfigRequest(HttpContext context) | |||
{ | |||
var enable = context.Request.Query.Keys.Any(k => k == "enable"); | |||
var disable = context.Request.Query.Keys.Any(k => k == "disable"); | |||
if (disable) | |||
{ | |||
_mustFail = false; | |||
await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); | |||
return; | |||
} | |||
if (enable) | |||
{ | |||
_mustFail = true; | |||
await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); | |||
return; | |||
} | |||
if (enable && disable) | |||
{ | |||
throw new ArgumentException("Must use enable or disable querystring values, but not both"); | |||
} | |||
// If reach here, that means that no valid parameter has been passed. Just output status | |||
await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled")); | |||
if (disable) | |||
{ | |||
_mustFail = false; | |||
await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); | |||
return; | |||
} | |||
private async Task SendOkResponse(HttpContext context, string message) | |||
if (enable) | |||
{ | |||
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; | |||
context.Response.ContentType = "text/plain"; | |||
await context.Response.WriteAsync(message); | |||
_mustFail = true; | |||
await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); | |||
return; | |||
} | |||
private bool MustFail(HttpContext context) | |||
{ | |||
var rpath = context.Request.Path.Value; | |||
// If reach here, that means that no valid parameter has been passed. Just output status | |||
await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled")); | |||
return; | |||
} | |||
if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase))) | |||
{ | |||
return false; | |||
} | |||
private async Task SendOkResponse(HttpContext context, string message) | |||
{ | |||
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; | |||
context.Response.ContentType = "text/plain"; | |||
await context.Response.WriteAsync(message); | |||
} | |||
private bool MustFail(HttpContext context) | |||
{ | |||
var rpath = context.Request.Path.Value; | |||
return _mustFail && | |||
(_options.EndpointPaths.Any(x => x == rpath) | |||
|| _options.EndpointPaths.Count == 0); | |||
if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase))) | |||
{ | |||
return false; | |||
} | |||
return _mustFail && | |||
(_options.EndpointPaths.Any(x => x == rpath) | |||
|| _options.EndpointPaths.Count == 0); | |||
} | |||
} |
@ -1,12 +1,10 @@ | |||
using System.Collections.Generic; | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
namespace Basket.API.Infrastructure.Middlewares | |||
public class FailingOptions | |||
{ | |||
public class FailingOptions | |||
{ | |||
public string ConfigPath = "/Failing"; | |||
public List<string> EndpointPaths { get; set; } = new List<string>(); | |||
public string ConfigPath = "/Failing"; | |||
public List<string> EndpointPaths { get; set; } = new List<string>(); | |||
public List<string> NotFilteredPaths { get; set; } = new List<string>(); | |||
} | |||
public List<string> NotFilteredPaths { get; set; } = new List<string>(); | |||
} | |||
@ -1,24 +1,20 @@ | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
using System; | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
namespace Basket.API.Infrastructure.Middlewares | |||
public class FailingStartupFilter : IStartupFilter | |||
{ | |||
public class FailingStartupFilter : IStartupFilter | |||
private readonly Action<FailingOptions> _options; | |||
public FailingStartupFilter(Action<FailingOptions> optionsAction) | |||
{ | |||
private readonly Action<FailingOptions> _options; | |||
public FailingStartupFilter(Action<FailingOptions> optionsAction) | |||
{ | |||
_options = optionsAction; | |||
} | |||
_options = optionsAction; | |||
} | |||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | |||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | |||
{ | |||
return app => | |||
{ | |||
return app => | |||
{ | |||
app.UseFailingMiddleware(_options); | |||
next(app); | |||
}; | |||
} | |||
app.UseFailingMiddleware(_options); | |||
next(app); | |||
}; | |||
} | |||
} | |||
@ -1,18 +1,14 @@ | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using System; | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
namespace Basket.API.Infrastructure.Middlewares | |||
public static class WebHostBuildertExtensions | |||
{ | |||
public static class WebHostBuildertExtensions | |||
public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action<FailingOptions> options) | |||
{ | |||
public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action<FailingOptions> options) | |||
builder.ConfigureServices(services => | |||
{ | |||
builder.ConfigureServices(services => | |||
{ | |||
services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options)); | |||
}); | |||
return builder; | |||
} | |||
services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options)); | |||
}); | |||
return builder; | |||
} | |||
} | |||
@ -1,73 +1,63 @@ | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.Extensions.Logging; | |||
using StackExchange.Redis; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using System.Text.Json; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; | |||
public class RedisBasketRepository : IBasketRepository | |||
{ | |||
public class RedisBasketRepository : IBasketRepository | |||
private readonly ILogger<RedisBasketRepository> _logger; | |||
private readonly ConnectionMultiplexer _redis; | |||
private readonly IDatabase _database; | |||
public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) | |||
{ | |||
private readonly ILogger<RedisBasketRepository> _logger; | |||
private readonly ConnectionMultiplexer _redis; | |||
private readonly IDatabase _database; | |||
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); | |||
_redis = redis; | |||
_database = redis.GetDatabase(); | |||
} | |||
public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) | |||
{ | |||
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); | |||
_redis = redis; | |||
_database = redis.GetDatabase(); | |||
} | |||
public async Task<bool> DeleteBasketAsync(string id) | |||
{ | |||
return await _database.KeyDeleteAsync(id); | |||
} | |||
public async Task<bool> DeleteBasketAsync(string id) | |||
{ | |||
return await _database.KeyDeleteAsync(id); | |||
} | |||
public IEnumerable<string> GetUsers() | |||
{ | |||
var server = GetServer(); | |||
var data = server.Keys(); | |||
public IEnumerable<string> GetUsers() | |||
{ | |||
var server = GetServer(); | |||
var data = server.Keys(); | |||
return data?.Select(k => k.ToString()); | |||
} | |||
return data?.Select(k => k.ToString()); | |||
} | |||
public async Task<CustomerBasket> GetBasketAsync(string customerId) | |||
{ | |||
var data = await _database.StringGetAsync(customerId); | |||
public async Task<CustomerBasket> GetBasketAsync(string customerId) | |||
if (data.IsNullOrEmpty) | |||
{ | |||
var data = await _database.StringGetAsync(customerId); | |||
if (data.IsNullOrEmpty) | |||
{ | |||
return null; | |||
} | |||
return JsonSerializer.Deserialize<CustomerBasket>(data, new JsonSerializerOptions | |||
{ | |||
PropertyNameCaseInsensitive = true | |||
}); | |||
return null; | |||
} | |||
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket) | |||
return JsonSerializer.Deserialize<CustomerBasket>(data, new JsonSerializerOptions | |||
{ | |||
var created = await _database.StringSetAsync(basket.BuyerId, JsonSerializer.Serialize(basket)); | |||
if (!created) | |||
{ | |||
_logger.LogInformation("Problem occur persisting the item."); | |||
return null; | |||
} | |||
_logger.LogInformation("Basket item persisted succesfully."); | |||
PropertyNameCaseInsensitive = true | |||
}); | |||
} | |||
return await GetBasketAsync(basket.BuyerId); | |||
} | |||
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket) | |||
{ | |||
var created = await _database.StringSetAsync(basket.BuyerId, JsonSerializer.Serialize(basket)); | |||
private IServer GetServer() | |||
if (!created) | |||
{ | |||
var endpoint = _redis.GetEndPoints(); | |||
return _redis.GetServer(endpoint.First()); | |||
_logger.LogInformation("Problem occur persisting the item."); | |||
return null; | |||
} | |||
_logger.LogInformation("Basket item persisted succesfully."); | |||
return await GetBasketAsync(basket.BuyerId); | |||
} | |||
private IServer GetServer() | |||
{ | |||
var endpoint = _redis.GetEndPoints(); | |||
return _redis.GetServer(endpoint.First()); | |||
} | |||
} |
@ -1,37 +1,29 @@ | |||
using Basket.API.IntegrationEvents.Events; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Serilog.Context; | |||
using System; | |||
using System.Threading.Tasks; | |||
namespace Basket.API.IntegrationEvents.EventHandling | |||
namespace Basket.API.IntegrationEvents.EventHandling; | |||
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent> | |||
{ | |||
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent> | |||
{ | |||
private readonly IBasketRepository _repository; | |||
private readonly ILogger<OrderStartedIntegrationEventHandler> _logger; | |||
private readonly IBasketRepository _repository; | |||
private readonly ILogger<OrderStartedIntegrationEventHandler> _logger; | |||
public OrderStartedIntegrationEventHandler( | |||
IBasketRepository repository, | |||
ILogger<OrderStartedIntegrationEventHandler> logger) | |||
{ | |||
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public OrderStartedIntegrationEventHandler( | |||
IBasketRepository repository, | |||
ILogger<OrderStartedIntegrationEventHandler> logger) | |||
{ | |||
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public async Task Handle(OrderStartedIntegrationEvent @event) | |||
public async Task Handle(OrderStartedIntegrationEvent @event) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||
{ | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||
await _repository.DeleteBasketAsync(@event.UserId.ToString()); | |||
} | |||
await _repository.DeleteBasketAsync(@event.UserId.ToString()); | |||
} | |||
} | |||
} | |||
@ -1,64 +1,53 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.Extensions.Logging; | |||
using Serilog.Context; | |||
using System; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling | |||
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent> | |||
{ | |||
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent> | |||
{ | |||
private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger; | |||
private readonly IBasketRepository _repository; | |||
private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger; | |||
private readonly IBasketRepository _repository; | |||
public ProductPriceChangedIntegrationEventHandler( | |||
ILogger<ProductPriceChangedIntegrationEventHandler> logger, | |||
IBasketRepository repository) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); | |||
} | |||
public ProductPriceChangedIntegrationEventHandler( | |||
ILogger<ProductPriceChangedIntegrationEventHandler> logger, | |||
IBasketRepository repository) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); | |||
} | |||
public async Task Handle(ProductPriceChangedIntegrationEvent @event) | |||
public async Task Handle(ProductPriceChangedIntegrationEvent @event) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) | |||
{ | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); | |||
var userIds = _repository.GetUsers(); | |||
var userIds = _repository.GetUsers(); | |||
foreach (var id in userIds) | |||
{ | |||
var basket = await _repository.GetBasketAsync(id); | |||
foreach (var id in userIds) | |||
{ | |||
var basket = await _repository.GetBasketAsync(id); | |||
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); | |||
} | |||
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); | |||
} | |||
} | |||
} | |||
private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket) | |||
private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket) | |||
{ | |||
var itemsToUpdate = basket?.Items?.Where(x => x.ProductId == productId).ToList(); | |||
if (itemsToUpdate != null) | |||
{ | |||
var itemsToUpdate = basket?.Items?.Where(x => x.ProductId == productId).ToList(); | |||
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); | |||
if (itemsToUpdate != null) | |||
foreach (var item in itemsToUpdate) | |||
{ | |||
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate); | |||
foreach (var item in itemsToUpdate) | |||
if (item.UnitPrice == oldPrice) | |||
{ | |||
if (item.UnitPrice == oldPrice) | |||
{ | |||
var originalPrice = item.UnitPrice; | |||
item.UnitPrice = newPrice; | |||
item.OldUnitPrice = originalPrice; | |||
} | |||
var originalPrice = item.UnitPrice; | |||
item.UnitPrice = newPrice; | |||
item.OldUnitPrice = originalPrice; | |||
} | |||
await _repository.UpdateBasketAsync(basket); | |||
} | |||
await _repository.UpdateBasketAsync(basket); | |||
} | |||
} | |||
} | |||
@ -1,15 +1,13 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
namespace Basket.API.IntegrationEvents.Events; | |||
namespace Basket.API.IntegrationEvents.Events | |||
// Integration Events notes: | |||
// An Event is “something that has happened in the past”, therefore its name has to be | |||
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. | |||
public record OrderStartedIntegrationEvent : IntegrationEvent | |||
{ | |||
// Integration Events notes: | |||
// An Event is “something that has happened in the past”, therefore its name has to be | |||
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. | |||
public record OrderStartedIntegrationEvent : IntegrationEvent | |||
{ | |||
public string UserId { get; init; } | |||
public string UserId { get; init; } | |||
public OrderStartedIntegrationEvent(string userId) | |||
=> UserId = userId; | |||
} | |||
public OrderStartedIntegrationEvent(string userId) | |||
=> UserId = userId; | |||
} | |||
@ -1,23 +1,21 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events | |||
// Integration Events notes: | |||
// An Event is “something that has happened in the past”, therefore its name has to be | |||
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. | |||
public record ProductPriceChangedIntegrationEvent : IntegrationEvent | |||
{ | |||
// Integration Events notes: | |||
// An Event is “something that has happened in the past”, therefore its name has to be | |||
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems. | |||
public record ProductPriceChangedIntegrationEvent : IntegrationEvent | |||
{ | |||
public int ProductId { get; private init; } | |||
public int ProductId { get; private init; } | |||
public decimal NewPrice { get; private init; } | |||
public decimal NewPrice { get; private init; } | |||
public decimal OldPrice { get; private init; } | |||
public decimal OldPrice { get; private init; } | |||
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice) | |||
{ | |||
ProductId = productId; | |||
NewPrice = newPrice; | |||
OldPrice = oldPrice; | |||
} | |||
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice) | |||
{ | |||
ProductId = productId; | |||
NewPrice = newPrice; | |||
OldPrice = oldPrice; | |||
} | |||
} | |||
@ -1,64 +1,59 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using System; | |||
namespace Basket.API.IntegrationEvents.Events; | |||
namespace Basket.API.IntegrationEvents.Events | |||
public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent | |||
{ | |||
public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent | |||
{ | |||
public string UserId { get; } | |||
public string UserName { get; } | |||
public string UserId { get; } | |||
public int OrderNumber { get; init; } | |||
public string UserName { get; } | |||
public string City { get; init; } | |||
public int OrderNumber { get; init; } | |||
public string Street { get; init; } | |||
public string City { get; init; } | |||
public string State { get; init; } | |||
public string Street { get; init; } | |||
public string Country { get; init; } | |||
public string State { get; init; } | |||
public string ZipCode { get; init; } | |||
public string Country { get; init; } | |||
public string CardNumber { get; init; } | |||
public string ZipCode { get; init; } | |||
public string CardHolderName { get; init; } | |||
public string CardNumber { get; init; } | |||
public DateTime CardExpiration { get; init; } | |||
public string CardHolderName { get; init; } | |||
public string CardSecurityNumber { get; init; } | |||
public DateTime CardExpiration { get; init; } | |||
public int CardTypeId { get; init; } | |||
public string CardSecurityNumber { get; init; } | |||
public string Buyer { get; init; } | |||
public int CardTypeId { get; init; } | |||
public Guid RequestId { get; init; } | |||
public string Buyer { get; init; } | |||
public CustomerBasket Basket { get; } | |||
public Guid RequestId { get; init; } | |||
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, | |||
string state, string country, string zipCode, string cardNumber, string cardHolderName, | |||
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, | |||
CustomerBasket basket) | |||
{ | |||
UserId = userId; | |||
UserName = userName; | |||
City = city; | |||
Street = street; | |||
State = state; | |||
Country = country; | |||
ZipCode = zipCode; | |||
CardNumber = cardNumber; | |||
CardHolderName = cardHolderName; | |||
CardExpiration = cardExpiration; | |||
CardSecurityNumber = cardSecurityNumber; | |||
CardTypeId = cardTypeId; | |||
Buyer = buyer; | |||
Basket = basket; | |||
RequestId = requestId; | |||
} | |||
public CustomerBasket Basket { get; } | |||
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, | |||
string state, string country, string zipCode, string cardNumber, string cardHolderName, | |||
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, | |||
CustomerBasket basket) | |||
{ | |||
UserId = userId; | |||
UserName = userName; | |||
City = city; | |||
Street = street; | |||
State = state; | |||
Country = country; | |||
ZipCode = zipCode; | |||
CardNumber = cardNumber; | |||
CardHolderName = cardHolderName; | |||
CardExpiration = cardExpiration; | |||
CardSecurityNumber = cardSecurityNumber; | |||
CardTypeId = cardTypeId; | |||
Buyer = buyer; | |||
Basket = basket; | |||
RequestId = requestId; | |||
} | |||
} |
@ -1,32 +1,27 @@ | |||
using System; | |||
namespace Basket.API.Model | |||
namespace Basket.API.Model; | |||
public class BasketCheckout | |||
{ | |||
public class BasketCheckout | |||
{ | |||
public string City { get; set; } | |||
public string City { get; set; } | |||
public string Street { get; set; } | |||
public string Street { get; set; } | |||
public string State { get; set; } | |||
public string State { get; set; } | |||
public string Country { get; set; } | |||
public string Country { get; set; } | |||
public string ZipCode { get; set; } | |||
public string ZipCode { get; set; } | |||
public string CardNumber { get; set; } | |||
public string CardNumber { get; set; } | |||
public string CardHolderName { get; set; } | |||
public string CardHolderName { get; set; } | |||
public DateTime CardExpiration { get; set; } | |||
public DateTime CardExpiration { get; set; } | |||
public string CardSecurityNumber { get; set; } | |||
public string CardSecurityNumber { get; set; } | |||
public int CardTypeId { get; set; } | |||
public int CardTypeId { get; set; } | |||
public string Buyer { get; set; } | |||
public string Buyer { get; set; } | |||
public Guid RequestId { get; set; } | |||
} | |||
public Guid RequestId { get; set; } | |||
} | |||
@ -1,27 +1,23 @@ | |||
using System.Collections.Generic; | |||
using System.ComponentModel.DataAnnotations; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||
public class BasketItem : IValidatableObject | |||
{ | |||
public class BasketItem : IValidatableObject | |||
public string Id { get; set; } | |||
public int ProductId { get; set; } | |||
public string ProductName { get; set; } | |||
public decimal UnitPrice { get; set; } | |||
public decimal OldUnitPrice { get; set; } | |||
public int Quantity { get; set; } | |||
public string PictureUrl { get; set; } | |||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) | |||
{ | |||
public string Id { get; set; } | |||
public int ProductId { get; set; } | |||
public string ProductName { get; set; } | |||
public decimal UnitPrice { get; set; } | |||
public decimal OldUnitPrice { get; set; } | |||
public int Quantity { get; set; } | |||
public string PictureUrl { get; set; } | |||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) | |||
{ | |||
var results = new List<ValidationResult>(); | |||
if (Quantity < 1) | |||
{ | |||
results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" })); | |||
} | |||
var results = new List<ValidationResult>(); | |||
return results; | |||
if (Quantity < 1) | |||
{ | |||
results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" })); | |||
} | |||
return results; | |||
} | |||
} |
@ -1,21 +1,18 @@ | |||
using System.Collections.Generic; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
public class CustomerBasket | |||
{ | |||
public class CustomerBasket | |||
{ | |||
public string BuyerId { get; set; } | |||
public string BuyerId { get; set; } | |||
public List<BasketItem> Items { get; set; } = new List<BasketItem>(); | |||
public List<BasketItem> Items { get; set; } = new List<BasketItem>(); | |||
public CustomerBasket() | |||
{ | |||
public CustomerBasket() | |||
{ | |||
} | |||
} | |||
public CustomerBasket(string customerId) | |||
{ | |||
BuyerId = customerId; | |||
} | |||
public CustomerBasket(string customerId) | |||
{ | |||
BuyerId = customerId; | |||
} | |||
} | |||
@ -1,13 +1,10 @@ | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model | |||
public interface IBasketRepository | |||
{ | |||
public interface IBasketRepository | |||
{ | |||
Task<CustomerBasket> GetBasketAsync(string customerId); | |||
IEnumerable<string> GetUsers(); | |||
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket); | |||
Task<bool> DeleteBasketAsync(string id); | |||
} | |||
Task<CustomerBasket> GetBasketAsync(string customerId); | |||
IEnumerable<string> GetUsers(); | |||
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket); | |||
Task<bool> DeleteBasketAsync(string id); | |||
} | |||
@ -1,7 +1,7 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
public interface IIdentityService | |||
{ | |||
public interface IIdentityService | |||
{ | |||
string GetUserIdentity(); | |||
} | |||
string GetUserIdentity(); | |||
} | |||
@ -1,21 +1,17 @@ | |||
| |||
using Microsoft.AspNetCore.Http; | |||
using System; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services | |||
public class IdentityService : IIdentityService | |||
{ | |||
public class IdentityService : IIdentityService | |||
{ | |||
private IHttpContextAccessor _context; | |||
private IHttpContextAccessor _context; | |||
public IdentityService(IHttpContextAccessor context) | |||
{ | |||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||
} | |||
public IdentityService(IHttpContextAccessor context) | |||
{ | |||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||
} | |||
public string GetUserIdentity() | |||
{ | |||
return _context.HttpContext.User.FindFirst("sub").Value; | |||
} | |||
public string GetUserIdentity() | |||
{ | |||
return _context.HttpContext.User.FindFirst("sub").Value; | |||
} | |||
} | |||
@ -1,363 +1,325 @@ | |||
using Autofac; | |||
using Autofac.Extensions.DependencyInjection; | |||
using Basket.API.Infrastructure.Filters; | |||
using Basket.API.IntegrationEvents.EventHandling; | |||
using Basket.API.IntegrationEvents.Events; | |||
using GrpcBasket; | |||
using HealthChecks.UI.Client; | |||
using Microsoft.AspNetCore.Authentication.JwtBearer; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.Azure.ServiceBus; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
using Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Diagnostics.HealthChecks; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using Microsoft.OpenApi.Models; | |||
using RabbitMQ.Client; | |||
using StackExchange.Redis; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IdentityModel.Tokens.Jwt; | |||
using System.IO; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API; | |||
public class Startup | |||
{ | |||
public class Startup | |||
public Startup(IConfiguration configuration) | |||
{ | |||
public Startup(IConfiguration configuration) | |||
{ | |||
Configuration = configuration; | |||
} | |||
Configuration = configuration; | |||
} | |||
public IConfiguration Configuration { get; } | |||
public IConfiguration Configuration { get; } | |||
// This method gets called by the runtime. Use this method to add services to the container. | |||
public virtual IServiceProvider ConfigureServices(IServiceCollection services) | |||
// This method gets called by the runtime. Use this method to add services to the container. | |||
public virtual IServiceProvider ConfigureServices(IServiceCollection services) | |||
{ | |||
services.AddGrpc(options => | |||
{ | |||
services.AddGrpc(options => | |||
{ | |||
options.EnableDetailedErrors = true; | |||
}); | |||
options.EnableDetailedErrors = true; | |||
}); | |||
RegisterAppInsights(services); | |||
RegisterAppInsights(services); | |||
services.AddControllers(options => | |||
{ | |||
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | |||
options.Filters.Add(typeof(ValidateModelStateFilter)); | |||
services.AddControllers(options => | |||
{ | |||
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | |||
options.Filters.Add(typeof(ValidateModelStateFilter)); | |||
}) // Added for functional tests | |||
.AddApplicationPart(typeof(BasketController).Assembly) | |||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||
}) // Added for functional tests | |||
.AddApplicationPart(typeof(BasketController).Assembly) | |||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||
services.AddSwaggerGen(options => | |||
services.AddSwaggerGen(options => | |||
{ | |||
options.SwaggerDoc("v1", new OpenApiInfo | |||
{ | |||
options.DescribeAllEnumsAsStrings(); | |||
options.SwaggerDoc("v1", new OpenApiInfo | |||
{ | |||
Title = "eShopOnContainers - Basket HTTP API", | |||
Version = "v1", | |||
Description = "The Basket Service HTTP API" | |||
}); | |||
Title = "eShopOnContainers - Basket HTTP API", | |||
Version = "v1", | |||
Description = "The Basket Service HTTP API" | |||
}); | |||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | |||
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme | |||
{ | |||
Type = SecuritySchemeType.OAuth2, | |||
Flows = new OpenApiOAuthFlows() | |||
{ | |||
Type = SecuritySchemeType.OAuth2, | |||
Flows = new OpenApiOAuthFlows() | |||
Implicit = new OpenApiOAuthFlow() | |||
{ | |||
Implicit = new OpenApiOAuthFlow() | |||
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | |||
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | |||
Scopes = new Dictionary<string, string>() | |||
{ | |||
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"), | |||
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"), | |||
Scopes = new Dictionary<string, string>() | |||
{ | |||
{ "basket", "Basket API" } | |||
} | |||
{ "basket", "Basket API" } | |||
} | |||
} | |||
}); | |||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||
} | |||
}); | |||
ConfigureAuthService(services); | |||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||
}); | |||
services.AddCustomHealthCheck(Configuration); | |||
ConfigureAuthService(services); | |||
services.Configure<BasketSettings>(Configuration); | |||
services.AddCustomHealthCheck(Configuration); | |||
//By connecting here we are making sure that our service | |||
//cannot start until redis is ready. This might slow down startup, | |||
//but given that there is a delay on resolving the ip address | |||
//and then creating the connection it seems reasonable to move | |||
//that cost to startup instead of having the first request pay the | |||
//penalty. | |||
services.AddSingleton<ConnectionMultiplexer>(sp => | |||
{ | |||
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; | |||
var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); | |||
configuration.ResolveDns = true; | |||
services.Configure<BasketSettings>(Configuration); | |||
return ConnectionMultiplexer.Connect(configuration); | |||
}); | |||
//By connecting here we are making sure that our service | |||
//cannot start until redis is ready. This might slow down startup, | |||
//but given that there is a delay on resolving the ip address | |||
//and then creating the connection it seems reasonable to move | |||
//that cost to startup instead of having the first request pay the | |||
//penalty. | |||
services.AddSingleton<ConnectionMultiplexer>(sp => | |||
{ | |||
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; | |||
var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); | |||
configuration.ResolveDns = true; | |||
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IServiceBusPersisterConnection>(sp => | |||
{ | |||
var serviceBusConnectionString = Configuration["EventBusConnection"]; | |||
var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); | |||
var subscriptionClientName = Configuration["SubscriptionClientName"]; | |||
return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName); | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton<IRabbitMQPersistentConnection>(sp => | |||
{ | |||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | |||
return ConnectionMultiplexer.Connect(configuration); | |||
}); | |||
var factory = new ConnectionFactory() | |||
{ | |||
HostName = Configuration["EventBusConnection"], | |||
DispatchConsumersAsync = true | |||
}; | |||
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) | |||
{ | |||
factory.UserName = Configuration["EventBusUserName"]; | |||
} | |||
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IServiceBusPersisterConnection>(sp => | |||
{ | |||
var serviceBusConnectionString = Configuration["EventBusConnection"]; | |||
var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); | |||
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) | |||
{ | |||
factory.Password = Configuration["EventBusPassword"]; | |||
} | |||
var subscriptionClientName = Configuration["SubscriptionClientName"]; | |||
return new DefaultServiceBusPersisterConnection(serviceBusConnection, subscriptionClientName); | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton<IRabbitMQPersistentConnection>(sp => | |||
{ | |||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) | |||
{ | |||
retryCount = int.Parse(Configuration["EventBusRetryCount"]); | |||
} | |||
var factory = new ConnectionFactory() | |||
{ | |||
HostName = Configuration["EventBusConnection"], | |||
DispatchConsumersAsync = true | |||
}; | |||
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | |||
}); | |||
} | |||
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) | |||
{ | |||
factory.UserName = Configuration["EventBusUserName"]; | |||
} | |||
RegisterEventBus(services); | |||
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) | |||
{ | |||
factory.Password = Configuration["EventBusPassword"]; | |||
} | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) | |||
{ | |||
retryCount = int.Parse(Configuration["EventBusRetryCount"]); | |||
} | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | |||
}); | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
services.AddTransient<IBasketRepository, RedisBasketRepository>(); | |||
services.AddTransient<IIdentityService, IdentityService>(); | |||
} | |||
services.AddOptions(); | |||
RegisterEventBus(services); | |||
var container = new ContainerBuilder(); | |||
container.Populate(services); | |||
return new AutofacServiceProvider(container.Build()); | |||
} | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
}); | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
services.AddTransient<IBasketRepository, RedisBasketRepository>(); | |||
services.AddTransient<IIdentityService, IdentityService>(); | |||
services.AddOptions(); | |||
var container = new ContainerBuilder(); | |||
container.Populate(services); | |||
return new AutofacServiceProvider(container.Build()); | |||
} | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
//loggerFactory.AddAzureWebAppDiagnostics(); | |||
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
{ | |||
//loggerFactory.AddAzureWebAppDiagnostics(); | |||
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); | |||
app.UsePathBase(pathBase); | |||
} | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
app.UseSwagger() | |||
.UseSwaggerUI(setup => | |||
{ | |||
app.UsePathBase(pathBase); | |||
} | |||
app.UseSwagger() | |||
.UseSwaggerUI(setup => | |||
{ | |||
setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); | |||
setup.OAuthClientId("basketswaggerui"); | |||
setup.OAuthAppName("Basket Swagger UI"); | |||
}); | |||
setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); | |||
setup.OAuthClientId("basketswaggerui"); | |||
setup.OAuthAppName("Basket Swagger UI"); | |||
}); | |||
app.UseRouting(); | |||
app.UseCors("CorsPolicy"); | |||
ConfigureAuth(app); | |||
app.UseRouting(); | |||
app.UseCors("CorsPolicy"); | |||
ConfigureAuth(app); | |||
app.UseStaticFiles(); | |||
app.UseStaticFiles(); | |||
app.UseEndpoints(endpoints => | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapGrpcService<BasketService>(); | |||
endpoints.MapDefaultControllerRoute(); | |||
endpoints.MapControllers(); | |||
endpoints.MapGet("/_proto/", async ctx => | |||
{ | |||
endpoints.MapGrpcService<BasketService>(); | |||
endpoints.MapDefaultControllerRoute(); | |||
endpoints.MapControllers(); | |||
endpoints.MapGet("/_proto/", async ctx => | |||
ctx.Response.ContentType = "text/plain"; | |||
using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); | |||
using var sr = new StreamReader(fs); | |||
while (!sr.EndOfStream) | |||
{ | |||
ctx.Response.ContentType = "text/plain"; | |||
using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); | |||
using var sr = new StreamReader(fs); | |||
while (!sr.EndOfStream) | |||
var line = await sr.ReadLineAsync(); | |||
if (line != "/* >>" || line != "<< */") | |||
{ | |||
var line = await sr.ReadLineAsync(); | |||
if (line != "/* >>" || line != "<< */") | |||
{ | |||
await ctx.Response.WriteAsync(line); | |||
} | |||
await ctx.Response.WriteAsync(line); | |||
} | |||
}); | |||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
} | |||
}); | |||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
}); | |||
ConfigureEventBus(app); | |||
} | |||
ConfigureEventBus(app); | |||
} | |||
private void RegisterAppInsights(IServiceCollection services) | |||
{ | |||
services.AddApplicationInsightsTelemetry(Configuration); | |||
services.AddApplicationInsightsKubernetesEnricher(); | |||
} | |||
private void RegisterAppInsights(IServiceCollection services) | |||
private void ConfigureAuthService(IServiceCollection services) | |||
{ | |||
// prevent from mapping "sub" claim to nameidentifier. | |||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||
var identityUrl = Configuration.GetValue<string>("IdentityUrl"); | |||
services.AddAuthentication(options => | |||
{ | |||
services.AddApplicationInsightsTelemetry(Configuration); | |||
services.AddApplicationInsightsKubernetesEnricher(); | |||
} | |||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||
private void ConfigureAuthService(IServiceCollection services) | |||
}).AddJwtBearer(options => | |||
{ | |||
// prevent from mapping "sub" claim to nameidentifier. | |||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||
options.Authority = identityUrl; | |||
options.RequireHttpsMetadata = false; | |||
options.Audience = "basket"; | |||
}); | |||
} | |||
var identityUrl = Configuration.GetValue<string>("IdentityUrl"); | |||
protected virtual void ConfigureAuth(IApplicationBuilder app) | |||
{ | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
} | |||
services.AddAuthentication(options => | |||
private void RegisterEventBus(IServiceCollection services) | |||
{ | |||
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IEventBus, EventBusServiceBus>(sp => | |||
{ | |||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
}).AddJwtBearer(options => | |||
{ | |||
options.Authority = identityUrl; | |||
options.RequireHttpsMetadata = false; | |||
options.Audience = "basket"; | |||
return new EventBusServiceBus(serviceBusPersisterConnection, logger, | |||
eventBusSubcriptionsManager, iLifetimeScope); | |||
}); | |||
} | |||
protected virtual void ConfigureAuth(IApplicationBuilder app) | |||
else | |||
{ | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
} | |||
private void RegisterEventBus(IServiceCollection services) | |||
{ | |||
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IEventBus, EventBusServiceBus>(sp => | |||
{ | |||
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
return new EventBusServiceBus(serviceBusPersisterConnection, logger, | |||
eventBusSubcriptionsManager, iLifetimeScope); | |||
}); | |||
} | |||
else | |||
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => | |||
{ | |||
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => | |||
var subscriptionClientName = Configuration["SubscriptionClientName"]; | |||
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) | |||
{ | |||
var subscriptionClientName = Configuration["SubscriptionClientName"]; | |||
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) | |||
{ | |||
retryCount = int.Parse(Configuration["EventBusRetryCount"]); | |||
} | |||
retryCount = int.Parse(Configuration["EventBusRetryCount"]); | |||
} | |||
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); | |||
}); | |||
} | |||
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); | |||
}); | |||
} | |||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | |||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | |||
services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); | |||
services.AddTransient<OrderStartedIntegrationEventHandler>(); | |||
} | |||
services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); | |||
services.AddTransient<OrderStartedIntegrationEventHandler>(); | |||
} | |||
private void ConfigureEventBus(IApplicationBuilder app) | |||
{ | |||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); | |||
private void ConfigureEventBus(IApplicationBuilder app) | |||
{ | |||
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>(); | |||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); | |||
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); | |||
} | |||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); | |||
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); | |||
} | |||
} | |||
public static class CustomExtensionMethods | |||
public static class CustomExtensionMethods | |||
{ | |||
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
var hcBuilder = services.AddHealthChecks(); | |||
var hcBuilder = services.AddHealthChecks(); | |||
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); | |||
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); | |||
hcBuilder | |||
.AddRedis( | |||
configuration["ConnectionString"], | |||
name: "redis-check", | |||
tags: new string[] { "redis" }); | |||
hcBuilder | |||
.AddRedis( | |||
configuration["ConnectionString"], | |||
name: "redis-check", | |||
tags: new string[] { "redis" }); | |||
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
hcBuilder | |||
.AddAzureServiceBusTopic( | |||
configuration["EventBusConnection"], | |||
topicName: "eshop_event_bus", | |||
name: "basket-servicebus-check", | |||
tags: new string[] { "servicebus" }); | |||
} | |||
else | |||
{ | |||
hcBuilder | |||
.AddRabbitMQ( | |||
$"amqp://{configuration["EventBusConnection"]}", | |||
name: "basket-rabbitmqbus-check", | |||
tags: new string[] { "rabbitmqbus" }); | |||
} | |||
return services; | |||
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
hcBuilder | |||
.AddAzureServiceBusTopic( | |||
configuration["EventBusConnection"], | |||
topicName: "eshop_event_bus", | |||
name: "basket-servicebus-check", | |||
tags: new string[] { "servicebus" }); | |||
} | |||
else | |||
{ | |||
hcBuilder | |||
.AddRabbitMQ( | |||
$"amqp://{configuration["EventBusConnection"]}", | |||
name: "basket-rabbitmqbus-check", | |||
tags: new string[] { "rabbitmqbus" }); | |||
} | |||
return services; | |||
} | |||
} |
@ -1,10 +1,6 @@ | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Http.Features; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API; | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API | |||
internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature | |||
{ | |||
internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature | |||
{ | |||
public IHeaderDictionary Trailers { get; set; } | |||
} | |||
} | |||
public IHeaderDictionary Trailers { get; set; } | |||
} |