Getting image names from DLL as a List? - c#

I created a DLL for encapsulating my Images and after that I want to get image names from DLL as a list. Before posting this post I googled about it and I saw an example that is below.
public static List<string> GetImageList()
{
List<string> imageList;
System.Reflection.Assembly BOAUIResources = System.Reflection.Assembly.GetExecutingAssembly();
string[] resources = BOAUIResources.GetManifestResourceNames();
return resources.ToList<string>();
}
This code just accessing image names that build action property is "embedded resource". because of accessing in WPF, my images build action type must define as "resource".
So How can I list image names, that build action property is defined as resource, from DLL ?

Image resources can be added to an assembly in a couple of different ways, that will have some impact on the code to enumerate the image names.
You can add images to a resx file.
You can add the images directly to the solution (as with your code files), and set their build action to 'Embedded Resource'.
The code sample that you supplied in your question will work in the second case. Note however that it will also list any other manifest resources (such as embedded resx files) and not just your images.
If you have added the images to a resx file you can enumerate resources using a ResourceSet obtained from a ResourceManager:
// This requires the following using statements in the file:
// using System.Resources;
// using System.Collections;
ResourceManager rm = new ResourceManager(typeof(Images));
using (ResourceSet rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true))
{
IDictionaryEnumerator resourceEnumerator = rs.GetEnumerator();
while (resourceEnumerator.MoveNext())
{
if (resourceEnumerator.Value is Image)
{
Console.WriteLine(resourceEnumerator.Key);
}
}
}
In the first line, where it says ResourceManager(typeof(Images)), you will need to exchange Images with the name of the resource file i which your images are located (in my sample, it was called "Images.resx").

Try this. (Taken from the book - Programming WPF By Chris Sells, Ian Griffiths)
public static List<string> GetImageList()
{
System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
System.Globalization.CultureInfo culture = Thread.CurrentThread.CurrentCulture;
string resourceName = asm.GetName().Name + ".g";
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(resourceName, asm);
System.Resources.ResourceSet resourceSet = rm.GetResourceSet(culture, true, true);
List<string> resources = new List<string>();
foreach (DictionaryEntry resource in resourceSet)
{
resources.Add((string)resource.Key);
}
rm.ReleaseAllResources();
return resources;
}

Related

C# Class library - resource files not loading

I am working on adding localisation to my class library. Currently I have two resource files: Strings.resx and Strings.es.resx.
Both files are under the 'internal' access modifier, although I have tried setting both to 'public' without any help.
My problem is that the Spanish resource file (Strings.es.resx) is not being loaded; and this problem will repeat with any more resource files I add for other languages. The Strings.resx works fine as it is the default resource file.
This code is used to grab which string resource files have been loaded; currently only the default file is loaded. Spanish does not appear:
private static void LoadLanguages()
{
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (var culture in cultures)
{
try
{
var rs = Properties.Lang.Strings.ResourceManager.GetResourceSet(culture, true, false);
if (rs != null) SupportedLanguages.Add(culture.Name.ToLower(), culture.NativeName);
}
catch (Exception)
{
// ignored
}
}
Log.Info("Loaded languages: " + SupportedLanguages.Count); //OUT: 1
}
I have made a discovery though. In my build output, there is a folder "es", and within that folder is a DLL called Project.resources.dll. If I copy that DLL to the root folder of the build output, the resource gets loaded.
The solution to this problem is to get those resource files loaded from the folders. For some reason this is not happening. Is there a known solution to this? Thanks.
It works out the threads current culture. An example can be seen in the docs over at Microsoft https://learn.microsoft.com/en-us/dotnet/framework/resources/creating-satellite-assemblies-for-desktop-apps (check code at step 13 in the end)
Below the example from the documentation. The localized resource is StringLibrary
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
string[] cultureNames = { "en-GB", "en-US", "fr-FR", "ru-RU" };
Random rnd = new Random();
string cultureName = cultureNames[rnd.Next(0, cultureNames.Length)];
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
Console.WriteLine("The current UI culture is {0}",
Thread.CurrentThread.CurrentUICulture.Name);
StringLibrary strLib = new StringLibrary();
string greeting = strLib.GetGreeting();
Console.WriteLine(greeting);
}
}

Manipulating files block them

I'm writing a WinForms program that uses MEF to load assemblies. Those assemblies are not located in the same folder than the executable.
As I need to perform some file maintenance, I implemented some code in the file Program.cs, before loading the actual WinForm, so the files (even if assemblies) are not loaded (or shouldn't if they are) by the program.
I'm performing two operations:
- Moving a folder from one location to an other one
- Unzipping files from an archive and overwrite dll files from the folder moved (if file from the archive is newer than the one moved)
The problem is that after moving the folder, files in it are locked and cannot be overwritten. I also tried to move files one by one by disposing them when the move is finished.
Can someone explain me why the files are blocked and how I could avoid that
Thanks
private static void InitializePluginsFolder()
{
if (!Directory.Exists(Paths.PluginsPath))
{
Directory.CreateDirectory(Paths.PluginsPath);
}
// Find archive that contains plugins to deploy
var assembly = Assembly.GetExecutingAssembly();
if (assembly.Location == null)
{
throw new NullReferenceException("Executing assembly is null!");
}
var currentDirectory = new FileInfo(assembly.Location).DirectoryName;
if (currentDirectory == null)
{
throw new NullReferenceException("Current folder is null!");
}
// Check if previous installation contains a "Plugins" folder
var currentPluginsPath = Path.Combine(currentDirectory, "Plugins");
if (Directory.Exists(currentPluginsPath))
{
foreach (FileInfo fi in new DirectoryInfo(currentPluginsPath).GetFiles())
{
using (FileStream sourceStream = new FileStream(fi.FullName, FileMode.Open))
{
using (FileStream destStream = new FileStream(Path.Combine(Paths.PluginsPath, fi.Name), FileMode.Create))
{
destStream.Lock(0, sourceStream.Length);
sourceStream.CopyTo(destStream);
}
}
}
Directory.Delete(currentPluginsPath, true);
}
// Then updates plugins with latest version of plugins (zipped)
var pluginsZipFilePath = Path.Combine(currentDirectory, "Plugins.zip");
// Extract content of plugins archive to a temporary folder
var tempPath = string.Format("{0}_Temp", Paths.PluginsPath);
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
ZipFile.ExtractToDirectory(pluginsZipFilePath, tempPath);
// Moves all plugins to appropriate folder if version is greater
// to the version in place
foreach (var fi in new DirectoryInfo(tempPath).GetFiles())
{
if (fi.Extension.ToLower() != ".dll")
{
continue;
}
var targetFile = Path.Combine(Paths.PluginsPath, fi.Name);
if (File.Exists(targetFile))
{
if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
{
// If version to deploy is newer than current version
// Delete current version and copy the new one
// FAILS HERE
File.Copy(fi.FullName, targetFile, true);
}
}
else
{
File.Move(fi.FullName, targetFile);
}
}
// Delete temporary folder
Directory.Delete(tempPath, true);
}
Check the implementation of the GetAssemblyVersion() method used in this part of code:
if (File.Exists(targetFile))
{
if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
{
// If version to deploy is newer than current version
// Delete current version and copy the new one
// FAILS HERE
File.Copy(fi.FullName, targetFile, true);
}
}
fi variable has type FileInfo, GetAssemblyVersion() looks like an extension method. You should check how assembly version is retrieved from the file. If this method loads an assembly it should also unload it to release the file.
The separate AppDomain is helpful if you need to load the assembly, do the job and after that unload it. Here is the GetAssemblyVersion method implementation:
public static Version GetAssemblyVersion(this FileInfo fi)
{
AppDomain checkFileDomain = AppDomain.CreateDomain("DomainToCheckFileVersion");
Assembly assembly = checkFileDomain.Load(new AssemblyName {CodeBase = fi.FullName});
Version fileVersion = assembly.GetName().Version;
AppDomain.Unload(checkFileDomain);
return fileVersion;
}
The following implementation of the GetAssemblyVersion() could retrieve the assembly version without loading assembly into your AppDomain. Thnx #usterdev for the hint. It also allows you to get the version without assembly references resolve:
public static Version GetAssemblyVersion(this FileInfo fi)
{
return AssemblyName.GetAssemblyName(fi.FullName).Version;
}
You have to make sure that you are not loading the Assembly into your domain to get the Version from it, otherwise the file gets locked.
By using the AssemblyName.GetAssemblyName() static method (see MSDN), the assembly file is loaded, version is read and then unloaded but not added to your domain.
Here an extension for FileInfo doing so:
public static Version GetAssemblyVersion(this FileInfo fi)
{
AssemblyName an = AssemblyName.GetAssemblyName(fi.FullName);
return an.Version;
}
The below statement locks the file
destStream.Lock(0, sourceStream.Length);
but after that you havent unlocked the file. Perhaps that is the cause of your problem.
I would start checking if you program has actually already loaded the assembly.
two suggestions:
1 - Call a method like this before calling your InitializePluginsFolder
static void DumpLoadedAssemblies()
{
var ads = AppDomain.CurrentDomain.GetAssemblies();
Console.WriteLine(ads.Length);
foreach (var ad in ads)
{
Console.WriteLine(ad.FullName);
// maybe this can be helpful as well
foreach (var f in ad.GetFiles())
Console.WriteLine(f.Name);
Console.WriteLine("*******");
}
}
2 - In the first line of Main, register for AssemblyLoad Event and dump Loaded Assembly in the event handler
public static void Main()
{
AppDomain.CurrentDomain.AssemblyLoad += OnAssemlyLoad;
...
}
static void OnAssemlyLoad(object sender, AssemblyLoadEventArgs args)
{
Console.WriteLine("Assembly Loaded: " + args.LoadedAssembly.FullName);
}
You definitely load assembly using AssemblyName.GetAssemblyName, unfortunately .NET has no conventional ways of checking assembly metadata without loading assembly. To avoid this you can:
Load assembly in separated AppDomain as Nikita suggested, I can add: load it with ReflectionOnlyLoad
Or get assembly version using Mono.Cecil library as Reflector does
Just for completeness: actually you can load assembly into same AppDomain without locking assembly file in two stage: read file contents into byte[] and using Assembly.Load(byte[] rawAssembly) but this way has serious "Loading Context" issues and what will you do with several loaded assemblies :)

Resources in modularized WPF (with Caliburn.Micro and MEF)

I have searched for an answer for this question all day without coming up with any solutions directly applicable to my case, or anything that works (in the one case I found that was applicable).
I have a Caliburn.Micro framework set up to use MEF, and I load my modularized elements just fine. The one thing missing is getting WPF to recognize the resources I use in one of my modules.
How modules are loaded in my app bootstrapper
[ImportMany]
private IEnumerable<IMyModule> _myModules;
protected override void Configure()
{
// Because Configure() is also called from SelectAssemblies(), we cannot instantiate MEF again because it will create conflicts.
if (_configured)
{
return;
}
AggregateCatalog aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(ConfigurationManager.AppSettings["MyModuleFolderLocation"]));
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
_container = new CompositionContainer(aggregateCatalog);
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
_configured = true;
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
// SelectAssemblies() is called before Configure(), so manually force Configure() to run first so that MEF is instantiated properly
Configure();
if (!_configured)
{
throw new Exception("Unable to configure assemblies");
}
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
// Need to add all module assemblies so that Caliburn will be able to find the View for a ViewModel
foreach(IMyModule myModule in _myModules)
{
Assembly assembly = myModule.GetType().Assembly;
assemblies.Add(assembly);
}
return assemblies.Distinct();
}
This works just fine to get a module to be displayed properly.
But when a module has used an image, this image is never displayed, because this kind of loading apparently doesn't take resources into account.
I create a Resources.resx file in the module project and add an image to that. The image file that is presented in Visual Studio then has a Build Action that says "Resource" and "Do not copy (to output directory)". This should mean that the image is embedded in the resulting DLL file.
The image is placed in a folder called "Resources" in the module project, and the XAML use it like this:
<Image Source="/Resources/myImage.png" />
The image is displayed in the preview in Visual Studio, but is not displayed when the application runs.
What I have tried that didn't work
Referencing the image in another way: <Image Source="pack://application:,,,/Resources/myImage.png" />
Getting the resources in BAML form and reinserting them into the executing assembly, like in this question: Instantiate ResourceDictionary xaml from other Assembly (which causes an OutOfMemoryException on this line var reader = new Baml2006Reader(stream);)
A lot of other answers that reference ResourceDictionary, but I have a Resource.resx file (which only generates an internal class that is not a ResourceDictionary)
The question remains
How can I get WPF/Caliburn.Micro to recognize resources from a DLL loaded by MEF?
Answer
Use this syntax for the Source property for images with Build Action: Resource
<Image Source="/AssemblyName;component/Resources/MyImage.png" />
Where AssemblyName is the name of the assembly (as defined in the project properties), and /Resource/MyImage.png is the path to the image (as defined in the project). component must always be present.
Side note
After a lot of help from #StepUp I initially decided to ask a new question using what was learned from this question and rephrasing everything to be more specific to my problem.
When writing this new question I ended up googling for phrases and commands that might help with the rephrasing, and I stumbled upon this page: http://www.geekchamp.com/tips/wp7-working-with-images-content-vs-resource-build-action
Apparently, the WPF Image control has a ton of ways to define the Source property. I had already tried quite a lot of various Source inputs and thought I had tried them all, but the page linked to above proved me wrong.
As far as I have been able to test, the syntax described above seems to work for images marked with Build Action: Resource. Therefore I no longer need to have a RESX file for the images, and I do not need any special handling when bootstrapping MEF.
At first, you should read the assembly with Style. Then, it is neccessary to read BAML files from external library using Baml2006Reader. Let me show an example:
private GetResourceDictionary()
{
string address = #"WpfCustomControlLibrary1.dll";
Assembly skinAssembly = Assembly.LoadFrom(address);
string[] resourceDictionaries = skinAssembly.GetManifestResourceNames();
Stream bamlStream = null;
string name = "themes/AllStylesDictionary.baml";//themes/AllStylesDictionary.baml
foreach (string resourceName in resourceDictionaries)
{
ManifestResourceInfo info = skinAssembly.GetManifestResourceInfo(resourceName);
if (info.ResourceLocation != ResourceLocation.ContainedInAnotherAssembly)
{
Stream resourceStream = skinAssembly.GetManifestResourceStream(resourceName);
using (ResourceReader reader = new ResourceReader(resourceStream))
{
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
}
}
}
}
}
ResourceDictionary rd = LoadBaml<ResourceDictionary>(bamlStream);
Application.Current.Resources.MergedDictionaries.Add(rd);
Style style = Application.Current.Resources.MergedDictionaries[0]["myStyle"] as Style;
button.Style = style;
}
and:
public static T LoadBaml<T>(Stream stream)
{
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while (reader.Read())
writer.WriteNode(reader);
return (T)writer.Result;
}
Update:
If you want to load an image from another libary, you should use the following code:
yourImage.Source = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.myimage.png"));
Update1:
To load an image from the external dll.
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
BitmapImage bmp = LoadImage(bamlStream);
img.Source = bmp;
}
}
public static BitmapImage LoadImage(Stream stream)
{
BitmapImage bmi;
using (MemoryStream ms1 = new MemoryStream())
{
stream.CopyTo(ms1);
bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = new MemoryStream(ms1.ToArray());
bmi.EndInit();
}
return bmi;
}

Cannot find the embedded schemas in the assembly

I have DefaultSchemaSet.xsd. Now I'm getting FileNotFoundException for the codes below. Give me any suggestion, please? May I know how to solve this?
public static void GetDefaultSchemas(string path, XmlSchemaSet schemas, ValidationEventHandler schemaValidationEventHandler)
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path))
{
if (stream == null)
{
throw new FileNotFoundException("Cannot find the embedded schemas in the assembly!");
}
var schema = XmlSchema.Read(stream, schemaValidationEventHandler);
schemas.Add(schema);
}
}
Check the format of the resource name:
DefaultNamespace[.Subfolder][...MoreSubfolers].FileName[.extension]
You need to set Build Action to Embedded Resource in project's file's properties.
Also, you need to check the namespace you use for your project:
Try to examine the available resources, so you can find if a particular one present:
var executingAssembly = Assembly.GetExecutingAssembly();
var resourceNames = executingAssembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
{
Console.WriteLine("Resource: " + resourceName);
Console.WriteLine("Contents:");
using (var sr = new StreamReader(executingAssembly.GetManifestResourceStream(resourceName)))
{
Console.WriteLine(sr.ReadToEnd());
}
}
Output:
Resource: EmbeddingTests.TextFile1.txt
Contents:
Hello
Resource: EmbeddingTests.NewFolder1.TextFile2.txt
Contents:
Hello 2
In order to make sure you can access it from your code you need to ensure that the file's build action is set to "Embedded Resource"
To help further we really need to see where the file lies in your solution (to give you an exact answer), however in the mean time if you ensure that your parameter "path" follows the pattern:
[DefaultNamespace].[AnySubFolders].[filename.fileextension]
note without the square brackets

How to get the current directory path of application's shortcut

I want to get the current directory path but not of the application location but of it's shortcut location.
I tried these but they return the application's location.
Directory.GetCurrentDirectory();
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
According to the process API reference in MSDN, the process STARTUPINFO struct for a given process contains the information about the shortcut .lnk file in the title member. There is a flag present in the dwFlags struct member that is set when this is the case - so it appears that this is not always set (im guessing if you ran the exe directly)
From MSDN:
STARTF_TITLEISLINKNAME: 0x00000800
The lpTitle member contains the path of the shortcut file (.lnk) that the user invoked
to start this process. This is typically set by the shell when a .lnk file pointing to
the launched application is invoked. Most applications will not need to set this value.
This flag cannot be used with STARTF_TITLEISAPPID.
Reference here.
If adding a COM Object reference is not a problem , Add COM Object Reference - Windows Script Host Object Model
i ran this code in my desktop folder and it did work. for current folder use - Environment.CurrentDirectory
using System;
using System.IO;
using IWshRuntimeLibrary; //COM object -Windows Script Host Object Model
namespace csCon
{
class Program
{
static void Main(string[] args)
{
// Folder is set to Desktop
string dir = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var di = new DirectoryInfo(dir);
FileInfo[] fis = di.GetFiles();
if (fis.Length > 0)
{
foreach (FileInfo fi in fis)
{
if (fi.FullName.EndsWith("lnk"))
{
IWshShell shell = new WshShell();
var lnk = shell.CreateShortcut(fi.FullName) as IWshShortcut;
if (lnk != null)
{
Console.WriteLine("Link name: {0}", lnk.FullName);
Console.WriteLine("link target: {0}", lnk.TargetPath);
Console.WriteLine("link working: {0}", lnk.WorkingDirectory);
Console.WriteLine("description: {0}", lnk.Description);
}
}
}
}
}
}
}
Code Reference from Forum : http://www.neowin.net/forum/topic/658928-c%23-resolve-lnk-files/
Try this:
Environment.CurrentDirectory
From MSDN:
Gets or sets the fully qualified path of the current working directory.
I think you will need to use COM and add a reference to "Microsoft Shell Control And Automation", as described in this blog post:
Here's an example using the code provided there:
namespace Shortcut
{
using System;
using System.Diagnostics;
using System.IO;
using Shell32;
class Program
{
public static string GetShortcutTargetFile(string shortcutFilename)
{
string pathOnly = System.IO.Path.GetDirectoryName(shortcutFilename);
string filenameOnly = System.IO.Path.GetFileName(shortcutFilename);
Shell shell = new Shell();
Folder folder = shell.NameSpace(pathOnly);
FolderItem folderItem = folder.ParseName(filenameOnly);
if (folderItem != null)
{
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
return link.Path;
}
return string.Empty;
}
static void Main(string[] args)
{
const string path = #"C:\link to foobar.lnk";
Console.WriteLine(GetShortcutTargetFile(path));
}
}
}
using System.Reflection;
string currentAssemblyDirectoryName = Path.GetDirectoryName(
Assembly.GetExecutingAssembly()
.Location
);
Also for webapplications you can use:
Web Applications:
Request.PhysicalApplicationPath
http://msdn.microsoft.com/en-us/library/system.web.httprequest.physicalapplicationpath.aspx
to grap the applicationpath :)
Since creating the shortcut is part of the workflow, just set the working directory to "%cd%" for the shortcut then, in the app, use:
Environment.CurrentDirectory
Obviously, you would want to capture this value before any code your app calls can change it.
When creating a shortcut using Windows Explorer, you don't have the option of setting the working directory. So, after creating it, open its property page by right-clicking on it and selecting Properties, then set the Start in field to %cd%. After creating such a shortcut, you can move or copy it to the folders in which you want the app to run.
If you do not want to use a COM object as suggested in the other answers
You can open the file as a regular text file, split on the \x00 0 char, and inspect the resulting string array. One of them will be obviously the link target: something like "C:\path\to\file" or in case of UNC "\\computers\path\to\file".
string lnkFilePath "...";
var file = System.IO.File.ReadAllText(lnkFilePath);
var contents = Regex.Split(file, "[\x00]+");
var paths = contents.Where(line => Regex.IsMatch(line, #"^([A-Z]:\\|^\\\\)\S+.*?"));
Use GetStartupInfoW, it will tell you the .lnk file that was launched to start the program.

Categories

Resources