Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

Commit c8f1e0f

Browse files
authored
Merge pull request #2075 from erjain/update/basket-api-webAppBuilder
Basket.API: Commit to migrate to WebApplication Builder
2 parents 4a86d5e + bb43074 commit c8f1e0f

File tree

4 files changed

+250
-385
lines changed

4 files changed

+250
-385
lines changed
Lines changed: 249 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,218 @@
1-
var configuration = GetConfiguration();
1+
using Autofac.Core;
2+
using Microsoft.Azure.Amqp.Framing;
3+
using Microsoft.Extensions.Configuration;
24

3-
Log.Logger = CreateSerilogLogger(configuration);
5+
var appName = "Basket.API";
6+
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
7+
Args = args,
8+
ApplicationName = typeof(Program).Assembly.FullName,
9+
ContentRootPath = Directory.GetCurrentDirectory()
10+
});
11+
if (builder.Configuration.GetValue<bool>("UseVault", false)) {
12+
TokenCredential credential = new ClientSecretCredential(
13+
builder.Configuration["Vault:TenantId"],
14+
builder.Configuration["Vault:ClientId"],
15+
builder.Configuration["Vault:ClientSecret"]);
16+
builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["Vault:Name"]}.vault.azure.net/"), credential);
17+
}
18+
19+
builder.Services.AddGrpc(options => {
20+
options.EnableDetailedErrors = true;
21+
});
22+
builder.Services.AddApplicationInsightsTelemetry(builder.Configuration);
23+
builder.Services.AddApplicationInsightsKubernetesEnricher();
24+
builder.Services.AddControllers(options => {
25+
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
26+
options.Filters.Add(typeof(ValidateModelStateFilter));
27+
28+
}) // Added for functional tests
29+
.AddApplicationPart(typeof(BasketController).Assembly)
30+
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
31+
builder.Services.AddSwaggerGen(options => {
32+
options.SwaggerDoc("v1", new OpenApiInfo {
33+
Title = "eShopOnContainers - Basket HTTP API",
34+
Version = "v1",
35+
Description = "The Basket Service HTTP API"
36+
});
37+
38+
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme {
39+
Type = SecuritySchemeType.OAuth2,
40+
Flows = new OpenApiOAuthFlows() {
41+
Implicit = new OpenApiOAuthFlow() {
42+
AuthorizationUrl = new Uri($"{builder.Configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
43+
TokenUrl = new Uri($"{builder.Configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
44+
Scopes = new Dictionary<string, string>() { { "basket", "Basket API" } }
45+
}
46+
}
47+
});
48+
49+
options.OperationFilter<AuthorizeCheckOperationFilter>();
50+
});
51+
52+
// prevent from mapping "sub" claim to nameidentifier.
53+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
54+
55+
var identityUrl = builder.Configuration.GetValue<string>("IdentityUrl");
56+
57+
builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => {
58+
options.Authority = identityUrl;
59+
options.RequireHttpsMetadata = false;
60+
options.Audience = "basket";
61+
options.TokenValidationParameters.ValidateAudience = false;
62+
});
63+
builder.Services.AddAuthorization(options => {
64+
options.AddPolicy("ApiScope", policy => {
65+
policy.RequireAuthenticatedUser();
66+
policy.RequireClaim("scope", "basket");
67+
});
68+
});
69+
70+
builder.Services.AddCustomHealthCheck(builder.Configuration);
71+
72+
builder.Services.Configure<BasketSettings>(builder.Configuration);
73+
74+
builder.Services.AddSingleton<ConnectionMultiplexer>(sp => {
75+
var settings = sp.GetRequiredService<IOptions<BasketSettings>>().Value;
76+
var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true);
77+
78+
return ConnectionMultiplexer.Connect(configuration);
79+
});
80+
81+
82+
if (builder.Configuration.GetValue<bool>("AzureServiceBusEnabled")) {
83+
builder.Services.AddSingleton<IServiceBusPersisterConnection>(sp => {
84+
var serviceBusConnectionString = builder.Configuration["EventBusConnection"];
85+
86+
return new DefaultServiceBusPersisterConnection(serviceBusConnectionString);
87+
});
88+
}
89+
else {
90+
builder.Services.AddSingleton<IRabbitMQPersistentConnection>(sp => {
91+
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
92+
93+
var factory = new ConnectionFactory() {
94+
HostName = builder.Configuration["EventBusConnection"],
95+
DispatchConsumersAsync = true
96+
};
97+
98+
if (!string.IsNullOrEmpty(builder.Configuration["EventBusUserName"])) {
99+
factory.UserName = builder.Configuration["EventBusUserName"];
100+
}
101+
102+
if (!string.IsNullOrEmpty(builder.Configuration["EventBusPassword"])) {
103+
factory.Password = builder.Configuration["EventBusPassword"];
104+
}
105+
106+
var retryCount = 5;
107+
if (!string.IsNullOrEmpty(builder.Configuration["EventBusRetryCount"])) {
108+
retryCount = int.Parse(builder.Configuration["EventBusRetryCount"]);
109+
}
110+
111+
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
112+
});
113+
}
114+
builder.Services.RegisterEventBus(builder.Configuration);
115+
builder.Services.AddCors(options => {
116+
options.AddPolicy("CorsPolicy",
117+
builder => builder
118+
.SetIsOriginAllowed((host) => true)
119+
.AllowAnyMethod()
120+
.AllowAnyHeader()
121+
.AllowCredentials());
122+
});
123+
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
124+
builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>();
125+
builder.Services.AddTransient<IIdentityService, IdentityService>();
126+
127+
builder.Services.AddOptions();
128+
builder.Configuration.SetBasePath(Directory.GetCurrentDirectory());
129+
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
130+
builder.Configuration.AddEnvironmentVariables();
131+
builder.WebHost.UseKestrel(options => {
132+
var ports = GetDefinedPorts(builder.Configuration);
133+
options.Listen(IPAddress.Any, ports.httpPort, listenOptions => {
134+
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
135+
});
136+
137+
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => {
138+
listenOptions.Protocols = HttpProtocols.Http2;
139+
});
140+
141+
});
142+
builder.WebHost.CaptureStartupErrors(false);
143+
builder.Host.UseSerilog(CreateSerilogLogger(builder.Configuration));
144+
builder.WebHost.UseFailing(options => {
145+
options.ConfigPath = "/Failing";
146+
options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" });
147+
});
148+
var app = builder.Build();
149+
150+
if (app.Environment.IsDevelopment()) {
151+
app.UseDeveloperExceptionPage();
152+
}
153+
else {
154+
app.UseExceptionHandler("/Home/Error");
155+
}
156+
157+
var pathBase = app.Configuration["PATH_BASE"];
158+
if (!string.IsNullOrEmpty(pathBase)) {
159+
app.UsePathBase(pathBase);
160+
}
161+
162+
app.UseSwagger()
163+
.UseSwaggerUI(setup => {
164+
setup.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Basket.API V1");
165+
setup.OAuthClientId("basketswaggerui");
166+
setup.OAuthAppName("Basket Swagger UI");
167+
});
168+
169+
app.UseRouting();
170+
app.UseCors("CorsPolicy");
171+
app.UseAuthentication();
172+
app.UseAuthorization();
173+
app.UseStaticFiles();
4174

5-
try
6-
{
175+
176+
app.MapGrpcService<BasketService>();
177+
app.MapDefaultControllerRoute();
178+
app.MapControllers();
179+
app.MapGet("/_proto/", async ctx => {
180+
ctx.Response.ContentType = "text/plain";
181+
using var fs = new FileStream(Path.Combine(app.Environment.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read);
182+
using var sr = new StreamReader(fs);
183+
while (!sr.EndOfStream) {
184+
var line = await sr.ReadLineAsync();
185+
if (line != "/* >>" || line != "<< */") {
186+
await ctx.Response.WriteAsync(line);
187+
}
188+
}
189+
});
190+
app.MapHealthChecks("/hc", new HealthCheckOptions() {
191+
Predicate = _ => true,
192+
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
193+
});
194+
app.MapHealthChecks("/liveness", new HealthCheckOptions {
195+
Predicate = r => r.Name.Contains("self")
196+
});
197+
ConfigureEventBus(app);
198+
try {
7199
Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName);
8-
var host = BuildWebHost(configuration, args);
200+
9201

10202
Log.Information("Starting web host ({ApplicationContext})...", Program.AppName);
11-
host.Run();
203+
await app.RunAsync();
12204

13205
return 0;
14206
}
15-
catch (Exception ex)
16-
{
207+
catch (Exception ex) {
17208
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName);
18209
return 1;
19210
}
20-
finally
21-
{
211+
finally {
22212
Log.CloseAndFlush();
23213
}
24214

25-
IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
26-
WebHost.CreateDefaultBuilder(args)
27-
.CaptureStartupErrors(false)
28-
.ConfigureKestrel(options =>
29-
{
30-
var ports = GetDefinedPorts(configuration);
31-
options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
32-
{
33-
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
34-
});
35-
36-
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
37-
{
38-
listenOptions.Protocols = HttpProtocols.Http2;
39-
});
40-
41-
})
42-
.ConfigureAppConfiguration(x => x.AddConfiguration(configuration))
43-
.UseFailing(options =>
44-
{
45-
options.ConfigPath = "/Failing";
46-
options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" });
47-
})
48-
.UseStartup<Startup>()
49-
.UseContentRoot(Directory.GetCurrentDirectory())
50-
.UseSerilog()
51-
.Build();
52-
53-
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
54-
{
215+
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) {
55216
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
56217
var logstashUrl = configuration["Serilog:LogstashgUrl"];
57218
return new LoggerConfiguration()
@@ -65,37 +226,59 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
65226
.CreateLogger();
66227
}
67228

68-
IConfiguration GetConfiguration()
69-
{
70-
var builder = new ConfigurationBuilder()
71-
.SetBasePath(Directory.GetCurrentDirectory())
72-
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
73-
.AddEnvironmentVariables();
74-
75-
var config = builder.Build();
76-
77-
if (config.GetValue<bool>("UseVault", false))
78-
{
79-
TokenCredential credential = new ClientSecretCredential(
80-
config["Vault:TenantId"],
81-
config["Vault:ClientId"],
82-
config["Vault:ClientSecret"]);
83-
builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential);
84-
}
85-
86-
return builder.Build();
87-
}
88-
89-
(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config)
90-
{
229+
(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) {
91230
var grpcPort = config.GetValue("GRPC_PORT", 5001);
92231
var port = config.GetValue("PORT", 80);
93232
return (port, grpcPort);
94233
}
234+
void ConfigureEventBus(IApplicationBuilder app) {
235+
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
95236

96-
public partial class Program
97-
{
237+
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
238+
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
239+
}
240+
public partial class Program {
98241

99-
public static string Namespace = typeof(Startup).Namespace;
242+
public static string Namespace = typeof(Program).Assembly.GetName().Name;
100243
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
101244
}
245+
246+
247+
public static class CustomExtensionMethods {
248+
249+
250+
public static IServiceCollection RegisterEventBus(this IServiceCollection services, IConfiguration configuration) {
251+
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) {
252+
services.AddSingleton<IEventBus, EventBusServiceBus>(sp => {
253+
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
254+
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
255+
var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
256+
string subscriptionName = configuration["SubscriptionClientName"];
257+
258+
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
259+
eventBusSubscriptionsManager, sp, subscriptionName);
260+
});
261+
}
262+
else {
263+
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => {
264+
var subscriptionClientName = configuration["SubscriptionClientName"];
265+
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
266+
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
267+
var eventBusSubscriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
268+
269+
var retryCount = 5;
270+
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) {
271+
retryCount = int.Parse(configuration["EventBusRetryCount"]);
272+
}
273+
274+
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, sp, eventBusSubscriptionsManager, subscriptionClientName, retryCount);
275+
});
276+
}
277+
278+
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
279+
280+
services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
281+
services.AddTransient<OrderStartedIntegrationEventHandler>();
282+
return services;
283+
}
284+
}

0 commit comments

Comments
 (0)