1
- var configuration = GetConfiguration ( ) ;
1
+ using Autofac . Core ;
2
+ using Microsoft . Azure . Amqp . Framing ;
3
+ using Microsoft . Extensions . Configuration ;
2
4
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 > ( )
45
+ {
46
+ { "basket" , "Basket API" }
47
+ }
48
+ }
49
+ }
50
+ } ) ;
51
+
52
+ options . OperationFilter < AuthorizeCheckOperationFilter > ( ) ;
53
+ } ) ;
54
+
55
+ // prevent from mapping "sub" claim to nameidentifier.
56
+ JwtSecurityTokenHandler . DefaultInboundClaimTypeMap . Remove ( "sub" ) ;
57
+
58
+ var identityUrl = builder . Configuration . GetValue < string > ( "IdentityUrl" ) ;
59
+
60
+ builder . Services . AddAuthentication ( "Bearer" ) . AddJwtBearer ( options => {
61
+ options . Authority = identityUrl ;
62
+ options . RequireHttpsMetadata = false ;
63
+ options . Audience = "basket" ;
64
+ options . TokenValidationParameters . ValidateAudience = false ;
65
+ } ) ;
66
+ builder . Services . AddAuthorization ( options => {
67
+ options . AddPolicy ( "ApiScope" , policy => {
68
+ policy . RequireAuthenticatedUser ( ) ;
69
+ policy . RequireClaim ( "scope" , "basket" ) ;
70
+ } ) ;
71
+ } ) ;
72
+
73
+ builder . Services . AddCustomHealthCheck ( builder . Configuration ) ;
74
+
75
+ builder . Services . Configure < BasketSettings > ( builder . Configuration ) ;
76
+
77
+ builder . Services . AddSingleton < ConnectionMultiplexer > ( sp => {
78
+ var settings = sp . GetRequiredService < IOptions < BasketSettings > > ( ) . Value ;
79
+ var configuration = ConfigurationOptions . Parse ( settings . ConnectionString , true ) ;
80
+
81
+ return ConnectionMultiplexer . Connect ( configuration ) ;
82
+ } ) ;
83
+
84
+
85
+ if ( builder . Configuration . GetValue < bool > ( "AzureServiceBusEnabled" ) ) {
86
+ builder . Services . AddSingleton < IServiceBusPersisterConnection > ( sp => {
87
+ var serviceBusConnectionString = builder . Configuration [ "EventBusConnection" ] ;
88
+
89
+ return new DefaultServiceBusPersisterConnection ( serviceBusConnectionString ) ;
90
+ } ) ;
91
+ }
92
+ else {
93
+ builder . Services . AddSingleton < IRabbitMQPersistentConnection > ( sp => {
94
+ var logger = sp . GetRequiredService < ILogger < DefaultRabbitMQPersistentConnection > > ( ) ;
95
+
96
+ var factory = new ConnectionFactory ( ) {
97
+ HostName = builder . Configuration [ "EventBusConnection" ] ,
98
+ DispatchConsumersAsync = true
99
+ } ;
100
+
101
+ if ( ! string . IsNullOrEmpty ( builder . Configuration [ "EventBusUserName" ] ) ) {
102
+ factory . UserName = builder . Configuration [ "EventBusUserName" ] ;
103
+ }
104
+
105
+ if ( ! string . IsNullOrEmpty ( builder . Configuration [ "EventBusPassword" ] ) ) {
106
+ factory . Password = builder . Configuration [ "EventBusPassword" ] ;
107
+ }
108
+
109
+ var retryCount = 5 ;
110
+ if ( ! string . IsNullOrEmpty ( builder . Configuration [ "EventBusRetryCount" ] ) ) {
111
+ retryCount = int . Parse ( builder . Configuration [ "EventBusRetryCount" ] ) ;
112
+ }
113
+
114
+ return new DefaultRabbitMQPersistentConnection ( factory , logger , retryCount ) ;
115
+ } ) ;
116
+ }
117
+ builder . Services . RegisterEventBus ( builder . Configuration ) ;
118
+ builder . Services . AddCors ( options => {
119
+ options . AddPolicy ( "CorsPolicy" ,
120
+ builder => builder
121
+ . SetIsOriginAllowed ( ( host ) => true )
122
+ . AllowAnyMethod ( )
123
+ . AllowAnyHeader ( )
124
+ . AllowCredentials ( ) ) ;
125
+ } ) ;
126
+ builder . Services . AddSingleton < IHttpContextAccessor , HttpContextAccessor > ( ) ;
127
+ builder . Services . AddTransient < IBasketRepository , RedisBasketRepository > ( ) ;
128
+ builder . Services . AddTransient < IIdentityService , IdentityService > ( ) ;
129
+
130
+ builder . Services . AddOptions ( ) ;
131
+ builder . Configuration . SetBasePath ( Directory . GetCurrentDirectory ( ) ) ;
132
+ builder . Configuration . AddJsonFile ( "appsettings.json" , optional : false , reloadOnChange : true ) ;
133
+ builder . Configuration . AddEnvironmentVariables ( ) ;
134
+ builder . WebHost . UseKestrel ( options => {
135
+ var ports = GetDefinedPorts ( builder . Configuration ) ;
136
+ options . Listen ( IPAddress . Any , ports . httpPort , listenOptions => {
137
+ listenOptions . Protocols = HttpProtocols . Http1AndHttp2 ;
138
+ } ) ;
139
+
140
+ options . Listen ( IPAddress . Any , ports . grpcPort , listenOptions => {
141
+ listenOptions . Protocols = HttpProtocols . Http2 ;
142
+ } ) ;
143
+
144
+ } ) ;
145
+ builder . WebHost . CaptureStartupErrors ( false ) ;
146
+ builder . Host . UseSerilog ( CreateSerilogLogger ( builder . Configuration ) ) ;
147
+ builder . WebHost . UseFailing ( options => {
148
+ options . ConfigPath = "/Failing" ;
149
+ options . NotFilteredPaths . AddRange ( new [ ] { "/hc" , "/liveness" } ) ;
150
+ } ) ;
151
+ var app = builder . Build ( ) ;
152
+
153
+ if ( app . Environment . IsDevelopment ( ) ) {
154
+ app . UseDeveloperExceptionPage ( ) ;
155
+ }
156
+ else {
157
+ app . UseExceptionHandler ( "/Home/Error" ) ;
158
+ }
159
+
160
+ var pathBase = app . Configuration [ "PATH_BASE" ] ;
161
+ if ( ! string . IsNullOrEmpty ( pathBase ) ) {
162
+ app . UsePathBase ( pathBase ) ;
163
+ }
164
+
165
+ app . UseSwagger ( )
166
+ . UseSwaggerUI ( setup => {
167
+ setup . SwaggerEndpoint ( $ "{ ( ! string . IsNullOrEmpty ( pathBase ) ? pathBase : string . Empty ) } /swagger/v1/swagger.json", "Basket.API V1" ) ;
168
+ setup . OAuthClientId ( "basketswaggerui" ) ;
169
+ setup . OAuthAppName ( "Basket Swagger UI" ) ;
170
+ } ) ;
171
+
172
+ app . UseRouting ( ) ;
173
+ app . UseCors ( "CorsPolicy" ) ;
174
+ app . UseAuthentication ( ) ;
175
+ app . UseAuthorization ( ) ;
176
+ app . UseStaticFiles ( ) ;
4
177
5
- try
6
- {
178
+
179
+ app . MapGrpcService < BasketService > ( ) ;
180
+ app . MapDefaultControllerRoute ( ) ;
181
+ app . MapControllers ( ) ;
182
+ app . MapGet ( "/_proto/" , async ctx => {
183
+ ctx . Response . ContentType = "text/plain" ;
184
+ using var fs = new FileStream ( Path . Combine ( app . Environment . ContentRootPath , "Proto" , "basket.proto" ) , FileMode . Open , FileAccess . Read ) ;
185
+ using var sr = new StreamReader ( fs ) ;
186
+ while ( ! sr . EndOfStream ) {
187
+ var line = await sr . ReadLineAsync ( ) ;
188
+ if ( line != "/* >>" || line != "<< */" ) {
189
+ await ctx . Response . WriteAsync ( line ) ;
190
+ }
191
+ }
192
+ } ) ;
193
+ app . MapHealthChecks ( "/hc" , new HealthCheckOptions ( ) {
194
+ Predicate = _ => true ,
195
+ ResponseWriter = UIResponseWriter . WriteHealthCheckUIResponse
196
+ } ) ;
197
+ app . MapHealthChecks ( "/liveness" , new HealthCheckOptions {
198
+ Predicate = r => r . Name . Contains ( "self" )
199
+ } ) ;
200
+ ConfigureEventBus ( app ) ;
201
+ try {
7
202
Log . Information ( "Configuring web host ({ApplicationContext})..." , Program . AppName ) ;
8
- var host = BuildWebHost ( configuration , args ) ;
203
+
9
204
10
205
Log . Information ( "Starting web host ({ApplicationContext})..." , Program . AppName ) ;
11
- host . Run ( ) ;
206
+ await app . RunAsync ( ) ;
12
207
13
208
return 0 ;
14
209
}
15
- catch ( Exception ex )
16
- {
210
+ catch ( Exception ex ) {
17
211
Log . Fatal ( ex , "Program terminated unexpectedly ({ApplicationContext})!" , Program . AppName ) ;
18
212
return 1 ;
19
213
}
20
- finally
21
- {
214
+ finally {
22
215
Log . CloseAndFlush ( ) ;
23
216
}
24
217
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
- {
218
+ Serilog . ILogger CreateSerilogLogger ( IConfiguration configuration ) {
55
219
var seqServerUrl = configuration [ "Serilog:SeqServerUrl" ] ;
56
220
var logstashUrl = configuration [ "Serilog:LogstashgUrl" ] ;
57
221
return new LoggerConfiguration ( )
@@ -65,37 +229,59 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
65
229
. CreateLogger ( ) ;
66
230
}
67
231
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
- {
232
+ ( int httpPort , int grpcPort ) GetDefinedPorts ( IConfiguration config ) {
91
233
var grpcPort = config . GetValue ( "GRPC_PORT" , 5001 ) ;
92
234
var port = config . GetValue ( "PORT" , 80 ) ;
93
235
return ( port , grpcPort ) ;
94
236
}
237
+ void ConfigureEventBus ( IApplicationBuilder app ) {
238
+ var eventBus = app . ApplicationServices . GetRequiredService < IEventBus > ( ) ;
95
239
96
- public partial class Program
97
- {
240
+ eventBus . Subscribe < ProductPriceChangedIntegrationEvent , ProductPriceChangedIntegrationEventHandler > ( ) ;
241
+ eventBus . Subscribe < OrderStartedIntegrationEvent , OrderStartedIntegrationEventHandler > ( ) ;
242
+ }
243
+ public partial class Program {
98
244
99
- public static string Namespace = typeof ( Startup ) . Namespace ;
245
+ public static string Namespace = typeof ( Program ) . Assembly . GetName ( ) . Name ;
100
246
public static string AppName = Namespace . Substring ( Namespace . LastIndexOf ( '.' , Namespace . LastIndexOf ( '.' ) - 1 ) + 1 ) ;
101
247
}
248
+
249
+
250
+ public static class CustomExtensionMethods {
251
+
252
+
253
+ public static IServiceCollection RegisterEventBus ( this IServiceCollection services , IConfiguration configuration ) {
254
+ if ( configuration . GetValue < bool > ( "AzureServiceBusEnabled" ) ) {
255
+ services . AddSingleton < IEventBus , EventBusServiceBus > ( sp => {
256
+ var serviceBusPersisterConnection = sp . GetRequiredService < IServiceBusPersisterConnection > ( ) ;
257
+ var logger = sp . GetRequiredService < ILogger < EventBusServiceBus > > ( ) ;
258
+ var eventBusSubscriptionsManager = sp . GetRequiredService < IEventBusSubscriptionsManager > ( ) ;
259
+ string subscriptionName = configuration [ "SubscriptionClientName" ] ;
260
+
261
+ return new EventBusServiceBus ( serviceBusPersisterConnection , logger ,
262
+ eventBusSubscriptionsManager , sp , subscriptionName ) ;
263
+ } ) ;
264
+ }
265
+ else {
266
+ services . AddSingleton < IEventBus , EventBusRabbitMQ > ( sp => {
267
+ var subscriptionClientName = configuration [ "SubscriptionClientName" ] ;
268
+ var rabbitMQPersistentConnection = sp . GetRequiredService < IRabbitMQPersistentConnection > ( ) ;
269
+ var logger = sp . GetRequiredService < ILogger < EventBusRabbitMQ > > ( ) ;
270
+ var eventBusSubscriptionsManager = sp . GetRequiredService < IEventBusSubscriptionsManager > ( ) ;
271
+
272
+ var retryCount = 5 ;
273
+ if ( ! string . IsNullOrEmpty ( configuration [ "EventBusRetryCount" ] ) ) {
274
+ retryCount = int . Parse ( configuration [ "EventBusRetryCount" ] ) ;
275
+ }
276
+
277
+ return new EventBusRabbitMQ ( rabbitMQPersistentConnection , logger , sp , eventBusSubscriptionsManager , subscriptionClientName , retryCount ) ;
278
+ } ) ;
279
+ }
280
+
281
+ services . AddSingleton < IEventBusSubscriptionsManager , InMemoryEventBusSubscriptionsManager > ( ) ;
282
+
283
+ services . AddTransient < ProductPriceChangedIntegrationEventHandler > ( ) ;
284
+ services . AddTransient < OrderStartedIntegrationEventHandler > ( ) ;
285
+ return services ;
286
+ }
287
+ }
0 commit comments