Created
January 19, 2024 13:41
-
-
Save codejockie/81c0075f64f17b237a43cdb744a772eb to your computer and use it in GitHub Desktop.
Using ResourceDescription to embed resource information
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/dotnet/roslyn/issues/7791#issuecomment-394133511 | |
var resourceDescription = new Microsoft.CodeAnalysis.ResourceDescription( | |
resourceFullName, | |
() => ProcessFile(resourceFilePath), | |
isPublic: true); | |
private static MemoryStream ProcessFile(string inFile) | |
{ | |
var readers = new List<ReaderInfo>(); | |
var resources = ReadResources(inFile); | |
using (var outStream = WriteResources(resources)) | |
{ | |
//outstream is closed, so we create a new memory stream based on its buffer. | |
var openStream = new MemoryStream(outStream.GetBuffer()); | |
return openStream; | |
} | |
} | |
private static MemoryStream WriteResources(ReaderInfo resources) | |
{ | |
var memoryStream = new MemoryStream(); | |
using (var resourceWriter = new ResourceWriter(memoryStream)) | |
{ | |
WriteResources(resources, resourceWriter); | |
} | |
return memoryStream; | |
} | |
private static void WriteResources(ReaderInfo readerInfo, ResourceWriter resourceWriter) | |
{ | |
foreach (ResourceEntry entry in readerInfo.resources) | |
{ | |
string key = entry.Name; | |
object value = entry.Value; | |
resourceWriter.AddResource(key, value); | |
} | |
} | |
private static ReaderInfo ReadResources(string fileName) | |
{ | |
ReaderInfo readerInfo = new ReaderInfo(); | |
var path = Path.GetDirectoryName(fileName); | |
var resXReader = new ResXResourceReader(fileName); | |
resXReader.BasePath = path; | |
using (resXReader) | |
{ | |
IDictionaryEnumerator resEnum = resXReader.GetEnumerator(); | |
while (resEnum.MoveNext()) | |
{ | |
string name = (string)resEnum.Key; | |
object value = resEnum.Value; | |
AddResource(readerInfo, name, value, fileName); | |
} | |
} | |
return readerInfo; | |
} | |
private static void AddResource(ReaderInfo readerInfo, string name, object value, string fileName) | |
{ | |
ResourceEntry entry = new ResourceEntry(name, value); | |
if (readerInfo.resourcesHashTable.ContainsKey(name)) | |
{ | |
// Duplicate resource name. We'll ignore and continue. | |
return; | |
} | |
readerInfo.resources.Add(entry); | |
readerInfo.resourcesHashTable.Add(name, value); | |
} | |
internal sealed class ReaderInfo | |
{ | |
// We use a list to preserve the resource ordering (primarily for easier testing), | |
// but also use a hash table to check for duplicate names. | |
public ArrayList resources { get; } | |
public Hashtable resourcesHashTable { get; } | |
public ReaderInfo() | |
{ | |
resources = new ArrayList(); | |
resourcesHashTable = new Hashtable(StringComparer.OrdinalIgnoreCase); | |
} | |
} | |
/// <summary> | |
/// Name value resource pair to go in resources list | |
/// </summary> | |
private class ResourceEntry(string name, object value) | |
{ | |
public string Name { get; } = name; | |
public object Value { get; } = value; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/dotnet/roslyn/issues/7791#issuecomment-394243401 | |
/// <summary> | |
/// Method to analyze the .csproj file and determine if there are embedded resource files and | |
/// to convert them into a collection of ResourceDescription objects. | |
/// | |
/// Embedded resource files are typically .resx files, but there are many other possibilities: | |
/// .txt, .xml, graphics files, etc. | |
/// | |
/// Parts of this code based on this Stack Overflow answer: | |
/// https://stackoverflow.com/a/44142655/253938 | |
/// </summary> | |
/// <returns>collection of ResourceDescription objects, | |
/// or null if no embedded resource files or error encountered</returns> | |
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] | |
private static IEnumerable<ResourceDescription> GetManifestResources(string csprojFileName) | |
{ | |
try | |
{ | |
XDocument csprojAsXml = XDocument.Load(csprojFileName); | |
XNamespace xmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; | |
IEnumerable<XElement> projectNamespaceElements = | |
csprojAsXml.Descendants(xmlNamespace + "RootNamespace"); | |
XElement projectNamespaceElement = projectNamespaceElements.First(); | |
if (projectNamespaceElement == null) | |
{ | |
DisplayErrorOrInfo( | |
"Unable to determine default namespace for project " + csprojFileName); | |
return null; | |
} | |
string projectNamespace = projectNamespaceElement.Value; | |
List<ResourceDescription> resourceDescriptions = new List<ResourceDescription>(); | |
foreach (XElement embeddedResourceElement in | |
csprojAsXml.Descendants(xmlNamespace + "EmbeddedResource")) | |
{ | |
XAttribute includeAttribute = embeddedResourceElement.Attribute("Include"); | |
if (includeAttribute == null) | |
continue; // Shouldn't be possible | |
string resourceFilename = includeAttribute.Value; | |
// ReSharper disable once AssignNullToNotNullAttribute | |
string resourceFullFilename = | |
Path.Combine(Path.GetDirectoryName(csprojFileName), resourceFilename); | |
string resourceName = | |
resourceFilename.EndsWith(".resx", StringComparison.OrdinalIgnoreCase) ? | |
resourceFilename.Remove(resourceFilename.Length - 5) + ".resources" : | |
resourceFilename; | |
resourceDescriptions.Add( | |
new ResourceDescription(projectNamespace + "." + resourceName, | |
() => ProvideResourceData(resourceFullFilename), true)); | |
} | |
return resourceDescriptions.Count == 0 ? null : resourceDescriptions; | |
} | |
catch (Exception e) | |
{ | |
DisplayErrorOrInfo("Exception while processing embedded resource files: " + e.Message); | |
return null; | |
} | |
} | |
/// <summary> | |
/// Method that gets called by ManagedResource.WriteData() in project CodeAnalysis during code | |
/// emitting to get the data for an embedded resource file. Caller guarantees that the | |
/// returned Stream object gets disposed. | |
/// </summary> | |
/// <param name="resourceFullFilename">full path and filename for resource file to embed</param> | |
/// <returns>MemoryStream containing .resource file data for a .resx file, | |
/// or FileStream providing image of a non-.resx file</returns> | |
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] | |
private static Stream ProvideResourceData(string resourceFullFilename) | |
{ | |
// For non-.resx files just create a FileStream object to read the file as binary data | |
if (!resourceFullFilename.EndsWith(".resx", StringComparison.OrdinalIgnoreCase)) | |
return new FileStream(resourceFullFilename, FileMode.Open); | |
// Remainder of this method converts a .resx file into .resource file data and returns it | |
// as a MemoryStream | |
MemoryStream shortLivedBackingStream = new MemoryStream(); | |
using (ResourceWriter resourceWriter = new ResourceWriter(shortLivedBackingStream)) | |
{ | |
resourceWriter.TypeNameConverter = TypeNameConverter; | |
using (ResXResourceReader resourceReader = new ResXResourceReader(resourceFullFilename)) | |
{ | |
IDictionaryEnumerator dictionaryEnumerator = resourceReader.GetEnumerator(); | |
while (dictionaryEnumerator.MoveNext()) | |
{ | |
string resourceKey = dictionaryEnumerator.Key as string; | |
if (resourceKey != null) // Should not be possible | |
resourceWriter.AddResource(resourceKey, dictionaryEnumerator.Value); | |
} | |
} | |
} | |
// This needed because shortLivedBackingStream is now closed | |
return new MemoryStream(shortLivedBackingStream.GetBuffer()); | |
} | |
/// <summary> | |
/// This is needed to fix a "Could not load file or assembly 'System.Drawing, Version=4.0.0.0" | |
/// exception, although I'm not sure why that exception was occurring. | |
/// | |
/// See also here: https://github.com/dotnet/corefx/issues/11083 - it says it doesn't work, | |
/// but it did save the day for me. | |
/// </summary> | |
private static string TypeNameConverter(Type objectType) | |
{ | |
// ReSharper disable once PossibleNullReferenceException | |
return objectType.AssemblyQualifiedName.Replace("4.0.0.0", "2.0.0.0"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment