Accessing Resources in a C# WPF Application (Same Assembly) - c#

(Before I jump into the nitty-gritty, I want to set the context: I'm trying to load a WPF frame with the contents of an .html file that I'm including in my project as a resource.)
I create a new WPF Application; I add a new folder called 'foofiles' to the project, and I add a couple of files (page1.foo and page2.foo) to that folder.
For each newly added .foo file, I right-click on it, go to "Properties," and set the Build Action to 'Resource,' and the Copy To Output Directory to "Copy always."
I want to be able to access those files both in XAML:
<Frame x:Name="bar" Source="/foofiles/page1.foo"/>
And in procedural code:
private void someFunc()
{
bar.Source = new Uri("/foofiles/page1.foo");
}
But I just can't figure out why this doesn't work -- I get a "Format of the URI could not be determined."
In the code-behind, I tried doing this:
private void someFunc()
{
bar.Source = new Uri("pack://application:,,,/foofiles/page1.foo");
}
which didn't throw an exception, but my main window crashed.
In my thinking, if I add a file of any type to my project, and if I mark it as "Resource" in "Build Action," I should be able to use that file per my examples above. Also, I would like to use that file like this:
private void someOtherFunc()
{
System.IO.StreamReader reader = new System.IO.StreamReader("/foofiles/page1.foo");
string bar = reader.ReadToEnd();
}
Any help would be appreciated... thanks in advance!

Try adding the component-part to your Pack URI like this
pack://application:,,,/AssemblyName;component/ResourceName
where AssemblyName is the name of your assembly. So for your case, the following statement should work:
bar.Source = new Uri("pack://application:,,,/AssemblyName;component/foofiles/page1.foo");
More practically, try the relative pack uri notation:
bar.Source = new Uri("AssemblyName;component/foofiles/page1.foo", UriKind.Relative));
For stream reading resources use
var streamResourceInfo = Application.GetResourceStream(uri);
using (var stream = streamResourceInfo.Stream)
{
// do fancy stuff with stream
}

Related

Can I store an Ini file in a Resources file?

I have a Windows Forms application, .Net Framework 4.6.1, and I want to store some DB connection data in an Ini file.
I then wanted to store it in the Resources file of the project (so I don't have to copy/paste the file in the Debug and Release folder manually, etc.) as a normal file, but when I tried to compile the program and read the Ini data with ini-parser, the following exception showed up: System.ArgumentException: 'Invalid characters in path access'.
I'm using Properties.Resources where I read the Ini file, so I guessed there would be no problem with the path. Could it be a problem with the Ini file itself?
The content of the Ini file is the following:
[Db]
host = (anIP)
port = (aPort)
db = (aDbName)
user = (aDbUser)
password = (aDbUserPwd)
And my method for reading the data:
public static void ParseIniData()
{
var parser = new FileIniDataParser();
IniData data = parser.ReadFile(Properties.Resources.dbc);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
I finally could do it using what #KlausGütter told me in the comments (thanks!).
Instead of using the FileIniDataParser you have to use the StreamIniDataParser, and get the Stream with Assembly.GetManifestResourceStream.
I found this a bit tricky, because using this method you need to set the Build Action in the file you want to read to Embedded Resource.
This file is then added as an embedded resource in compile time and you can retrieve its stream.
So my method ended up the following way:
public static void ParseIniData()
{
var parser = new StreamIniDataParser();
dbcReader = new StreamReader(_Assembly.GetManifestResourceStream("NewsEditor.Resources.dbc.ini"));
IniData data = parser.ReadData(dbcReader);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
where _Assembly is a private static attribute: private static Assembly _Assembly = Assembly.GetExecutingAssembly();. This gets you the assembly that's being executed when running the code (you could also use this code directly in the method, but I used the Assembly on another method in my class, so I decided to set an attribute... DRY I guess).

Load Image from Embedded resource WP8 / C#

I am trying to load an image from an Embedded resource in a windows phone application
Here is my project setup:
Module1 = Phone Application
Module2 = ClassLibrary.dll
Module1 calls Module2 to create all of the data objects for the phone app.
When Module2 is creating the objects, I wanted to load an image from a resource.
The resource is "Default.png" and saved in the "Images" directory ( Build Action = Embedded Resource, Copy to Output Directory = Copy Always)
The code I am using produces an exception
ex = {"The request is not supported. "}
Here is the code:
private void LoadImage()
{
Assembly curAssembly = null;
curAssembly = Assembly.GetExecutingAssembly();
string [] names = curAssembly.GetManifestResourceNames();
// names[0] = "Storage.Images.Default.png"
// so I know I am using the correct name
Stream resStream = curAssembly.GetManifestResourceStream("Storage.Images.Default.png");
BitmapImage bitMapImage = new BitmapImage();
try
{
bitMapImage.SetSource(resStream);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
Can you help a newbie out?
Thanks
Steps to get that working:
First Appraoch
Right-click on your project in VS and choose "Add New Item"
From "General" tab choose "Resources File"
Open that resource_file.resx which you just added to your project and choose "Add Existing Item" and then choose your image.
Then then in your LoadImage method, do this:
private void LoadImage()
{
Drawing.Bitmap bitMapImage = resource_file.name_of_your_image;
}
Note: In this code, I assumed that the name you choose for your resource file is "resource_file"
Second Approach
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(this.GetType().Assembly.GetName().Name + ".resource_file", this.GetType().Assembly);
System.Drawing.Bitmap bmp= (System.Drawing.Bitmap)rm.GetObject("Baba");
or use can use System.Resources.ResourceReader and other approaches
Figured it out...
The name I was using to load the resource was wrong...
Using the name from the array returned in this call
string [] names = curAssembly.GetManifestResourceNames();
And it works flawlessly

Create designer.cs file from ResXRersourcewriter generated resource file

I got a programm that generates .resx resource files. Those resource files are used in other projects, that isnt in the same solution as the project that generates the resource files.
I wonder now, if its possible to generate a designer.cs file from the resource file, so that you can access the resources directly without using the resxresourcereader.
Open the resx file and on its toolbar there's an Access Modifier menu. Set this to Public. This will generate a *.Designer.cs file.
Right click on the Resources.resx and select "Run Custom Tool".
If the file is added to a Visual Studio Project you have to set the Custom Tool property of the .resx file to ResXFileCodeGenerator. Then will VS automatically create the needed designer file.
In one project I made a T4 script that scans the folder within the project for all images and let create a corresponding ressource file at a click.
Here is the needed part out of the T4 script:
var rootPath = Path.GetDirectoryName(this.Host.TemplateFile);
var imagesPath = Path.Combine(rootPath, "Images");
var resourcesPath = Path.Combine(rootPath, "Resources");
var pictures = Directory.GetFiles(imagesPath, "*.png", SearchOption.AllDirectories);
EnvDTE.DTE dte = (EnvDTE.DTE)((IServiceProvider)this.Host)
.GetService(typeof(EnvDTE.DTE));
EnvDTE.Projects projects = dte.Solution.Projects;
EnvDTE.Project iconProject = projects.Cast<EnvDTE.Project>().Where(p => p.Name == "Icons").Single();
EnvDTE.ProjectItem resourcesFolder = iconProject.ProjectItems.Cast<EnvDTE.ProjectItem>().Where(item => item.Name == "Resources").Single();
// Delete all existing resource files to avoid any conflicts.
foreach (var item in resourcesFolder.ProjectItems.Cast<EnvDTE.ProjectItem>())
{
item.Delete();
}
// Create the needed .resx file fore each picture.
foreach (var picture in pictures)
{
var resourceFilename = Path.GetFileNameWithoutExtension(picture) + ".resx";
var resourceFilePath = Path.Combine(resourcesPath, resourceFilename);
using (var writer = new ResXResourceWriter(resourceFilePath))
{
foreach (var picture in picturesByBitmapCollection)
{
writer.AddResource(picture.PictureName, new ResXFileRef(picture, typeof(Bitmap).AssemblyQualifiedName));
}
}
}
// Add the .resx file to the project and set the CustomTool property.
foreach (var resourceFile in Directory.GetFiles(resourcesPath, "*.resx"))
{
var createdItem = resourcesFolder.Collection.AddFromFile(resourceFile);
var allProperties = createdItem.Properties.Cast<EnvDTE.Property>().ToList();
createdItem.Properties.Item("CustomTool").Value = "ResXFileCodeGenerator";
}
I have flattened the above code a little bit, cause in my real solution i use a custom class for each picture instead of the simple filename to also support the same filename in different sub folders (by using a part of the folder structure for the namespace generation). But for a first shot the above should help you.
You can also do this in code:
(Taken from here: msdn)
StreamWriter sw = new StreamWriter(#".\DemoResources.cs");
string[] errors = null;
CSharpCodeProvider provider = new CSharpCodeProvider();
CodeCompileUnit code = StronglyTypedResourceBuilder.Create("Demo.resx", "DemoResources",
"DemoApp", provider,
false, out errors);
if (errors.Length > 0)
foreach (var error in errors)
Console.WriteLine(error);
provider.GenerateCodeFromCompileUnit(code, sw, new CodeGeneratorOptions());
sw.Close();
You need to reference system.design.dll
This also worked for me: double click and open the resx file, add a dummy resource, click save. the .designer.cs file is generated.
If you deleted it or added it to .gitignore because you thought you didn't need it. this is how you regenerate the file.
Go to the Access modifier and change it from (Public/Internal) to "No Code Generation"
Now put it back to Public/Internal.
VS will regenerate the Designer file for you.

How to read a text file in project's root directory?

I want to read the first line of a text file that I added to the root directory of my project. Meaning, my solution explorer is showing the .txt file along side my .cs files in my project.
So, I tried to do:
TextReader tr = new StreamReader(#"myfile.txt");
string myText = tr.ReadLine();
But this doesn't work since it's referring to the Bin Folder and my file isn't in there... How can I make this work? :/
Thanks
From Solution Explorer, right click on myfile.txt and choose "Properties"
From there, set the Build Action to content
and Copy to Output Directory to either Copy always or Copy if newer
You can use the following to get the root directory of a website project:
String FilePath;
FilePath = Server.MapPath("/MyWebSite");
Or you can get the base directory like so:
AppDomain.CurrentDomain.BaseDirectory
Add a Resource File to your project (Right Click Project->Properties->Resources). Where it says "strings", you can switch to be "files". Choose "Add Resource" and select your file.
You can now reference your file through the Properties.Resources collection.
private string _filePath = Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory);
The method above will bring you something like this:
"C:\Users\myuser\Documents\Visual Studio 2015\Projects\myProjectNamespace\bin\Debug"
From here you can navigate backwards using System.IO.Directory.GetParent:
_filePath = Directory.GetParent(_filePath).FullName;
1 time will get you to \bin, 2 times will get you to \myProjectNamespace, so it would be like this:
_filePath = Directory.GetParent(Directory.GetParent(_filePath).FullName).FullName;
Well, now you have something like "C:\Users\myuser\Documents\Visual Studio 2015\Projects\myProjectNamespace", so just attach the final path to your fileName, for example:
_filePath += #"\myfile.txt";
TextReader tr = new StreamReader(_filePath);
Hope it helps.
You can have it embedded (build action set to Resource) as well, this is how to retrieve it from there:
private static UnmanagedMemoryStream GetResourceStream(string resName)
{
var assembly = Assembly.GetExecutingAssembly();
var strResources = assembly.GetName().Name + ".g.resources";
var rStream = assembly.GetManifestResourceStream(strResources);
var resourceReader = new ResourceReader(rStream);
var items = resourceReader.OfType<DictionaryEntry>();
var stream = items.First(x => (x.Key as string) == resName.ToLower()).Value;
return (UnmanagedMemoryStream)stream;
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
string resName = "Test.txt";
var file = GetResourceStream(resName);
using (var reader = new StreamReader(file))
{
var line = reader.ReadLine();
MessageBox.Show(line);
}
}
(Some code taken from this answer by Charles)
You have to use absolute path in this case. But if you set the CopyToOutputDirectory = CopyAlways, it will work as you are doing it.
In this code you access to root directory project:
string _filePath = Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory);
then:
StreamReader r = new StreamReader(_filePath + "/cities2.json"))

A problem with Relative Path Resolution when setting an Image Source

I have built a small WPF application that allows users to upload documents and then select one to display.
The following is the code for the file copy.
public static void MoveFile( string directory, string subdirectory)
{
var open = new OpenFileDialog {Multiselect = false, Filter = "AllFiles|*.*"};
var newLocation = CreateNewDirectory( directory, subdirectory, open.FileName);
if ((bool) open.ShowDialog())
CopyFile(open.FileName, newLocation);
else
"You must select a file to upload".Show();
}
private static void CopyFile( string oldPath, string newPath)
{
if(!File.Exists(newPath))
File.Copy(oldPath, newPath);
else
string.Format("The file {0} already exists in the current directory.", Path.GetFileName(newPath)).Show();
}
The file is copied without incident. However, when the user tries to select a file they just copied to display, A file not found exception. After debugging, I've found that the UriSource for the dynamic image is resolving the relative path 'Files{selected file}' to the directory that was just browsed by the file select in the above code instead of the Application directory as it seems like it should.
This problem only occurs when a newly copied file is selected. If you restart the application and select the new file it works fine.
Here's the code that dynamically sets the Image source:
//Cover = XAML Image
Cover.Source(string.Format(#"Files\{0}\{1}", item.ItemID, item.CoverImage), "carton.ico");
...
public static void Source( this Image image, string filePath, string alternateFilePath)
{
try
{image.Source = GetSource(filePath);}
catch(Exception)
{image.Source = GetSource(alternateFilePath);}
}
private static BitmapImage GetSource(string filePath)
{
var source = new BitmapImage();
source.BeginInit();
source.UriSource = new Uri( filePath, UriKind.Relative);
//Without this option, the image never finishes loading if you change the source dynamically.
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
return source;
}
I'm stumped. Any thought's would be appreciated.
Although I don't have a direct answer, you should use caution for such allowing people to upload files. I was at a seminar where they had good vs bad hackers to simulate real life exploits. One was such that files were allowed to be uploaded. They uploaded malicious asp.net files and called the files directly as they new where the images were ultimately presented to the users, and were able to eventually take over a system. You may want to verify somehow what TYPES of files are being allowed and maybe have stored in a non-exeucting directory of your web server.
It turns out I was missing an option in the constructor of my openfiledialogue. The dialogue was changing the current directory which was causing the relative paths to resolve incorrectly.
If you replace the open file with the following:
var open = new OpenFileDialog{ Multiselect = true, Filter = "AllFiles|*.*", RestoreDirectory = true};
The issue is resolved.

Categories