Using log4net With Visual Studio Debugger - c#

I'm having trouble getting my log4net.config file to load when using Visual Studio in debug mode for an Excel VSTO Plugin. The config file is in the top level directory of my project. I have the property "Copy to Output Directory" set to "Copy Always". This ensures the file is copied to bin/Debug/log4net.config. I can verify this is the case when I build.
However, the file won't load when I run in Debug mode. I gave up on trying to get the file to load automatically and decided to do it by code, as per the OP's code at the bottom of this question.
However, I realised that I needed to use an absolute path to the config file, as relative paths weren't picking it up. On further investigation, I realised that the executing DLL wasn't actually the DLL in the debug/bin folder. It was in the following location:
C:\Users\cbhandal\AppData\Local\assembly\dl3\MO52QQWP.9ZL\K36XZHGN.1PB\230751e6\d09b7fb2_19f6d401
Also the current working directory, as found by System.IO.Directory.GetCurrentDirectory(); was set to "C:\\Users\\cbhandal\\Documents".
Hard-coding the path as an absolute path works as in the following code:
var log4netConfig = "C:\\" + path + "\\Log4net.config";
var log4netInfo = new FileInfo(log4netConfig);
log4net.Config.XmlConfigurator.ConfigureAndWatch(log4netInfo);
But that's not a solution I can deploy. I'm stuck here. Wondering if there's a way to either force Visual studio to copy the .config file to that appdata/temp location, or if there's a way to programatically reference the folder where the original DLL lay- the one that was built. Or if anyone had any other solution?

For me the easiest solution was to use this:
https://stackoverflow.com/a/6963420/4754981
But there are several other solutions on that link for different approaches, each with their caveats.
So mine looks like this:
using System.Reflection;
using System.IO;
using System;
public static class Extensions {
private static string GetDirectory(this Assembly a) {
string codeBase = a.CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
private static void AlterLogPath(this log4net.Repository.ILoggerRepository repo, string newPath, string directory="") {
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy) repo;
foreach (log4net.Appender.IAppender a in h.Root.Appenders) {
if (a is log4net.Appender.FileAppender) {
var fa = (log4net.Appender.FileAppender)a;
var fileName = Path.GetFileName(fa.File);
fa.File = newPath + (String.IsNullOrEmpty(directory)?"":(directory + Path.DirectorySeparatorChar.ToString())); // edit: filename is attached after next line automatically.
fa.ActivateOptions();
break;
}
}
}
}
and in the bootup (via [assembly: System.Web.PreApplicationStartMethod] or otherwise for asp), or main app..
static void Main() {
var PATH = Assembly.GetExecutingAssembly().GetDirectory() + Path.DirectorySeparatorChar.ToString();
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(PATH + "log4net.config"));
log4net.LogManager.GetRepository().AlterLogPath(PATH, "Logs");
}

Related

Codegeneration at runtime from a string to console exe is not working in C# .NET6

I have some code which must be able to generated a console application at runtime (Codegeneration with System.CodeDom). I did this already a lot, but in NET 6 now I am struggling with that and the new API. In the code below I try to compile simply from a string. See below the static class with method Start() which then should generates the application.
The compilations seems fine, no errors at the end. But when starting the generated AppCodegenerated.exe, it shows some reference exception with System.Runtime.
Please help, any Idea? Already researched a lot but could not find any useful solution..
//-
I used the Visual Studio 2022 / NET 6 and theses Nuget's:
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace CompilerSimplified
{
public static class Compiler
{
public static bool Start()
{
string FileName = "AppCodegenerated";
string ExePath = AppDomain.CurrentDomain.BaseDirectory + #"\" + FileName + ".exe";
string code = #"using System; Console.WriteLine(""Hello.""); Console.ReadLine(); ";
// ------- References -------------
// .net platform references
List<MetadataReference> References = new List<MetadataReference>();
foreach (var item in ReferenceAssemblies.Net60) // ReferenceAssemblies from Nuget: Basic.Reference.Assemblies;
References.Add(item);
// or tried this: loop manually through system platform
//string[] fileEntries = Directory.GetFiles(#"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\6.0.0\ref\net6.0\", "*.dll");
//foreach (string fileName in fileEntries)
// references.Add(MetadataReference.CreateFromFile(fileName));MetadataReference.CreateFromFile(fileName));
// ------- References END -------------
// delete existing file
if (File.Exists(ExePath))
File.Delete(ExePath);
// compiler options
CSharpCompilationOptions DefaultCompilationOptions =
new CSharpCompilationOptions(outputKind: OutputKind.ConsoleApplication, platform: Platform.AnyCpu)
.WithOverflowChecks(true).WithOptimizationLevel(OptimizationLevel.Release);
// encode soucre code
string sourceCode = SourceText.From(code, Encoding.UTF8).ToString();
// CSharp options
var parsedSyntaxTree = Parse(sourceCode, "", CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
// compilation
var compilation = CSharpCompilation.Create(FileName, new SyntaxTree[] { parsedSyntaxTree }, references: References, DefaultCompilationOptions);
var result = compilation.Emit(ExePath);
// return
if (result.Success)
return true;
else
return false;
}
private static SyntaxTree Parse(string text, string filename = "", CSharpParseOptions options = null)
{
var stringText = SourceText.From(text, Encoding.UTF8);
return SyntaxFactory.ParseSyntaxTree(stringText, options, filename);
}
}
}
Above code runs fine without error and exports the AppCodegenerated.exe into the project /bin folder.
Execution of this generated AppCodegenerated.exe shows following on the output console:
Unhandled exception: System.IO.FileNotFoundException:
The file or assembly "System.Runtime, Version = 6.0.0.0, Culture = neutral,
PublicKeyToken = b03f5f7f11d50a3a" or a dependency on it was not found.
The system can not find the stated file.
It is not possible to codegenerate directly a console application like the initial approach above. One possible solution is to generate first a dll (what I mentioned above in the example code is working fine), and from there include that .dll into a .exe, from where the functionality can run.

Folder of running application

I have problem with my C# app, when is opened via file association, it works in file directory. For example, when I create copy of opened file:
File.Copy("C:\Photo\car.jpg", ".\car2.jpg"); // this is only ilustration code.
It makes new file "C:\Photo\car2.jpg", but I want to make file in my app directory (".\car2.jpg").
So, I think, when app is opened via file association, it run with working folder of that file ("C:\Photo\"). Is there way, how to keep working directory as directory with app.exe?
Edit:
This is not solution, I need to get equals of ".\" and System.AppDomain.CurrentDomain.BaseDirectory:
File.Copy("C:\Photo\car.jpg", Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "car2.jpg"));
I have to use this on many places in application, solution can be sets:
Environment.CurrentDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
but I prefer set it in startup application via file association and not in running program - it looks cleaner.
Thanks,
Jakub
To get the path of your application, you can use:
System.AppDomain.CurrentDomain.BaseDirectory
Use Path.Combine to build the destination path as follows:
File.Copy("C:\Photo\car.jpg", Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "car2.jpg"));
I'd like to try and offer an alternative. I'm relatively new to this, but I figured out a solution that works for me:
Make 2 static variables in your MainWindow.xaml.cs:
public static string fileOpen;
public static string workingDirectory;
In your app.xaml.cs file, add the following code:
protected override void OnStartup(StartupEventArgs e)
{
if (e.Args.Count() > 0)
{
var a = File.Exists(e.Args[0]);
var path = Path.GetFullPath(e.Args[0]);
MainICPUI.workingDirectory = Directory.GetCurrentDirectory();
MainICPUI.fileOpen = e.Args[0];
}
base.OnStartup(e);
}
When you open a file associated with your program from any directory, the full file name is added to the StartupEventArgs, including the directory. This code saves the directory.
Back to your MainWindow.xaml.cs file:
public static string fileOpen;
public static string workingDirectory;
public MainWindow()
{
InitializeComponent();
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
// This sets the directory back to the program directory, so you can do what you need
// to with files associated with your program
}
// Make sure your MainWindow has an OnLoaded event assigned to:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
// Now I'm setting the working directory back to the file location
Directory.SetCurrentDirectory(workingDirectory);
if (File.Exists(fileOpen))
{
var path = Path.GetFullPath(fileOpen);
// This should be the full file path of the file you clicked on.
}
}

Changing the base path of an assembly from the bin directory to a different directory

I have a Test.cs file in C:\ This test file reads from an input file and writes the same to an output file.
Test.cs
public class Test
{
public static int Main(string[] args)
{
var reader = new StreamReader("in.txt");
string input = reader.ReadLine();
var writer = new StreamWriter("out.txt");
writer.WriteLine(input);
return 0;
}
}
Here it should be noted that the code only uses the filename and not the full file path, which means the file is expected to be in the directory where the program is running. And I have created the in.txt in C:\
Now, there is a c# code called Runner.cs in a solution in C:\Project\Runner.cs, that dynamically compiles the Test.cs code and runs it using reflection. Now, when the Test.cs runs, it expects the in.txt file to be in C:\Project\bin\Debug\in.txt , but it is actually present in C:\in.txt
So, my question is, is there a way to make the code to get the file from C:\in.txt and not from the bin directory without changing the path of the file in the Test.cs code file.
Edit: It is my bad that I forgot to mention why I am in need of this requirement.
The Test.cs file comes from over the wire. And I felt it will not be a good choice to edit this file and set the file path accordingly. I want to compile it and run it as it is.
I hope I am clear. If not, please feel free to ask for more information.
If it is as simple as you show in your code switching the CurrentDirectory works for this example:
var mainMembers = new CSharpCodeProvider()
.CreateCompiler()
.CompileAssemblyFromSource(
new CompilerParameters { GenerateInMemory = true }
, #"
using System;
using System.IO;
public class M {
public static int Main() {
Console.WriteLine(""CurDir = ""+ Environment.CurrentDirectory);
var reader = new StreamReader(""in.txt"");
string input = reader.ReadLine();
var writer = new StreamWriter(""out.txt"");
writer.WriteLine(input);
return 0;
}
}")
.CompiledAssembly
.GetType("M")
.GetMember("Main");
// inspect
Environment.CurrentDirectory.Dump("current");
// keep
var oldcd = Environment.CurrentDirectory;
// switch
Environment.CurrentDirectory = "c:\\temp";
// invoke external code
((MethodInfo) mainMembers[0]).Invoke(null,null);
// restore
Environment.CurrentDirectory = oldcd;
In a multi threaded scenario this becomes unreliable.

nunit test working directory

I have the following code (sample1.evol - file attached to my unit test project):
[Test]
public void LexicalTest1()
{
var codePath = Path.GetFullPath(#"\EvolutionSamples\sample1.evol");
//.....
}
I found that the working directory of test execution is not the assembly directory: (in my case codepath variable assigned to d:\EvolutionSamples\sample1.evol).
So, how can I change the execution working directory (without hardcode)? What will be the best practice to load any files attached to test case?
You can use following to get the directory of assembly running the code something like
var AssemblyDirectory = TestContext.CurrentContext.TestDirectory
I use this for integration tests that need to access data files.
On any machine the test needs to run create a system environment variable named TestDataDirectory that points to the root of where your test data is.
Then have a static method that gets the file path for you..
public static class TestHelper
{
const string EnvironmentVariable = "TestDataDirectory";
static string testDataDir = Environment.GetEnvironmentVariable(EnvironmentVariable);
public static string GetTestFile(string partialPath)
{
return Path.Combine(testDataDir, partialPath);
}
}
...
[Test]
public void LexicalTest1()
{
var codePath = TestHelper.GetTestFile(#"\EvolutionSamples\sample1.evol");
//.....
}
I am using this code:
var str = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
if (str.StartsWith(#"file:\")){
str = str.Substring(6);
}
Getting in str variable the assembly directory.
We were having a problem where tests run using ReSharper and NCrunch would work, but the native VS Test Runner would not be able to find the files, when given just a relative file path for the test to use. I solved it by creating a function that you pass the relative test file path into, and it will give you the absolute file path.
private static string _basePath = Path.GetDirectoryName(typeof(NameOfYourTestClassGoesHere).Assembly.Location);
private string GetAbsoluteTestFilePath(string relativePath) => Path.Combine(_basePath, relativePath);
You would then use the function like so:
var input = File.ReadAllLines(GetAbsoluteTestFilePath(#"TestData/YourTestDataFile.txt"));

Why AppDomain.CurrentDomain.BaseDirectory not contains "bin" in asp.net app?

I have a web project like:
namespace Web
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
lbResult.Text = PathTest.GetBasePath();
}
}
}
The method PathTest.GetBasePath() is defined in another Project like:
namespace TestProject
{
public class PathTest
{
public static string GetBasePath()
{
return AppDomain.CurrentDomain.BaseDirectory;
}
}
}
Why it's display ...\Web\ while the TestProject assembly is compiled into bin folder(in other words it should display ...\Web\bin in my thought).
Now I got a troublesome if I modified method into:
namespace TestProject
{
public class FileReader
{
private const string m_filePath = #"\File.config";
public static string Read()
{
FileStream fs = null;
fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + m_filePath,FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fs);
return reader.ReadToEnd();
}
}
}
The File.config is created in TestProject. Now AppDomain.CurrentDomain.BaseDirectory + m_filePath will returen ..\Web\File.config (actually the file was be copied into ..\Web\bin\File.config), an exception will be thrown.
You could say that I should modified m_filePath to #"\bin\File.config". However If I use this method in a Console app in your suggest, AppDomain.CurrentDomain.BaseDirectory + m_filePath will return ..\Console\bin\Debug\bin\File.config (actually the file was copyed into .\Console\bin\Debug\File.config), an exception will be thrown due to surplus bin.
In other words, in web app, AppDomain.CurrentDomain.BaseDirectory is a different path where file be copyed into (lack of /bin), but in console app it's the same one path.
Any one can help me?
Per MSDN, an App Domain "Represents an application domain, which is an isolated environment where applications execute." When you think about an ASP.Net application the root where the app resides is not the bin folder. It is totally possible, and in some cases reasonable, to have no files in your bin folder, and possibly no bin folder at all. Since AppDomain.CurrentDomain refers to the same object regardless of whether you call the code from code behind or from a dll in the bin folder you will end up with the root path to the web site.
When I've written code designed to run under both asp.net and windows apps usually I create a property that looks something like this:
public static string GetBasePath()
{
if(System.Web.HttpContext.Current == null) return AppDomain.CurrentDomain.BaseDirectory;
else return Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"bin");
}
Another (untested) option would be to use:
public static string GetBasePath()
{
return System.Reflection.Assembly.GetExecutingAssembly().Location;
}
In case you want a solution that works for WinForms and Web Apps:
public string ApplicationPath
{
get
{
if (String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
//exe folder for WinForms, Consoles, Windows Services
return AppDomain.CurrentDomain.BaseDirectory;
}
else
{
//bin folder for Web Apps
return AppDomain.CurrentDomain.RelativeSearchPath;
}
}
}
The above code snippet is for binaries locations.
The AppDomain.CurrentDomain.BaseDirectory is still a valid path for Web Apps, it's just the root folder where the web.config and Global.asax are, and is same as Server.MapPath(#"~\");
If you use AppDomain.CurrentDomain.SetupInformation.PrivateBinPath instead of BaseDirectory, then you should get the correct path.
When ASP.net builds your site it outputs build assemblies in its special place for them. So getting path in that way is strange.
For asp.net hosted applications you can use:
string path = HttpContext.Current.Server.MapPath("~/App_Data/somedata.xml");

Categories