Introduction
Application deployment may occur in many different ways and one of them is a setup process. Any kind of script can be added to the installation process to modify application settings. One of the alternative ways is to use the UI application to change configuration settings. In this article, I would like to show how to manipulate a .config file or any XML document to update its node/attribute values.
Background
This application uses the TreeView
, XML XPath, recursive calls, and controls resizing.
Using the code
First, let us see how the UI for configuration editor works. The UI application looks inside the installed directory for any files with the extension .config:
private void BindList() {
try {
string dir = string.IsNullOrEmpty(appDir) ? pplication.StartupPath : appDir;
string[] confFiles = Directory.GetFiles(dir, "*.config");
foreach (string fn in confFiles) {
cbConfFiels.Items.Add(fn.Substring(fn.LastIndexOf('\\') + 1));
}
cbConfFiels.SelectedIndex = 0;
} catch {
cbConfFiels.SelectedIndex = -1;
}
}
After the config file is found, it will be parsed and loaded into the tree view control:
private void LoadConfigFileData() {
tvNodes.Nodes.Clear();
useXPath = false;
cbNodes.Items.Clear();
XPathDocument doc = null;
try {
appConfigFileName = localPath + "\\" + configFileName;
XmlDocument xdoc = new XmlDocument();
doc = new XPathDocument(appConfigFileName);
} catch (IOException ioe) {
string msg = configFileName + " is not found in this directory:" +
localPath + "\nSelect App.config file";
MessageBox.Show(msg);
doc = LoadCopyFile();
}
LoadDocument(doc);
ReadNodes(appConfigFileName);
}
At the same time, all available XPath for the current config file are loaded into the XPath drop down list:
void GetNode(XmlNode node) {
int index = xPath.Length;
xPath.Append(node.Name + "/");
foreach (XmlNode n in node.ChildNodes) {
if (n.NodeType == XmlNodeType.Element) {
foreach (XmlAttribute at in n.Attributes) {
}
if (n.HasChildNodes) {
GetNode(n);
}
}
}
xPath.Remove(index, node.Name.Length + 1);
try{
if(!nodeNames.Keys.Equals(xPath.ToString())){
string xpath = xPath.ToString();
if (!xPath.ToString().Equals("//")) {
nodeNames.Add(xPath.ToString(), xpath);
}
}
}catch{}
}
By selecting an XPath from the drop down list, it will load the appropriate nodes into the tree view for editing.
private void cbNodes_SelectedIndexChanged(object sender, EventArgs e) {
useXPath = true;
string xpath = ((System.Windows.Forms.ComboBox)(sender)).Text + "*";
XPathDocument xpDoc = new XPathDocument(appConfigFileName);
XPathNavigator nav = xpDoc.CreateNavigator();
XPathNodeIterator iter = nav.Select(xpath);
iter.MoveNext();
tvNodes.Nodes.Clear();
string rootName = xpath.Remove(xpath.Length - 2);
int lastIndexOf = rootName.LastIndexOf("/");
rootName = rootName.Substring(lastIndexOf + 1, rootName.Length - lastIndexOf-1);
XmlDocument doc = new XmlDocument();
if (string.IsNullOrEmpty(rootName))
return;
doc.LoadXml("<" + rootName + ">" +
iter.Current.InnerXml + "</" +
rootName + ">");
XPathNavigator rootElement = doc.CreateNavigator().SelectSingleNode("/*");
TreeNode root = new TreeNode(rootElement.LocalName);
tvNodes.Nodes.Add(root);
XmlNodeList addNodeList = appDoc.SelectNodes(xpath);
foreach (XmlNode node in addNodeList) {
TreeNode eltNode = new TreeNode(node.Name);
root.Nodes.Add(eltNode);
if (node.HasChildNodes) {
AddTreeNode(eltNode, node);
}
}
tvNodes.ExpandAll();
}
By clicking on the node in the tree view, the value from the selected node is loaded into the value field which can be updated and saved.
private void btnUpdate_Click(object sender, EventArgs e) {
if (!useXPath) {
UnicodeEncoding uniEncoding = new UnicodeEncoding();
XmlNode addNode = appDoc.SelectSingleNode("configuration/" +
"appSettings/add[@key='" + nodeToEdit + "']");
XmlNode valueNode = addNode.SelectSingleNode("@value");
valueNode.InnerText = txtValue.Text;
StringBuilder sb = new StringBuilder(appDoc.InnerXml.Length);
sb.Append(appDoc.InnerXml);
File.WriteAllLines(appConfigFileName,
new string[] { appDoc.InnerXml }, Encoding.UTF8);
lblValue.Text += " - UPDATED";
lblValue.ForeColor = Color.Blue;
} else {
UpdateXPath();
}
}
private void UpdateXPath() {
UnicodeEncoding uniEncoding = new UnicodeEncoding();
XmlNode addNode = appDoc.SelectSingleNode(cbNodes.SelectedItem +
"/@" + nodeToEdit);
XmlNode valueNode = addNode.SelectSingleNode(".");
valueNode.Value = txtValue.Text;
StringBuilder sb = new StringBuilder(appDoc.InnerXml.Length);
sb.Append(appDoc.InnerXml);
File.WriteAllLines(appConfigFileName,
new string[] { appDoc.InnerXml }, Encoding.UTF8);
lblValue.Text = nodeToEdit + " - UPDATED";
lblValue.ForeColor = Color.Blue;
}
Installation process:
After creating your installation project, add the primary output from your project into a custom action for the install process:
Set CustomActionData
to /dir="[TARGETDIR]\" – this value or the installation directory will be passed as an argument to the installer class:
public override void Install(System.Collections.IDictionary stateSaver) {
base.Install(stateSaver);
Process();
}
private void Process() {
string dir = "";
try {
dir = this.Context.Parameters["dir"];
MainForm mf = new MainForm(dir);
mf.ShowDialog();
mf.Close();
} catch (Exception ex){
StreamWriter sw = File.CreateText(dir + "SetupLog.txt");
sw.WriteLine("Error log: " + DateTime.Now.ToLocalTime());
if (this.Context.Parameters.Count > 0) {
foreach (string s in this.Context.Parameters.Keys) {
sw.WriteLine("parameter: " + s + " value: " +
this.Context.Parameters[s]);
}
}
sw.WriteLine(ex.Message);
sw.Flush();
sw.Close();
}
}
It is important to override the Install
method in order to get a reference to your application. If the install directory is not specified, then Application.StartupPath
will point to your windows\system32 directory. This UI will work with any XML document if you need a simple editor/parser. The complete code is attached. The App.config file is transformed to the ExecutbleName.exe.config file at build time.