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 > ( ) { { "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 ( ) ;
4
174
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 {
7
199
Log . Information ( "Configuring web host ({ApplicationContext})..." , Program . AppName ) ;
8
- var host = BuildWebHost ( configuration , args ) ;
200
+
9
201
10
202
Log . Information ( "Starting web host ({ApplicationContext})..." , Program . AppName ) ;
11
- host . Run ( ) ;
203
+ await app . RunAsync ( ) ;
12
204
13
205
return 0 ;
14
206
}
15
- catch ( Exception ex )
16
- {
207
+ catch ( Exception ex ) {
17
208
Log . Fatal ( ex , "Program terminated unexpectedly ({ApplicationContext})!" , Program . AppName ) ;
18
209
return 1 ;
19
210
}
20
- finally
21
- {
211
+ finally {
22
212
Log . CloseAndFlush ( ) ;
23
213
}
24
214
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 ) {
55
216
var seqServerUrl = configuration [ "Serilog:SeqServerUrl" ] ;
56
217
var logstashUrl = configuration [ "Serilog:LogstashgUrl" ] ;
57
218
return new LoggerConfiguration ( )
@@ -65,37 +226,59 @@ Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
65
226
. CreateLogger ( ) ;
66
227
}
67
228
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 ) {
91
230
var grpcPort = config . GetValue ( "GRPC_PORT" , 5001 ) ;
92
231
var port = config . GetValue ( "PORT" , 80 ) ;
93
232
return ( port , grpcPort ) ;
94
233
}
234
+ void ConfigureEventBus ( IApplicationBuilder app ) {
235
+ var eventBus = app . ApplicationServices . GetRequiredService < IEventBus > ( ) ;
95
236
96
- public partial class Program
97
- {
237
+ eventBus . Subscribe < ProductPriceChangedIntegrationEvent , ProductPriceChangedIntegrationEventHandler > ( ) ;
238
+ eventBus . Subscribe < OrderStartedIntegrationEvent , OrderStartedIntegrationEventHandler > ( ) ;
239
+ }
240
+ public partial class Program {
98
241
99
- public static string Namespace = typeof ( Startup ) . Namespace ;
242
+ public static string Namespace = typeof ( Program ) . Assembly . GetName ( ) . Name ;
100
243
public static string AppName = Namespace . Substring ( Namespace . LastIndexOf ( '.' , Namespace . LastIndexOf ( '.' ) - 1 ) + 1 ) ;
101
244
}
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