embedded resources paths change - c#

At work we use the EmbeddedFileProvider to load SQL files and access the files via relative paths to the resource like so: path/to/file.sql.
Yesterday I worked on a private project and thought I could use this to read some files I need in my program but it did not work and when I checked the output of Assembly.GetManifestResourceNames() I noticed the names of the resources did not include any path so I tried referencing them by just their filenames and it worked.
Today I checked out the same project on my Laptop and tried running it only to get an Exception in my resource loader, that the files could not be found. After checking GetManifestResourceNames() again I saw that the resource names changed the format to path.to.resource.txt. How can this be? I used the same dotnet sdk everytime and the only difference was the machine the code ran on. I even had the exact same OS installed.
public static class EmbeddedResourceLoader
{
public static byte[] Load(string name)
{
var provider = new EmbeddedFileProvider(Assembly.GetCallingAssembly());
using var resourceStream = provider.GetFileInfo(name).CreateReadStream();
using var reader = new BinaryReader(resourceStream);
return reader.ReadBytes((int)resourceStream.Length);
}
}
This is my implementation of the resource loader and the files are located relative to the project directory inside an assets folder.

Related

Excel files are not being read when executing Selenium C# Scripts on Azure Releases

I have some Selenium C# tests hosted on Azure which they need to look for the pre-built excel file in project tree, stored inside bin folder, to execute some file upload validation scenarios.
When they are executed locally these scenarios pass without any problem, but when it comes to be executed on the Azure they receive the following error.
invalid argument: File not found : D:\a\r1\a_Selenium_Tests\TestApplication\bin\Debug\netcoreapp3.1\Files\SC003_CT014_ActiveEmployees.xlsx
The files do exists in the following path: ...\bin\Debug\netcoreapp3.1\Files...
And the code I use to them is:
string root = Directory.GetCurrentDirectory() + "\\Files\\" + file;
Do you know if there's a missing file configuration or building the filePath in another way?
Thanks for your help :D
Directory.GetCurrentDirection() returns the current working directory, not the folder in which the DLL file resides. The current working directory is a different thing. In your case, the current working directory is probably something like D:\a\r1\. Instead, you need to get the folder in which the test assembly resides:
var binDirectory = Path.GetDirectoryName(GetType().Assembly.Location);
// ^^^^^^^^^
var excelFilePath = Path.Combine(binDirectory, "Files", "SC003_CT014_ActiveEmployees.xlsx");
Note: Replace GetType() with typeof(ClassName) if you are executing your code from a static method, or you would like to specify a path to a different assembly than the one that is currently executing.

How to determine if C# executable is being run from zip file?

I'm trying to ensure that users of my C# .NET Core program always unzip it before running, however, I can't seem to find a good solution for this.
I tried using Assembly.GetExecutingAssembly().Location and checking if it contained Temp but it did not work as intended. My understanding is that when running an executable within a zipped file, Windows will automatically unzip (?) to a temp directory and then proceed.
Any ideas on how to go about doing this?
Thanks!
One solution might be that you put another file beside the exe file inside the zip file and make the exe find for this file in the same location of the exe.
When an exe is executed from inside a zip, the unzip program will unzip the exe but not the other file and checking for this file you can test whether just the exe was unzziped or if the whole files where unzipped and the guess it was installed.
(Anyway, I agree with Ian Kemp, as it seems to be an XY problem)..
I can think of two checks that will work in many cases. Obviously, these checks are not bullet-proof.
Most .zip viewers will extract the clicked .exe in a temporary folder.
Windows Explorer zip folder view runs the .exe from a temporary folder and sets the ReadOnly attribute.
Sample app
using System;
using System.IO;
namespace SimpleApp
{
class Program
{
private static bool IsLikelyRunFromZipFolder()
{
var path = System.Reflection.Assembly.GetEntryAssembly().Location;
var fileInfo = new FileInfo(path);
return fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly);
}
private static bool IsRunFromTempFolder()
{
var path = System.Reflection.Assembly.GetEntryAssembly().Location;
var temp = Path.GetTempPath();
return path.IndexOf(temp, StringComparison.OrdinalIgnoreCase) == 0;
}
static void Main(string[] args)
{
var isTemp = IsRunFromTempFolder();
var isZip = IsLikelyRunFromZipFolder();
Console.WriteLine($"Run from TEMP folder? {isTemp}");
Console.WriteLine($"LIKELY run from Windows Explorer ZIP folder? {isZip}");
Console.ReadKey();
}
}
}
I disagree with the sceptics in this thread. Running an exe from a "zip folder" in Windows Explorer is indeed a common source of error. By showing a warning, inexperienced users could get some immediate advice.

How to create a folder and file inside of the folder where the app is installed? c#

I want to be able to set default folder and file creating inside of the folder where the app is installed? Because this app will be used on multiple machines so I cannot specify path like C://Users/PcName/etc.. Is there any very simple way of doing it?
What you are trying to do is not advisable; if your application is installed using recommended default methods (following Microsoft guidelines) the app will be in a directory under C:\Program Files (or where the program files folder may be redirected) and the user that runs the app will not have write access to that directory, so the directory creation will fail.
That said, you cannot use the Environment.CurrentDirectory, because it may or may not be the directory where your application's executable files reside, neither CurrentDomain.BaseDirectory, because that is not significative too (documentation says it's the directory where the loader will search for assemblies, but that may or may not be the directory of your application's executable files).
Copying from this other answer, the correct way to find the directory of your assembly is
public static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
Once you have the path, you can try to create the directory with System.IO.Directory.CreateDirectory() and a file with System.IO.File.WriteAllText() or its siblings, or any other standard method of creating files.
You may also want to use the newer Assembly.GetExecutingAssembly().Location property, and use Path.GetDirectoryName() on that.
This applies to both web and windows apps:
Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
You can get the path for the executable using this code (most of the time, actually it returns the current working directory)
System.Environment.CurrentDirectory
If you only specify a path that doesn't start with a drive letter then the path will be relative to where the application is running. e.g. The following program will create a folder and file in the application's local folder.
class Program
{
static void Main(string[] args)
{
var installedLocation = Directory.GetParent(Assembly.GetExecutingAssembly().Location);
var di = installedLocation.CreateSubdirectory("MyFolder");
File.WriteAllText(Path.Combine(di.FullName, "File.txt"), "This will be written to the file");
var installedPath = AppDomain.CurrentDomain.BaseDirectory;
var di2 = Directory.CreateDirectory(Path.Combine(installedPath, "MyFolder2"));
File.WriteAllText(Path.Combine(di.FullName, "File2.txt"), "This will be written to the file");
}
}

DbMigration.SqlFile difference in base directory

We are using the new DbMigration.SqlFile method in EF Migrations 6.1.2 to run a migration script in our migration. According to the documentation, the file has to be relative to the current AppDomain BaseDirectory. We have included these files in the project, and set them to copy to output directory.
Locally this all runs fine. They get output to the bin directory, and run fine.
When deploying the software to a server running IIS however, the migration fails, because it suddenly expects the files to be relative to the root. When I copy them there, the migration works.
How can I use DbMigration.SqlFile so it runs correctly both locally and on the server?
The SqlFile method uses the CurrentDomain.BaseDirectory if a relative path is given. A workaround is to map the path yourself and give an absolute path to the method. A solution would look like this:
var sqlFile = "MigrationScripts/script1.sql";
var filePath = Path.Combine(GetBasePath(), sqlFile);
SqlFile(filePath);
public static string GetBasePath()
{
if(System.Web.HttpContext.Current == null) return AppDomain.CurrentDomain.BaseDirectory;
else return Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");
}
BasePath solution taken from: Why AppDomain.CurrentDomain.BaseDirectory not contains "bin" in asp.net app?
We're using it like this from within the migration: SqlFile(#"..\..\Sql\views\SomeView.sql");

Reference files local to the service executable by default

I have created a windows service in c# .Net 4.0.
In VS it works great, I have built the project and installed the exe as a service which does run.
I have found that when operating as a service the application does not reference application configuration files I have placed in the same directory as the service exe.
If I strongly type the full file path in code there is no problem, however this is not an ideal solution as users could install things where ever they like.
How can I make the application reference (look for) files local to the EXE and not I assume where the .Net service wrapper is located.
Nothing seems to be helpful on google.
Example
Does Work:
var config =
ClassLib.XmlInterface.DeserializeConfiguration(
"C:\\Users\\Damo\\Documents\\Visual Studio 2010\\Projects\\FileDownloadService\\FileDownloadService\\bin\\Debug\\config.xml");
Does not work: (dispute been local to the EXE)
var config =
ClassLib.XmlInterface.DeserializeConfiguration(
"config.xml");
There are a couple of ways to resolve this.
You could create an environment variable, and read the path from it. This is kind of kludgy, if you don't have a Setup application, and it's prone to breakage if users muck with their settings. But it will work if you do it correctly.
If you have a setup application, you could also store the path to your executable in the Registry, and read it at runtime.
Or, you can try retrieving the path to your assembly at runtime using something like this:
public static string GetCoreAssemblyPathRoot()
{
const string AssemblyName = "MyAssemblyName,";
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var path = (from assembly in loadedAssemblies
where assembly.FullName.StartsWith(AssemblyName)
select Path.GetDirectoryName(assembly.Location))
.FirstOrDefault();
if (path == null)
{
return null;
}
// The last part of the path is "\bin". Remove it, and return the remainder.
var index = path.IndexOf("\\bin\\");
return index == -1
? path
: path.Substring(0, index);
}

Categories