Code re-factorings and formatting for Basket.API project

This commit is contained in:
Rafsanul Hasan 2018-09-09 05:45:35 +06:00
parent 386c118616
commit d44c12e718
No known key found for this signature in database
GPG Key ID: FC57FD2D87BE60DD
36 changed files with 9751 additions and 9771 deletions

View File

@ -1,28 +1,28 @@
(function ($, swaggerUi) { (function ($, swaggerUi) {
$(function () { $(function () {
var settings = { var settings = {
authority: 'https://localhost:5105', authority: 'https://localhost:5105',
client_id: 'js', client_id: 'js',
popup_redirect_uri: window.location.protocol popup_redirect_uri: window.location.protocol
+ '//' + '//'
+ window.location.host + window.location.host
+ '/tokenclient/popup.html', + '/tokenclient/popup.html',
response_type: 'id_token token', response_type: 'id_token token',
scope: 'openid profile basket', scope: 'openid profile basket',
filter_protocol_claims: true filter_protocol_claims: true
}, },
manager = new OidcTokenManager(settings), manager = new OidcTokenManager(settings),
$inputApiKey = $('#input_apiKey'); $inputApiKey = $('#input_apiKey');
$inputApiKey.on('dblclick', function () { $inputApiKey.on('dblclick', function () {
manager.openPopupForTokenAsync() manager.openPopupForTokenAsync()
.then(function () { .then(function () {
$inputApiKey.val(manager.access_token).change(); $inputApiKey.val(manager.access_token).change();
}, function (error) { }, function (error) {
console.error(error); console.error(error);
}); });
}); });
}); });
})(jQuery, window.swaggerUi); })(jQuery, window.swaggerUi);

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title></title> <title></title>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body> <body>
<script type="text/javascript" src="oidc-token-manager.min.js"></script> <script type="text/javascript" src="oidc-token-manager.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
new OidcTokenManager().processTokenPopup(); new OidcTokenManager().processTokenPopup();
</script> </script>
</body> </body>
</html> </html>

View File

@ -6,28 +6,30 @@ 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(Operation operation, OperationFilterContext context) public void Apply(Operation operation, OperationFilterContext context)
{ {
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors; var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter); var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter); var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous) if (isAuthorized && !allowAnonymous)
{ {
if (operation.Parameters == null) if (operation.Parameters == null)
operation.Parameters = new List<IParameter>(); {
operation.Parameters = new List<IParameter>();
}
operation.Parameters.Add(new NonBodyParameter operation.Parameters.Add(new NonBodyParameter
{ {
Name = "Authorization", Name = "Authorization",
In = "header", In = "header",
Description = "access token", Description = "access token",
Required = true, Required = true,
Type = "string" Type = "string"
}); });
} }
} }
} }
} }

View File

@ -3,18 +3,18 @@ using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server
{ {
public class IdentitySecurityScheme:SecurityScheme public class IdentitySecurityScheme : SecurityScheme
{ {
public IdentitySecurityScheme() public IdentitySecurityScheme()
{ {
Type = "IdentitySecurityScheme"; Type = "IdentitySecurityScheme";
Description = "Security definition that provides to the user of Swagger a mechanism to obtain a token from the identity service that secures the api"; Description = "Security definition that provides to the user of Swagger a mechanism to obtain a token from the identity service that secures the api";
Extensions.Add("authorizationUrl", "http://localhost:5103/Auth/Client/popup.html"); Extensions.Add("authorizationUrl", "http://localhost:5103/Auth/Client/popup.html");
Extensions.Add("flow", "implicit"); Extensions.Add("flow", "implicit");
Extensions.Add("scopes", new List<string> Extensions.Add("scopes", new List<string>
{ {
"basket" "basket"
}); });
} }
} }
} }

View File

@ -1,9 +1,9 @@
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; }
public string EventBusConnection { get; set; } public string EventBusConnection { get; set; }
} }
} }

View File

@ -11,86 +11,86 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{ {
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[Authorize] [Authorize]
public class BasketController : Controller public class BasketController : Controller
{ {
private readonly IBasketRepository _repository; private readonly IBasketRepository _repository;
private readonly IIdentityService _identitySvc; private readonly IIdentityService _identitySvc;
private readonly IEventBus _eventBus; private readonly IEventBus _eventBus;
public BasketController(IBasketRepository repository, public BasketController(IBasketRepository repository,
IIdentityService identityService, IIdentityService identityService,
IEventBus eventBus) IEventBus eventBus)
{ {
_repository = repository; _repository = repository;
_identitySvc = identityService; _identitySvc = identityService;
_eventBus = eventBus; _eventBus = eventBus;
} }
// GET /id // GET /id
[HttpGet("{id}")] [HttpGet("{id}")]
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Get(string id) public async Task<IActionResult> Get(string id)
{ {
var basket = await _repository.GetBasketAsync(id); var basket = await _repository.GetBasketAsync(id);
if (basket == null) if (basket == null)
{ {
return Ok(new CustomerBasket(id) { }); return Ok(new CustomerBasket(id) { });
} }
return Ok(basket); return Ok(basket);
} }
// POST /value // POST /value
[HttpPost] [HttpPost]
[ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(CustomerBasket), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Post([FromBody]CustomerBasket value) public async Task<IActionResult> Post([FromBody]CustomerBasket value)
{ {
var basket = await _repository.UpdateBasketAsync(value); var basket = await _repository.UpdateBasketAsync(value);
return Ok(basket); return Ok(basket);
} }
[Route("checkout")] [Route("checkout")]
[HttpPost] [HttpPost]
[ProducesResponseType((int)HttpStatusCode.Accepted)] [ProducesResponseType((int)HttpStatusCode.Accepted)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Checkout([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId) public async Task<IActionResult> Checkout([FromBody]BasketCheckout basketCheckout, [FromHeader(Name = "x-requestid")] string requestId)
{ {
var userId = _identitySvc.GetUserIdentity(); var userId = _identitySvc.GetUserIdentity();
basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
var basket = await _repository.GetBasketAsync(userId); basketCheckout.RequestId = (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) ?
guid : basketCheckout.RequestId;
if (basket == null) var basket = await _repository.GetBasketAsync(userId);
{
return BadRequest();
}
var userName = User.FindFirst(x => x.Type == "unique_name").Value; if (basket == null)
{
return BadRequest();
}
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, var userName = User.FindFirst(x => x.Type == "unique_name").Value;
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 var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street,
// ordering.api to convert basket to order and proceeds with basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName,
// order creation process basketCheckout.CardExpiration, basketCheckout.CardSecurityNumber, basketCheckout.CardTypeId, basketCheckout.Buyer, basketCheckout.RequestId, basket);
_eventBus.Publish(eventMessage);
return Accepted(); // Once basket is checkout, sends an integration event to
} // ordering.api to convert basket to order and proceeds with
// order creation process
_eventBus.Publish(eventMessage);
// DELETE api/values/5 return Accepted();
[HttpDelete("{id}")] }
public void Delete(string id)
{
_repository.DeleteBasketAsync(id);
}
} // DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(string id)
{
_repository.DeleteBasketAsync(id);
}
}
} }

View File

@ -2,12 +2,10 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{ {
public class HomeController : Controller public class HomeController : Controller
{ {
// GET: /<controller>/ // GET: /<controller>/
public IActionResult Index() public IActionResult Index()
{ => new RedirectResult("~/swagger");
return new RedirectResult("~/swagger"); }
}
}
} }

View File

@ -3,12 +3,10 @@ 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) public InternalServerErrorObjectResult(object error)
: base(error) : base(error)
{ => StatusCode = StatusCodes.Status500InternalServerError;
StatusCode = StatusCodes.Status500InternalServerError; }
}
}
} }

View File

@ -2,20 +2,20 @@
namespace Basket.API.Infrastructure.Exceptions namespace Basket.API.Infrastructure.Exceptions
{ {
/// <summary> /// <summary>
/// Exception type for app exceptions /// Exception type for app exceptions
/// </summary> /// </summary>
public class BasketDomainException : Exception public class BasketDomainException : Exception
{ {
public BasketDomainException() 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

@ -3,18 +3,16 @@ 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)
{ => 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

@ -6,25 +6,25 @@ using System.Linq;
namespace Basket.API.Infrastructure.Filters namespace Basket.API.Infrastructure.Filters
{ {
public class AuthorizeCheckOperationFilter : IOperationFilter public class AuthorizeCheckOperationFilter : IOperationFilter
{ {
public void Apply(Operation operation, OperationFilterContext context) public void Apply(Operation operation, OperationFilterContext context)
{ {
// Check for authorize attribute // Check for authorize attribute
var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() || var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any(); context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();
if (hasAuthorize) if (hasAuthorize)
{ {
operation.Responses.Add("401", new Response { Description = "Unauthorized" }); operation.Responses.Add("401", new Response { Description = "Unauthorized" });
operation.Responses.Add("403", new Response { Description = "Forbidden" }); operation.Responses.Add("403", new Response { Description = "Forbidden" });
operation.Security = new List<IDictionary<string, IEnumerable<string>>>(); operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
operation.Security.Add(new Dictionary<string, IEnumerable<string>> operation.Security.Add(new Dictionary<string, IEnumerable<string>>
{ {
{ "oauth2", new [] { "basketapi" } } { "oauth2", new [] { "basketapi" } }
}); });
} }
} }
} }
} }

View File

@ -8,49 +8,49 @@ 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 IHostingEnvironment env; private readonly IHostingEnvironment env;
private readonly ILogger<HttpGlobalExceptionFilter> logger; private readonly ILogger<HttpGlobalExceptionFilter> logger;
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
{ {
this.env = env; this.env = env;
this.logger = logger; this.logger = logger;
} }
public void OnException(ExceptionContext context) public void OnException(ExceptionContext context)
{ {
logger.LogError(new EventId(context.Exception.HResult), logger.LogError(new EventId(context.Exception.HResult),
context.Exception, context.Exception,
context.Exception.Message); context.Exception.Message);
if (context.Exception.GetType() == typeof(BasketDomainException)) if (context.Exception.GetType() == typeof(BasketDomainException))
{ {
var json = new JsonErrorResponse var json = new JsonErrorResponse
{ {
Messages = new[] { context.Exception.Message } Messages = new[] { context.Exception.Message }
}; };
context.Result = new BadRequestObjectResult(json); context.Result = new BadRequestObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
} }
else else
{ {
var json = new JsonErrorResponse var json = new JsonErrorResponse
{ {
Messages = new[] { "An error occurred. Try it again." } Messages = new[] { "An error occurred. Try it again." }
}; };
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
json.DeveloperMessage = context.Exception; json.DeveloperMessage = context.Exception;
} }
context.Result = new InternalServerErrorObjectResult(json); context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
} }
context.ExceptionHandled = true; context.ExceptionHandled = true;
} }
} }
} }

View File

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

View File

@ -4,27 +4,27 @@ using System.Linq;
namespace Basket.API.Infrastructure.Filters namespace Basket.API.Infrastructure.Filters
{ {
public class ValidateModelStateFilter : ActionFilterAttribute public class ValidateModelStateFilter : ActionFilterAttribute
{ {
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
{ {
if (context.ModelState.IsValid) if (context.ModelState.IsValid)
{ {
return; return;
} }
var validationErrors = context.ModelState var validationErrors = context.ModelState
.Keys .Keys
.SelectMany(k => context.ModelState[k].Errors) .SelectMany(k => context.ModelState[k].Errors)
.Select(e => e.ErrorMessage) .Select(e => e.ErrorMessage)
.ToArray(); .ToArray();
var json = new JsonErrorResponse var json = new JsonErrorResponse
{ {
Messages = validationErrors Messages = validationErrors
}; };
context.Result = new BadRequestObjectResult(json); context.Result = new BadRequestObjectResult(json);
} }
} }
} }

View File

@ -1,79 +1,78 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Basket.API.Infrastructure.Middlewares namespace Basket.API.Infrastructure.Middlewares
{ {
class ByPassAuthMiddleware class ByPassAuthMiddleware
{ {
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private string _currentUserId; private string _currentUserId;
public ByPassAuthMiddleware(RequestDelegate next) public ByPassAuthMiddleware(RequestDelegate next)
{ {
_next = next; _next = next;
_currentUserId = null; _currentUserId = null;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var path = context.Request.Path; var path = context.Request.Path;
if (path == "/noauth") if (path == "/noauth")
{ {
var userid = context.Request.Query["userid"]; var userid = context.Request.Query["userid"];
if (!string.IsNullOrEmpty(userid)) if (!string.IsNullOrEmpty(userid))
{ {
_currentUserId = userid; _currentUserId = userid;
} }
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
context.Response.ContentType = "text/string"; context.Response.ContentType = "text/string";
await context.Response.WriteAsync($"User set to {_currentUserId}"); await context.Response.WriteAsync($"User set to {_currentUserId}");
} }
else if (path == "/noauth/reset") else if (path == "/noauth/reset")
{ {
_currentUserId = null; _currentUserId = null;
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
context.Response.ContentType = "text/string"; context.Response.ContentType = "text/string";
await context.Response.WriteAsync($"User set to none. Token required for protected endpoints."); await context.Response.WriteAsync($"User set to none. Token required for protected endpoints.");
} }
else else
{ {
var currentUserId = _currentUserId; var currentUserId = _currentUserId;
var authHeader = context.Request.Headers["Authorization"]; var authHeader = context.Request.Headers["Authorization"];
if (authHeader != StringValues.Empty) if (authHeader != StringValues.Empty)
{ {
var header = authHeader.FirstOrDefault(); var header = authHeader.FirstOrDefault();
if (!string.IsNullOrEmpty(header) && header.StartsWith("Email ") && header.Length > "Email ".Length) if (!string.IsNullOrEmpty(header) && header.StartsWith("Email ") && header.Length > "Email ".Length)
{ {
currentUserId = header.Substring("Email ".Length); currentUserId = header.Substring("Email ".Length);
} }
} }
if (!string.IsNullOrEmpty(currentUserId)) if (!string.IsNullOrEmpty(currentUserId))
{ {
var user = new ClaimsIdentity(new[] { var user = new ClaimsIdentity(new[] {
new Claim("emails", currentUserId), new Claim("emails", currentUserId),
new Claim("name", "Test user"), new Claim("name", "Test user"),
new Claim("nonce", Guid.NewGuid().ToString()), new Claim("nonce", Guid.NewGuid().ToString()),
new Claim("ttp://schemas.microsoft.com/identity/claims/identityprovider", "ByPassAuthMiddleware"), new Claim("ttp://schemas.microsoft.com/identity/claims/identityprovider", "ByPassAuthMiddleware"),
new Claim("nonce", Guid.NewGuid().ToString()), new Claim("nonce", Guid.NewGuid().ToString()),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname","User"), new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname","User"),
new Claim("sub", "1234"), new Claim("sub", "1234"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname","Microsoft")} new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname","Microsoft")}
, "ByPassAuth"); , "ByPassAuth");
context.User = new ClaimsPrincipal(user); context.User = new ClaimsPrincipal(user);
} }
await _next.Invoke(context); await _next.Invoke(context);
} }
} }
} }
} }

View File

@ -5,78 +5,78 @@ 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 readonly RequestDelegate _next;
private bool _mustFail; private bool _mustFail;
private readonly FailingOptions _options; private readonly FailingOptions _options;
public FailingMiddleware(RequestDelegate next, FailingOptions options) public FailingMiddleware(RequestDelegate next, FailingOptions options)
{ {
_next = next; _next = next;
_options = options; _options = options;
_mustFail = false; _mustFail = false;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
var path = context.Request.Path; var path = context.Request.Path;
if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase)) if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase))
{ {
await ProcessConfigRequest(context); await ProcessConfigRequest(context);
return; return;
} }
if (MustFail(context)) if (MustFail(context))
{ {
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Failed due to FailingMiddleware enabled."); await context.Response.WriteAsync("Failed due to FailingMiddleware enabled.");
} }
else else
{ {
await _next.Invoke(context); await _next.Invoke(context);
} }
} }
private async Task ProcessConfigRequest(HttpContext context) private async Task ProcessConfigRequest(HttpContext context)
{ {
var enable = context.Request.Query.Keys.Any(k => k == "enable"); var enable = context.Request.Query.Keys.Any(k => k == "enable");
var disable = context.Request.Query.Keys.Any(k => k == "disable"); var disable = context.Request.Query.Keys.Any(k => k == "disable");
if (enable && disable) if (enable && disable)
{ {
throw new ArgumentException("Must use enable or disable querystring values, but not both"); throw new ArgumentException("Must use enable or disable querystring values, but not both");
} }
if (disable) if (disable)
{ {
_mustFail = false; _mustFail = false;
await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed."); await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed.");
return; return;
} }
if (enable) if (enable)
{ {
_mustFail = true; _mustFail = true;
await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500"); await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500");
return; return;
} }
// If reach here, that means that no valid parameter has been passed. Just output status // 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 SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled"));
return; return;
} }
private async Task SendOkResponse(HttpContext context, string message) private async Task SendOkResponse(HttpContext context, string message)
{ {
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
context.Response.ContentType = "text/plain"; context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(message); await context.Response.WriteAsync(message);
} }
private bool MustFail(HttpContext context) private bool MustFail(HttpContext context)
{ {
return _mustFail && return _mustFail &&
(_options.EndpointPaths.Any(x => x == context.Request.Path.Value) (_options.EndpointPaths.Any(x => x == context.Request.Path.Value)
|| _options.EndpointPaths.Count == 0); || _options.EndpointPaths.Count == 0);
} }
} }
} }

View File

@ -2,9 +2,9 @@
namespace Basket.API.Infrastructure.Middlewares namespace Basket.API.Infrastructure.Middlewares
{ {
public class FailingOptions public class FailingOptions
{ {
public string ConfigPath = "/Failing"; public string ConfigPath = "/Failing";
public List<string> EndpointPaths { get; set; } = new List<string>(); public List<string> EndpointPaths { get; set; } = new List<string>();
} }
} }

View File

@ -4,21 +4,21 @@ 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; private readonly Action<FailingOptions> _options;
public FailingStartupFilter(Action<FailingOptions> optionsAction) 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); app.UseFailingMiddleware(_options);
next(app); next(app);
}; };
} }
} }
} }

View File

@ -4,15 +4,15 @@ 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)); services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options));
}); });
return builder; return builder;
} }
} }
} }

View File

@ -6,20 +6,20 @@ 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 IBasketRepository _repository;
public OrderStartedIntegrationEventHandler(IBasketRepository repository) public OrderStartedIntegrationEventHandler(IBasketRepository repository)
{ {
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); _repository = repository ?? throw new ArgumentNullException(nameof(repository));
} }
public async Task Handle(OrderStartedIntegrationEvent @event) public async Task Handle(OrderStartedIntegrationEvent @event)
{ {
await _repository.DeleteBasketAsync(@event.UserId.ToString()); await _repository.DeleteBasketAsync(@event.UserId.ToString());
} }
} }
} }

View File

@ -7,46 +7,46 @@ 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 IBasketRepository _repository; private readonly IBasketRepository _repository;
public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository) public ProductPriceChangedIntegrationEventHandler(IBasketRepository repository)
{ {
_repository = repository ?? throw new ArgumentNullException(nameof(repository)); _repository = repository ?? throw new ArgumentNullException(nameof(repository));
} }
public async Task Handle(ProductPriceChangedIntegrationEvent @event) public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{ {
var userIds = _repository.GetUsers(); var userIds = _repository.GetUsers();
foreach (var id in userIds)
{
var basket = await _repository.GetBasketAsync(id);
await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket); foreach (var id in userIds)
} {
} var basket = await _repository.GetBasketAsync(id);
private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket) await UpdatePriceInBasketItems(@event.ProductId, @event.NewPrice, @event.OldPrice, basket);
{ }
string match = productId.ToString(); }
var itemsToUpdate = basket?.Items?.Where(x => x.ProductId == match).ToList();
if (itemsToUpdate != null) private async Task UpdatePriceInBasketItems(int productId, decimal newPrice, decimal oldPrice, CustomerBasket basket)
{ {
foreach (var item in itemsToUpdate) string match = productId.ToString();
{ var itemsToUpdate = basket?.Items?.Where(x => x.ProductId == match).ToList();
if(item.UnitPrice == oldPrice)
{ if (itemsToUpdate != null)
var originalPrice = item.UnitPrice; {
item.UnitPrice = newPrice; foreach (var item in itemsToUpdate)
item.OldUnitPrice = originalPrice; {
} if (item.UnitPrice == oldPrice)
} {
await _repository.UpdateBasketAsync(basket); var originalPrice = item.UnitPrice;
} item.UnitPrice = newPrice;
} item.OldUnitPrice = originalPrice;
} }
}
await _repository.UpdateBasketAsync(basket);
}
}
}
} }

View File

@ -2,14 +2,14 @@
namespace Basket.API.IntegrationEvents.Events namespace Basket.API.IntegrationEvents.Events
{ {
// Integration Events notes: // Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be // 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. // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class OrderStartedIntegrationEvent : IntegrationEvent public class OrderStartedIntegrationEvent : IntegrationEvent
{ {
public string UserId { get; set; } public string UserId { get; set; }
public OrderStartedIntegrationEvent(string userId) public OrderStartedIntegrationEvent(string userId)
=> UserId = userId; => UserId = userId;
} }
} }

View File

@ -2,22 +2,22 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events namespace Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events
{ {
// Integration Events notes: // Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be // 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. // An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class ProductPriceChangedIntegrationEvent : IntegrationEvent public class ProductPriceChangedIntegrationEvent : IntegrationEvent
{ {
public int ProductId { get; private set; } public int ProductId { get; private set; }
public decimal NewPrice { get; private set; } public decimal NewPrice { get; private set; }
public decimal OldPrice { get; private set; } public decimal OldPrice { get; private set; }
public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice) public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice, decimal oldPrice)
{ {
ProductId = productId; ProductId = productId;
NewPrice = newPrice; NewPrice = newPrice;
OldPrice = oldPrice; OldPrice = oldPrice;
} }
} }
} }

View File

@ -4,61 +4,61 @@ using System;
namespace Basket.API.IntegrationEvents.Events namespace Basket.API.IntegrationEvents.Events
{ {
public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent
{ {
public string UserId { get; } public string UserId { get; }
public string UserName { get; } public string UserName { get; }
public int OrderNumber { get; set; } public int OrderNumber { get; set; }
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; }
public CustomerBasket Basket { get; } public CustomerBasket Basket { get; }
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street,
string state, string country, string zipCode, string cardNumber, string cardHolderName, string state, string country, string zipCode, string cardNumber, string cardHolderName,
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId,
CustomerBasket basket) CustomerBasket basket)
{ {
UserId = userId; UserId = userId;
UserName = userName; UserName = userName;
City = city; City = city;
Street = street; Street = street;
State = state; State = state;
Country = country; Country = country;
ZipCode = zipCode; ZipCode = zipCode;
CardNumber = cardNumber; CardNumber = cardNumber;
CardHolderName = cardHolderName; CardHolderName = cardHolderName;
CardExpiration = cardExpiration; CardExpiration = cardExpiration;
CardSecurityNumber = cardSecurityNumber; CardSecurityNumber = cardSecurityNumber;
CardTypeId = cardTypeId; CardTypeId = cardTypeId;
Buyer = buyer; Buyer = buyer;
Basket = basket; Basket = basket;
RequestId = requestId; RequestId = requestId;
} }
} }
} }

View File

@ -2,31 +2,31 @@
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

@ -3,25 +3,25 @@ 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 string Id { get; set; }
public string ProductId { get; set; } public string ProductId { get; set; }
public string ProductName { get; set; } public string ProductName { get; set; }
public decimal UnitPrice { get; set; } public decimal UnitPrice { get; set; }
public decimal OldUnitPrice { get; set; } public decimal OldUnitPrice { get; set; }
public int Quantity { get; set; } public int Quantity { get; set; }
public string PictureUrl { get; set; } public string PictureUrl { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{ {
var results = new List<ValidationResult>(); var results = new List<ValidationResult>();
if (Quantity < 1) if (Quantity < 1)
{ {
results.Add(new ValidationResult("Invalid number of units", new []{ "Quantity" })); results.Add(new ValidationResult("Invalid number of units", new[] { "Quantity" }));
} }
return results; return results;
} }
} }
} }

View File

@ -2,15 +2,15 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{ {
public class CustomerBasket public class CustomerBasket
{ {
public string BuyerId { get; set; } public string BuyerId { get; set; }
public List<BasketItem> Items { get; set; } public List<BasketItem> Items { get; set; }
public CustomerBasket(string customerId) public CustomerBasket(string customerId)
{ {
BuyerId = customerId; BuyerId = customerId;
Items = new List<BasketItem>(); Items = new List<BasketItem>();
} }
} }
} }

View File

@ -3,11 +3,11 @@ 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); Task<CustomerBasket> GetBasketAsync(string customerId);
IEnumerable<string> GetUsers(); IEnumerable<string> GetUsers();
Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket); Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket);
Task<bool> DeleteBasketAsync(string id); Task<bool> DeleteBasketAsync(string id);
} }
} }

View File

@ -7,61 +7,61 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Model namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
{ {
public class RedisBasketRepository : IBasketRepository public class RedisBasketRepository : IBasketRepository
{ {
private readonly ILogger<RedisBasketRepository> _logger; private readonly ILogger<RedisBasketRepository> _logger;
private readonly ConnectionMultiplexer _redis; private readonly ConnectionMultiplexer _redis;
private readonly IDatabase _database; private readonly IDatabase _database;
public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis) public RedisBasketRepository(ILoggerFactory loggerFactory, ConnectionMultiplexer redis)
{ {
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); _logger = loggerFactory.CreateLogger<RedisBasketRepository>();
_redis = redis; _redis = redis;
_database = redis.GetDatabase(); _database = redis.GetDatabase();
} }
public async Task<bool> DeleteBasketAsync(string id) public async Task<bool> DeleteBasketAsync(string id)
{ {
return await _database.KeyDeleteAsync(id); return await _database.KeyDeleteAsync(id);
} }
public IEnumerable<string> GetUsers() public IEnumerable<string> GetUsers()
{ {
var server = GetServer(); var server = GetServer();
var data = server.Keys(); var data = server.Keys();
return data?.Select(k => k.ToString()); return data?.Select(k => k.ToString());
} }
public async Task<CustomerBasket> GetBasketAsync(string customerId) public async Task<CustomerBasket> GetBasketAsync(string customerId)
{ {
var data = await _database.StringGetAsync(customerId); var data = await _database.StringGetAsync(customerId);
if (data.IsNullOrEmpty) if (data.IsNullOrEmpty)
{ {
return null; return null;
} }
return JsonConvert.DeserializeObject<CustomerBasket>(data); return JsonConvert.DeserializeObject<CustomerBasket>(data);
} }
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket) public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket basket)
{ {
var created = await _database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket)); var created = await _database.StringSetAsync(basket.BuyerId, JsonConvert.SerializeObject(basket));
if (!created) if (!created)
{ {
_logger.LogInformation("Problem occur persisting the item."); _logger.LogInformation("Problem occur persisting the item.");
return null; return null;
} }
_logger.LogInformation("Basket item persisted succesfully."); _logger.LogInformation("Basket item persisted succesfully.");
return await GetBasketAsync(basket.BuyerId); return await GetBasketAsync(basket.BuyerId);
} }
private IServer GetServer() private IServer GetServer()
{ {
var endpoint = _redis.GetEndPoints(); var endpoint = _redis.GetEndPoints();
return _redis.GetServer(endpoint.First()); return _redis.GetServer(endpoint.First());
} }
} }
} }

View File

@ -1,55 +1,61 @@
using Basket.API.Infrastructure.Middlewares; using Basket.API.Infrastructure.Middlewares;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using static Microsoft.AspNetCore.Hosting.ApplicationInsightsWebHostBuilderExtensions;
using System.IO; using static Microsoft.AspNetCore.Hosting.HealthCheckWebHostBuilderExtension;
using static Microsoft.AspNetCore.Hosting.HostingAbstractionsWebHostBuilderExtensions;
using static Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions;
using static Microsoft.AspNetCore.Hosting.WebHostExtensions;
using static Microsoft.Extensions.Configuration.AzureKeyVaultConfigurationExtensions;
using static Microsoft.Extensions.Configuration.ChainedBuilderExtensions;
using static Microsoft.Extensions.Configuration.EnvironmentVariablesExtensions;
using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder;
using Convert = System.Convert;
using Directory = System.IO.Directory;
using IWebHost = Microsoft.AspNetCore.Hosting.IWebHost;
using WebHost = Microsoft.AspNetCore.WebHost;
namespace Microsoft.eShopOnContainers.Services.Basket.API namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
public class Program public class Program
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ => BuildWebHost(args).Run();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) => public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args) WebHost.CreateDefaultBuilder(args)
.UseFailing(options => .UseFailing(options =>
{ {
options.ConfigPath = "/Failing"; options.ConfigPath = "/Failing";
}) })
.UseHealthChecks("/hc") .UseHealthChecks("/hc")
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>() .UseStartup<Startup>()
.ConfigureAppConfiguration((builderContext, config) => .ConfigureAppConfiguration((builderContext, config) =>
{ {
var builtConfig = config.Build(); var builtConfig = config.Build();
var configurationBuilder = new ConfigurationBuilder(); var configurationBuilder = new ConfigurationBuilder();
if (Convert.ToBoolean(builtConfig["UseVault"])) if (Convert.ToBoolean(builtConfig["UseVault"]))
{ {
configurationBuilder.AddAzureKeyVault( configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/", $"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"], builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]); builtConfig["Vault:ClientSecret"]);
} }
configurationBuilder.AddEnvironmentVariables(); configurationBuilder.AddEnvironmentVariables();
config.AddConfiguration(configurationBuilder.Build()); config.AddConfiguration(configurationBuilder.Build());
}) })
.ConfigureLogging((hostingContext, builder) => .ConfigureLogging((hostingContext, builder) =>
{ {
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole(); builder.AddConsole();
builder.AddDebug(); builder.AddDebug();
}) })
.UseApplicationInsights() .UseApplicationInsights()
.Build(); .Build();
} }
} }

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

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

View File

@ -34,281 +34,285 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Basket.API namespace Microsoft.eShopOnContainers.Services.Basket.API
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services) public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
RegisterAppInsights(services); RegisterAppInsights(services);
// Add framework services. // Add framework services.
services.AddMvc(options => services
{ .AddMvc(options =>
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); {
options.Filters.Add(typeof(ValidateModelStateFilter)); options.Filters.Add(typeof(HttpGlobalExceptionFilter));
options.Filters.Add(typeof(ValidateModelStateFilter));
}).AddControllersAsServices(); })
.AddControllersAsServices();
ConfigureAuthService(services); ConfigureAuthService(services);
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")), checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask<IHealthCheckResult>(HealthCheckResult.Healthy("Ok")),
TimeSpan.Zero //No cache for this HealthCheck, better just for demos TimeSpan.Zero //No cache for this HealthCheck, better just for demos
); );
}); });
services.Configure<BasketSettings>(Configuration); services.Configure<BasketSettings>(Configuration);
//By connecting here we are making sure that our service //By connecting here we are making sure that our service
//cannot start until redis is ready. This might slow down startup, //cannot start until redis is ready. This might slow down startup,
//but given that there is a delay on resolving the ip address //but given that there is a delay on resolving the ip address
//and then creating the connection it seems reasonable to move //and then creating the connection it seems reasonable to move
//that cost to startup instead of having the first request pay the //that cost to startup instead of having the first request pay the
//penalty. //penalty.
services.AddSingleton<ConnectionMultiplexer>(sp => services.AddSingleton(sp =>
{ {
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value; var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true); var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true);
configuration.ResolveDns = true; configuration.ResolveDns = true;
return ConnectionMultiplexer.Connect(configuration); return ConnectionMultiplexer.Connect(configuration);
}); });
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{ {
services.AddSingleton<IServiceBusPersisterConnection>(sp => services.AddSingleton<IServiceBusPersisterConnection>(sp =>
{ {
var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>(); var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>();
var serviceBusConnectionString = Configuration["EventBusConnection"]; var serviceBusConnectionString = Configuration["EventBusConnection"];
var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString);
return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger);
}); });
} }
else else
{ {
services.AddSingleton<IRabbitMQPersistentConnection>(sp => services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{ {
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory() var factory = new ConnectionFactory()
{ {
HostName = Configuration["EventBusConnection"] HostName = Configuration["EventBusConnection"]
}; };
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
{ {
factory.UserName = Configuration["EventBusUserName"]; factory.UserName = Configuration["EventBusUserName"];
} }
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
{ {
factory.Password = Configuration["EventBusPassword"]; factory.Password = Configuration["EventBusPassword"];
} }
var retryCount = 5; var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{ {
retryCount = int.Parse(Configuration["EventBusRetryCount"]); retryCount = int.Parse(Configuration["EventBusRetryCount"]);
} }
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
}); });
} }
RegisterEventBus(services); RegisterEventBus(services);
services.AddSwaggerGen(options => services.AddSwaggerGen(options =>
{ {
options.DescribeAllEnumsAsStrings(); options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Info options.SwaggerDoc("v1", new Info
{ {
Title = "Basket HTTP API", Title = "Basket HTTP API",
Version = "v1", Version = "v1",
Description = "The Basket Service HTTP API", Description = "The Basket Service HTTP API",
TermsOfService = "Terms Of Service" TermsOfService = "Terms Of Service"
}); });
options.AddSecurityDefinition("oauth2", new OAuth2Scheme options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{ {
Type = "oauth2", Type = "oauth2",
Flow = "implicit", Flow = "implicit",
AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize", AuthorizationUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize",
TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token", TokenUrl = $"{Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token",
Scopes = new Dictionary<string, string>() Scopes = new Dictionary<string, string>()
{ {
{ "basket", "Basket API" } { "basket", "Basket API" }
} }
}); });
options.OperationFilter<AuthorizeCheckOperationFilter>(); options.OperationFilter<AuthorizeCheckOperationFilter>();
}); });
services.AddCors(options => services.AddCors(options =>
{ {
options.AddPolicy("CorsPolicy", options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin() builder => builder.AllowAnyOrigin()
.AllowAnyMethod() .AllowAnyMethod()
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials()); .AllowCredentials());
}); });
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IBasketRepository, RedisBasketRepository>(); services.AddTransient<IBasketRepository, RedisBasketRepository>();
services.AddTransient<IIdentityService, IdentityService>(); services.AddTransient<IIdentityService, IdentityService>();
services.AddOptions(); services.AddOptions();
var container = new ContainerBuilder(); var container = new ContainerBuilder();
container.Populate(services); container.Populate(services);
return new AutofacServiceProvider(container.Build()); 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, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"]; // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
if (!string.IsNullOrEmpty(pathBase)) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{ {
app.UsePathBase(pathBase); loggerFactory.AddAzureWebAppDiagnostics();
} loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200));
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
app.UseStaticFiles(); app.UseStaticFiles();
app.UseCors("CorsPolicy"); app.UseCors("CorsPolicy");
ConfigureAuth(app); ConfigureAuth(app);
app.UseMvcWithDefaultRoute(); app.UseMvcWithDefaultRoute();
app.UseSwagger() app.UseSwagger()
.UseSwaggerUI(c => .UseSwaggerUI(c =>
{ {
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1");
c.OAuthClientId ("basketswaggerui"); c.OAuthClientId("basketswaggerui");
c.OAuthAppName("Basket Swagger UI"); c.OAuthAppName("Basket Swagger UI");
}); });
ConfigureEventBus(app); ConfigureEventBus(app);
} }
private void RegisterAppInsights(IServiceCollection services) private void RegisterAppInsights(IServiceCollection services)
{ {
services.AddApplicationInsightsTelemetry(Configuration); services.AddApplicationInsightsTelemetry(Configuration);
var orchestratorType = Configuration.GetValue<string>("OrchestratorType"); var orchestratorType = Configuration.GetValue<string>("OrchestratorType");
if (orchestratorType?.ToUpper() == "K8S")
{
// Enable K8s telemetry initializer
services.EnableKubernetes();
}
if (orchestratorType?.ToUpper() == "SF")
{
// Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer());
}
}
private void ConfigureAuthService(IServiceCollection services) if (orchestratorType?.ToUpper() == "K8S")
{ {
// prevent from mapping "sub" claim to nameidentifier. // Enable K8s telemetry initializer
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.EnableKubernetes();
}
if (orchestratorType?.ToUpper() == "SF")
{
// Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer());
}
}
var identityUrl = Configuration.GetValue<string>("IdentityUrl"); private void ConfigureAuthService(IServiceCollection services)
{
services.AddAuthentication(options => // prevent from mapping "sub" claim to nameidentifier.
{ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => var identityUrl = Configuration.GetValue<string>("IdentityUrl");
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "basket";
});
}
protected virtual void ConfigureAuth(IApplicationBuilder app) services
{ .AddAuthentication(options =>
if (Configuration.GetValue<bool>("UseLoadTest")) {
{ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
app.UseMiddleware<ByPassAuthMiddleware>(); options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
app.UseAuthentication(); })
} .AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "basket";
});
}
private void RegisterEventBus(IServiceCollection services) protected virtual void ConfigureAuth(IApplicationBuilder app)
{ {
var subscriptionClientName = Configuration["SubscriptionClientName"]; if (Configuration.GetValue<bool>("UseLoadTest"))
{
app.UseMiddleware<ByPassAuthMiddleware>();
}
if (Configuration.GetValue<bool>("AzureServiceBusEnabled")) app.UseAuthentication();
{ }
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, private void RegisterEventBus(IServiceCollection services)
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); {
}); var subscriptionClientName = Configuration["SubscriptionClientName"];
}
else
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
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 (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) {
{ services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
retryCount = int.Parse(Configuration["EventBusRetryCount"]); {
} var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); return new EventBusServiceBus(serviceBusPersisterConnection, logger,
}); eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
} });
}
else
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
services.AddTransient<OrderStartedIntegrationEventHandler>(); });
} }
private void ConfigureEventBus(IApplicationBuilder app) services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); services.AddTransient<OrderStartedIntegrationEventHandler>();
} }
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
}
}
} }

View File

@ -1,24 +1,24 @@
{ {
"Logging": { "Logging": {
"IncludeScopes": false, "IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",
"System": "Information", "System": "Information",
"Microsoft": "Information" "Microsoft": "Information"
} }
}, },
"IdentityUrl": "http://localhost:5105", "IdentityUrl": "http://localhost:5105",
"ConnectionString": "127.0.0.1", "ConnectionString": "127.0.0.1",
"AzureServiceBusEnabled": false, "AzureServiceBusEnabled": false,
"SubscriptionClientName": "Basket", "SubscriptionClientName": "Basket",
"ApplicationInsights": { "ApplicationInsights": {
"InstrumentationKey": "" "InstrumentationKey": ""
}, },
"EventBusRetryCount": 5, "EventBusRetryCount": 5,
"UseVault": false, "UseVault": false,
"Vault": { "Vault": {
"Name": "eshop", "Name": "eshop",
"ClientId": "your-clien-id", "ClientId": "your-clien-id",
"ClientSecret": "your-client-secret" "ClientSecret": "your-client-secret"
} }
} }

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<!-- <!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380 Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
--> -->
<system.webServer> <system.webServer>
<handlers> <handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers> </handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/> <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer> </system.webServer>
</configuration> </configuration>