Click here to Skip to main content
Click here to Skip to main content

Setup and App config editor

, 10 Nov 2008
Rate this:
Please Sign up or sign in to vote.
Edit config file at app setup and after. Describes recursive node parsing and populating a tree view control.

AppConfigEditor

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:

/// <summary>
/// Walk through document and pick up XPath to each node
/// </summary>
/// <param name="node"></param>
void GetNode(XmlNode node) {
    int index = xPath.Length;
    xPath.Append(node.Name + "/");
    //Console.Out.WriteLine("Parent: {0}\n", xPath.ToString());

    foreach (XmlNode n in node.ChildNodes) {
    //Console.Out.WriteLine("Node name:'{0}{1}'", n.Name, 
    //    n.HasChildNodes == true ? "/" : "");
    if (n.NodeType == XmlNodeType.Element) {
        foreach (XmlAttribute at in n.Attributes) {
           //Console.Out.WriteLine("\tattribute:'{0}' 
           //   value:'{1}'", at.Name, at.Value);
        }
        if (n.HasChildNodes) {
          GetNode(n);
        }
    }
    }
    //Remove last character '/'
    xPath.Remove(index, node.Name.Length + 1);
    try{
    if(!nodeNames.Keys.Equals(xPath.ToString())){
        string xpath = xPath.ToString();//.Substring(0, xPath.Length - 1);
        if (!xPath.ToString().Equals("//")) {
        nodeNames.Add(xPath.ToString(), xpath);
        }
    }
    }catch{}
}

fig_1.JPG

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();

    //Exit function no root found
    if (string.IsNullOrEmpty(rootName))
    return;

    //Add root element
    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);

    //Recursive call to fill all nested nodes
    foreach (XmlNode node in addNodeList) {
    TreeNode eltNode = new TreeNode(node.Name);
    root.Nodes.Add(eltNode);
    if (node.HasChildNodes) {
        AddTreeNode(eltNode, node);
    }       
    }
    tvNodes.ExpandAll();
}

fig_2.JPG

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.

fig_3.JPG

 /// <summary>
/// Update @key attribute value in appSettings node
/// or route to generic fuction for attributes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
    }
}
/// <summary>
/// Update attribute value and save XML/Config document.
/// </summary>
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:

fig_4.JPG

Set CustomActionData to /dir="[TARGETDIR]\" – this value or the installation directory will be passed as an argument to the installer class:

fig_5.JPG

public override void Install(System.Collections.IDictionary stateSaver) {
    base.Install(stateSaver);
    Process();
}

private void Process() {
    string dir = "";
    try {
    //this is the value from setup project CustomActionData
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Slava Khristich
Software Developer (Senior) Tateeda Media Networks
United States United States

Software development is my passion as well as photography.


If you got a sec stop by to see my photography work at http://sk68.com


Tateeda Media Network

Comments and Discussions

 
GeneralGreat Pinmemberfratester26-May-09 2:12 
GeneralUseful Pinmemberscott.leckie10-Nov-08 18:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140902.1 | Last Updated 10 Nov 2008
Article Copyright 2008 by Slava Khristich
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid