@ -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": { | "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; | ||||
global using Grpc.Core.Interceptors; | |||||
global using GrpcBasket; | 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.Authentication; | ||||
global using Microsoft.AspNetCore.Authorization; | global using Microsoft.AspNetCore.Authorization; | ||||
global using Microsoft.AspNetCore.Builder; | 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.Http; | ||||
global using Microsoft.AspNetCore.Mvc; | global using Microsoft.AspNetCore.Mvc; | ||||
global using Microsoft.AspNetCore; | |||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; | 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.Infrastructure; | ||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; | global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; | ||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; | global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; | ||||
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator; | |||||
global using Microsoft.Extensions.Configuration; | global using Microsoft.Extensions.Configuration; | ||||
global using Microsoft.Extensions.DependencyInjection; | 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.Logging; | ||||
global using Microsoft.Extensions.Options; | 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": { | "profiles": { | ||||
"IIS Express": { | |||||
"commandName": "IISExpress", | |||||
"launchBrowser": true, | |||||
"launchUrl": "api/values", | |||||
"environmentVariables": { | |||||
"ASPNETCORE_ENVIRONMENT": "Development" | |||||
} | |||||
}, | |||||
"PurchaseForMvc": { | |||||
"Web.Shopping.HttpAggregator": { | |||||
"commandName": "Project", | "commandName": "Project", | ||||
"launchBrowser": true, | "launchBrowser": true, | ||||
"launchUrl": "api/values", | |||||
"applicationUrl": "http://localhost:5229/", | |||||
"environmentVariables": { | "environmentVariables": { | ||||
"ASPNETCORE_ENVIRONMENT": "Development" | "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": { | "Logging": { | ||||
"IncludeScopes": false, | |||||
"Debug": { | |||||
"LogLevel": { | |||||
"Default": "Debug" | |||||
} | |||||
}, | |||||
"Console": { | |||||
"LogLevel": { | |||||
"Default": "Debug" | |||||
} | |||||
"LogLevel": { | |||||
"Default": "Information", | |||||
"Microsoft.AspNetCore": "Warning" | |||||
} | } | ||||
} | } | ||||
} | } |
@ -1,15 +1,138 @@ | |||||
{ | { | ||||
"Logging": { | "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"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<configuration> | <configuration> | ||||
<config> | |||||
<add key="repositoryPath" value="packages" /> | |||||
</config> | |||||
<packageSources> | <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> | </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"> | <Project Sdk="Microsoft.NET.Sdk.Web"> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>net6.0</TargetFramework> | |||||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> | |||||
<TargetFramework>net7.0</TargetFramework> | |||||
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> | <DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> | ||||
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> | |||||
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled> | |||||
<UserSecretsId>2964ec8e-0d48-4541-b305-94cab537f867</UserSecretsId> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <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> | ||||
<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> | ||||
<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> | ||||
<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> | </ItemGroup> | ||||
</Project> | </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(); |