Custom MSBuild Task not reading from App.config [C#] - c#

I wrote a custom build task that reads values form the appsettings in it's App.config file. When I compile my task as an executable and run it, the task works perfectly. The correct values are read from the config file. However when I compile it as an assembly and run it from a target in my build script I get a System.NullReferenceException. The exception occurs in the foreach loop because the configuration manager returns null.
IEnumerable<string> tables = ConfigurationManager.AppSettings.GetValues(key);
foreach (string txt in tables)
{
Logic.....
}
I'm calling the custom task correctly because I commented out the issue and it builds successfully.
Does anyone know why this might be happening? or if I'm even able to use a App.config file with custom build tasks?
Thanks in advance

If you compile a project as a library, then it reads from the app.config of the calling executable. If you compile a project as an executable, then it reads from it's own app.config.

If anyone is interested I used the following code to access the custom config
private static string[] GetConfigFile()
{
var map = new ExeConfigurationFileMap();
map.ExeConfigFilename = #"C:\ConfigFile.config";
config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
return config.AppSettings.Settings.AllKeys;
}
The above code gets the list of keys from the specified config file.
The return value is stored in a string array which I run through using a foreach loop as seen below
string[] keyNames = GetConfigFile();
foreach (string keys in keyNames )
{
KeyValueConfigurationElement keyval = config.AppSettings.Settings[keys];
Console.WriteLine(keyval.Value);
}

Related

Dynamically create nunit test cases based off config file

I'm looking for a way to dynamically create nunit test cases based off config file. In my App.config, I have a list of filenames and its expected operation. I would like to be able to add a new entry and have a test case be created dynamically based off that. My code is as follows:
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--syntax is filename#operation where 0 = Valid File, 1 = Missing Column, 2 = Invalid File-->
<add key="FILENAME_0" value="C:\Users\me\Desktop\file_0.csv#0" />
<add key="FILENAME_1" value="C:\Users\me\Desktop\file_1.csv#1" />
<add key="FILENAME_2" value="C:\Users\me\Desktop\file_2.csv#2" />
</appSettings>
</configuration>
In my test fixture, I initialize a list of test case data, which is created by parsing the App.config file as follows:
[TestFixture]
public class MyTests
{
public enum Operation
{
ValidFile = 0,
MissingColumns = 1,
InvalidFile = 2
};
/// <summary>
/// Gets test case data with the file name and an enum of the expected operation.
/// </summary>
private static IEnumerable<TestCaseData> FilenameCases()
{
int i = 0;
bool moreFilesToTest = true;
var files = new Dictionary<string, Operation>();
while (moreFilesToTest)
{
string filenameAndOp = ConfigurationManager.AppSettings[$"FILENAME_{i}"];
if (filenameAndOp == null)
moreFilesToTest = false;
else
{
string filename = filenameAndOp.Split('#')[0];
int operation = Int32.Parse(filenameAndOp.Split('#')[1]);
Operation op = (Operation)operation;
files.Add(filename, op);
}
i++;
}
foreach (var pair in files)
yield return new TestCaseData(pair.Key, pair.Value);
}
[Test, TestCaseSource("FilenameCases")]
public void ShouldLoadFiles(string FILENAME, Operation expected)
{
// ... run test cases
}
}
This works, however, would need to rebuild the project each time in order for any changes in the App.config to be recognized. The code also only works if the TestCaseData is static. What is the correct way to have new test cases be created dynamically based off the configuration file.
After researching this post, I have found out that the issue is not with the code, but rather the the configuration file. As I was running the test via the command line, the test .dll was not referencing my App.config, but another file.
When I compiled my project, I noticed it produced a config file based off the name of the project. So a test project titled MyTestProject would produce a file called MyTestProject.dll.config. Therefore, when I made changes to that config file, my test cases reflected the changes, without recompiling the project.
Credit to both Charlie and Amittai Shapira, as NUnit doesn't support dynamic test cases and TestCaseSource needs to be static. However, the code I tried was the best work around, as the end result did produced dynamic test cases.
TestCaseSource methods need to be static. There are a few ways around this if you need it but I see no problem with using static in your case. Just because a method is static, that doesn't mean it has to always return the same values.
There is no reason for you to recompile your code when you change the config file. However, you have to reload the test assembly in order for the new values in the config to be read. In some environments, like under VS, it may be easier to just recompile in order to force that to happen.
Essentially, I'd say you have already found the best way to do this, although my personal tendency would be to use a flat file rather than the config file.

exePath must be specified when not running inside a stand alone exe

When i am using a web application, the line of code below
Configuration objConfig =
ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.None);
in class library are giving this error:
"exePath must be specified when not running inside a stand alone exe."
Previously a console application was being used, and the code could access the app.config. I tried using the System.Web.Configuration in class library but the dll was not present in the .Net tab for "Add reference".
Kindly help :)
You need to use a different configuration manager in a web context. The following code
block shows an example of how to deal with this:
System.Configuration.Configuration configuration = null;
if (System.Web.HttpContext.Current != null)
{
configuration =
System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
}
else
{
configuration =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
}
I'm not sure what you're doing; but at first glance it looks like you're trying to use code written for a WinForms application in a web environment. This almost certainly will not work, since your web app won't have the permissions you need.
Try looking up how to do this in a web environment (since you seem to be dealing with config files, try searching on WEB.CONFIG to start)
I tried to use the answer from #shane but ended up with the same exception using Hangfire. This code worked for me though:
System.Configuration.Configuration configFile = null;
if (System.Web.HttpContext.Current != null)
{
configFile =
System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
}
else
{
System.Configuration.ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = $"{System.AppDomain.CurrentDomain.BaseDirectory}Web.Config" };
configFile = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
}
Note that editing Web.config will cause the application pool to restart!

How to run a executable program with different configuration file?

I want to run a program with different configuration file, the program write with C# 2.0, I make some different file name {program_name}.exe.config, I mean one exe with different config file, for example I have 3 config file, then I will run 3 exe with the different config file, bu the exe file is the same one.
Can I do not modify the program for read the different config file (I don`t want to put the config file path in the exe command parameters) to do that(like use the batch file or other method.) ?
Thanks.
You can change the configuration file for the application domain in which the exe is loaded. This is done using the SetData method of the AppDomain class. Ensure that this line of code is executed as the first line of your application.
I have used following code to share 1 exe.config file between 3 different executables.
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE","yourSharedConfig.exe.config");
You can look at the following blog entry
Binding to custom app.config
If you want to run the same exe with 3 different configs, I believe the same approach will work with bit of customization. You can pass the name of the config file while invoking the exe as a command line parameter and using the SetData method you can dynamically set the config.
I make it with LINQ and passing the parameter as config=path2file
public partial class App : Application {
private void startup(object sender, StartupEventArgs e) {
if (null != e) {
if (null != e.Args && 0 < e.Args.Length) {
string config = e.Args.Where(a => a.StartsWith("config=")).FirstOrDefault();
if (null != config) {
config = config.Substring("config=".Length);
if (File.Exists(config)) {
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", config);
}
}
}
}
}
The main issue you have with three configs and one executable is that you need to specify to the executable which config to use.
One option is to make 3 copies of your executable, exe1.exe, exe2.exe and exe3.exe and have a similarly named config for each - exe1.exe.config, exe2.exe.config and exe3.exe.config.
When running each executable, it will use the correct config.
Another option is to have several batch files that will rename the different config files according to which one you want to use. Then you have a single exe and three configs.
You create a second executable, and always run that one first. In it, all you do is rename one configfile to the correct name and fire the main application.
string currentConfig = "application.exe.config";
string someOtherName = "firstconfig.config";
string configFileYouWant = "secondconfig.config";
string application = "application.exe";
File.Move(currentConfig, someOtherName);
File.Move(configFileYouWant, currentConfig);
Process.Start(application);

Changes in App.config do not reflect after restarting the application

I am using an app.config file to store the dynamic parameters of my application. The problem is, when I change a value in app.config file, and start the application, it doesn't load the new value from config file. Seems like the values in app.config file are being read and embedded in exe file only at compile time!
This is how I read the config file:
public class Helper
{
static Helper()
{
Foo = ConfigurationManager.AppSettings["Foo"];
}
public static string Foo { get; set; }
}
Am I missing something?
Are you sure you are changing the correct file? You don't want to change the app.config file, but the <exename>.exe.config file, in the same directory as the .exe
The app.config file is what you edit in the ide, but when you compile your app this file is renamed to <exename>.exe.config and copied to the output directory when you compile. The .exe looks for a file with the same name as itself with the .config extension when looking for the default configuration.
The static nature of your class and method may be causing you the issue. Maybe refactor it to the following...
public static class Helper
{
public static string Foo
{
get
{
return ConfigurationManager.AppSettings["Foo"];
}
}
}
Actually, thinking about it, it doesn't help you a great deal since ConfigurationManager.AppSettings["Foo"] is already (effectively) a static call - you're just adding another layer of abstraction that may well not be required.

How can I set application settings at install time (via installer class)

I have a Visual Studio setup project that has an Installer class. In the installer class I set a setting as follows:
MessageBox.Show(Properties.Settings.Default.MySetting);
Properties.Settings.Default.MySetting = "Foo";
Properties.Settings.Default.Save();
MessageBox.Show(Properties.Settings.Default.MySetting);
The problem is that even though I know that this code is being executed (I am doing other stuff), the setting is never set!!
The message boxes do suggest that the value is being set, but when I go to the .config file the value is still blank!
Anyone have any ideas why and/or a possible workaround?
What I do for my installers is to use the "file" attribute in App.Config. The appSettings block takes a "file" attribute, like so:
<appSettings file="user.config">
<add key="foo" value="some value unchanged by setup"/>
</appSettings>
The "file" attribute is sort of like CSS, in that the most specific setting wins. If you have "foo" defined in user.config as well as App.config, the value in user.config is used.
Then, I have a config generator that writes out a second appSettings block to user.config (or whatever you want to call it), using values in a dictionary.
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace Utils
{
public class ConfigGenerator
{
public static void WriteExternalAppConfig(string configFilePath, IDictionary<string, string> userConfiguration)
{
using (XmlTextWriter xw = new XmlTextWriter(configFilePath, Encoding.UTF8))
{
xw.Formatting = Formatting.Indented;
xw.Indentation = 4;
xw.WriteStartDocument();
xw.WriteStartElement("appSettings");
foreach (KeyValuePair<string, string> pair in userConfiguration)
{
xw.WriteStartElement("add");
xw.WriteAttributeString("key", pair.Key);
xw.WriteAttributeString("value", pair.Value);
xw.WriteEndElement();
}
xw.WriteEndElement();
xw.WriteEndDocument();
}
}
}
}
In your installer, just add something like the following in your Install method:
string configFilePath = string.Format("{0}{1}User.config", targetDir, Path.DirectorySeparatorChar);
IDictionary<string, string> userConfiguration = new Dictionary<string, string>();
userConfiguration["Server"] = Context.Parameters["Server"];
userConfiguration["Port"] = Context.Parameters["Port"];
ConfigGenerator.WriteExternalAppConfig(configFilePath, userConfiguration);
We use it for our test, training, and production servers, so all we have to do is specify the machine name and password during the install, and everything's taken care of for us. It used to be a 3-hour process, including going through multiple config files to set passwords. Now it's almost entirely automated.
Hope this helps.
Well in the end I gave up and had a RunOnce type of method to do this stuff after the app was installed.
I honestly don't know if this is supported during an installer - but if it is, make sure you're calling Save() on Settings.Default.
The short answer is that it's not supported in installer classes. You just need to understand that installer class methods are called from msiexec.exe running from the system directory, and that environment can't possibly know that you have a settings file somewhere in a directory that it is completely unaware of. That's why it works with code that explicitly goes to the installed location of the file and updates it there.

Categories

Resources