src/Relecloud.Web.CallCenter.Api/Startup.cs (153 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All Rights Reserved. // Licensed under the MIT License. using Azure.Core; using Azure.Storage.Blobs; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.IdentityModel.Logging; using Relecloud.Web.Api.Infrastructure; using Relecloud.Web.Api.Services; using Relecloud.Web.Api.Services.MockServices; using Relecloud.Web.Api.Services.Search; using Relecloud.Web.Api.Services.SqlDatabaseConcertRepository; using Relecloud.Web.Api.Services.TicketManagementService; using Relecloud.Web.Models.Services; using Relecloud.Web.Services.Search; using System.Diagnostics; namespace Relecloud.Web.Api { public class Startup { private readonly TokenCredential _credential; public Startup(IConfiguration configuration, TokenCredential credential) { Configuration = configuration; _credential = credential; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { // Add services to the container. AddMicrosoftEntraIdServices(services); services.AddControllers(); services.AddAzureAppConfiguration(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle services.AddEndpointsApiExplorer(); services.AddSwaggerGen(); services.AddApplicationInsightsTelemetry(options => { options.ConnectionString = Configuration["App:Api:ApplicationInsights:ConnectionString"]; }); AddAzureSearchService(services); AddConcertContextServices(services); AddDistributedSession(services, _credential); AddPaymentGatewayService(services); AddTicketManagementService(services); AddTicketImageService(services); // The ApplicationInitializer is injected in the Configure method with all its dependencies and will ensure // they are all properly initialized upon construction. services.AddScoped<ApplicationInitializer, ApplicationInitializer>(); services.AddHealthChecks(); } private void AddMicrosoftEntraIdServices(IServiceCollection services) { // Adds Microsoft Identity platform (AAD v2.0) support to protect this Api services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "Api:MicrosoftEntraId"); } private void AddTicketManagementService(IServiceCollection services) { var sqlDatabaseConnectionString = Configuration["App:SqlDatabase:ConnectionString"]; if (string.IsNullOrWhiteSpace(sqlDatabaseConnectionString)) { services.AddScoped<ITicketManagementService, MockTicketManagementService>(); services.AddScoped<ITicketRenderingService, MockTicketRenderingService>(); } else { services.AddScoped<ITicketManagementService, TicketManagementService>(); services.AddScoped<ITicketRenderingService, TicketRenderingService>(); } } private void AddAzureSearchService(IServiceCollection services) { var azureSearchServiceName = Configuration["App:AzureSearch:ServiceName"]; var sqlDatabaseConnectionString = Configuration["App:SqlDatabase:ConnectionString"]; if (string.IsNullOrWhiteSpace(azureSearchServiceName) && string.IsNullOrWhiteSpace(sqlDatabaseConnectionString)) { // Add a dummy concert search service in case the Azure Search service isn't provisioned and configured yet. services.AddScoped<IConcertSearchService, MockConcertSearchService>(); } else if (string.IsNullOrWhiteSpace(azureSearchServiceName)) { services.AddScoped<IConcertSearchService, SqlDatabaseConcertSearchService>(); } else { // Add a concert search service based on Azure Search. services.AddScoped<IConcertSearchService>(x => new AzureSearchConcertSearchService(azureSearchServiceName, sqlDatabaseConnectionString!)); } } private void AddConcertContextServices(IServiceCollection services) { var sqlDatabaseConnectionString = Configuration["App:SqlDatabase:ConnectionString"]; if (string.IsNullOrWhiteSpace(sqlDatabaseConnectionString)) { services.AddScoped<IConcertRepository, MockConcertRepository>(); } else { // Add a concert repository based on Azure SQL Database. services.AddDbContextPool<ConcertDataContext>(options => options.UseSqlServer(sqlDatabaseConnectionString, sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(3), errorNumbersToAdd: null); })); services.AddScoped<IConcertRepository, SqlDatabaseConcertRepository>(); } } private void AddDistributedSession(IServiceCollection services, TokenCredential credential) { var connectionString = Configuration["App:RedisCache:ConnectionString"]; if (!string.IsNullOrWhiteSpace(connectionString)) { // If we have a connection string to Redis, use that as the distributed cache. // If not, ASP.NET Core automatically injects an in-memory cache. services.AddAzureStackExchangeRedisCache(connectionString, credential); } else { services.AddDistributedMemoryCache(); } } private void AddPaymentGatewayService(IServiceCollection services) { services.AddScoped<IPaymentGatewayService, MockPaymentGatewayService>(); } private void AddTicketImageService(IServiceCollection services) { // It is best practice to create Azure SDK clients once and reuse them. // https://learn.microsoft.com/azure/storage/blobs/storage-blob-client-management#manage-client-objects // https://devblogs.microsoft.com/azure-sdk/lifetime-management-and-thread-safety-guarantees-of-azure-sdk-net-clients/ services.AddSingleton<ITicketImageService, TicketImageService>(); var storageAccountUri = Configuration["App:StorageAccount:Uri"] ?? throw new InvalidOperationException("Required configuration missing. Could not find App:StorageAccount:Uri setting."); services.AddSingleton(sp => new BlobServiceClient(new Uri(storageAccountUri), _credential)); } public void Configure(WebApplication app, IWebHostEnvironment env) { // Allows refreshing configuration values from Azure App Configuration app.UseAzureAppConfiguration(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } using var serviceScope = app.Services.CreateScope(); serviceScope.ServiceProvider.GetRequiredService<ApplicationInitializer>().Initialize(); // Configure the HTTP request pipeline. if (!env.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } else if (Debugger.IsAttached) { // By default, we do not include any potential PII (personally identifiable information) in our exceptions in order to be in compliance with GDPR. // https://aka.ms/IdentityModel/PII IdentityModelEventSource.ShowPII = true; } app.UseIntermittentErrorRequestMiddleware(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapHealthChecks("/healthz"); app.MapGet("/", () => "Default Web API endpoint"); app.MapControllers(); } } }