More Corelib cleanup (dotnet/coreclr#26872)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Resources / ResourceManager.Uap.cs
blob8fe97a968f7698c12e443a3ab57588f8912c75cd
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.IO;
6 using System.Globalization;
7 using System.Reflection;
8 using System.Diagnostics;
9 using Internal.Resources;
11 namespace System.Resources
13 public partial class ResourceManager
15 private WindowsRuntimeResourceManagerBase? _WinRTResourceManager;
16 private PRIExceptionInfo? _PRIExceptionInfo;
17 private bool _PRIInitialized;
18 private bool _useUapResourceManagement;
20 private string? GetStringFromPRI(string stringName, CultureInfo? culture, string? neutralResourcesCulture)
22 Debug.Assert(_useUapResourceManagement);
23 Debug.Assert(_WinRTResourceManager != null);
24 Debug.Assert(_PRIInitialized);
26 // If the caller explicitly passed in a culture that was obtained by calling CultureInfo.CurrentUICulture,
27 // null it out, so that we re-compute it. If we use modern resource lookup, we may end up getting a "better"
28 // match, since CultureInfo objects can't represent all the different languages the Uap resource model supports.
29 if (object.ReferenceEquals(culture, CultureInfo.CurrentUICulture))
31 culture = null;
34 string? startingCulture = culture?.Name;
36 if (!_PRIInitialized)
38 // Always throw if we did not fully succeed in initializing the WinRT Resource Manager.
40 if (_PRIExceptionInfo != null && _PRIExceptionInfo.PackageSimpleName != null && _PRIExceptionInfo.ResWFile != null)
41 throw new MissingManifestResourceException(SR.Format(SR.MissingManifestResource_ResWFileNotLoaded, _PRIExceptionInfo.ResWFile, _PRIExceptionInfo.PackageSimpleName));
43 throw new MissingManifestResourceException(SR.MissingManifestResource_NoPRIresources);
46 if (stringName.Length == 0)
47 return null;
49 // Do not handle exceptions. See the comment in SetUapConfiguration about throwing
50 // exception types that the ResourceManager class is not documented to throw.
51 return _WinRTResourceManager.GetString(
52 stringName,
53 string.IsNullOrEmpty(startingCulture) ? null : startingCulture,
54 string.IsNullOrEmpty(neutralResourcesCulture) ? null : neutralResourcesCulture);
57 // Since we can't directly reference System.Runtime.WindowsRuntime from System.Private.CoreLib, we have to get the type via reflection.
58 // It would be better if we could just implement WindowsRuntimeResourceManager in System.Private.CoreLib, but we can't, because
59 // we can do very little with WinRT in System.Private.CoreLib.
60 internal static WindowsRuntimeResourceManagerBase GetWinRTResourceManager()
62 #if FEATURE_APPX
63 Type WinRTResourceManagerType = Type.GetType("System.Resources.WindowsRuntimeResourceManager, System.Runtime.WindowsRuntime", throwOnError: true)!;
64 #else // ENABLE_WINRT
65 Assembly hiddenScopeAssembly = Assembly.Load(Internal.Runtime.Augments.RuntimeAugments.HiddenScopeAssemblyName);
66 Type WinRTResourceManagerType = hiddenScopeAssembly.GetType("System.Resources.WindowsRuntimeResourceManager", true);
67 #endif
68 return (WindowsRuntimeResourceManagerBase)Activator.CreateInstance(WinRTResourceManagerType, nonPublic: true)!;
71 // CoreCLR: When running under AppX, the following rules apply for resource lookup:
73 // 1) For Framework assemblies, we always use satellite assembly based lookup.
74 // 2) For non-FX assemblies:
76 // a) If the assembly lives under PLATFORM_RESOURCE_ROOTS (as specified by the host during AppDomain creation),
77 // then we will use satellite assembly based lookup in assemblies like *.resources.dll.
79 // b) For any other non-FX assembly, we will use the modern resource manager with the premise that app package
80 // contains the PRI resources.
82 // .NET Native: If it is framework assembly we'll return true. The reason is in .NetNative we don't merge the
83 // resources to the app PRI file.
84 // The framework assemblies are tagged with attribute [assembly: AssemblyMetadata(".NETFrameworkAssembly", "")]
85 private static bool ShouldUseUapResourceManagement(Assembly resourcesAssembly)
87 if (resourcesAssembly == typeof(object).Assembly) // We are not loading resources for System.Private.CoreLib
88 return false;
90 #if FEATURE_APPX
91 // Check to see if the assembly is under PLATFORM_RESOURCE_ROOTS. If it is, then we should use satellite assembly lookup for it.
92 string? platformResourceRoots = (string?)AppContext.GetData("PLATFORM_RESOURCE_ROOTS");
93 if (!string.IsNullOrEmpty(platformResourceRoots))
95 string resourceAssemblyPath = resourcesAssembly.Location;
97 // Loop through the PLATFORM_RESOURCE_ROOTS and see if the assembly is contained in it.
98 foreach (string pathPlatformResourceRoot in platformResourceRoots.Split(Path.PathSeparator))
100 if (resourceAssemblyPath.StartsWith(pathPlatformResourceRoot, StringComparison.CurrentCultureIgnoreCase))
102 // Found the resource assembly to be present in one of the PLATFORM_RESOURCE_ROOT, so stop the enumeration loop.
103 return false;
107 #else // ENABLE_WINRT
108 foreach (var attrib in resourcesAssembly.GetCustomAttributes())
110 AssemblyMetadataAttribute? meta = attrib as AssemblyMetadataAttribute;
111 if (meta != null && meta.Key.Equals(".NETFrameworkAssembly"))
113 return false;
116 #endif
118 return true;
121 // Only call SetUapConfiguration from ResourceManager constructors, and nowhere else.
122 // Throws MissingManifestResourceException and WinRT HResults
123 private void SetUapConfiguration()
125 Debug.Assert(!_useUapResourceManagement); // Only this function writes to this member
126 Debug.Assert(_WinRTResourceManager == null); // Only this function writes to this member
127 Debug.Assert(!_PRIInitialized); // Only this function writes to this member
128 Debug.Assert(_PRIExceptionInfo == null); // Only this function writes to this member
130 #if FEATURE_APPX
131 if (!ApplicationModel.IsUap)
132 return;
133 #else // ENABLE_WINRT
134 Internal.Runtime.Augments.WinRTInteropCallbacks callbacks = Internal.Runtime.Augments.WinRTInterop.UnsafeCallbacks;
135 if (!(callbacks != null && callbacks.IsAppxModel()))
136 return;
137 #endif
139 Debug.Assert(MainAssembly != null);
140 if (!ShouldUseUapResourceManagement(MainAssembly))
141 return;
143 _useUapResourceManagement = true;
145 // If we have the type information from the ResourceManager(Type) constructor, we use it. Otherwise, we use BaseNameField.
146 string? reswFilename = _locationInfo == null ? BaseNameField : _locationInfo.FullName;
148 // The only way this can happen is if a class inherited from ResourceManager and
149 // did not set the BaseNameField before calling the protected ResourceManager() constructor.
150 // For other constructors, we would already have thrown an ArgumentNullException by now.
151 // Throwing an ArgumentNullException now is not the right thing to do because technically
152 // ResourceManager() takes no arguments, and because it is not documented as throwing
153 // any exceptions. Instead, let's go through the rest of the initialization with this set to
154 // an empty string. We may in fact fail earlier for another reason, but otherwise we will
155 // throw a MissingManifestResourceException when GetString is called indicating that a
156 // resW filename called "" could not be found.
157 reswFilename ??= string.Empty;
159 // At this point it is important NOT to set _useUapResourceManagement to false
160 // if the PRI file does not exist because we are now certain we need to load PRI
161 // resources. We want to fail by throwing a MissingManifestResourceException
162 // if WindowsRuntimeResourceManager.Initialize fails to locate the PRI file. We do not
163 // want to fall back to using satellite assemblies anymore. Note that we would not throw
164 // the MissingManifestResourceException from this function, but from GetString. See the
165 // comment below on the reason for this.
167 _WinRTResourceManager = GetWinRTResourceManager();
171 _PRIInitialized = _WinRTResourceManager.Initialize(MainAssembly.Location, reswFilename, out _PRIExceptionInfo);
172 // Note that _PRIExceptionInfo might be null - this is OK.
173 // In that case we will just throw the generic
174 // MissingManifestResource_NoPRIresources exception.
175 // See the implementation of GetString for more details.
177 // We would like to be able to throw a MissingManifestResourceException here if PRI resources
178 // could not be loaded for a recognized reason. However, the ResourceManager constructors
179 // that call SetUapConfiguration are not documented as throwing MissingManifestResourceException,
180 // and since they are part of the portable profile, we cannot start throwing a new exception type
181 // as that would break existing portable libraries. Hence we must save the exception information
182 // now and throw the exception on the first call to GetString.
183 catch (FileNotFoundException)
185 // We will throw MissingManifestResource_NoPRIresources from GetString
186 // when we see that _PRIInitialized is false.
188 catch (Exception e)
190 // ERROR_MRM_MAP_NOT_FOUND can be thrown by the call to ResourceManager.get_AllResourceMaps
191 // in WindowsRuntimeResourceManager.Initialize.
192 // In this case _PRIExceptionInfo is now null and we will just throw the generic
193 // MissingManifestResource_NoPRIresources exception.
194 // See the implementation of GetString for more details.
195 if (e.HResult != HResults.ERROR_MRM_MAP_NOT_FOUND)
196 throw; // Unexpected exception code. Bubble it up to the caller.
199 if (!_PRIInitialized)
201 _useUapResourceManagement = false;
204 // Allow all other exception types to bubble up to the caller.
206 // Yes, this causes us to potentially throw exception types that are not documented.
208 // Ultimately the tradeoff is the following:
209 // -We could ignore unknown exceptions or rethrow them as inner exceptions
210 // of exceptions that the ResourceManager class is already documented as throwing.
211 // This would allow existing portable libraries to gracefully recover if they don't care
212 // too much about the ResourceManager object they are using. However it could
213 // mask potentially fatal errors that we are not aware of, such as a disk drive failing.
215 // The alternative, which we chose, is to throw unknown exceptions. This may tear
216 // down the process if the portable library and app don't expect this exception type.
217 // On the other hand, this won't mask potentially fatal errors we don't know about.