Modifying app.config via custom msi installer - c#

I need to create an address string in app.config as:
<client>
<endpoint address="http://ServerName/xxx/yyy.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IClientIInfoService"
contract="DocuHealthLinkSvcRef.IClientIInfoService" name="BasicHttpBinding_IClientIInfoService" />
</client>
The ServerName need to be entered by the user during installation.
For that i have created a new UI dialog in the Installer. I have also written an Installer.cs class and overrided the install () as:
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string exePath = string.Format("{0}myapp.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["ServerName"].Value = ServerName;
config.Save();
}
}
But how do i use this ServerName in my app.config to create the specified string.
I'm working on VS2010.

You could use WiX (Windows Installer XML toolset) to build your MSI, in which case you can use the XmlFile utility tag to update the server name:
<util:XmlFile Id="UpdateServerName" File="[INSTALLLOCATION]AppName.exe.config" Action="setValue" ElementPath="/client/endpoint" Name="address" Value="http://[SERVERNAME]/xxx/yyy.svc" />
You can capture the server name during installation using a WixUI extension form.
Advantages of WiX: WiX is msbuild compliant (unlike .vdproj files), and gives you much finer-grained control over your installer, among other things

Assuming you are using the full ServiceModel section group in the app.config
Essentially you follow these steps:
Load ServiceModel config section
Get Client Section
Get ChannelEndpoint Element
Change Address value by replacing string "ServerName" with entered value
Set Address attribute to new value
Save config
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string exePath = string.Format("{0}myapp.exe", targetDirectory);
Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
config.AppSettings.Settings["ServerName"].Value = ServerName;
//Get ServiceModelSectionGroup from config
ServiceModelSectionGroup group = ServiceModelSectionGroup.GetSectionGroup (config);
//get the client section
ClientSection clientSection = group.Client;
//get the first endpoint
ChannelEndpointElement channelEndpointElement = clientSection.Endpoints[0];
//get the address attribute and replace servername in the string.
string address = channelEndpointElement.Address.ToString().Replace("ServerName", ServerName);
//set the Address attribute to the new value
channelEndpointElement.Address = new Uri(address);
config.Save();
}

At the end of the day, app.config is xml file. You can use Linq To XML or XPathNavigator to replace the address attribute of endpoint element.
Below code uses Linq to Xml
using System.Xml.Linq;
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
string targetDirectory = Context.Parameters["targetdir"];
string ServerName = Context.Parameters["ServerName"];
System.Diagnostics.Debugger.Break();
string configPath = string.Format("{0myapp.exe.config", targetDirectory);
XElement root = XElement.Load(configPath);
var endPointElements = root.Descendants("endpoint");
foreach(var element in endPointElements)
{
element.Attribute("address").Value = ServerName;
}
root.Save(configPath);
}
}

Since you have a windows-installer tag, I assume you either have an MSI package, or can create one...
Then:
You can create a public MSI property like ENDPOINTSERVER you require
during installation.
Add a custom action that modifies app.config to
run after "InstallFinalize" with the value of ENDPOINTSERVER
A silent installation will be possible using:
msiexec /i app.msi ENDPOINTSERVER=www.MyServer.com /qb-

try to use below two lines before saving the config file changes:
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("Section Name");
root.Save(configPath);
P.S: it doesn't update the solution item 'app.config', but the '.exe.config' one in the bin/ folder if you run it with F5.

Related

Modify NLog configurations specified with Configuration API through NLog config file xml

I have a project which uses the below code to create a NLog instance.
public FileTarget CreateNLogFileTarget(string layout, FileArchivePeriod archiveMode, int maxArchiveFiles,
bool keepFileOpen, bool enableConcurrentWrites, ArchiveNumberingMode archiveNumberingMode, string fileName)
{
FileTarget fileTarget = new FileTarget();
fileTarget.Layout = layout;
fileTarget.ArchiveEvery = archiveMode;
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.KeepFileOpen = keepFileOpen;
fileTarget.ConcurrentWrites = enableConcurrentWrites;
fileTarget.ArchiveNumbering = archiveNumberingMode;
fileTarget.FileName = fileName;
return fileTarget;
}
FileTarget infoLogFileTarget = CreateNLogFileTarget(#"${longdate} ${message}",
FileArchivePeriod.Hour, 70, false, true, ArchiveNumberingMode.Rolling, "${basedir}/Logs/" + infoLogName + "/${shortdate}{#}.log");
I am using this project in another project and I need to use this NLog utility class to create my loggers. But I need to override these configurations. How can I override these configurations through the xml file? Any help would be much appreciated.
To use the FileTarget from CreateNLogFileTarget in your XML config, you should first find out the target name of the FileTarget it's probably in other parts of the code. Then you could use the target in your config:
<logger name='*' minlevel="Trace" writeTo='theTarget' />
Maybe by using NLog-variables. Change your CreateNLogFileTarget to setup the parameters to get their value from NLog-variables.
Then on startup check if these NLog variables already exists in the loaded NLog-configuration. If not then they are set by the runtime, before calling CreateNLogFileTarget.
https://github.com/NLog/NLog/wiki/Configuration-file#variables

Data Source in connection string - Setup project

I'm creating a setup project for my C# desktop application.
What the data source should be written in the connection string for the access database ?and where I should put my database file in the solution project ?
Assuming you're using the VS setup project, you need to add the access database file as content and place it in the application directory, for example. To specify the location in the configuration file, you need to write a custom action that modifies the connection string accordingly.
The following example is an installer class that sets the connection string after install phase (not tested):
[RunInstaller(true)]
public partial class Installer1 : System.Configuration.Install.Installer
{
public Installer1()
{
InitializeComponent();
this.AfterInstall += new InstallEventHandler(Installer1_AfterInstall);
}
void Installer1_AfterInstall(object sender, InstallEventArgs e)
{
string sTargetDir = Context.Parameters["TargetDir"];
string sAppConfig = Path.Combine(sTargetDir, "<your app>.exe.config");
string sDBPath = Path.Combine(sTargetDir, "<your db>.mdb");
XDocument doc = XDocument.Load(sAppConfig);
var elem = doc.Root.Element("/configuration/connectionStrings/add[#name='<your connection name>']");
string connectionString = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};", sDBPath);
elem.SetAttributeValue("connectionString", connectionString);
doc.Save(sAppConfig);
}
}
Alternatively, you can use Wix which has the XmlFile utility in the util extension which does it for you without you writing a custom action.

Problems in reading DLL app.setting

i have problems in reading DLL application setting in c# Visual Studio 2010.
I post a sample code of the get workarounded using reflection because with the ConfigurationManager fails.
private string LDAPDomain
{
get
{
string strPath = System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName;
string val = GetValues(strPath, "LDAPDomain");
return val;
}
}
//strPath is path of the file.
//strKey is key to access
private string GetValues(string strPath, string strKey)
{
System.Configuration.Configuration con = System.Configuration.ConfigurationManager.OpenExeConfiguration(strPath);
string strValue = con.AppSettings.Settings[strKey].Value;
return strValue;
}
If you are expecting the main project referencing the DLL to pick up the app settings it doesn't work like that. The ConfigurationManager will read the config for the executing assembly, you need to put all the necessary configuration into your app if you want to use this.
Alternatively you can manually read the contents of your DLL's app.config file - see this question for some example code.

Deployable database Test Methods

I am currently in the middle of creating an app that uses a sql CE database, I have made this database deploy-able with the application, however the problem I'm having at the moment is I need to run TestMethods but this is erroring out when it doesn't find the database as its looking in the "testingProject" folder under debug or release as that is it's Data Directory
using (SqlCeConnection sqlCon = new SqlCeConnection(#"Data Source=|DataDirectory|\database.sdf;Persist Security Info=False;"))
The code above is my connection string, so I'm guessing that means that the test is running and searching for a database in its own data directory
Any help on what I could do without changing the database connection string, database location and still leaving my application deployable? or am I asking something impossible?
EDIT
[TestMethod]
public void TestForReadingFromDB()
{
List<string> list = class.readDB();
Assert.IsNotNull(list);
Assert.AreNotEqual(0, list.Count);
}
just added in the test method that's currently failing
In the test project you can override the DataDirectory location using
AppDomain.CurrentDomain.SetData("DataDirectory", <PATH_TO_DATA_DIRECTORY>);
For instance in my app.config file the testing projects I have
<appSettings>
<add key="DataDirectory" value="..\..\Database"/>
</appSettings>
In my test fixture base I have:
var dataDirectory = ConfigurationManager.AppSettings["DataDirectory"];
var absoluteDataDirectory = Path.GetFullPath(dataDirectory);
AppDomain.CurrentDomain.SetData("DataDirectory", absoluteDataDirectory);
This sets the DataDirectory to the folder /Database under the test project folder structure.
Once I drop or create a copy of the database in there I can easily run Integration Tests.
this is how I specify the data directory path for testing in my initialize data class
public class TestClasse
{
public TestClass()
{
GetAppDataDirectoryForTesting();
}
private static string GetAppDataDirectoryForTesting()
{ //NOTE: must be using visual studio test tools for this to work
string path = AppDomain.CurrentDomain.BaseDirectory;
var dirs = path.Split(Path.DirectorySeparatorChar);
var appDataPath = "";
for (int i = 0; i < dirs.Length - 3; i++)
{
appDataPath += dirs[i] + Path.DirectorySeparatorChar.ToString();
}
appDataPath = appDataPath + "[foldername(i.e. in my case project name)]" + Path.DirectorySeparatorChar.ToString() + "App_Data";
return appDataPath;
}
[TestMethod]
public void SomeTestMethod()
{
....test code
}
}

Change app.config at install time

How can I dynamically change a connectionString in the app.config file?
I have an application written with windows forms, c# 3.0 and Linq to Sql. I need to change the connection string when i install the application. How i do this?
When the user installs the program it must show a form with an option to change the connection string if exists or add one if it doesn't.
If you are using a .NET deployment project, can achieve this by using Custom Actions.
Write a secondary config file with an appSettings block using the settings from the installer. In your main config file, use the file attribute in appSettings to reference the second config file, like so:
<appSettings file="User.config">
Settings in the secondary config will override any matching keys in the main config.
In your installer:
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
string server = Context.Parameters["Server"];
string port = Context.Parameters["Port"];
string targetDir = Context.Parameters["TargetDir"];
// Build your connection string from user-input parameters and add them to dictionary
WriteAppConfig(targetDir, server, port);
}
private void WriteAppConfig(string targetDir, string server, string port)
{
string configFilePath = Path.Combine(targetDir, "User.config");
IDictionary<string, string> userConfiguration = new Dictionary<string, string>();
userConfiguration["Server"] = server;
userConfiguration["Port"] = port;
ConfigGenerator.WriteExternalAppConfig(configFilePath, userConfiguration);
}
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();
}
}
}
Check out this question. It has what you need to change values in the app.config dynamically through code.

Categories

Resources