11using System . Diagnostics ;
22using System . Security . Cryptography ;
33using System . Security . Cryptography . X509Certificates ;
4+ using System . Text ;
45using Intersect . Core ;
6+ using Intersect . Framework . Reflection ;
57using Intersect . Server . Web . Configuration ;
68using Microsoft . AspNetCore . Authentication . JwtBearer ;
79using Microsoft . IdentityModel . Tokens ;
@@ -15,58 +17,162 @@ internal partial class ApiService
1517 private const string SelfSignedCertificateName = "self-signed.crt" ;
1618 private const string SelfSignedKeyName = "self-signed.key" ;
1719
18- private static void UnpackAppSettings ( )
20+ private const string FileNameAppsettingsJson = "appsettings.json" ;
21+ private static readonly string FileNameAppsettingsEnvironmentJson ;
22+
23+ private static readonly JObject ? AppsettingsToken ;
24+ private static readonly JObject ? AppsettingsEnvironmentToken ;
25+
26+ static ApiService ( )
1927 {
20- ApplicationContext . Context . Value ? . Logger . LogInformation ( "Unpacking appsettings..." ) ;
21- var hostBuilder = Host . CreateApplicationBuilder ( ) ;
28+ var dummyHostBuilder = Host . CreateApplicationBuilder ( ) ;
29+ FileNameAppsettingsEnvironmentJson = $ "appsettings. { dummyHostBuilder . Environment . EnvironmentName } .json" ;
2230
23- var names = new [ ] { "appsettings.json" , $ "appsettings.{ hostBuilder . Environment . EnvironmentName } .json" } ;
24- var manifestResourceNamePairs = Assembly . GetManifestResourceNames ( )
25- . Where ( mrn => names . Any ( mrn . EndsWith ) )
26- . Select ( mrn => ( mrn , names . First ( mrn . EndsWith ) ) )
27- . ToArray ( ) ;
31+ AppsettingsToken = ParseEmbeddedAppsettings ( FileNameAppsettingsJson ) ;
32+ AppsettingsEnvironmentToken = ParseEmbeddedAppsettings ( FileNameAppsettingsEnvironmentJson ) ;
33+ }
2834
29- foreach ( var ( mrn , name ) in manifestResourceNamePairs )
35+ private static JObject ? ParseEmbeddedAppsettings ( string name )
36+ {
37+ try
3038 {
31- if ( string . IsNullOrWhiteSpace ( mrn ) || string . IsNullOrWhiteSpace ( name ) )
39+ if ( ! Assembly . TryFindResource ( name , out var resourceName ) )
3240 {
33- ApplicationContext . Context . Value ? . Logger . LogWarning ( $ "Manifest resource name or file name is null/empty: ( { mrn } , { name } )" ) ;
34- continue ;
41+ ApplicationContext . CurrentContext . Logger . LogWarning ( "Failed to find '{AppsettingsName}'" , name ) ;
42+ return null ;
3543 }
3644
37- if ( File . Exists ( name ) )
45+ using var resourceStream = Assembly . GetManifestResourceStream ( resourceName ) ;
46+ if ( resourceStream == default )
3847 {
39- ApplicationContext . Context . Value ? . Logger . LogDebug ( $ "'{ name } ' already exists, not unpacking '{ mrn } '") ;
40- continue ;
48+ ApplicationContext . Context . Value ? . Logger . LogError (
49+ "Failed to open resource stream for '{ResourceName}'" ,
50+ resourceName
51+ ) ;
52+ return null ;
4153 }
4254
43- using var mrs = Assembly . GetManifestResourceStream ( mrn ) ;
44- if ( mrs == default )
55+ using var reader = new StreamReader ( resourceStream , Encoding . UTF8 ) ;
56+ var rawJson = reader . ReadToEnd ( ) ;
57+ if ( ! string . IsNullOrWhiteSpace ( rawJson ) )
4558 {
46- ApplicationContext . Context . Value ? . Logger . LogWarning ( $ "Unable to open stream for embedded content: '{ mrn } '") ;
47- continue ;
59+ return JObject . Parse ( rawJson ) ;
60+ }
61+
62+ ApplicationContext . Context . Value ? . Logger . LogError (
63+ "Invalid JSON, embedded resouce was empty or whitespace: '{ResourceName}'" ,
64+ resourceName
65+ ) ;
66+ return null ;
67+ }
68+ catch ( Exception exception )
69+ {
70+ ApplicationContext . CurrentContext . Logger . LogError (
71+ exception ,
72+ "Exception thrown while loading and parsing '{Name}'" ,
73+ name
74+ ) ;
75+ return null ;
76+ }
77+ }
78+
79+ private static void Dump ( string fileName , JObject ? token )
80+ {
81+ try
82+ {
83+ if ( token == null )
84+ {
85+ ApplicationContext . Context . Value ? . Logger . LogDebug (
86+ "Skipping dumping '{FileName}' because the token is null" ,
87+ fileName
88+ ) ;
89+ return ;
4890 }
4991
50- ApplicationContext . Context . Value ? . Logger . LogInformation ( $ "Unpacking '{ name } ' in { Environment . CurrentDirectory } ") ;
92+ if ( File . Exists ( FileNameAppsettingsJson ) )
93+ {
94+ ApplicationContext . Context . Value ? . Logger . LogDebug (
95+ "Skipping dumping '{FileName}' because the file already exists" ,
96+ fileName
97+ ) ;
98+ return ;
99+ }
51100
52- using var fs = File . OpenWrite ( name ) ;
53- mrs . CopyTo ( fs ) ;
101+ var rawJson = token . ToString ( Formatting . Indented ) ;
102+ File . WriteAllText ( fileName , rawJson , Encoding . UTF8 ) ;
103+ }
104+ catch ( Exception exception )
105+ {
106+ ApplicationContext . CurrentContext . Logger . LogError (
107+ exception ,
108+ "Exception thrown while dumping '{FileName}'" ,
109+ fileName
110+ ) ;
111+ }
112+ }
113+
114+ private static void UnpackAppSettings ( )
115+ {
116+ ApplicationContext . Context . Value ? . Logger . LogInformation ( "Unpacking appsettings..." ) ;
117+ Dump ( FileNameAppsettingsJson , AppsettingsToken ) ;
118+ Dump ( FileNameAppsettingsEnvironmentJson , AppsettingsEnvironmentToken ) ;
119+ }
120+
121+ private static JObject ? LoadOrFallbackTo ( FileInfo fileInfo , JObject ? fallback )
122+ {
123+ if ( ! fileInfo . Exists )
124+ {
125+ return fallback ;
126+ }
127+
128+ try
129+ {
130+ using var reader = fileInfo . OpenText ( ) ;
131+ var rawJson = reader . ReadToEnd ( ) ;
132+ JObject parsedObject = JObject . Parse ( rawJson ) ;
133+ return parsedObject ;
134+ }
135+ catch ( Exception exception )
136+ {
137+ ApplicationContext . CurrentContext . Logger . LogError (
138+ exception ,
139+ "Exception thrown while loading and parsing '{Name}' and will return the fallback instead" ,
140+ Path . GetRelativePath ( Environment . CurrentDirectory , fileInfo . FullName )
141+ ) ;
142+ return fallback ;
54143 }
55144 }
56145
57146 private static void ValidateConfiguration ( )
58147 {
59148 var builder = Host . CreateApplicationBuilder ( ) ;
60149
61- var environmentAppSettingsFileName = $ "appsettings.{ builder . Environment . EnvironmentName } .json";
62- var rawConfiguration = File . ReadAllText ( environmentAppSettingsFileName ) ;
63- if ( string . IsNullOrWhiteSpace ( rawConfiguration ) || rawConfiguration . Trim ( ) . Length < 2 )
150+ FileInfo genericConfigurationFileInfo = new ( FileNameAppsettingsJson ) ;
151+ JObject ? genericConfigurationToken = LoadOrFallbackTo ( genericConfigurationFileInfo , AppsettingsToken ) ;
152+
153+ FileInfo environmentConfigurationFileInfo = new ( FileNameAppsettingsEnvironmentJson ) ;
154+ JObject ? environmentConfigurationToken = LoadOrFallbackTo ( environmentConfigurationFileInfo , AppsettingsToken ) ;
155+
156+ JsonMergeSettings jsonMergeSettings = new ( )
157+ {
158+ MergeArrayHandling = MergeArrayHandling . Replace ,
159+ MergeNullValueHandling = MergeNullValueHandling . Merge ,
160+ PropertyNameComparison = StringComparison . Ordinal ,
161+ } ;
162+
163+ JObject mergedConfiguration = new ( ) ;
164+
165+ if ( genericConfigurationToken is not null )
166+ {
167+ mergedConfiguration . Merge ( genericConfigurationToken , jsonMergeSettings ) ;
168+ }
169+
170+ if ( environmentConfigurationToken is not null )
64171 {
65- rawConfiguration = "{}" ;
172+ mergedConfiguration . Merge ( environmentConfigurationToken , jsonMergeSettings ) ;
66173 }
67174
68- var configurationJObject = JsonConvert . DeserializeObject < JObject > ( rawConfiguration ) ;
69- if ( ! configurationJObject . TryGetValue ( "Api" , out var apiSectionJToken ) )
175+ if ( ! mergedConfiguration . TryGetValue ( "Api" , out var apiSectionJToken ) )
70176 {
71177 apiSectionJToken = JObject . FromObject ( new ApiConfiguration ( ) ) ;
72178 }
@@ -103,14 +209,11 @@ private static void ValidateConfiguration()
103209
104210 if ( apiConfiguration . StaticFilePaths == default )
105211 {
106- apiConfiguration . StaticFilePaths = new List < StaticFilePathOptions >
107- {
108- new ( "wwwroot" )
109- } ;
212+ apiConfiguration . StaticFilePaths = [ new StaticFilePathOptions ( "wwwroot" ) ] ;
110213 }
111214
112215 var updatedApiConfigurationJObject = JObject . FromObject ( apiConfiguration ) ;
113- configurationJObject [ "Api" ] = updatedApiConfigurationJObject ;
216+ mergedConfiguration [ "Api" ] = updatedApiConfigurationJObject ;
114217
115218 updatedApiConfigurationJObject [ nameof ( JwtBearerOptions ) ] = apiSectionJToken [ nameof ( JwtBearerOptions ) ] ;
116219 var jwtBearerOptionsToken = updatedApiConfigurationJObject [ nameof ( JwtBearerOptions ) ] ;
@@ -155,10 +258,12 @@ out var validIssuerToken
155258 tokenValidationParameters [ nameof ( TokenValidationParameters . ValidIssuer ) ] = validIssuerToken ;
156259 }
157260
158- var updatedAppSettingsJson = configurationJObject . ToString ( ) ;
159- File . WriteAllText ( environmentAppSettingsFileName , updatedAppSettingsJson ) ;
261+ var updatedAppSettingsJson = mergedConfiguration . ToString ( ) ;
262+ File . WriteAllText ( FileNameAppsettingsEnvironmentJson , updatedAppSettingsJson ) ;
263+
264+ // Below this point should be only self-signed certificate generation
160265
161- if ( ! configurationJObject . TryGetValue ( "Kestrel" , out var kestrelToken ) || kestrelToken is not JObject kestrel )
266+ if ( ! mergedConfiguration . TryGetValue ( "Kestrel" , out var kestrelToken ) || kestrelToken is not JObject kestrel )
162267 {
163268 return ;
164269 }
0 commit comments