Use file-scoped namespaces

This commit is contained in:
Sumit Ghosh 2021-08-12 18:56:38 +05:30
parent 5157b01e81
commit 76ddee7756
31 changed files with 955 additions and 1150 deletions

View File

@ -1,33 +1,26 @@
using Microsoft.AspNetCore.Mvc.Authorization; namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
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);
if (isAuthorized && !allowAnonymous)
{ {
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; if (operation.Parameters == null)
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); operation.Parameters = new List<OpenApiParameter>();
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous)
operation.Parameters.Add(new OpenApiParameter
{ {
if (operation.Parameters == null) Name = "Authorization",
operation.Parameters = new List<OpenApiParameter>(); In = ParameterLocation.Header,
Description = "access token",
Required = true
operation.Parameters.Add(new OpenApiParameter });
{
Name = "Authorization",
In = ParameterLocation.Header,
Description = "access token",
Required = true
});
}
} }
} }
} }

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

View File

@ -1,103 +1,89 @@
using Basket.API.IntegrationEvents.Events; namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
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 [Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
{ {
[Route("api/v1/[controller]")] private readonly IBasketRepository _repository;
[Authorize] private readonly IIdentityService _identityService;
[ApiController] private readonly IEventBus _eventBus;
public class BasketController : ControllerBase private readonly ILogger<BasketController> _logger;
public BasketController(
ILogger<BasketController> logger,
IBasketRepository repository,
IIdentityService identityService,
IEventBus eventBus)
{ {
private readonly IBasketRepository _repository; _logger = logger;
private readonly IIdentityService _identityService; _repository = repository;
private readonly IEventBus _eventBus; _identityService = identityService;
private readonly ILogger<BasketController> _logger; _eventBus = eventBus;
}
public BasketController( [HttpGet("{id}")]
ILogger<BasketController> logger, [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
IBasketRepository repository, public async Task<ActionResult<CustomerBasket>> GetBasketByIdAsync(string id)
IIdentityService identityService, {
IEventBus eventBus) var basket = await _repository.GetBasketAsync(id);
return Ok(basket ?? new CustomerBasket(id));
}
[HttpPost]
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
public async Task<ActionResult<CustomerBasket>> UpdateBasketAsync([FromBody] CustomerBasket value)
{
return Ok(await _repository.UpdateBasketAsync(value));
}
[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();
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
var basket = await _repository.GetBasketAsync(userId);
if (basket == null)
{ {
_logger = logger; return BadRequest();
_repository = repository;
_identityService = identityService;
_eventBus = eventBus;
} }
[HttpGet("{id}")] var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value;
[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)); 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);
// 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);
throw;
} }
[HttpPost] return Accepted();
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] }
public async Task<ActionResult<CustomerBasket>> UpdateBasketAsync([FromBody] CustomerBasket value)
{
return Ok(await _repository.UpdateBasketAsync(value));
}
[Route("checkout")] // DELETE api/values/5
[HttpPost] [HttpDelete("{id}")]
[ProducesResponseType((int)HttpStatusCode.Accepted)] [ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task DeleteBasketByIdAsync(string id)
public async Task<ActionResult> CheckoutAsync([FromBody] BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) {
{ await _repository.DeleteBasketAsync(id);
var userId = _identityService.GetUserIdentity();
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
var basket = await _repository.GetBasketAsync(userId);
if (basket == null)
{
return BadRequest();
}
var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value;
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);
// 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);
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);
}
} }
} }

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>/ return new RedirectResult("~/swagger");
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
} }
} }

View File

@ -1,102 +1,93 @@
using Grpc.Core; namespace GrpcBasket;
using Microsoft.AspNetCore.Authorization; public class BasketService : Basket.BasketBase
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
namespace GrpcBasket
{ {
public class BasketService : Basket.BasketBase private readonly IBasketRepository _repository;
private readonly ILogger<BasketService> _logger;
public BasketService(IBasketRepository repository, ILogger<BasketService> logger)
{ {
private readonly IBasketRepository _repository; _repository = repository;
private readonly ILogger<BasketService> _logger; _logger = logger;
}
public BasketService(IBasketRepository repository, ILogger<BasketService> logger) [AllowAnonymous]
public override async Task<CustomerBasketResponse> GetBasketById(BasketRequest request, ServerCallContext context)
{
_logger.LogInformation("Begin grpc call from method {Method} for basket id {Id}", context.Method, request.Id);
var data = await _repository.GetBasketAsync(request.Id);
if (data != null)
{ {
_repository = repository; context.Status = new Status(StatusCode.OK, $"Basket with id {request.Id} do exist");
_logger = logger;
return MapToCustomerBasketResponse(data);
}
else
{
context.Status = new Status(StatusCode.NotFound, $"Basket with id {request.Id} do not exist");
} }
[AllowAnonymous] return new CustomerBasketResponse();
public override async Task<CustomerBasketResponse> GetBasketById(BasketRequest request, ServerCallContext context) }
public override async Task<CustomerBasketResponse> UpdateBasket(CustomerBasketRequest request, ServerCallContext context)
{
_logger.LogInformation("Begin grpc call BasketService.UpdateBasketAsync for buyer id {Buyerid}", request.Buyerid);
var customerBasket = MapToCustomerBasket(request);
var response = await _repository.UpdateBasketAsync(customerBasket);
if (response != null)
{ {
_logger.LogInformation("Begin grpc call from method {Method} for basket id {Id}", context.Method, request.Id); return MapToCustomerBasketResponse(response);
var data = await _repository.GetBasketAsync(request.Id);
if (data != null)
{
context.Status = new Status(StatusCode.OK, $"Basket with id {request.Id} do exist");
return MapToCustomerBasketResponse(data);
}
else
{
context.Status = new Status(StatusCode.NotFound, $"Basket with id {request.Id} do not exist");
}
return new CustomerBasketResponse();
} }
public override async Task<CustomerBasketResponse> UpdateBasket(CustomerBasketRequest request, ServerCallContext context) context.Status = new Status(StatusCode.NotFound, $"Basket with buyer id {request.Buyerid} do not exist");
return null;
}
private CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket)
{
var response = new CustomerBasketResponse
{ {
_logger.LogInformation("Begin grpc call BasketService.UpdateBasketAsync for buyer id {Buyerid}", request.Buyerid); Buyerid = customerBasket.BuyerId
};
var customerBasket = MapToCustomerBasket(request); customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse
var response = await _repository.UpdateBasketAsync(customerBasket);
if (response != null)
{
return MapToCustomerBasketResponse(response);
}
context.Status = new Status(StatusCode.NotFound, $"Basket with buyer id {request.Buyerid} do not exist");
return null;
}
private CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket)
{ {
var response = new CustomerBasketResponse Id = item.Id,
{ Oldunitprice = (double)item.OldUnitPrice,
Buyerid = customerBasket.BuyerId Pictureurl = item.PictureUrl,
}; Productid = item.ProductId,
Productname = item.ProductName,
Quantity = item.Quantity,
Unitprice = (double)item.UnitPrice
}));
customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse return response;
{ }
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 CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest)
} {
var response = new CustomerBasket
private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest)
{ {
var response = new CustomerBasket BuyerId = customerBasketRequest.Buyerid
{ };
BuyerId = customerBasketRequest.Buyerid
};
customerBasketRequest.Items.ToList().ForEach(item => response.Items.Add(new BasketItem customerBasketRequest.Items.ToList().ForEach(item => response.Items.Add(new BasketItem
{ {
Id = item.Id, Id = item.Id,
OldUnitPrice = (decimal)item.Oldunitprice, OldUnitPrice = (decimal)item.Oldunitprice,
PictureUrl = item.Pictureurl, PictureUrl = item.Pictureurl,
ProductId = item.Productid, ProductId = item.Productid,
ProductName = item.Productname, ProductName = item.Productname,
Quantity = item.Quantity, Quantity = item.Quantity,
UnitPrice = (decimal)item.Unitprice UnitPrice = (decimal)item.Unitprice
})); }));
return response; return response;
}
} }
} }

View File

@ -1,14 +1,11 @@
using Microsoft.AspNetCore.Http; namespace Basket.API.Infrastructure.ActionResults;
using Microsoft.AspNetCore.Mvc;
namespace Basket.API.Infrastructure.ActionResults public class InternalServerErrorObjectResult : ObjectResult
{ {
public class InternalServerErrorObjectResult : ObjectResult public InternalServerErrorObjectResult(object error)
: base(error)
{ {
public InternalServerErrorObjectResult(object error) StatusCode = StatusCodes.Status500InternalServerError;
: base(error)
{
StatusCode = StatusCodes.Status500InternalServerError;
}
} }
} }

View File

@ -1,21 +1,16 @@
using System; namespace Basket.API.Infrastructure.Exceptions;
namespace Basket.API.Infrastructure.Exceptions public class BasketDomainException : Exception
{ {
/// <summary> public BasketDomainException()
/// Exception type for app exceptions { }
/// </summary>
public class BasketDomainException : Exception
{
public BasketDomainException()
{ }
public BasketDomainException(string message) public BasketDomainException(string message)
: base(message) : base(message)
{ } { }
public BasketDomainException(string message, Exception innerException) public BasketDomainException(string message, Exception innerException)
: base(message, innerException) : base(message, innerException)
{ } { }
}
} }

View File

@ -1,20 +1,17 @@
using Microsoft.AspNetCore.Builder; namespace Basket.API.Infrastructure.Middlewares;
using System;
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);
{ }
return UseFailingMiddleware(builder, null); public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action)
} {
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action) var options = new FailingOptions();
{ action?.Invoke(options);
var options = new FailingOptions(); builder.UseMiddleware<FailingMiddleware>(options);
action?.Invoke(options); return builder;
builder.UseMiddleware<FailingMiddleware>(options);
return builder;
}
} }
} }

View File

@ -1,58 +1,47 @@
using Basket.API.Infrastructure.ActionResults; namespace Basket.API.Infrastructure.Filters;
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;
public partial class HttpGlobalExceptionFilter : IExceptionFilter
namespace Basket.API.Infrastructure.Filters
{ {
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; this.env = env;
private readonly ILogger<HttpGlobalExceptionFilter> logger; this.logger = logger;
}
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) public void OnException(ExceptionContext context)
{
logger.LogError(new EventId(context.Exception.HResult),
context.Exception,
context.Exception.Message);
if (context.Exception.GetType() == typeof(BasketDomainException))
{ {
this.env = env; var json = new JsonErrorResponse
this.logger = logger; {
} Messages = new[] { context.Exception.Message }
};
public void OnException(ExceptionContext context) context.Result = new BadRequestObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{ {
logger.LogError(new EventId(context.Exception.HResult), var json = new JsonErrorResponse
context.Exception,
context.Exception.Message);
if (context.Exception.GetType() == typeof(BasketDomainException))
{ {
var json = new JsonErrorResponse Messages = new[] { "An error occurred. Try it again." }
{ };
Messages = new[] { context.Exception.Message }
};
context.Result = new BadRequestObjectResult(json); if (env.IsDevelopment())
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{ {
var json = new JsonErrorResponse json.DeveloperMessage = context.Exception;
{
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;
} }
context.ExceptionHandled = true;
context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
} }
context.ExceptionHandled = true;
} }
} }

View File

@ -1,9 +1,9 @@
namespace Basket.API.Infrastructure.Filters namespace Basket.API.Infrastructure.Filters;
{
public class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; } public class JsonErrorResponse
} {
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; }
} }

View File

@ -1,30 +1,26 @@
using Microsoft.AspNetCore.Mvc; namespace Basket.API.Infrastructure.Filters;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq;
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 json = new JsonErrorResponse
{
Messages = validationErrors
};
context.Result = new BadRequestObjectResult(json);
} }
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k].Errors)
.Select(e => e.ErrorMessage)
.ToArray();
var json = new JsonErrorResponse
{
Messages = validationErrors
};
context.Result = new BadRequestObjectResult(json);
} }
} }

View File

@ -1,36 +1,29 @@
using Microsoft.AspNetCore.Authorization; namespace Basket.API.Infrastructure.Filters;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
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();
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{ {
// Check for authorize attribute Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || };
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (!hasAuthorize) return; operation.Security = new List<OpenApiSecurityRequirement>
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" } new OpenApiSecurityRequirement
};
operation.Security = new List<OpenApiSecurityRequirement>
{ {
new OpenApiSecurityRequirement [ oAuthScheme ] = new [] { "basketapi" }
{ }
[ oAuthScheme ] = new [] { "basketapi" } };
}
};
}
} }
} }

View File

@ -1,95 +1,88 @@
using Microsoft.AspNetCore.Http; namespace Basket.API.Infrastructure.Middlewares;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
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; _next = next;
private bool _mustFail; _options = options;
private readonly FailingOptions _options; _mustFail = false;
private readonly ILogger _logger; _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; await ProcessConfigRequest(context);
_options = options;
_mustFail = false;
_logger = logger;
}
public async Task Invoke(HttpContext 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);
}
}
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 (enable && disable)
{
throw new ArgumentException("Must use enable or disable querystring values, but not both");
}
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 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; return;
} }
private async Task SendOkResponse(HttpContext context, string message) if (MustFail(context))
{ {
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; _logger.LogInformation("Response for path {Path} will fail.", path);
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(message); await context.Response.WriteAsync("Failed due to FailingMiddleware enabled.");
} }
else
private bool MustFail(HttpContext context)
{ {
var rpath = context.Request.Path.Value; await _next.Invoke(context);
if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase)))
{
return false;
}
return _mustFail &&
(_options.EndpointPaths.Any(x => x == rpath)
|| _options.EndpointPaths.Count == 0);
} }
} }
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 (enable && disable)
{
throw new ArgumentException("Must use enable or disable querystring values, but not both");
}
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 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;
}
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;
if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase)))
{
return false;
}
return _mustFail &&
(_options.EndpointPaths.Any(x => x == rpath)
|| _options.EndpointPaths.Count == 0);
}
} }

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

View File

@ -1,24 +1,20 @@
using Microsoft.AspNetCore.Builder; namespace Basket.API.Infrastructure.Middlewares;
using Microsoft.AspNetCore.Hosting;
using System;
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; _options = optionsAction;
public FailingStartupFilter(Action<FailingOptions> 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);
};
}
} }
} }

View File

@ -1,18 +1,14 @@
using Microsoft.AspNetCore.Hosting; namespace Basket.API.Infrastructure.Middlewares;
using Microsoft.Extensions.DependencyInjection;
using System;
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));
{ });
services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options)); return builder;
});
return builder;
}
} }
} }

View File

@ -1,73 +1,63 @@
using Microsoft.eShopOnContainers.Services.Basket.API.Model; namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories;
using Microsoft.Extensions.Logging; public class RedisBasketRepository : IBasketRepository
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
{ {
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; _logger = loggerFactory.CreateLogger<RedisBasketRepository>();
private readonly ConnectionMultiplexer _redis; _redis = redis;
private readonly IDatabase _database; _database = redis.GetDatabase();
}
public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) public async Task<bool> DeleteBasketAsync(string id)
{
return await _database.KeyDeleteAsync(id);
}
public IEnumerable<string> GetUsers()
{
var server = GetServer();
var data = server.Keys();
return data?.Select(k => k.ToString());
}
public async Task<CustomerBasket> GetBasketAsync(string customerId)
{
var data = await _database.StringGetAsync(customerId);
if (data.IsNullOrEmpty)
{ {
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); return null;
_redis = redis;
_database = redis.GetDatabase();
} }
public async Task<bool> DeleteBasketAsync(string id) return JsonSerializer.Deserialize<CustomerBasket>(data, new JsonSerializerOptions
{ {
return await _database.KeyDeleteAsync(id); PropertyNameCaseInsensitive = true
});
}
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket)
{
var created = await _database.StringSetAsync(basket.BuyerId, JsonSerializer.Serialize(basket));
if (!created)
{
_logger.LogInformation("Problem occur persisting the item.");
return null;
} }
public IEnumerable<string> GetUsers() _logger.LogInformation("Basket item persisted succesfully.");
{
var server = GetServer();
var data = server.Keys();
return data?.Select(k => k.ToString()); return await GetBasketAsync(basket.BuyerId);
} }
public async Task<CustomerBasket> GetBasketAsync(string customerId) private IServer GetServer()
{ {
var data = await _database.StringGetAsync(customerId); var endpoint = _redis.GetEndPoints();
return _redis.GetServer(endpoint.First());
if (data.IsNullOrEmpty)
{
return null;
}
return JsonSerializer.Deserialize<CustomerBasket>(data, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket)
{
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.");
return await GetBasketAsync(basket.BuyerId);
}
private IServer GetServer()
{
var endpoint = _redis.GetEndPoints();
return _redis.GetServer(endpoint.First());
}
} }
} }

View File

@ -1,37 +1,29 @@
using Basket.API.IntegrationEvents.Events; namespace Basket.API.IntegrationEvents.EventHandling;
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 public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent>
{ {
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent> private readonly IBasketRepository _repository;
private readonly ILogger<OrderStartedIntegrationEventHandler> _logger;
public OrderStartedIntegrationEventHandler(
IBasketRepository repository,
ILogger<OrderStartedIntegrationEventHandler> logger)
{ {
private readonly IBasketRepository _repository; _repository = repository ?? throw new ArgumentNullException(nameof(repository));
private readonly ILogger<OrderStartedIntegrationEventHandler> _logger; _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public OrderStartedIntegrationEventHandler( public async Task Handle(OrderStartedIntegrationEvent @event)
IBasketRepository repository, {
ILogger<OrderStartedIntegrationEventHandler> logger) using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{ {
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderStartedIntegrationEvent @event) await _repository.DeleteBasketAsync(@event.UserId.ToString());
{
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
await _repository.DeleteBasketAsync(@event.UserId.ToString());
}
} }
} }
} }

View File

@ -1,64 +1,53 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
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 public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent>
{ {
public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandler<ProductPriceChangedIntegrationEvent> private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger;
private readonly IBasketRepository _repository;
public ProductPriceChangedIntegrationEventHandler(
ILogger<ProductPriceChangedIntegrationEventHandler> logger,
IBasketRepository repository)
{ {
private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger; _logger = logger ?? throw new ArgumentNullException(nameof(logger));
private readonly IBasketRepository _repository; _repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public ProductPriceChangedIntegrationEventHandler( public async Task Handle(ProductPriceChangedIntegrationEvent @event)
ILogger<ProductPriceChangedIntegrationEventHandler> logger, {
IBasketRepository repository) using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task Handle(ProductPriceChangedIntegrationEvent @event) var userIds = _repository.GetUsers();
{
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}")) foreach (var id in userIds)
{ {
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event); var basket = await _repository.GetBasketAsync(id);
var userIds = _repository.GetUsers(); await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket);
foreach (var id in userIds)
{
var basket = await _repository.GetBasketAsync(id);
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, 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)
{
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate);
foreach (var item in itemsToUpdate)
{
if (item.UnitPrice == oldPrice)
{
var originalPrice = item.UnitPrice;
item.UnitPrice = newPrice;
item.OldUnitPrice = originalPrice;
}
}
await _repository.UpdateBasketAsync(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)
{
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate);
foreach (var item in itemsToUpdate)
{
if (item.UnitPrice == oldPrice)
{
var originalPrice = item.UnitPrice;
item.UnitPrice = newPrice;
item.OldUnitPrice = originalPrice;
}
}
await _repository.UpdateBasketAsync(basket);
}
}
}

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: public string UserId { get; init; }
// 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 OrderStartedIntegrationEvent(string userId) public OrderStartedIntegrationEvent(string userId)
=> UserId = userId; => UserId = userId;
}
} }

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: public int ProductId { get; private init; }
// 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 decimal NewPrice { get; private init; }
public record ProductPriceChangedIntegrationEvent : IntegrationEvent
public decimal OldPrice { get; private init; }
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
{ {
public int ProductId { get; private init; } ProductId = productId;
NewPrice = newPrice;
public decimal NewPrice { get; private init; } OldPrice = oldPrice;
public decimal OldPrice { get; private init; }
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
{
ProductId = productId;
NewPrice = newPrice;
OldPrice = oldPrice;
}
} }
} }

View File

@ -1,64 +1,59 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Basket.API.IntegrationEvents.Events;
using Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System;
namespace Basket.API.IntegrationEvents.Events public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
{ {
public record UserCheckoutAcceptedIntegrationEvent : IntegrationEvent public string UserId { get; }
public string UserName { get; }
public int OrderNumber { get; init; }
public string City { get; init; }
public string Street { get; init; }
public string State { get; init; }
public string Country { get; init; }
public string ZipCode { get; init; }
public string CardNumber { get; init; }
public string CardHolderName { get; init; }
public DateTime CardExpiration { get; init; }
public string CardSecurityNumber { get; init; }
public int CardTypeId { get; init; }
public string Buyer { get; init; }
public Guid RequestId { get; init; }
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)
{ {
public string UserId { get; } UserId = userId;
UserName = userName;
public string UserName { get; } City = city;
Street = street;
public int OrderNumber { get; init; } State = state;
Country = country;
public string City { get; init; } ZipCode = zipCode;
CardNumber = cardNumber;
public string Street { get; init; } CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
public string State { get; init; } CardSecurityNumber = cardSecurityNumber;
CardTypeId = cardTypeId;
public string Country { get; init; } Buyer = buyer;
Basket = basket;
public string ZipCode { get; init; } RequestId = requestId;
public string CardNumber { get; init; }
public string CardHolderName { get; init; }
public DateTime CardExpiration { get; init; }
public string CardSecurityNumber { get; init; }
public int CardTypeId { get; init; }
public string Buyer { get; init; }
public Guid RequestId { get; init; }
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;
}
} }
} }

View File

@ -1,32 +1,27 @@
using System; namespace Basket.API.Model;
public class BasketCheckout
namespace Basket.API.Model
{ {
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; }
}
} }

View File

@ -1,27 +1,23 @@
using System.Collections.Generic; namespace Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System.ComponentModel.DataAnnotations;
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; } var results = new List<ValidationResult>();
public int ProductId { get; set; }
public string ProductName { get; set; } if (Quantity < 1)
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>(); results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" }));
if (Quantity < 1)
{
results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" }));
}
return results;
} }
return results;
} }
} }

View File

@ -1,21 +1,18 @@
using System.Collections.Generic; namespace Microsoft.eShopOnContainers.Services.Basket.API.Model;
public class CustomerBasket
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{ {
public class CustomerBasket public string BuyerId { get; set; }
public List<BasketItem> Items { get; set; } = new List<BasketItem>();
public CustomerBasket()
{ {
public string BuyerId { get; set; }
public List<BasketItem> Items { get; set; } = new List<BasketItem>(); }
public CustomerBasket() public CustomerBasket(string customerId)
{ {
BuyerId = customerId;
}
public CustomerBasket(string customerId)
{
BuyerId = customerId;
}
} }
} }

View File

@ -1,13 +1,10 @@
using System.Collections.Generic; namespace Microsoft.eShopOnContainers.Services.Basket.API.Model;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model public interface IBasketRepository
{ {
public interface IBasketRepository Task<CustomerBasket> GetBasketAsync(string customerId);
{ IEnumerable<string> GetUsers();
Task<CustomerBasket> GetBasketAsync(string customerId); Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket);
IEnumerable<string> GetUsers(); Task<bool> DeleteBasketAsync(string id);
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket);
Task<bool> DeleteBasketAsync(string id);
}
} }

View File

@ -1,18 +1,4 @@
using Basket.API.Infrastructure.Middlewares; var configuration = GetConfiguration();
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();
Log.Logger = CreateSerilogLogger(configuration); Log.Logger = CreateSerilogLogger(configuration);

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

View File

@ -1,21 +1,17 @@
 namespace Microsoft.eShopOnContainers.Services.Basket.API.Services;
using Microsoft.AspNetCore.Http;
using System;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Services public class IdentityService : IIdentityService
{ {
public class IdentityService : IIdentityService private IHttpContextAccessor _context;
public IdentityService(IHttpContextAccessor context)
{ {
private IHttpContextAccessor _context; _context = context ?? throw new ArgumentNullException(nameof(context));
}
public IdentityService(IHttpContextAccessor context) public string GetUserIdentity()
{ {
_context = context ?? throw new ArgumentNullException(nameof(context)); return _context.HttpContext.User.FindFirst("sub").Value;
}
public string GetUserIdentity()
{
return _context.HttpContext.User.FindFirst("sub").Value;
}
} }
} }

View File

@ -1,363 +1,325 @@
using Autofac; namespace Microsoft.eShopOnContainers.Services.Basket.API;
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 public class Startup
{ {
public class Startup public Startup(IConfiguration configuration)
{ {
public Startup(IConfiguration configuration) Configuration = configuration;
{
Configuration = configuration;
}
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)
{
services.AddGrpc(options =>
{
options.EnableDetailedErrors = true;
});
RegisterAppInsights(services);
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);
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "eShopOnContainers - Basket HTTP API",
Version = "v1",
Description = "The Basket Service HTTP API"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
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>()
{
{ "basket", "Basket API" }
}
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
ConfigureAuthService(services);
services.AddCustomHealthCheck(Configuration);
services.Configure<BasketSettings>(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;
return ConnectionMultiplexer.Connect(configuration);
});
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>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"],
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
{
factory.UserName = Configuration["EventBusUserName"];
}
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
{
factory.Password = Configuration["EventBusPassword"];
}
var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
});
}
RegisterEventBus(services);
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);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
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");
});
app.UseRouting();
app.UseCors("CorsPolicy");
ConfigureAuth(app);
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
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)
{
var line = await sr.ReadLineAsync();
if (line != "/* >>" || 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")
});
});
ConfigureEventBus(app);
}
private void RegisterAppInsights(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddApplicationInsightsKubernetesEnricher();
}
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 =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "basket";
});
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
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 =>
{
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"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<OrderStartedIntegrationEventHandler>();
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}
} }
public static class CustomExtensionMethods 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)
{ {
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration) services.AddGrpc(options =>
{ {
var hcBuilder = services.AddHealthChecks(); options.EnableDetailedErrors = true;
});
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); RegisterAppInsights(services);
hcBuilder services.AddControllers(options =>
.AddRedis(
configuration["ConnectionString"],
name: "redis-check",
tags: new string[] { "redis" });
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{ {
hcBuilder options.Filters.Add(typeof(HttpGlobalExceptionFilter));
.AddAzureServiceBusTopic( options.Filters.Add(typeof(ValidateModelStateFilter));
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; }) // Added for functional tests
.AddApplicationPart(typeof(BasketController).Assembly)
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "eShopOnContainers - Basket HTTP API",
Version = "v1",
Description = "The Basket Service HTTP API"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
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>()
{
{ "basket", "Basket API" }
}
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
ConfigureAuthService(services);
services.AddCustomHealthCheck(Configuration);
services.Configure<BasketSettings>(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;
return ConnectionMultiplexer.Connect(configuration);
});
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>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"],
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
{
factory.UserName = Configuration["EventBusUserName"];
}
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
{
factory.Password = Configuration["EventBusPassword"];
}
var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
});
}
RegisterEventBus(services);
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);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
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");
});
app.UseRouting();
app.UseCors("CorsPolicy");
ConfigureAuth(app);
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
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)
{
var line = await sr.ReadLineAsync();
if (line != "/* >>" || 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")
});
});
ConfigureEventBus(app);
}
private void RegisterAppInsights(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddApplicationInsightsKubernetesEnricher();
}
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 =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "basket";
});
}
protected virtual void ConfigureAuth(IApplicationBuilder app)
{
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 =>
{
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"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
services.AddTransient<OrderStartedIntegrationEventHandler>();
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}
}
public static class CustomExtensionMethods
{
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{
var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
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;
} }
} }

View File

@ -1,10 +1,6 @@
using Microsoft.AspNetCore.Http; namespace Microsoft.eShopOnContainers.Services.Basket.API;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.eShopOnContainers.Services.Basket.API internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature
{ {
internal class TestHttpResponseTrailersFeature : IHttpResponseTrailersFeature public IHeaderDictionary Trailers { get; set; }
{
public IHeaderDictionary Trailers { get; set; }
}
} }