Browse Source

Use file-scoped namespaces

pull/1755/head
Sumit Ghosh 3 years ago
parent
commit
76ddee7756
31 changed files with 844 additions and 1039 deletions
  1. +17
    -24
      src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs
  2. +5
    -5
      src/Services/Basket/Basket.API/BasketSettings.cs
  3. +71
    -85
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  4. +6
    -8
      src/Services/Basket/Basket.API/Controllers/HomeController.cs
  5. +71
    -80
      src/Services/Basket/Basket.API/Grpc/BasketService.cs
  6. +6
    -9
      src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs
  7. +11
    -16
      src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs
  8. +12
    -15
      src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs
  9. +33
    -44
      src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  10. +6
    -6
      src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs
  11. +17
    -21
      src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs
  12. +20
    -27
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs
  13. +65
    -72
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs
  14. +6
    -8
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs
  15. +13
    -17
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs
  16. +8
    -12
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs
  17. +46
    -56
      src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs
  18. +18
    -26
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  19. +33
    -44
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs
  20. +9
    -11
      src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  21. +14
    -16
      src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs
  22. +39
    -44
      src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs
  23. +14
    -19
      src/Services/Basket/Basket.API/Model/BasketCheckout.cs
  24. +16
    -20
      src/Services/Basket/Basket.API/Model/BasketItem.cs
  25. +11
    -14
      src/Services/Basket/Basket.API/Model/CustomerBasket.cs
  26. +7
    -10
      src/Services/Basket/Basket.API/Model/IBasketRepository.cs
  27. +1
    -15
      src/Services/Basket/Basket.API/Program.cs
  28. +5
    -5
      src/Services/Basket/Basket.API/Services/IIdentityService.cs
  29. +11
    -15
      src/Services/Basket/Basket.API/Services/IdentityService.cs
  30. +249
    -287
      src/Services/Basket/Basket.API/Startup.cs
  31. +4
    -8
      src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs

+ 17
- 24
src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs View File

@ -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
});
}
}
}

+ 5
- 5
src/Services/Basket/Basket.API/BasketSettings.cs View File

@ -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; }
}

+ 71
- 85
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -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);
}
}

+ 6
- 8
src/Services/Basket/Basket.API/Controllers/HomeController.cs View File

@ -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");
}
}

+ 71
- 80
src/Services/Basket/Basket.API/Grpc/BasketService.cs View File

@ -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;
}
}

+ 6
- 9
src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs View File

@ -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;
}
}

+ 11
- 16
src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs View File

@ -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)
{ }
}

+ 12
- 15
src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs View File

@ -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;
}
}

+ 33
- 44
src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -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;
}
}

+ 6
- 6
src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs View File

@ -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; }
}

+ 17
- 21
src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs View File

@ -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);
}
}

+ 20
- 27
src/Services/Basket/Basket.API/Infrastructure/Middlewares/AuthorizeCheckOperationFilter.cs View File

@ -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" }
}
};
}
}
}

+ 65
- 72
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs View File

@ -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);
}
}

+ 6
- 8
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs View File

@ -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>();
}

+ 13
- 17
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs View File

@ -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);
};
}
}

+ 8
- 12
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs View File

@ -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;
}
}

+ 46
- 56
src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs View File

@ -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());
}
}

+ 18
- 26
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs View File

@ -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());
}
}
}

+ 33
- 44
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs View File

@ -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);
}
}
}

+ 9
- 11
src/Services/Basket/Basket.API/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -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;
}

+ 14
- 16
src/Services/Basket/Basket.API/IntegrationEvents/Events/ProductPriceChangedIntegrationEvent.cs View File

@ -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;
}
}

+ 39
- 44
src/Services/Basket/Basket.API/IntegrationEvents/Events/UserCheckoutAcceptedIntegrationEvent.cs View File

@ -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;
}
}

+ 14
- 19
src/Services/Basket/Basket.API/Model/BasketCheckout.cs View File

@ -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; }
}

+ 16
- 20
src/Services/Basket/Basket.API/Model/BasketItem.cs View File

@ -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;
}
}

+ 11
- 14
src/Services/Basket/Basket.API/Model/CustomerBasket.cs View File

@ -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;
}
}

+ 7
- 10
src/Services/Basket/Basket.API/Model/IBasketRepository.cs View File

@ -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
- 15
src/Services/Basket/Basket.API/Program.cs View File

@ -1,18 +1,4 @@
using Basket.API.Infrastructure.Middlewares;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.eShopOnContainers.Services.Basket.API;
using Microsoft.Extensions.Configuration;
using Azure.Identity;
using Serilog;
using System;
using System.IO;
using System.Net;
using Azure.Core;
var configuration = GetConfiguration();
var configuration = GetConfiguration();
Log.Logger = CreateSerilogLogger(configuration);


+ 5
- 5
src/Services/Basket/Basket.API/Services/IIdentityService.cs View File

@ -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();
}

+ 11
- 15
src/Services/Basket/Basket.API/Services/IdentityService.cs View File

@ -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;
}
}

+ 249
- 287
src/Services/Basket/Basket.API/Startup.cs View File

@ -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;
}
}

+ 4
- 8
src/Services/Basket/Basket.API/TestHttpResponseTrailersFeature.cs View File

@ -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; }
}

Loading…
Cancel
Save