How can I convert Assembly.CodeBase into a filesystem path in C#?

C#.NetNunitCodebase

C# Problem Overview


I have a project that stores templates in a \Templates folder next to the DLLs and EXE.

I want to determine this file path at runtime, but using a technique that will work inside a unit test as well as in production (and I don't want to disable shadow-copying in NUnit!)

Assembly.Location is no good because it returns the shadow-copied assembly's path when running under NUnit.

Environment.CommandLine is also of limited use because in NUnit et al it returns the path to NUnit, not to my project.

Assembly.CodeBase looks promising, but it's a UNC path:

file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe

Now I could turn this into a local filesystem path using string manipulation, but I suspect there's a cleaner way of doing it buried in the .NET framework somewhere. Anyone know a recommended way of doing this?

(Throwing an exception if the UNC path is not a file:/// URL is absolutely fine in this context)

C# Solutions


Solution 1 - C#

You need to use System.Uri.LocalPath:

string localPath = new Uri("file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe").LocalPath;

So if you want the original location of the currently executing assembly:

string localPath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;

The LocalPath includes the assembly's file name, e.g.,

D:\projects\MyApp\MyApp\bin\debug\MyApp.exe

If you want the assembly's directory, then use System.IO.Path.GetDirectoryName():

string localDirectory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);

which will give you:

D:\projects\MyApp\MyApp\bin\debug

Solution 2 - C#

> Assembly.CodeBase looks promising, but it's a UNC path:

Do note that it is something approximating a file uri, not an UNC path.


You solve this by doing string manipulation by hand. Seriously.

Try all other methods you can find on SO with the following directory (verbatim):

C:\Test\Space( )(h#)(p%20){[a&],t@,p%,+}.,\Release

This is a valid, if somewhat unusual, Windows path. (Some people will have either one of these characters in there paths, and you would want you method to work for all of those, right?)

The available code base (we do not want Location, right?) properties are then (on my Win7 with .NET 4):

assembly.CodeBase -> file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release

assembly.EscapedCodeBase -> file:///C:/Test/Space(%20)(h%23)(p%20)%7B%5Ba%26%5D,t@,p%,+%7D.,/Release

You will note:

  • CodeBase is not escaped at all, it's just the regular local path prefixed with file:/// and the backslashes replaced. As such, it does not work to feed this to System.Uri.
  • EscapedCodeBase is not escaped completely (I do not know if this is a bug or if this is a shortcoming of the URI scheme):
    • Note how the space character ( ) translates to %20
    • but the %20 sequence also translates to %20! (percent % is not escaped at all)
    • No one can rebuild the original from this mangled form!

For local files (And that's really all I'd care about for the CodeBasestuff, because if the file ain't local, you probably want to use .Location anyway, the following works for me (note that it isn't the prettiest either:

	public static string GetAssemblyFullPath(Assembly assembly)
	{
	    string codeBasePseudoUrl = assembly.CodeBase; // "pseudo" because it is not properly escaped
		if (codeBasePseudoUrl != null) {
			const string filePrefix3 = @"file:///";
			if (codeBasePseudoUrl.StartsWith(filePrefix3)) {
				string sPath = codeBasePseudoUrl.Substring(filePrefix3.Length);
				string bsPath = sPath.Replace('/', '\\');
				Console.WriteLine("bsPath: " + bsPath);
				string fp = Path.GetFullPath(bsPath);
				Console.WriteLine("fp: " + fp);
				return fp;
			}
		}
		System.Diagnostics.Debug.Assert(false, "CodeBase evaluation failed! - Using Location as fallback.");
		return Path.GetFullPath(assembly.Location);
	}

I am sure one can come up with better solutions, probably one could even come up with a solution that does proper URL en-/decoding of the CodeBase property if it's a local path, but given that one can just strip off the file:/// and be done with it, I'd say this solution stands as good enough, if certainly really ugly.

Solution 3 - C#

This should work:

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
Assembly asm = Assembly.GetCallingAssembly();
String path = Path.GetDirectoryName(new Uri(asm.EscapedCodeBase).LocalPath);
        
string strLog4NetConfigPath = System.IO.Path.Combine(path, "log4net.config");

I am using this to be able to log from within the dll libraries using a standalone log4net.config file.

Solution 4 - C#

One more solution, including complex paths:

	public static string GetPath(this Assembly assembly)
	{
		return Path.GetDirectoryName(assembly.GetFileName());
	}

	public static string GetFileName(this Assembly assembly)
	{
		return assembly.CodeBase.GetPathFromUri();
	}

	public static string GetPathFromUri(this string uriString)
	{
		var uri = new Uri(Uri.EscapeUriString(uriString));
		return String.Format("{0}{1}", Uri.UnescapeDataString(uri.PathAndQuery), Uri.UnescapeDataString(uri.Fragment));
	}

and tests:

	[Test]
	public void GetPathFromUriTest()
	{
		Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release", @"file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
		Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release",  @"file://C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
	}

	[Test]
	public void AssemblyPathTest()
	{
		var asm = Assembly.GetExecutingAssembly();

		var path = asm.GetPath();
		var file = asm.GetFileName();

		Assert.IsNotEmpty(path);
		Assert.IsNotEmpty(file);

		Assert.That(File     .Exists(file));
		Assert.That(Directory.Exists(path));
	}

Solution 5 - C#

Since you tagged this question NUnit, you can also use AssemblyHelper.GetDirectoryName to get the original directory of the executing assembly:

using System.Reflection;
using NUnit.Framework.Internal;
...
string path = AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly())

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDylan BeattieView Question on Stackoverflow
Solution 1 - C#PolyfunView Answer on Stackoverflow
Solution 2 - C#Martin BaView Answer on Stackoverflow
Solution 3 - C#mmmmmmView Answer on Stackoverflow
Solution 4 - C#iliView Answer on Stackoverflow
Solution 5 - C#wezzixView Answer on Stackoverflow