@ -0,0 +1,132 @@ | |||
############################### | |||
# Core EditorConfig Options # | |||
############################### | |||
root = true | |||
# All files | |||
[*] | |||
indent_style = space | |||
# XML project files | |||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] | |||
indent_size = 2 | |||
# XML config files | |||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] | |||
indent_size = 2 | |||
# Code files | |||
[*.{cs,csx,vb,vbx}] | |||
indent_size = 4 | |||
insert_final_newline = true | |||
charset = utf-8-bom | |||
############################### | |||
# .NET Coding Conventions # | |||
############################### | |||
[*.{cs,vb}] | |||
# Organize usings | |||
dotnet_sort_system_directives_first = true | |||
# this. preferences | |||
dotnet_style_qualification_for_field = false:silent | |||
dotnet_style_qualification_for_property = false:silent | |||
dotnet_style_qualification_for_method = false:silent | |||
dotnet_style_qualification_for_event = false:silent | |||
# Language keywords vs BCL types preferences | |||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent | |||
dotnet_style_predefined_type_for_member_access = true:silent | |||
# Parentheses preferences | |||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent | |||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent | |||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent | |||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent | |||
# Modifier preferences | |||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent | |||
dotnet_style_readonly_field = true:suggestion | |||
# Expression-level preferences | |||
dotnet_style_object_initializer = true:suggestion | |||
dotnet_style_collection_initializer = true:suggestion | |||
dotnet_style_explicit_tuple_names = true:suggestion | |||
dotnet_style_null_propagation = true:suggestion | |||
dotnet_style_coalesce_expression = true:suggestion | |||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent | |||
dotnet_style_prefer_inferred_tuple_names = true:suggestion | |||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion | |||
dotnet_style_prefer_auto_properties = true:silent | |||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent | |||
dotnet_style_prefer_conditional_expression_over_return = true:silent | |||
############################### | |||
# Naming Conventions # | |||
############################### | |||
# Style Definitions | |||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case | |||
# Use PascalCase for constant fields | |||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion | |||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields | |||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style | |||
dotnet_naming_symbols.constant_fields.applicable_kinds = field | |||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = * | |||
dotnet_naming_symbols.constant_fields.required_modifiers = const | |||
############################### | |||
# C# Coding Conventions # | |||
############################### | |||
[*.cs] | |||
# var preferences | |||
csharp_style_var_for_built_in_types = true:silent | |||
csharp_style_var_when_type_is_apparent = true:silent | |||
csharp_style_var_elsewhere = true:silent | |||
# Expression-bodied members | |||
csharp_style_expression_bodied_methods = false:silent | |||
csharp_style_expression_bodied_constructors = false:silent | |||
csharp_style_expression_bodied_operators = false:silent | |||
csharp_style_expression_bodied_properties = true:silent | |||
csharp_style_expression_bodied_indexers = true:silent | |||
csharp_style_expression_bodied_accessors = true:silent | |||
# Pattern matching preferences | |||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion | |||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion | |||
# Null-checking preferences | |||
csharp_style_throw_expression = true:suggestion | |||
csharp_style_conditional_delegate_call = true:suggestion | |||
# Modifier preferences | |||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion | |||
# Expression-level preferences | |||
csharp_prefer_braces = true:silent | |||
csharp_style_deconstructed_variable_declaration = true:suggestion | |||
csharp_prefer_simple_default_expression = true:suggestion | |||
csharp_style_prefer_local_over_anonymous_function = true:suggestion | |||
csharp_style_inlined_variable_declaration = true:suggestion | |||
############################### | |||
# C# Formatting Rules # | |||
############################### | |||
# New line preferences | |||
csharp_new_line_before_open_brace = all | |||
csharp_new_line_before_else = true | |||
csharp_new_line_before_catch = true | |||
csharp_new_line_before_finally = true | |||
csharp_new_line_before_members_in_object_initializers = true | |||
csharp_new_line_before_members_in_anonymous_types = true | |||
csharp_new_line_between_query_expression_clauses = true | |||
# Indentation preferences | |||
csharp_indent_case_contents = true | |||
csharp_indent_switch_labels = true | |||
csharp_indent_labels = flush_left | |||
# Space preferences | |||
csharp_space_after_cast = false | |||
csharp_space_after_keywords_in_control_flow_statements = true | |||
csharp_space_between_method_call_parameter_list_parentheses = false | |||
csharp_space_between_method_declaration_parameter_list_parentheses = false | |||
csharp_space_between_parentheses = false | |||
csharp_space_before_colon_in_inheritance_clause = true | |||
csharp_space_after_colon_in_inheritance_clause = true | |||
csharp_space_around_binary_operators = before_and_after | |||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false | |||
csharp_space_between_method_call_name_and_opening_parenthesis = false | |||
csharp_space_between_method_call_empty_parameter_list_parentheses = false | |||
# Wrapping preferences | |||
csharp_preserve_single_line_statements = true | |||
csharp_preserve_single_line_blocks = true | |||
############################### | |||
# VB Coding Conventions # | |||
############################### | |||
[*.vb] | |||
# Modifier preferences | |||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion |
@ -0,0 +1,62 @@ | |||
internal static class Extensions | |||
{ | |||
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); | |||
return services; | |||
} | |||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddHealthChecks() | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); | |||
return services; | |||
} | |||
public static IServiceCollection AddApplicationServices(this IServiceCollection services) | |||
{ | |||
// Register delegating handlers | |||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||
// Register http services | |||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>(); | |||
return services; | |||
} | |||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||
{ | |||
services.AddTransient<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IBasketService, BasketService>(); | |||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||
{ | |||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||
options.Address = new Uri(basketApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<ICatalogService, CatalogService>(); | |||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||
{ | |||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||
options.Address = new Uri(catalogApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IOrderingService, OrderingService>(); | |||
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) => | |||
{ | |||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||
options.Address = new Uri(orderingApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
return services; | |||
} | |||
} |
@ -1,33 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters | |||
{ | |||
namespace Basket.API.Infrastructure.Filters | |||
{ | |||
public class AuthorizeCheckOperationFilter : IOperationFilter | |||
{ | |||
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 | |||
{ | |||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | |||
}; | |||
operation.Security = new List<OpenApiSecurityRequirement> | |||
{ | |||
new() | |||
{ | |||
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } | |||
} | |||
}; | |||
} | |||
} | |||
} | |||
} |
@ -1,44 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; | |||
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler | |||
{ | |||
private readonly IHttpContextAccessor _httpContextAccessor; | |||
private readonly ILogger<HttpClientAuthorizationDelegatingHandler> _logger; | |||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpClientAuthorizationDelegatingHandler> logger) | |||
{ | |||
_httpContextAccessor = httpContextAccessor; | |||
_logger = logger; | |||
} | |||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |||
{ | |||
request.Version = new System.Version(2, 0); | |||
request.Method = HttpMethod.Get; | |||
var authorizationHeader = _httpContextAccessor.HttpContext | |||
.Request.Headers["Authorization"]; | |||
if (!string.IsNullOrEmpty(authorizationHeader)) | |||
{ | |||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader }); | |||
} | |||
var token = await GetToken(); | |||
if (token != null) | |||
{ | |||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); | |||
} | |||
return await base.SendAsync(request, cancellationToken); | |||
} | |||
async Task<string> GetToken() | |||
{ | |||
const string ACCESS_TOKEN = "access_token"; | |||
return await _httpContextAccessor.HttpContext | |||
.GetTokenAsync(ACCESS_TOKEN); | |||
} | |||
} |
@ -1,23 +1,24 @@ | |||
await BuildWebHost(args).RunAsync(); | |||
IWebHost BuildWebHost(string[] args) => | |||
WebHost | |||
.CreateDefaultBuilder(args) | |||
.ConfigureAppConfiguration(cb => | |||
{ | |||
var sources = cb.Sources; | |||
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() | |||
{ | |||
Optional = true, | |||
Path = "appsettings.localhost.json", | |||
ReloadOnChange = false | |||
}); | |||
}) | |||
.UseStartup<Startup>() | |||
.UseSerilog((builderContext, config) => | |||
{ | |||
config | |||
.MinimumLevel.Information() | |||
.Enrich.FromLogContext() | |||
.WriteTo.Console(); | |||
}) | |||
.Build(); | |||
var builder = WebApplication.CreateBuilder(args); | |||
builder.AddServiceDefaults(); | |||
builder.Services.AddReverseProxy(builder.Configuration); | |||
builder.Services.AddControllers(); | |||
builder.Services.AddHealthChecks(builder.Configuration); | |||
builder.Services.AddApplicationServices(); | |||
builder.Services.AddGrpcServices(); | |||
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls")); | |||
var app = builder.Build(); | |||
app.UseServiceDefaults(); | |||
app.UseHttpsRedirection(); | |||
app.MapControllers(); | |||
app.MapReverseProxy(); | |||
await app.RunAsync(); |
@ -1,196 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; | |||
public class Startup | |||
{ | |||
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 void ConfigureServices(IServiceCollection services) | |||
{ | |||
services.AddHealthChecks() | |||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) | |||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); | |||
services.AddCustomMvc(Configuration) | |||
.AddCustomAuthentication(Configuration) | |||
.AddDevspaces() | |||
.AddHttpServices() | |||
.AddGrpcServices(); | |||
} | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
{ | |||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||
app.UsePathBase(pathBase); | |||
} | |||
if (env.IsDevelopment()) | |||
{ | |||
app.UseDeveloperExceptionPage(); | |||
} | |||
app.UseSwagger().UseSwaggerUI(c => | |||
{ | |||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); | |||
c.OAuthClientId("mobileshoppingaggswaggerui"); | |||
c.OAuthClientSecret(string.Empty); | |||
c.OAuthRealm(string.Empty); | |||
c.OAuthAppName("Purchase BFF Swagger UI"); | |||
}); | |||
app.UseRouting(); | |||
app.UseCors("CorsPolicy"); | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapDefaultControllerRoute(); | |||
endpoints.MapControllers(); | |||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
}); | |||
} | |||
} | |||
public static class ServiceCollectionExtensions | |||
{ | |||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddOptions(); | |||
services.Configure<UrlsConfig>(configuration.GetSection("urls")); | |||
services.AddControllers() | |||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||
services.AddSwaggerGen(options => | |||
{ | |||
options.DescribeAllEnumsAsStrings(); | |||
options.SwaggerDoc("v1", new OpenApiInfo | |||
{ | |||
Title = "Shopping Aggregator for Mobile Clients", | |||
Version = "v1", | |||
Description = "Shopping Aggregator for Mobile Clients" | |||
}); | |||
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>() | |||
{ | |||
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } | |||
} | |||
} | |||
} | |||
}); | |||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||
}); | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowCredentials()); | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||
var identityUrl = configuration.GetValue<string>("urls:identity"); | |||
services.AddAuthentication(options => | |||
{ | |||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||
}) | |||
.AddJwtBearer(options => | |||
{ | |||
options.Authority = identityUrl; | |||
options.RequireHttpsMetadata = false; | |||
options.Audience = "mobileshoppingagg"; | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddHttpServices(this IServiceCollection services) | |||
{ | |||
//register delegating handlers | |||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
//register http services | |||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||
.AddDevspacesSupport(); | |||
return services; | |||
} | |||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||
{ | |||
services.AddTransient<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IBasketService, BasketService>(); | |||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||
{ | |||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||
options.Address = new Uri(basketApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<ICatalogService, CatalogService>(); | |||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||
{ | |||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||
options.Address = new Uri(catalogApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IOrderingService, OrderingService>(); | |||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) => | |||
{ | |||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||
options.Address = new Uri(orderingApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
return services; | |||
} | |||
} |
@ -1,15 +1,138 @@ | |||
{ | |||
"Logging": { | |||
"IncludeScopes": false, | |||
"Debug": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
"LogLevel": { | |||
"Default": "Information", | |||
"Microsoft.AspNetCore": "Warning", | |||
"System.Net.Http": "Warning" | |||
} | |||
}, | |||
"OpenApi": { | |||
"Endpoint": { | |||
"Name": "Purchase BFF V1" | |||
}, | |||
"Document": { | |||
"Description": "Shopping Aggregator for Mobile Clients", | |||
"Title": "Shopping Aggregator for Mobile Clients", | |||
"Version": "v1" | |||
}, | |||
"Auth": { | |||
"ClientId": "mobileshoppingaggswaggerui", | |||
"AppName": "Mobile shopping BFF Swagger UI" | |||
} | |||
}, | |||
"Identity": { | |||
"Url": "http://localhost:5223", | |||
"Audience": "mobileshoppingagg", | |||
"Scopes": { | |||
"webshoppingagg": "Shopping Aggregator for Mobile Clients" | |||
} | |||
}, | |||
"ReverseProxy": { | |||
"Routes": { | |||
"c-short": { | |||
"ClusterId": "catalog", | |||
"Match": { | |||
"Path": "c/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/c" } | |||
] | |||
}, | |||
"c-long": { | |||
"ClusterId": "catalog", | |||
"Match": { | |||
"Path": "catalog-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/catalog-api" } | |||
] | |||
}, | |||
"b-short": { | |||
"ClusterId": "basket", | |||
"Match": { | |||
"Path": "b/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/b" } | |||
] | |||
}, | |||
"b-long": { | |||
"ClusterId": "basket", | |||
"Match": { | |||
"Path": "basket-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/basket-api" } | |||
] | |||
}, | |||
"o-short": { | |||
"ClusterId": "orders", | |||
"Match": { | |||
"Path": "o/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/o" } | |||
] | |||
}, | |||
"o-long": { | |||
"ClusterId": "orders", | |||
"Match": { | |||
"Path": "ordering-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/ordering-api" } | |||
] | |||
}, | |||
"h-long": { | |||
"ClusterId": "signalr", | |||
"Match": { | |||
"Path": "hub/notificationhub/{**catch-all}" | |||
} | |||
} | |||
}, | |||
"Console": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
"Clusters": { | |||
"basket": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5221" | |||
} | |||
} | |||
}, | |||
"catalog": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5222" | |||
} | |||
} | |||
}, | |||
"orders": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5224" | |||
} | |||
} | |||
}, | |||
"signalr": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5225" | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
"Urls": { | |||
"Basket": "http://localhost:5221", | |||
"Catalog": "http://localhost:5222", | |||
"Orders": "http://localhost:5224", | |||
"Identity": "http://localhost:5223", | |||
"Signalr": "http://localhost:5225", | |||
"GrpcBasket": "http://localhost:6221", | |||
"GrpcCatalog": "http://localhost:6222", | |||
"GrpcOrdering": "http://localhost:6224" | |||
}, | |||
"CatalogUrlHC": "http://localhost:5222/hc", | |||
"OrderingUrlHC": "http://localhost:5224/hc", | |||
"BasketUrlHC": "http://localhost:5221/hc", | |||
"IdentityUrlHC": "http://localhost:5223/hc" | |||
} |
@ -1,55 +0,0 @@ | |||
kind: helm-release | |||
apiVersion: 1.1 | |||
build: | |||
context: ..\..\..\.. | |||
dockerfile: Dockerfile | |||
install: | |||
chart: ../../../../k8s/helm/mobileshoppingagg | |||
set: | |||
image: | |||
tag: $(tag) | |||
pullPolicy: Never | |||
ingress: | |||
annotations: | |||
kubernetes.io/ingress.class: traefik-azds | |||
hosts: | |||
# This expands to [space.s.]apigwms.<guid>.<region>.aksapp.io | |||
- $(spacePrefix)eshop$(hostSuffix) | |||
inf: | |||
k8s: | |||
dns: $(spacePrefix)eshop$(hostSuffix) | |||
values: | |||
- values.dev.yaml? | |||
- secrets.dev.yaml? | |||
- app.yaml | |||
- inf.yaml | |||
configurations: | |||
develop: | |||
build: | |||
useGitIgnore: true | |||
dockerfile: Dockerfile.develop | |||
container: | |||
syncTarget: /src | |||
sync: | |||
- '**/Pages/**' | |||
- '**/Views/**' | |||
- '**/wwwroot/**' | |||
- '!**/*.{sln,csproj}' | |||
command: | |||
- dotnet | |||
- run | |||
- --no-restore | |||
- --no-build | |||
- --no-launch-profile | |||
- -c | |||
- ${Configuration:-Debug} | |||
iterate: | |||
processesToKill: | |||
- dotnet | |||
- vsdbg | |||
buildCommands: | |||
- - dotnet | |||
- build | |||
- --no-restore | |||
- -c | |||
- ${Configuration:-Debug} |
@ -1,11 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers; | |||
[Route("")] | |||
public class HomeController : Controller | |||
{ | |||
[HttpGet] | |||
public IActionResult Index() | |||
{ | |||
return new RedirectResult("~/swagger"); | |||
} | |||
} |
@ -0,0 +1,63 @@ | |||
internal static class Extensions | |||
{ | |||
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); | |||
return services; | |||
} | |||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddHealthChecks() | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); | |||
return services; | |||
} | |||
public static IServiceCollection AddApplicationServices(this IServiceCollection services) | |||
{ | |||
// Register delegating handlers | |||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||
// Register http services | |||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>(); | |||
return services; | |||
} | |||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||
{ | |||
services.AddTransient<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IBasketService, BasketService>(); | |||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||
{ | |||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||
options.Address = new Uri(basketApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<ICatalogService, CatalogService>(); | |||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||
{ | |||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||
options.Address = new Uri(catalogApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IOrderingService, OrderingService>(); | |||
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) => | |||
{ | |||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||
options.Address = new Uri(orderingApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
return services; | |||
} | |||
} |
@ -1,34 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters | |||
{ | |||
namespace Basket.API.Infrastructure.Filters | |||
{ | |||
public class AuthorizeCheckOperationFilter : IOperationFilter | |||
{ | |||
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 | |||
{ | |||
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } | |||
}; | |||
operation.Security = new List<OpenApiSecurityRequirement> | |||
{ | |||
new() | |||
{ | |||
[ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" } | |||
} | |||
}; | |||
} | |||
} | |||
} | |||
} |
@ -1,41 +1,27 @@ | |||
global using CatalogApi; | |||
global using Devspaces.Support; | |||
global using Grpc.Core.Interceptors; | |||
global using System; | |||
global using System.Collections.Generic; | |||
global using System.Linq; | |||
global using System.Net; | |||
global using System.Net.Http; | |||
global using System.Net.Http.Headers; | |||
global using System.Text.Json; | |||
global using System.Threading; | |||
global using System.Threading.Tasks; | |||
global using CatalogApi; | |||
global using Grpc.Core; | |||
global using Grpc.Core.Interceptors; | |||
global using GrpcBasket; | |||
global using GrpcOrdering; | |||
global using HealthChecks.UI.Client; | |||
global using Microsoft.AspNetCore.Authentication.JwtBearer; | |||
global using Microsoft.AspNetCore.Authentication; | |||
global using Microsoft.AspNetCore.Authorization; | |||
global using Microsoft.AspNetCore.Builder; | |||
global using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||
global using Microsoft.AspNetCore.Hosting; | |||
global using Microsoft.AspNetCore.Http; | |||
global using Microsoft.AspNetCore.Mvc; | |||
global using Microsoft.AspNetCore; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; | |||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; | |||
global using Microsoft.Extensions.Configuration; | |||
global using Microsoft.Extensions.DependencyInjection; | |||
global using Microsoft.Extensions.Diagnostics.HealthChecks; | |||
global using Microsoft.Extensions.Hosting; | |||
global using Microsoft.Extensions.Logging; | |||
global using Microsoft.Extensions.Options; | |||
global using Microsoft.OpenApi.Models; | |||
global using Serilog; | |||
global using Swashbuckle.AspNetCore.SwaggerGen; | |||
global using System.Collections.Generic; | |||
global using System.IdentityModel.Tokens.Jwt; | |||
global using System.Linq; | |||
global using System.Net.Http.Headers; | |||
global using System.Net.Http; | |||
global using System.Net; | |||
global using System.Text.Json; | |||
global using System.Threading.Tasks; | |||
global using System.Threading; | |||
global using System; | |||
global using Services.Common; |
@ -1,40 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; | |||
public class HttpClientAuthorizationDelegatingHandler | |||
: DelegatingHandler | |||
{ | |||
private readonly IHttpContextAccessor _httpContextAccessor; | |||
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor) | |||
{ | |||
_httpContextAccessor = httpContextAccessor; | |||
} | |||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |||
{ | |||
var authorizationHeader = _httpContextAccessor.HttpContext | |||
.Request.Headers["Authorization"]; | |||
if (!string.IsNullOrWhiteSpace(authorizationHeader)) | |||
{ | |||
request.Headers.Add("Authorization", new List<string>() { authorizationHeader }); | |||
} | |||
var token = await GetTokenAsync(); | |||
if (token != null) | |||
{ | |||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); | |||
} | |||
return await base.SendAsync(request, cancellationToken); | |||
} | |||
Task<string> GetTokenAsync() | |||
{ | |||
const string ACCESS_TOKEN = "access_token"; | |||
return _httpContextAccessor.HttpContext | |||
.GetTokenAsync(ACCESS_TOKEN); | |||
} | |||
} |
@ -1,24 +1,38 @@ | |||
await BuildWebHost(args).RunAsync(); | |||
IWebHost BuildWebHost(string[] args) => | |||
WebHost | |||
.CreateDefaultBuilder(args) | |||
.ConfigureAppConfiguration(cb => | |||
{ | |||
var sources = cb.Sources; | |||
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource() | |||
{ | |||
Optional = true, | |||
Path = "appsettings.localhost.json", | |||
ReloadOnChange = false | |||
}); | |||
}) | |||
.UseStartup<Startup>() | |||
.UseSerilog((builderContext, config) => | |||
{ | |||
config | |||
.MinimumLevel.Information() | |||
.Enrich.FromLogContext() | |||
.WriteTo.Console(); | |||
}) | |||
.Build(); | |||
var builder = WebApplication.CreateBuilder(args); | |||
builder.AddServiceDefaults(); | |||
builder.Services.AddReverseProxy(builder.Configuration); | |||
builder.Services.AddControllers(); | |||
builder.Services.AddHealthChecks(builder.Configuration); | |||
builder.Services.AddCors(options => | |||
{ | |||
// TODO: Read allowed origins from configuration | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
}); | |||
builder.Services.AddApplicationServices(); | |||
builder.Services.AddGrpcServices(); | |||
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls")); | |||
var app = builder.Build(); | |||
app.UseServiceDefaults(); | |||
app.UseHttpsRedirection(); | |||
app.UseCors("CorsPolicy"); | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
app.MapControllers(); | |||
app.MapReverseProxy(); | |||
await app.RunAsync(); |
@ -1,29 +1,12 @@ | |||
{ | |||
"iisSettings": { | |||
"windowsAuthentication": false, | |||
"anonymousAuthentication": true, | |||
"iisExpress": { | |||
"applicationUrl": "http://localhost:57425/", | |||
"sslPort": 0 | |||
} | |||
}, | |||
"profiles": { | |||
"IIS Express": { | |||
"commandName": "IISExpress", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
} | |||
}, | |||
"PurchaseForMvc": { | |||
"Web.Shopping.HttpAggregator": { | |||
"commandName": "Project", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"applicationUrl": "http://localhost:5229/", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
}, | |||
"applicationUrl": "http://localhost:61632/" | |||
} | |||
} | |||
} | |||
} |
@ -1,199 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; | |||
public class Startup | |||
{ | |||
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 void ConfigureServices(IServiceCollection services) | |||
{ | |||
services.AddHealthChecks() | |||
.AddCheck("self", () => HealthCheckResult.Healthy()) | |||
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) | |||
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) | |||
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) | |||
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) | |||
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); | |||
services.AddCustomMvc(Configuration) | |||
.AddCustomAuthentication(Configuration) | |||
.AddDevspaces() | |||
.AddApplicationServices() | |||
.AddGrpcServices(); | |||
} | |||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
{ | |||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||
app.UsePathBase(pathBase); | |||
} | |||
if (env.IsDevelopment()) | |||
{ | |||
app.UseDeveloperExceptionPage(); | |||
} | |||
app.UseHttpsRedirection(); | |||
app.UseSwagger().UseSwaggerUI(c => | |||
{ | |||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); | |||
c.OAuthClientId("webshoppingaggswaggerui"); | |||
c.OAuthClientSecret(string.Empty); | |||
c.OAuthRealm(string.Empty); | |||
c.OAuthAppName("web shopping bff Swagger UI"); | |||
}); | |||
app.UseRouting(); | |||
app.UseCors("CorsPolicy"); | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapDefaultControllerRoute(); | |||
endpoints.MapControllers(); | |||
endpoints.MapHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
}); | |||
} | |||
} | |||
public static class ServiceCollectionExtensions | |||
{ | |||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||
var identityUrl = configuration.GetValue<string>("urls:identity"); | |||
services.AddAuthentication(options => | |||
{ | |||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||
}) | |||
.AddJwtBearer(options => | |||
{ | |||
options.Authority = identityUrl; | |||
options.RequireHttpsMetadata = false; | |||
options.Audience = "webshoppingagg"; | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddOptions(); | |||
services.Configure<UrlsConfig>(configuration.GetSection("urls")); | |||
services.AddControllers() | |||
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); | |||
services.AddSwaggerGen(options => | |||
{ | |||
options.DescribeAllEnumsAsStrings(); | |||
options.SwaggerDoc("v1", new OpenApiInfo | |||
{ | |||
Title = "Shopping Aggregator for Web Clients", | |||
Version = "v1", | |||
Description = "Shopping Aggregator for Web Clients" | |||
}); | |||
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>() | |||
{ | |||
{ "webshoppingagg", "Shopping Aggregator for Web Clients" } | |||
} | |||
} | |||
} | |||
}); | |||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||
}); | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddApplicationServices(this IServiceCollection services) | |||
{ | |||
//register delegating handlers | |||
services.AddTransient<HttpClientAuthorizationDelegatingHandler>(); | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
//register http services | |||
services.AddHttpClient<IOrderApiClient, OrderApiClient>() | |||
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>() | |||
.AddDevspacesSupport(); | |||
return services; | |||
} | |||
public static IServiceCollection AddGrpcServices(this IServiceCollection services) | |||
{ | |||
services.AddTransient<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IBasketService, BasketService>(); | |||
services.AddGrpcClient<Basket.BasketClient>((services, options) => | |||
{ | |||
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket; | |||
options.Address = new Uri(basketApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<ICatalogService, CatalogService>(); | |||
services.AddGrpcClient<Catalog.CatalogClient>((services, options) => | |||
{ | |||
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog; | |||
options.Address = new Uri(catalogApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
services.AddScoped<IOrderingService, OrderingService>(); | |||
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) => | |||
{ | |||
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering; | |||
options.Address = new Uri(orderingApi); | |||
}).AddInterceptor<GrpcExceptionInterceptor>(); | |||
return services; | |||
} | |||
} |
@ -1,15 +1,8 @@ | |||
{ | |||
"Logging": { | |||
"IncludeScopes": false, | |||
"Debug": { | |||
"LogLevel": { | |||
"Default": "Debug" | |||
} | |||
}, | |||
"Console": { | |||
"LogLevel": { | |||
"Default": "Debug" | |||
} | |||
"LogLevel": { | |||
"Default": "Information", | |||
"Microsoft.AspNetCore": "Warning" | |||
} | |||
} | |||
} |
@ -1,15 +1,138 @@ | |||
{ | |||
"Logging": { | |||
"IncludeScopes": false, | |||
"Debug": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
"LogLevel": { | |||
"Default": "Information", | |||
"Microsoft.AspNetCore": "Warning", | |||
"System.Net.Http": "Warning" | |||
} | |||
}, | |||
"OpenApi": { | |||
"Endpoint": { | |||
"Name": "Purchase BFF V1" | |||
}, | |||
"Document": { | |||
"Description": "Shopping Aggregator for Web Clients", | |||
"Title": "Shopping Aggregator for Web Clients", | |||
"Version": "v1" | |||
}, | |||
"Auth": { | |||
"ClientId": "webshoppingaggswaggerui", | |||
"AppName": "Web Shopping BFF Swagger UI" | |||
} | |||
}, | |||
"Identity": { | |||
"Url": "http://localhost:5223", | |||
"Audience": "webshoppingagg", | |||
"Scopes": { | |||
"webshoppingagg": "Shopping Aggregator for Web Clients" | |||
} | |||
}, | |||
"ReverseProxy": { | |||
"Routes": { | |||
"c-short": { | |||
"ClusterId": "catalog", | |||
"Match": { | |||
"Path": "c/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/c" } | |||
] | |||
}, | |||
"c-long": { | |||
"ClusterId": "catalog", | |||
"Match": { | |||
"Path": "catalog-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/catalog-api" } | |||
] | |||
}, | |||
"b-short": { | |||
"ClusterId": "basket", | |||
"Match": { | |||
"Path": "b/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/b" } | |||
] | |||
}, | |||
"b-long": { | |||
"ClusterId": "basket", | |||
"Match": { | |||
"Path": "basket-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/basket-api" } | |||
] | |||
}, | |||
"o-short": { | |||
"ClusterId": "orders", | |||
"Match": { | |||
"Path": "o/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/o" } | |||
] | |||
}, | |||
"o-long": { | |||
"ClusterId": "orders", | |||
"Match": { | |||
"Path": "ordering-api/{**catch-all}" | |||
}, | |||
"Transforms": [ | |||
{ "PathRemovePrefix": "/ordering-api" } | |||
] | |||
}, | |||
"h-long": { | |||
"ClusterId": "signalr", | |||
"Match": { | |||
"Path": "hub/notificationhub/{**catch-all}" | |||
} | |||
} | |||
}, | |||
"Console": { | |||
"LogLevel": { | |||
"Default": "Warning" | |||
"Clusters": { | |||
"basket": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5221" | |||
} | |||
} | |||
}, | |||
"catalog": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5222" | |||
} | |||
} | |||
}, | |||
"orders": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5224" | |||
} | |||
} | |||
}, | |||
"signalr": { | |||
"Destinations": { | |||
"destination0": { | |||
"Address": "http://localhost:5225" | |||
} | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
"Urls": { | |||
"Basket": "http://localhost:5221", | |||
"Catalog": "http://localhost:5222", | |||
"Orders": "http://localhost:5224", | |||
"Identity": "http://localhost:5223", | |||
"Signalr": "http://localhost:5225", | |||
"GrpcBasket": "http://localhost:6221", | |||
"GrpcCatalog": "http://localhost:6222", | |||
"GrpcOrdering": "http://localhost:6224" | |||
}, | |||
"CatalogUrlHC": "http://localhost:5222/hc", | |||
"OrderingUrlHC": "http://localhost:5224/hc", | |||
"BasketUrlHC": "http://localhost:5221/hc", | |||
"IdentityUrlHC": "http://localhost:5223/hc" | |||
} |
@ -1,11 +0,0 @@ | |||
{ | |||
"urls": { | |||
"basket": "http://localhost:55103", | |||
"catalog": "http://localhost:55101", | |||
"orders": "http://localhost:55102", | |||
"identity": "http://localhost:55105", | |||
"grpcBasket": "http://localhost:5580", | |||
"grpcCatalog": "http://localhost:81", | |||
"grpcOrdering": "http://localhost:5581" | |||
} | |||
} |
@ -1,55 +0,0 @@ | |||
kind: helm-release | |||
apiVersion: 1.1 | |||
build: | |||
context: ..\..\..\.. | |||
dockerfile: Dockerfile | |||
install: | |||
chart: ../../../../k8s/helm/webshoppingagg | |||
set: | |||
image: | |||
tag: $(tag) | |||
pullPolicy: Never | |||
ingress: | |||
annotations: | |||
kubernetes.io/ingress.class: traefik-azds | |||
hosts: | |||
# This expands to [space.s.]apigwms.<guid>.<region>.aksapp.io | |||
- $(spacePrefix)eshop$(hostSuffix) | |||
inf: | |||
k8s: | |||
dns: $(spacePrefix)eshop$(hostSuffix) | |||
values: | |||
- values.dev.yaml? | |||
- secrets.dev.yaml? | |||
- app.yaml | |||
- inf.yaml | |||
configurations: | |||
develop: | |||
build: | |||
useGitIgnore: true | |||
dockerfile: Dockerfile.develop | |||
container: | |||
syncTarget: /src | |||
sync: | |||
- '**/Pages/**' | |||
- '**/Views/**' | |||
- '**/wwwroot/**' | |||
- '!**/*.{sln,csproj}' | |||
command: | |||
- dotnet | |||
- run | |||
- --no-restore | |||
- --no-build | |||
- --no-launch-profile | |||
- -c | |||
- ${Configuration:-Debug} | |||
iterate: | |||
processesToKill: | |||
- dotnet | |||
- vsdbg | |||
buildCommands: | |||
- - dotnet | |||
- build | |||
- --no-restore | |||
- -c | |||
- ${Configuration:-Debug} |
@ -1,11 +0,0 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> | |||
</ItemGroup> | |||
</Project> |
@ -1,22 +0,0 @@ | |||
namespace Devspaces.Support; | |||
public class DevspacesMessageHandler : DelegatingHandler | |||
{ | |||
private const string DevspacesHeaderName = "azds-route-as"; | |||
private readonly IHttpContextAccessor _httpContextAccessor; | |||
public DevspacesMessageHandler(IHttpContextAccessor httpContextAccessor) | |||
{ | |||
_httpContextAccessor = httpContextAccessor; | |||
} | |||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |||
{ | |||
var req = _httpContextAccessor.HttpContext.Request; | |||
if (req.Headers.ContainsKey(DevspacesHeaderName)) | |||
{ | |||
request.Headers.Add(DevspacesHeaderName, req.Headers[DevspacesHeaderName] as IEnumerable<string>); | |||
} | |||
return base.SendAsync(request, cancellationToken); | |||
} | |||
} |
@ -1,6 +0,0 @@ | |||
global using Microsoft.AspNetCore.Http; | |||
global using Microsoft.Extensions.DependencyInjection; | |||
global using System.Collections.Generic; | |||
global using System.Net.Http; | |||
global using System.Threading.Tasks; | |||
global using System.Threading; |
@ -1,10 +0,0 @@ | |||
namespace Devspaces.Support; | |||
public static class HttpClientBuilderDevspacesExtensions | |||
{ | |||
public static IHttpClientBuilder AddDevspacesSupport(this IHttpClientBuilder builder) | |||
{ | |||
builder.AddHttpMessageHandler<DevspacesMessageHandler>(); | |||
return builder; | |||
} | |||
} |
@ -1,10 +0,0 @@ | |||
namespace Devspaces.Support; | |||
public static class ServiceCollectionDevspacesExtensions | |||
{ | |||
public static IServiceCollection AddDevspaces(this IServiceCollection services) | |||
{ | |||
services.AddTransient<DevspacesMessageHandler>(); | |||
return services; | |||
} | |||
} |
@ -0,0 +1,93 @@ | |||
<Project> | |||
<PropertyGroup> | |||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> | |||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageVersion Include="AspNetCore.HealthChecks.AzureServiceBus" Version="6.1.0" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.AzureStorage" Version="6.1.2" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.Rabbitmq" Version="6.0.2" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.Redis" Version="6.0.4" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.SqlServer" Version="6.0.2" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI" Version="6.0.5" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="6.0.5" /> | |||
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="6.0.3" /> | |||
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" /> | |||
<PackageVersion Include="Azure.Identity" Version="1.8.2" /> | |||
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.12.0" /> | |||
<PackageVersion Include="BuildBundlerMinifier" Version="3.2.449" /> | |||
<PackageVersion Include="Dapper" Version="2.0.123" /> | |||
<PackageVersion Include="Duende.IdentityServer" Version="6.2.3" /> | |||
<PackageVersion Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.3" /> | |||
<PackageVersion Include="Duende.IdentityServer.EntityFramework" Version="6.2.3" /> | |||
<PackageVersion Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.3" /> | |||
<PackageVersion Include="Duende.IdentityServer.Storage" Version="6.2.3" /> | |||
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.2.2" /> | |||
<PackageVersion Include="Google.Protobuf" Version="3.22.0" /> | |||
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.51.0" /> | |||
<PackageVersion Include="Grpc.AspNetCore" Version="2.51.0" /> | |||
<PackageVersion Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.51.0" /> | |||
<PackageVersion Include="Grpc.Core" Version="2.46.6" /> | |||
<PackageVersion Include="Grpc.Net.Client" Version="2.51.0" /> | |||
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.51.0" /> | |||
<PackageVersion Include="Grpc.Tools" Version="2.51.0" PrivateAssets="All" /> | |||
<PackageVersion Include="MediatR" Version="12.0.0" /> | |||
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0-beta1" /> | |||
<PackageVersion Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.22.0-beta1" /> | |||
<PackageVersion Include="Microsoft.ApplicationInsights.Kubernetes" Version="6.0.0" /> | |||
<PackageVersion Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.4.0" /> | |||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> | |||
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.Extensions.Logging.AzureAppServices" Version="7.0.3" /> | |||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> | |||
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.0" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.4" /> | |||
<PackageVersion Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" /> | |||
<PackageVersion Include="Moq" Version="4.18.4" /> | |||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" /> | |||
<PackageVersion Include="Polly" Version="7.2.3" /> | |||
<PackageVersion Include="RabbitMQ.Client" Version="6.4.0" /> | |||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" /> | |||
<PackageVersion Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" /> | |||
<PackageVersion Include="System.Data.SqlClient" Version="4.8.5" /> | |||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" /> | |||
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" /> | |||
<PackageVersion Include="xunit" Version="2.4.2" /> | |||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" /> | |||
<PackageVersion Include="Yarp.ReverseProxy" Version="2.0.0" /> | |||
</ItemGroup> | |||
</Project> |
@ -1,9 +1,10 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<configuration> | |||
<config> | |||
<add key="repositoryPath" value="packages" /> | |||
</config> | |||
<packageSources> | |||
<add key="NuGet" value="https://api.nuget.org/v3/index.json" /> | |||
<clear /> | |||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> | |||
</packageSources> | |||
</configuration> | |||
<solution> | |||
<add key="disableSourceControlIntegration" value="true" /> | |||
</solution> | |||
</configuration> |
@ -1,28 +0,0 @@ | |||
(function ($, swaggerUi) { | |||
$(function () { | |||
var settings = { | |||
authority: 'https://localhost:5105', | |||
client_id: 'js', | |||
popup_redirect_uri: window.location.protocol | |||
+ '//' | |||
+ window.location.host | |||
+ '/tokenclient/popup.html', | |||
response_type: 'id_token token', | |||
scope: 'openid profile basket', | |||
filter_protocol_claims: true | |||
}, | |||
manager = new OidcTokenManager(settings), | |||
$inputApiKey = $('#input_apiKey'); | |||
$inputApiKey.on('dblclick', function () { | |||
manager.openPopupForTokenAsync() | |||
.then(function () { | |||
$inputApiKey.val(manager.access_token).change(); | |||
}, function (error) { | |||
console.error(error); | |||
}); | |||
}); | |||
}); | |||
})(jQuery, window.swaggerUi); |
@ -1,13 +0,0 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title></title> | |||
<meta charset="utf-8" /> | |||
</head> | |||
<body> | |||
<script type="text/javascript" src="oidc-token-manager.min.js"></script> | |||
<script type="text/javascript"> | |||
new OidcTokenManager().processTokenPopup(); | |||
</script> | |||
</body> | |||
</html> |
@ -1,25 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server; | |||
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter | |||
{ | |||
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) | |||
{ | |||
operation.Parameters ??= new List<OpenApiParameter>(); | |||
operation.Parameters.Add(new OpenApiParameter | |||
{ | |||
Name = "Authorization", | |||
In = ParameterLocation.Header, | |||
Description = "access token", | |||
Required = true | |||
}); | |||
} | |||
} | |||
} |
@ -1,60 +1,25 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFramework>net6.0</TargetFramework> | |||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> | |||
<TargetFramework>net7.0</TargetFramework> | |||
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> | |||
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> | |||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled> | |||
<UserSecretsId>2964ec8e-0d48-4541-b305-94cab537f867</UserSecretsId> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Content Update="web.config"> | |||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> | |||
</Content> | |||
<PackageReference Include="Grpc.AspNetCore" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.Redis" /> | |||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.12.2" /> | |||
<PackageReference Include="Azure.Identity" Version="1.5.0-beta.3" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="5.1.1" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="5.0.1" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="5.0.2" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" /> | |||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0-preview.1" /> | |||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.1" /> | |||
<PackageReference Include="Azure.Identity" Version="1.4.0" /> | |||
<PackageReference Include="Google.Protobuf" Version="3.15.0" /> | |||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.34.0" /> | |||
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.18.0" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.18.0" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="2.0.2-beta2" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.18" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="6.0.0" /> | |||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.11.1" /> | |||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.1-dev-00229" /> | |||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.1-dev-00787" /> | |||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0-dev-00291" /> | |||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1-dev-00876" /> | |||
<PackageReference Include="Serilog.Sinks.Http" Version="8.0.0-beta.9" /> | |||
<PackageReference Include="Serilog.Sinks.Seq" Version="4.1.0-dev-00166" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.1" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.2.1" /> | |||
<ItemGroup> | |||
<Protobuf Include="Proto\basket.proto" GrpcServices="Server" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Protobuf Include="Proto\basket.proto" GrpcServices="Server" Generator="MSBuild:Compile" /> | |||
<Content Include="@(Protobuf)" /> | |||
<None Remove="@(Protobuf)" /> | |||
<ProjectReference Include="..\..\Services.Common\Services.Common.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" /> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" /> | |||
<InternalsVisibleTo Include="Basket.FunctionalTests" /> | |||
</ItemGroup> | |||
</Project> |
@ -1,11 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers; | |||
public class HomeController : Controller | |||
{ | |||
// GET: /<controller>/ | |||
public IActionResult Index() | |||
{ | |||
return new RedirectResult("~/swagger"); | |||
} | |||
} | |||
@ -1,37 +0,0 @@ | |||
namespace Microsoft.eShopOnContainers.Services.Basket.API; | |||
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; | |||
} | |||
} |
@ -0,0 +1,20 @@ | |||
public static class Extensions | |||
{ | |||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddHealthChecks() | |||
.AddRedis(_ => configuration.GetRequiredConnectionString("redis"), "redis", tags: new[] { "ready", "liveness" }); | |||
return services; | |||
} | |||
public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
return services.AddSingleton(sp => | |||
{ | |||
var redisConfig = ConfigurationOptions.Parse(configuration.GetRequiredConnectionString("redis"), true); | |||
return ConnectionMultiplexer.Connect(redisConfig); | |||
}); | |||
} | |||
} |
@ -1,61 +1,29 @@ | |||
global using Autofac.Extensions.DependencyInjection; | |||
global using Autofac; | |||
global using Azure.Core; | |||
global using Azure.Identity; | |||
global using Basket.API.Infrastructure.ActionResults; | |||
global using Basket.API.Infrastructure.Exceptions; | |||
global using Basket.API.Infrastructure.Filters; | |||
global using Basket.API.Infrastructure.Middlewares; | |||
global using Basket.API.IntegrationEvents.EventHandling; | |||
global using Basket.API.IntegrationEvents.Events; | |||
global using Basket.API.Model; | |||
global using Grpc.Core; | |||
global using GrpcBasket; | |||
global using HealthChecks.UI.Client; | |||
global using Microsoft.AspNetCore.Authentication.JwtBearer; | |||
global using Microsoft.AspNetCore.Authorization; | |||
global using Microsoft.AspNetCore.Builder; | |||
global using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||
global using Microsoft.AspNetCore.Hosting; | |||
global using Microsoft.AspNetCore.Http.Features; | |||
global using Microsoft.AspNetCore.Http; | |||
global using Microsoft.AspNetCore.Mvc.Authorization; | |||
global using Microsoft.AspNetCore.Mvc.Filters; | |||
global using Microsoft.AspNetCore.Mvc; | |||
global using Microsoft.AspNetCore.Server.Kestrel.Core; | |||
global using Microsoft.AspNetCore; | |||
global using Azure.Messaging.ServiceBus; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Controllers; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API; | |||
global using Microsoft.Extensions.Configuration; | |||
global using Microsoft.Extensions.DependencyInjection; | |||
global using Microsoft.Extensions.Diagnostics.HealthChecks; | |||
global using Microsoft.Extensions.Hosting; | |||
global using Microsoft.Extensions.Logging; | |||
global using Microsoft.Extensions.Options; | |||
global using Microsoft.OpenApi.Models; | |||
global using RabbitMQ.Client; | |||
global using Serilog.Context; | |||
global using Serilog; | |||
global using StackExchange.Redis; | |||
global using Swashbuckle.AspNetCore.SwaggerGen; | |||
global using System.Collections.Generic; | |||
global using System.ComponentModel.DataAnnotations; | |||
global using System.IdentityModel.Tokens.Jwt; | |||
global using System.IO; | |||
global using System.Linq; | |||
global using System.Net; | |||
global using System.Security.Claims; | |||
global using System.Text.Json; | |||
global using System.Threading.Tasks; | |||
global using System; | |||
global using System; | |||
global using System.Collections.Generic; | |||
global using System.ComponentModel.DataAnnotations; | |||
global using System.Linq; | |||
global using System.Net; | |||
global using System.Security.Claims; | |||
global using System.Text.Json; | |||
global using System.Threading.Tasks; | |||
global using Basket.API.IntegrationEvents.EventHandling; | |||
global using Basket.API.IntegrationEvents.Events; | |||
global using Basket.API.Model; | |||
global using Basket.API.Repositories; | |||
global using Grpc.Core; | |||
global using GrpcBasket; | |||
global using Microsoft.AspNetCore.Authorization; | |||
global using Microsoft.AspNetCore.Builder; | |||
global using Microsoft.AspNetCore.Http; | |||
global using Microsoft.AspNetCore.Mvc; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||
global using Microsoft.eShopOnContainers.Services.Basket.API.Services; | |||
global using Microsoft.Extensions.Configuration; | |||
global using Microsoft.Extensions.DependencyInjection; | |||
global using Microsoft.Extensions.Logging; | |||
global using Services.Common; | |||
global using StackExchange.Redis; |
@ -1,11 +0,0 @@ | |||
namespace Basket.API.Infrastructure.ActionResults; | |||
public class InternalServerErrorObjectResult : ObjectResult | |||
{ | |||
public InternalServerErrorObjectResult(object error) | |||
: base(error) | |||
{ | |||
StatusCode = StatusCodes.Status500InternalServerError; | |||
} | |||
} | |||
@ -1,16 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Exceptions; | |||
public class BasketDomainException : Exception | |||
{ | |||
public BasketDomainException() | |||
{ } | |||
public BasketDomainException(string message) | |||
: base(message) | |||
{ } | |||
public BasketDomainException(string message, Exception innerException) | |||
: base(message, innerException) | |||
{ } | |||
} | |||
@ -1,18 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
public static class FailingMiddlewareAppBuilderExtensions | |||
{ | |||
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; | |||
} | |||
} | |||
@ -1,47 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Filters; | |||
public partial class HttpGlobalExceptionFilter : IExceptionFilter | |||
{ | |||
private readonly IWebHostEnvironment env; | |||
private readonly ILogger<HttpGlobalExceptionFilter> logger; | |||
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | |||
{ | |||
this.env = env; | |||
this.logger = logger; | |||
} | |||
public void OnException(ExceptionContext context) | |||
{ | |||
logger.LogError(new EventId(context.Exception.HResult), | |||
context.Exception, | |||
context.Exception.Message); | |||
if (context.Exception.GetType() == typeof(BasketDomainException)) | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { context.Exception.Message } | |||
}; | |||
context.Result = new BadRequestObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
else | |||
{ | |||
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; | |||
} | |||
context.ExceptionHandled = true; | |||
} | |||
} |
@ -1,9 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Filters; | |||
public class JsonErrorResponse | |||
{ | |||
public string[] Messages { get; set; } | |||
public object DeveloperMessage { get; set; } | |||
} | |||
@ -1,26 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Filters; | |||
public class ValidateModelStateFilter : ActionFilterAttribute | |||
{ | |||
public override void OnActionExecuting(ActionExecutingContext context) | |||
{ | |||
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); | |||
} | |||
} | |||
@ -1,90 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
using Microsoft.Extensions.Logging; | |||
public class FailingMiddleware | |||
{ | |||
private readonly RequestDelegate _next; | |||
private bool _mustFail; | |||
private readonly FailingOptions _options; | |||
private readonly ILogger _logger; | |||
public FailingMiddleware(RequestDelegate next, ILogger<FailingMiddleware> logger, FailingOptions options) | |||
{ | |||
_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")); | |||
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); | |||
} | |||
} |
@ -1,10 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
public class FailingOptions | |||
{ | |||
public string ConfigPath = "/Failing"; | |||
public List<string> EndpointPaths { get; set; } = new List<string>(); | |||
public List<string> NotFilteredPaths { get; set; } = new List<string>(); | |||
} | |||
@ -1,20 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
public class FailingStartupFilter : IStartupFilter | |||
{ | |||
private readonly Action<FailingOptions> _options; | |||
public FailingStartupFilter(Action<FailingOptions> optionsAction) | |||
{ | |||
_options = optionsAction; | |||
} | |||
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) | |||
{ | |||
return app => | |||
{ | |||
app.UseFailingMiddleware(_options); | |||
next(app); | |||
}; | |||
} | |||
} | |||
@ -1,14 +0,0 @@ | |||
namespace Basket.API.Infrastructure.Middlewares; | |||
public static class WebHostBuildertExtensions | |||
{ | |||
public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action<FailingOptions> options) | |||
{ | |||
builder.ConfigureServices(services => | |||
{ | |||
services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options)); | |||
}); | |||
return builder; | |||
} | |||
} | |||
@ -1,101 +1,30 @@ | |||
var configuration = GetConfiguration(); | |||
var builder = WebApplication.CreateBuilder(args); | |||
Log.Logger = CreateSerilogLogger(configuration); | |||
builder.AddServiceDefaults(); | |||
try | |||
{ | |||
Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName); | |||
var host = BuildWebHost(configuration, args); | |||
builder.Services.AddGrpc(); | |||
builder.Services.AddControllers(); | |||
builder.Services.AddProblemDetails(); | |||
Log.Information("Starting web host ({ApplicationContext})...", Program.AppName); | |||
host.Run(); | |||
builder.Services.AddHealthChecks(builder.Configuration); | |||
builder.Services.AddRedis(builder.Configuration); | |||
return 0; | |||
} | |||
catch (Exception ex) | |||
{ | |||
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName); | |||
return 1; | |||
} | |||
finally | |||
{ | |||
Log.CloseAndFlush(); | |||
} | |||
builder.Services.AddTransient<ProductPriceChangedIntegrationEventHandler>(); | |||
builder.Services.AddTransient<OrderStartedIntegrationEventHandler>(); | |||
IWebHost BuildWebHost(IConfiguration configuration, string[] args) => | |||
WebHost.CreateDefaultBuilder(args) | |||
.CaptureStartupErrors(false) | |||
.ConfigureKestrel(options => | |||
{ | |||
var ports = GetDefinedPorts(configuration); | |||
options.Listen(IPAddress.Any, ports.httpPort, listenOptions => | |||
{ | |||
listenOptions.Protocols = HttpProtocols.Http1AndHttp2; | |||
}); | |||
builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>(); | |||
builder.Services.AddTransient<IIdentityService, IdentityService>(); | |||
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => | |||
{ | |||
listenOptions.Protocols = HttpProtocols.Http2; | |||
}); | |||
var app = builder.Build(); | |||
}) | |||
.ConfigureAppConfiguration(x => x.AddConfiguration(configuration)) | |||
.UseFailing(options => | |||
{ | |||
options.ConfigPath = "/Failing"; | |||
options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" }); | |||
}) | |||
.UseStartup<Startup>() | |||
.UseContentRoot(Directory.GetCurrentDirectory()) | |||
.UseSerilog() | |||
.Build(); | |||
app.UseServiceDefaults(); | |||
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) | |||
{ | |||
var seqServerUrl = configuration["Serilog:SeqServerUrl"]; | |||
var logstashUrl = configuration["Serilog:LogstashgUrl"]; | |||
return new LoggerConfiguration() | |||
.MinimumLevel.Verbose() | |||
.Enrich.WithProperty("ApplicationContext", Program.AppName) | |||
.Enrich.FromLogContext() | |||
.WriteTo.Console() | |||
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) | |||
.WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) | |||
.ReadFrom.Configuration(configuration) | |||
.CreateLogger(); | |||
} | |||
app.MapGrpcService<BasketService>(); | |||
app.MapControllers(); | |||
IConfiguration GetConfiguration() | |||
{ | |||
var builder = new ConfigurationBuilder() | |||
.SetBasePath(Directory.GetCurrentDirectory()) | |||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) | |||
.AddEnvironmentVariables(); | |||
var eventBus = app.Services.GetRequiredService<IEventBus>(); | |||
var config = builder.Build(); | |||
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>(); | |||
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>(); | |||
if (config.GetValue<bool>("UseVault", false)) | |||
{ | |||
TokenCredential credential = new ClientSecretCredential( | |||
config["Vault:TenantId"], | |||
config["Vault:ClientId"], | |||
config["Vault:ClientSecret"]); | |||
builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential); | |||
} | |||
return builder.Build(); | |||
} | |||
(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) | |||
{ | |||
var grpcPort = config.GetValue("GRPC_PORT", 5001); | |||
var port = config.GetValue("PORT", 80); | |||
return (port, grpcPort); | |||
} | |||
public partial class Program | |||
{ | |||
public static string Namespace = typeof(Startup).Namespace; | |||
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); | |||
} | |||
await app.RunAsync(); |