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;
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);
if (isAuthorized && !allowAnonymous)
{
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 (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
if (isAuthorized && !allowAnonymous)
operation.Parameters.Add(new OpenApiParameter
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "Authorization",
In = ParameterLocation.Header,
Description = "access token",
Required = true
});
}
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;
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)
{
private readonly IBasketRepository _repository;
private readonly IIdentityService _identityService;
private readonly IEventBus _eventBus;
private readonly ILogger<BasketController> _logger;
_logger = logger;
_repository = repository;
_identityService = identityService;
_eventBus = eventBus;
}
public BasketController(
ILogger<BasketController> logger,
IBasketRepository repository,
IIdentityService identityService,
IEventBus eventBus)
[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));
}
[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;
_repository = repository;
_identityService = identityService;
_eventBus = eventBus;
return BadRequest();
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
public async Task<ActionResult<CustomerBasket>> GetBasketByIdAsync(string id)
{
var basket = await _repository.GetBasketAsync(id);
var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value;
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]
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
public async Task<ActionResult<CustomerBasket>> UpdateBasketAsync([FromBody] CustomerBasket value)
{
return Ok(await _repository.UpdateBasketAsync(value));
}
return Accepted();
}
[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)
{
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);
}
// 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>/
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
return new RedirectResult("~/swagger");
}
}

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)
{
private readonly IBasketRepository _repository;
private readonly ILogger<BasketService> _logger;
_repository = repository;
_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;
_logger = logger;
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");
}
[AllowAnonymous]
public override async Task<CustomerBasketResponse> GetBasketById(BasketRequest request, ServerCallContext context)
return new CustomerBasketResponse();
}
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);
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();
return MapToCustomerBasketResponse(response);
}
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);
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)
customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse
{
var response = new CustomerBasketResponse
{
Buyerid = customerBasket.BuyerId
};
Id = item.Id,
Oldunitprice = (double)item.OldUnitPrice,
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
{
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;
}
return response;
}
private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest)
private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest)
{
var response = new CustomerBasket
{
var response = new CustomerBasket
{
BuyerId = customerBasketRequest.Buyerid
};
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
}));
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;
}
return response;
}
}

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

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

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

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)
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;
this.logger = logger;
}
var json = new JsonErrorResponse
{
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),
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[] { "An error occurred. Try it again." }
};
context.Result = new BadRequestObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
if (env.IsDevelopment())
{
var json = new JsonErrorResponse
{
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;
json.DeveloperMessage = context.Exception;
}
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
{
public class JsonErrorResponse
{
public string[] Messages { get; set; }
namespace Basket.API.Infrastructure.Filters;
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;
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;
}
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);
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);
}
}

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();
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
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
operation.Security = new List<OpenApiSecurityRequirement>
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
operation.Security = new List<OpenApiSecurityRequirement>
new OpenApiSecurityRequirement
{
new OpenApiSecurityRequirement
{
[ oAuthScheme ] = new [] { "basketapi" }
}
};
}
[ oAuthScheme ] = new [] { "basketapi" }
}
};
}
}
}

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;
}
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"));
await ProcessConfigRequest(context);
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";
await context.Response.WriteAsync(message);
await context.Response.WriteAsync("Failed due to FailingMiddleware enabled.");
}
private bool MustFail(HttpContext context)
else
{
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);
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;
}
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;
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);
};
}
}

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

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)
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>();
_redis = redis;
_database = redis.GetDatabase();
return null;
}
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()
{
var server = GetServer();
var data = server.Keys();
_logger.LogInformation("Basket item persisted succesfully.");
return data?.Select(k => k.ToString());
}
return await GetBasketAsync(basket.BuyerId);
}
public async Task<CustomerBasket> GetBasketAsync(string customerId)
{
var data = await _database.StringGetAsync(customerId);
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());
}
private IServer GetServer()
{
var endpoint = _redis.GetEndPoints();
return _redis.GetServer(endpoint.First());
}
}

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

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;
public ProductPriceChangedIntegrationEventHandler(
ILogger<ProductPriceChangedIntegrationEventHandler> logger,
IBasketRepository repository)
{
private readonly ILogger<ProductPriceChangedIntegrationEventHandler> _logger;
private readonly IBasketRepository _repository;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public ProductPriceChangedIntegrationEventHandler(
ILogger<ProductPriceChangedIntegrationEventHandler> logger,
IBasketRepository repository)
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
var userIds = _repository.GetUsers();
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();
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);
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);
}
}
}

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

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

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

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)
{
var results = new List<ValidationResult>();
if (Quantity < 1)
{
results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" }));
}
return results;
results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" }));
}
return results;
}
}

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

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

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

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;
public IdentityService(IHttpContextAccessor context)
{
private 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;
}
}

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;
}
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>();
}
Configuration = configuration;
}
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
.AddRedis(
configuration["ConnectionString"],
name: "redis-check",
tags: new string[] { "redis" });
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
services.AddControllers(options =>
{
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" });
}
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
options.Filters.Add(typeof(ValidateModelStateFilter));
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;
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; }
}