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 ;
28using 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