Skip to content

Commit 647f128

Browse files
refactor: consolidate Program.cs and remove Startup.cs; migrate configuration and service setup
1 parent c2350ab commit 647f128

6 files changed

Lines changed: 200 additions & 312 deletions

File tree

Lines changed: 200 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,208 @@
1-
using Azure.Identity;
1+
using Azure.Identity;
2+
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
3+
using Microsoft.ApplicationInsights.Extensibility;
4+
using Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation;
5+
using Microsoft.AspNetCore.HttpOverrides;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.EntityFrameworkCore;
28
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
9+
using Microsoft.Extensions.Primitives;
10+
using MX.GeoLocation.Api.Client.V1;
11+
using MX.InvisionCommunity.Api.Client;
12+
using XtremeIdiots.Portal.Integrations.Forums;
13+
using XtremeIdiots.Portal.Integrations.Forums.Extensions;
14+
using XtremeIdiots.Portal.Integrations.Servers.Api.Client.V1;
15+
using XtremeIdiots.Portal.Repository.Api.Client.V1;
16+
using XtremeIdiots.Portal.Web;
17+
using XtremeIdiots.Portal.Web.Areas.Identity.Data;
18+
using XtremeIdiots.Portal.Web.Extensions;
19+
using XtremeIdiots.Portal.Web.Services;
320

4-
namespace XtremeIdiots.Portal.Web;
21+
var builder = WebApplication.CreateBuilder(args);
522

6-
public class Program
23+
// Azure App Configuration
24+
var appConfigEndpoint = builder.Configuration["AzureAppConfiguration:Endpoint"];
25+
26+
if (!string.IsNullOrWhiteSpace(appConfigEndpoint))
727
{
8-
public static void Main(string[] args)
28+
var managedIdentityClientId = builder.Configuration["AzureAppConfiguration:ManagedIdentityClientId"];
29+
var environmentLabel = builder.Configuration["AzureAppConfiguration:Environment"] ?? builder.Environment.EnvironmentName;
30+
31+
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
32+
{
33+
ManagedIdentityClientId = managedIdentityClientId,
34+
});
35+
36+
builder.Configuration.AddAzureAppConfiguration(options =>
937
{
10-
CreateHostBuilder(args).Build().Run();
11-
}
38+
options.Connect(new Uri(appConfigEndpoint), credential)
39+
.Select("XtremeIdiots.Portal.Web:*", environmentLabel)
40+
.TrimKeyPrefix("XtremeIdiots.Portal.Web:")
41+
.Select("RepositoryApi:*", environmentLabel)
42+
.Select("ServersIntegrationApi:*", environmentLabel)
43+
.Select("GeoLocationApi:*", environmentLabel)
44+
.Select("XtremeIdiots:*", environmentLabel)
45+
.Select("ProxyCheck:*", environmentLabel)
46+
.Select("GameTracker:*", environmentLabel)
47+
.Select("Google:*", environmentLabel)
48+
.Select("FeatureManagement:*", environmentLabel)
49+
.ConfigureRefresh(refresh =>
50+
refresh.Register("Sentinel", environmentLabel, refreshAll: true)
51+
.SetRefreshInterval(TimeSpan.FromMinutes(5)));
52+
53+
options.ConfigureKeyVault(kv =>
54+
{
55+
kv.SetCredential(credential);
56+
kv.SetSecretRefreshInterval(TimeSpan.FromHours(1));
57+
});
58+
});
59+
}
60+
61+
// Adaptive sampling settings
62+
var samplingSettings = new SamplingPercentageEstimatorSettings
63+
{
64+
InitialSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:InitialSamplingPercentage"], out var initPct) ? initPct : 5,
65+
MinSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MinSamplingPercentage"], out var minPct) ? minPct : 5,
66+
MaxSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MaxSamplingPercentage"], out var maxPct) ? maxPct : 60
67+
};
68+
69+
// Services
70+
builder.Services.AddAzureAppConfiguration();
71+
72+
builder.Services.AddSingleton<ITelemetryInitializer, TelemetryInitializer>();
73+
builder.Services.AddLogging();
74+
75+
builder.Services.Configure<TelemetryConfiguration>(telemetryConfiguration =>
76+
{
77+
var telemetryProcessorChainBuilder = telemetryConfiguration.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
78+
telemetryProcessorChainBuilder.UseAdaptiveSampling(
79+
settings: samplingSettings,
80+
callback: null,
81+
excludedTypes: "Exception");
82+
telemetryProcessorChainBuilder.Build();
83+
});
84+
85+
builder.Services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
86+
{
87+
EnableAdaptiveSampling = false,
88+
});
89+
90+
builder.Services.AddServiceProfiler();
91+
92+
builder.Services.AddInvisionApiClient(options => options
93+
.WithBaseUrl(GetConfigValue(builder.Configuration, "XtremeIdiots:Forums:BaseUrl", "XtremeIdiots:Forums:BaseUrl configuration is required"))
94+
.WithApiKeyAuthentication(GetConfigValue(builder.Configuration, "XtremeIdiots:Forums:ApiKey", "XtremeIdiots:Forums:ApiKey configuration is required"), "key", MX.Api.Client.Configuration.ApiKeyLocation.QueryParameter));
95+
96+
builder.Services.AddAdminActionTopics();
97+
builder.Services.AddScoped<IDemoManager, DemoManager>();
98+
99+
builder.Services.AddRepositoryApiClient(options => options
100+
.WithBaseUrl(GetConfigValue(builder.Configuration, "RepositoryApi:BaseUrl", "RepositoryApi:BaseUrl configuration is required"))
101+
.WithEntraIdAuthentication(GetConfigValue(builder.Configuration, "RepositoryApi:ApplicationAudience", "RepositoryApi:ApplicationAudience configuration is required")));
102+
103+
builder.Services.AddServersApiClient(options => options
104+
.WithBaseUrl(GetConfigValue(builder.Configuration, "ServersIntegrationApi:BaseUrl", "ServersIntegrationApi:BaseUrl configuration is required"))
105+
.WithEntraIdAuthentication(GetConfigValue(builder.Configuration, "ServersIntegrationApi:ApplicationAudience", "ServersIntegrationApi:ApplicationAudience configuration is required")));
106+
107+
builder.Services.AddGeoLocationApiClient(options => options
108+
.WithBaseUrl(GetConfigValue(builder.Configuration, "GeoLocationApi:BaseUrl", "GeoLocationApi:BaseUrl configuration is required"))
109+
.WithApiKeyAuthentication(GetConfigValue(builder.Configuration, "GeoLocationApi:ApiKey", "GeoLocationApi:ApiKey configuration is required"))
110+
.WithEntraIdAuthentication(GetConfigValue(builder.Configuration, "GeoLocationApi:ApplicationAudience", "GeoLocationApi:ApplicationAudience configuration is required")));
111+
112+
builder.Services.AddXtremeIdiotsAuth();
113+
builder.Services.AddAuthorization(options => options.AddXtremeIdiotsPolicies());
114+
115+
builder.Services.AddCors(options =>
116+
{
117+
var corsOrigin = GetConfigValue(builder.Configuration, "XtremeIdiots:Forums:BaseUrl", "XtremeIdiots:Forums:BaseUrl configuration is required");
118+
options.AddPolicy("CorsPolicy",
119+
policy => policy
120+
.WithOrigins(corsOrigin)
121+
.AllowAnyMethod()
122+
.AllowAnyHeader()
123+
.AllowCredentials());
124+
});
12125

13-
public static IHostBuilder CreateHostBuilder(string[] args)
126+
// Add MVC with conditional Razor runtime compilation
127+
var mvcBuilder = builder.Services.AddControllersWithViews();
128+
129+
#if DEBUG
130+
// Only add runtime compilation in Debug builds for development productivity
131+
mvcBuilder.AddRazorRuntimeCompilation();
132+
#endif
133+
134+
builder.Services.Configure<CookieTempDataProviderOptions>(options => options.Cookie.IsEssential = true);
135+
136+
builder.Services.AddHttpClient();
137+
builder.Services.AddMemoryCache();
138+
builder.Services.AddScoped<IProxyCheckService, ProxyCheckService>();
139+
140+
builder.Services.Configure<ForwardedHeadersOptions>(options =>
141+
{
142+
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
143+
options.KnownNetworks.Clear();
144+
options.KnownProxies.Clear();
145+
});
146+
147+
builder.Services.AddHealthChecks();
148+
149+
var app = builder.Build();
150+
151+
// Middleware pipeline
152+
app.UseForwardedHeaders();
153+
app.UseAzureAppConfiguration();
154+
155+
// Update adaptive sampling settings when configuration refreshes
156+
ChangeToken.OnChange(
157+
app.Configuration.GetReloadToken,
158+
() =>
14159
{
15-
return Host.CreateDefaultBuilder(args)
16-
.ConfigureAppConfiguration((context, builder) =>
17-
{
18-
var builtConfig = builder.Build();
19-
var appConfigEndpoint = builtConfig["AzureAppConfiguration:Endpoint"];
20-
21-
if (!string.IsNullOrWhiteSpace(appConfigEndpoint))
22-
{
23-
var managedIdentityClientId = builtConfig["AzureAppConfiguration:ManagedIdentityClientId"];
24-
var environmentLabel = builtConfig["AzureAppConfiguration:Environment"] ?? context.HostingEnvironment.EnvironmentName;
25-
26-
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
27-
{
28-
ManagedIdentityClientId = managedIdentityClientId,
29-
});
30-
31-
builder.AddAzureAppConfiguration(options =>
32-
{
33-
options.Connect(new Uri(appConfigEndpoint), credential)
34-
.Select("XtremeIdiots.Portal.Web:*", environmentLabel)
35-
.TrimKeyPrefix("XtremeIdiots.Portal.Web:")
36-
.Select("RepositoryApi:*", environmentLabel)
37-
.Select("ServersIntegrationApi:*", environmentLabel)
38-
.Select("GeoLocationApi:*", environmentLabel)
39-
.Select("XtremeIdiots:*", environmentLabel)
40-
.Select("ProxyCheck:*", environmentLabel)
41-
.Select("GameTracker:*", environmentLabel)
42-
.Select("Google:*", environmentLabel)
43-
.Select("FeatureManagement:*", environmentLabel)
44-
.ConfigureRefresh(refresh =>
45-
refresh.Register("Sentinel", environmentLabel, refreshAll: true)
46-
.SetRefreshInterval(TimeSpan.FromMinutes(5)));
47-
48-
options.ConfigureKeyVault(kv =>
49-
{
50-
kv.SetCredential(credential);
51-
kv.SetSecretRefreshInterval(TimeSpan.FromHours(1));
52-
});
53-
});
54-
}
55-
})
56-
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
57-
}
160+
if (double.TryParse(app.Configuration["ApplicationInsights:MinSamplingPercentage"], out var min))
161+
samplingSettings.MinSamplingPercentage = min;
162+
if (double.TryParse(app.Configuration["ApplicationInsights:MaxSamplingPercentage"], out var max))
163+
samplingSettings.MaxSamplingPercentage = max;
164+
});
165+
166+
if (app.Environment.IsDevelopment())
167+
{
168+
app.UseDeveloperExceptionPage();
169+
}
170+
else
171+
{
172+
app.UseExceptionHandler("/Errors/Display/500");
173+
app.UseHsts();
174+
}
175+
176+
app.UseHttpsRedirection();
177+
app.UseStaticFiles();
178+
app.UseCookiePolicy();
179+
app.UseRouting();
180+
181+
app.UseCors();
182+
app.UseAuthentication();
183+
app.UseAuthorization();
184+
185+
app.UseStatusCodePagesWithRedirects("/Errors/Display/{0}");
186+
187+
app.MapControllers();
188+
app.MapControllerRoute(
189+
name: "default",
190+
pattern: "{controller=Home}/{action=Index}/{id?}");
191+
app.MapInfoEndpoint();
192+
193+
app.UseHealthChecks(new PathString("/api/health"));
194+
195+
using (var scope = app.Services.CreateScope())
196+
{
197+
var identityDataContext = scope.ServiceProvider.GetRequiredService<IdentityDataContext>();
198+
identityDataContext.Database.Migrate();
199+
}
200+
201+
app.Run();
202+
203+
static string GetConfigValue(IConfiguration configuration, string key, string missingMessage)
204+
{
205+
return configuration[key]
206+
?? configuration[$"XtremeIdiots.Portal.Web:{key}"]
207+
?? throw new InvalidOperationException(missingMessage);
58208
}

0 commit comments

Comments
 (0)