Reference files local to the service executable by default - c#

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);
}

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.

CefSharp.offscreen in LinqPad

LinqPad is my goto REPL and there isn't much I throw at it that it cant handle.
However I cannot for the life of me get CefSharp (specifically OffScreen) to run.
I'm constantly met with either of the below errors
Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or one of its dependencies. The system cannot find the file specified.
BadImageFormatException: Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138'. An attempt was made to load a program with an incorrect format.
I have tried
LP5/6 32 and 64 bit
Adding Cefsharp via nuget
Referencing .dll's manually from the file system
Referencing x86 or x64 .dll's
Copying .dll's into assembly search paths
Adding nuget paths to Environment path
And what seems like every combination of the above.
I don't understand the assembly resolution process that Visual Studio uses with the nuget package, but whatever it does I would like to at least simulate in Linqpad so I can avoid the VS ceremony when testing something simple.
I assume that manually referencing the correct .dll's and maybe setting a path somewhere should be sufficient, but I'm ideas=>EOF.
Can CefSharp be run outside of VS / MSBuild ?
It doesn't work because of the shadow-copying that LinqPad is using. Here is a hack to make your problem go away (spoiler alert: not really, read on):
For LinqPad v5
Copy all CefSharp libraries to a separate folder (don't forget cef.redist).
In LinqPad Preferences dialog (Advanced/Execution), set Do not shadow assembly references to True, restart LinqPad.
Write your code in the LinqPad query.
Reference CefSharp libraries from the folder you've set up on step 1.
Run the query.
For previous LinqPad (earlier than v5)
Write your code in the LinqPad query.
Reference CefSharp libraries, so you get an exception from your question
Find a LinqPad working directory (usually something like C:\Users\<user>\AppData\Local\Temp\LINQPad5\_yyigmhzg).
Copy all CefSharp libraries to this folder (don't forget cef.redist).
In LinqPad, click Ctrl + Shift + F5; this will reset the query state.
Rerun the query.
Now all the referenced libraries should load. But you will likely face more problems after that.
I couldn't make CefSharp.MinimalExample work. LinqPad kept crashing for me with the cryptic message Query ended because an uncatchable exception was thrown and a crashdump.
Although I am not sure if you will make CefSharp work as intended under LinqPad, maybe this can get you a bit further.
Found the answer with motivation from #Sasha's post and #amaitland's note about BadImageFormatException's being more than just incorrect architectures.
The below is all in reference to LP6 and CefSharp.Offscreen.NetCore. I have not pushed the efforts into LP5 but the process should be similar.
After some trial and error I narrowed down all of the necessary dependencies and worked out why CefSharp would not run in LinqPad.
Here are the steps to make it run -
Add CefSharp.Offscreen.NetCore package as normal to query
Enable Copy all NuGet assemblies into a single local folder (F4->Advanced)
Add the OnInit() and queryPath code as below to the query
Ensure the BrowserSubprocessPath is set before Initializing Cef
Here is the code.
async Task Main()
{
var are = new AutoResetEvent(false);//my technique for waiting for the browser
var sett = new CefSettings();
sett.BrowserSubprocessPath = this.queryPath + #"\CefSharp.BrowserSubprocess.exe"; //CefSharp will complain it cant find it
if (!Cef.IsInitialized)
Cef.Initialize(sett);
var browser = new ChromiumWebBrowser("http://www.google.com");
browser.LoadingStateChanged += (sender, args) => { if (!args.IsLoading) are.Set(); };
are.WaitOne();
await browser.WaitForInitialLoadAsync();
var html = await browser.GetBrowser().MainFrame.GetSourceAsync();
html.Dump("winner winner chicken dinner");
}
//this is the location of the queries shaddow folder
string queryPath = Path.GetDirectoryName(typeof(CefSettings).Assembly.Location);
void OnInit() // Executes when the query process first loads
{
if (!Directory.Exists(queryPath + #"\locales")) //subdirectory of cef.redist
{
var nugetPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var sources = new[] {
/*paths here are hardcoded version dependant. Can get cefsharp.common.netcore version
from Util.CurrentQuery.NuGetReferences, but cef.redist not available via that method. */
#"cef.redist.x64\95.7.14\CEF", //contans all the Cef dependencies needed
#"cefsharp.common.netcore\95.7.141\runtimes\win-x64\lib\netcoreapp3.1", //mainly for ijwhost.dll
#"cefsharp.common.netcore\95.7.141\runtimes\win-x64\native"}; //contains BrowserSubprocess & friends
var dst = new DirectoryInfo(queryPath);
foreach (var path in sources)
{
var src = new DirectoryInfo($#"{nugetPath}\.nuget\packages\{path}");
CopyFilesRecursively(src, dst);
}
}
}
//curtesy of https://stackoverflow.com/a/58779/2738249 with slight mod
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
{
var dst = Path.Combine(target.FullName, file.Name);
if (!File.Exists(dst))
file.CopyTo(dst);
}
}
The why for those interested -
CefSharp needs every dependency to be in the same directory so they can be resolved at runtime, but Linqpad only copies a few key dll's from the NuGet package. None of the cef.redist files, ijwhost.dll or BrowserSubprocess.exe et al. come across. Dependencies are scattered between NuGet packages and trying to resolve them directly from the .nuget cache just does not work. So all these need to be brought in manually to the running query shadow path.
I did initially copy all files into the Assembly.GetExecutingAssembly().Location path, but this approach requires adding the assembly directory to the "path" environment variable.
Internally Linqpad seems to have the shadow path set, so copying the dependencies to the shadow folder skips the need to set the environment variable.

DLL in bin folder works in IIS but not in visual studio 2012

I have a website where I load a third party DLL using this function, which I got from an open source project using the same DLL:
public static void LoadCodecs(string path = null, string search = null) {
if (path == null)
path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var catalog = (search == null) ?
new DirectoryCatalog(path) :
new DirectoryCatalog(path, search);
var container = new CompositionContainer(catalog);
foreach (var lazy in container.GetExports<ICodec>()) {
var codec = lazy.Value;
_codecs[codec.TransferSyntax] = codec;
}
}
I have the DLL in the Bin folder along with my DLLs on a server running IIS. It works fine there (on the webserver). When I try to debug the website in Visual Studio (2012, ultimate edition, on my development machine) I get a "Could not load file or assembly" exception complaining about this specific DLL when trying to access any page. Both the server and my development machine are 64 bit, so I don't think that's the problem. When I delete the DLL, the website works fine (except for the pages that use the codecs loaded by the function above).
[EDIT]
It looks like my problem is that Visual Studio is running a 32 bit web hosting environment - when I put the 32 bit version of my DLL, it works.
Now I'm using this to select the correct DLL:
string file = "Codecs";//name of 32 bit DLL minus extension
if (Environment.Is64BitProcess)
{
file += "64";//64 bit dll is Codecs64
}
file += ".dll";
LoadCodecs(file);//just illustrative, actually need correct path here
But this still only works if I only put the correct version in the Bin directory. Is there a more elegant solution?

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");

Application root path

I've recently been having some issues with correctly discovering application root path in c#. I want my applications to use the correct folder in following instances:
web application in debug (visual studio)
web application in release
deployed web application
console application in debug
console application in release
deployed console application
windows service (really same as console application)
Namely I need this for a logging assembly which is shared across all those types of applications. It's using log4net and it needs to correctly resolve physical path internally - inside logging assembly.
Because log4net requires either a call to BasicConfiguration.Configure to load it from web.config / app.config. Issue with this is it doesn't set up a file watcher so changes are not monitored. Solution is to have a log4net.config file separately.
Now, I don't like placing things inside bin, bin/debug or bin/release folder since it is never included in source control. Instead for things like that I have a Config folder in application root. So that you end up with ~\Application\Config\log4net.config.
In the static logger there are 2 methods:
public static string GetRootPath()
{
var debugPath = string.Empty;
#if (DEBUG)
debugPath = "..\\..\\";
#endif
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, debugPath);
}
public static void Init(string loggerName)
{
LoggerName = loggerName;
XmlConfigurator.Configure(new FileInfo(Path.Combine(GetRootPath(), "Config\\log4net.config")));
}
So you can just call Logger.Init() in Application_Start or inside Main() for console apps. This works great for console applications but not for web applications since AppDomain.CurrentDomain.BaseDirectory points to web application root and not to it's bin folder (which also has no debug or release).
Has anyone got a reliable way to resolve root path for all above requirements? So - what should GetRootPath be doing?
PS: I know I could be checking if (HttpContext.Current != null) then don't merge debug path in but there must be a more elegant way?
You could use the CodeBase property of the Assembly class to determine the path to the executing assembly:
public static string GetRootPath()
{
var debugPath = string.Empty;
#if (DEBUG)
debugPath = "..\\..\\";
#endif
return Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase).LocalPath), debugPath);
}
Note, for web applications and windows services the file path is in file URI scheme format. So, I use the Uri class to convert the path to standard windows path format.
Hope, this helps.

Categories