My WPF application reads a directory structure from a location relative to the main application executable.
Then it visualizes the directory structure in a TreeView
.
It works fine when I just run the application, however, when I preview it in the XAML designer, it does not work. I found it's loaded from a different location like this:
C:\Users\Adam\AppData\Local\Microsoft\VisualStudio\17.0_108779a0\Designer\Cache\1481370356x64DA\
Yes, it's the value of the System.AppDomain.CurrentDomain.BaseDirectory
property.
I get similar location from assembly.Location
.
I know there are similar questions asked a long time ago with answers valid for previous Visual Studio versions and previous .NET versions. None of them works for .NET 6 and Visual Studio 2022. Please kindly do not mark this as duplicate of those questions.
To verify those answers don't work with .NET 6 - just create a new .NET 6 WPF project, in any code executed by a view insert following code:
throw new Exception($"Path: {System.AppDomain.CurrentDomain.BaseDirectory}");
Reload the designer, and you will see what am I talking about.
I already found a very ugly workaround, but it just hhhhhhideous! When I intentionally throw and catch an exception. I will get a path to my code in the stack trace. Then I can extract my project directory from there, then find the output path in the project file and that's it. But come on! There must be a cleaner way!
UPDATE: Here's the hhhhhhideous hack:
using System.IO;
using System.Reflection;
using System.Xml;
static class Abominations {
/// <summary>
/// Gets the calling project's output directory in a hideous way (from debug information). Use when all else fails.
/// </summary>
/// <param name="action">Action that throws an exception.</param>
/// <returns>Calling project's output directory.</returns>
public static string GetCallingProjectOutputDirectory(Action action) {
try {
action();
}
catch (Exception exception) {
var stacktrace = exception.StackTrace!.Split(Environment.NewLine).First();
var p1 = stacktrace.IndexOf(" in ") + 4;
var p2 = stacktrace.IndexOf(":line");
var pathLength = p2 - p1;
if (p1 < 0 || p2 < 0 || pathLength < 1) throw new InvalidOperationException("No debug information");
var callingSource = stacktrace[p1..p2];
var directory = new DirectoryInfo(Path.GetDirectoryName(callingSource)!);
FileInfo? projectFile;
do {
projectFile = directory.GetFiles("*.csproj").FirstOrDefault();
if (projectFile is null) directory = directory.Parent!;
}
while (projectFile is null);
var projectXml = new XmlDocument();
projectXml.Load(projectFile.FullName);
var baseOutputPath = projectXml.GetElementsByTagName("BaseOutputPath").OfType<XmlElement>().FirstOrDefault()?.InnerText;
var outputDirectory = directory.FullName;
outputDirectory = baseOutputPath is not null
? Path.GetFullPath(Path.Combine(outputDirectory, baseOutputPath))
: Path.Combine(outputDirectory, "bin");
var buildConfiguration =
Assembly.GetCallingAssembly().GetCustomAttribute<AssemblyConfigurationAttribute>()!.Configuration;
var targetFramework =
projectXml.GetElementsByTagName("TargetFramework").OfType<XmlElement>().FirstOrDefault()!.InnerText;
outputDirectory = Path.Combine(outputDirectory, buildConfiguration, targetFramework);
return outputDirectory;
}
throw new InvalidOperationException("Provided action should throw");
}
}
I tested it and it works. But it's just an atrocious abomination and something that should be killed with fire.