3
\$\begingroup\$

The task is to build a WinForm app which populates a tree from 3 different data sources and shows its content.

  • 1 ComboBox at top - to choose datasource. (File System, XMLFile, SQLDB)
  • 1 Button next to ComboBox - to select datasource path. (Or Connection String)
  • 1 TreeView Control at left - to show tree.
  • 1 RichTextBox to show content. (in case of file system showing txt files content).

MainForm.cs:

public partial class MainForm : Form
    {
        private TreeProvider treeProvider;
        private Action pathSelectFunction;

        public MainForm()
        {
            InitializeComponent();
            InitializeDataSourceComboBox();
        }

        private void InitializeDataSourceComboBox()
        {
            DataSourceComboBox.DataSource = ConfigurationManager.DataSourceTypes.dataSourceTypes;
            DataSourceComboBox.ValueMember = "Type";
            DataSourceComboBox.DisplayMember = "Name";
        }


        private void DataSourceComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            DataSourceType selItem = (DataSourceType)DataSourceComboBox.SelectedItem;
            switch (selItem.Type)
            {
                case "XMLFile":
                    SelectDataSourcePathButton.Text = "Select XML File";
                    pathSelectFunction = SelectXMLFilePath;
                    break;
                case "FileSystem":
                    SelectDataSourcePathButton.Text = "Select Root Folder";
                    pathSelectFunction = SelectRootFolderPath;
                    break;
                case "SQLDB":
                    SelectDataSourcePathButton.Text = "Specify SQL connection string";               
                    break;
                default:
                    return;
            }
        }


        private void SelectDataSourcePathButton_Click(object sender, EventArgs e)
        {
            pathSelectFunction();
        }

        private void SelectXMLFilePath()
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "XML files (*.xml)|*.xml";
            DialogResult result =  fileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                treeProvider=new TreeProvider(new XMLTreeSource(fileDialog.FileName));
                populateTreeView();
            }
          }

        private void SelectRootFolderPath()
        {
            FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();

            DialogResult result = folderBrowserDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                treeProvider = new TreeProvider(new FileSystemTreeSource(folderBrowserDialog.SelectedPath));
                populateTreeView();
            }
        }

        private void SpecifySQLConnection()
        {
            //TODO
        }

        private void populateTreeView()
        {
            GetAndAddRootNodesToTreeView(MyTreeView);
        }


        private void TreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            MyTreeNode treeViewNode = (MyTreeNode)e.Node;
            MyTreeNode firstChildNode = (MyTreeNode)treeViewNode.Nodes[0];
            if (firstChildNode.IsVirtual)
            {
                treeViewNode.Nodes.Clear();
                GetAndAddChildNodesToTreeView((TreeView)sender, treeViewNode);
            }
        }
        private void GetAndAddChildNodesToTreeView(TreeView treeView, MyTreeNode nodeToAdd)
        {
            nodeToAdd.Nodes.Clear();
            foreach (var data in treeProvider.GetChildNodes(nodeToAdd.Data))
            {
                MyTreeNode newTreeNode = new MyTreeNode(data);
                newTreeNode.Text = data.Identity;
                if (data.ChildCount > 0)
                {
                    newTreeNode.Nodes.Add(new MyTreeNode(true));
                }
                nodeToAdd.Nodes.Add(newTreeNode);
            }
        }
        private void GetAndAddRootNodesToTreeView(TreeView treeView)
        {
            treeView.Nodes.Clear();
            foreach (var data in treeProvider.GetRootNodes())
            {
                MyTreeNode newTreeNode = new MyTreeNode(data);
                newTreeNode.Text = data.Identity;

                if (data.ChildCount > 0)
                {
                    newTreeNode.Nodes.Add(new MyTreeNode(true));
                }

                treeView.Nodes.Add(newTreeNode);
            }
        }


        private void TreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            MyTreeNode node = (MyTreeNode)e.Node;
            rtbDataViewer.Text = node.Data.Content;
        }


    }

    public class MyTreeNode : TreeNode
    {
        public NodeBase Data { get; set; }

        private bool isVirtual=false;
        public bool IsVirtual
        {
            get { return isVirtual; }
            set
            {
                if (value)
                {
                    Text = "Please Wait";
                }
                isVirtual = value;
            }
        }
        public MyTreeNode(NodeBase data)
        {
            Data = data;
        }
        public MyTreeNode(bool isVirtual = false)
        {
            IsVirtual = isVirtual;
        }

    }

NodeBase.cs:

    public class NodeBase
{
    public string Identity { get; set; }
    public string ParentIdentity { get; set; }
    public string Content { get; set; }
    public int ChildCount { get; set; }

    public NodeBase()
    {

    }



    public NodeBase(string identity, string parentIdentity, string content, int childCount)
    {
        Identity = identity;
        ParentIdentity = parentIdentity;
        Content = content;
        ChildCount = childCount;
    }
}

ITreeSource.cs:

    public interface ITreeSource
{
    IEnumerable<NodeBase> GetRootNodes();
    IEnumerable<NodeBase> GetChildNodes(NodeBase node);
}

TreeSource.cs:

   public class TreeProvider : ITreeSource
{
    private ITreeSource provider { get; set; }

    public TreeProvider(ITreeSource provider)
    {
        this.provider = provider;
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        return provider.GetRootNodes();
    }

    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        return provider.GetChildNodes(node);
    }

}

FileSystemTreeSource.cs:

  class FileSystemTreeSource : ITreeSource
{
    DirectoryInfo rootDirectory;
    public FileSystemTreeSource(string rootDirectoryPath)
    {
        rootDirectory = new DirectoryInfo(rootDirectoryPath);
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        foreach (DirectoryInfo directory in rootDirectory.GetDirectories())
        {
            if (GetDirectoryNode(directory) != null)
            {
                yield return GetDirectoryNode(directory);
            }
        }
        foreach (var file in rootDirectory.GetFiles("*.txt"))
        {
                           if (GetFileNode(file) != null)
            {
                yield return GetFileNode(file);
            }
        }
    }


    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        DirectoryInfo currentDirectoryInfo = new DirectoryInfo(node.ParentIdentity + "\\" + node.Identity);
        foreach (var directory in currentDirectoryInfo.GetDirectories())
        {
            if (GetDirectoryNode(directory)!=null)
            {
                yield return GetDirectoryNode(directory);
            }
        }
        foreach (var file in currentDirectoryInfo.GetFiles("*.txt"))
        {
            if (GetFileNode(file) != null)
            {
                yield return GetFileNode(file);
            }
        }
    }

    private NodeBase GetDirectoryNode(DirectoryInfo directory)
    {
        NodeBase nodeBase = new NodeBase() ;
        try{
            nodeBase.Identity = directory.Name;
            nodeBase.ParentIdentity = directory.Parent.FullName;
            nodeBase.Content = null;
            nodeBase.ChildCount=directory.GetDirectories().Count();
            nodeBase.ChildCount += directory.GetFiles("*.txt").Count() ;
        }
        catch
        {
            //log
            return null;
        }
        return nodeBase;
    }
    private NodeBase GetFileNode(FileInfo file)
    {
        NodeBase nodeBase = new NodeBase();
        try
        {
            nodeBase.Identity = file.Name;
            nodeBase.ParentIdentity = file.DirectoryName;
            using (StreamReader streamReader = file.OpenText())
            {
                nodeBase.Content = streamReader.ReadToEnd();
            }
            nodeBase.ChildCount = 0;
        }
        catch
        {
            //log
            return null;
        }
        return nodeBase;
    }
}

SQTreeSource.cs:

   class SQLTreeSource :ITreeSource
    {
        string connectionString;
        string tableName;
        string keyColumnName;
        string parentkeyColumnName;
        string contentColumnName;

        public SQLTreeSource(string connectionString,
                                string tableName, 
                                string keyColumnName, 
                                string parentkeyColumnName, 
                                string contentColumnName)
        {
            this.connectionString = connectionString;
            this.tableName = tableName;
            this.keyColumnName = keyColumnName;
            this.parentkeyColumnName = parentkeyColumnName;
            this.contentColumnName = contentColumnName;
        }

       private SqlCommand GetChildNodesCountCommand(SqlConnection connection, int key)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT Count(" + keyColumnName + ") FROM " + tableName + " WHERE " + parentkeyColumnName + "=@keyColumnName";
            command.Parameters.AddWithValue("@keyColumnName", key);
            return command;
        }

        public int GetChildNodesCount(int key)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetChildNodesCountCommand(conn, key))
                {
                    var result = comm.ExecuteScalar();
                    return (int)result;
                }
            }
        }
        private SqlCommand GetRootNodesCommand(SqlConnection connection)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT * FROM " + tableName + " WHERE " + parentkeyColumnName + " is null";

            return command;
        }
        public IEnumerable<NodeBase> GetRootNodes()
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetRootNodesCommand(conn))
                {
                    var reader = comm.ExecuteReader();
                    while (reader.Read())
                    {
                        yield return new NodeBase(reader[keyColumnName].ToString(),
                                        reader[parentkeyColumnName].ToString(),
                                        reader[contentColumnName].ToString(),
                                        GetChildNodesCount((int)reader[keyColumnName]));
                    }
                }
            }
        }

        private SqlCommand GetChildNodesCommand(SqlConnection connection, string key)
        {
            SqlCommand command = new SqlCommand();
            command.Connection = connection;
            command.CommandText = "SELECT * FROM " + tableName + " WHERE " + parentkeyColumnName + "=@key";
            command.Parameters.AddWithValue("@key", key);
            return command;
        }
        public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                using (SqlCommand comm = GetChildNodesCommand(conn, node.Identity))
                {
                    var reader = comm.ExecuteReader();
                    while (reader.Read())
                    {
                        yield return new NodeBase(reader[keyColumnName].ToString(), 
                                        reader[parentkeyColumnName].ToString(),
                                        reader[contentColumnName].ToString(),
                                        GetChildNodesCount((int)reader[keyColumnName]));
                    }
                }
            }
        }
    }

XMLTreeSource.cs:

public class XMLTreeSource : ITreeSource
{
    string xmlFilePath;
    XmlDocument xmlDocument;

    public XMLTreeSource(string xmlFilePath)
    {
        this.xmlFilePath = xmlFilePath;
    }

    private XmlDocument GetXMLDOM()
    {
        if (xmlDocument == null)
        {
            this.xmlDocument = new XmlDocument();
            xmlDocument.Load(xmlFilePath);
        }
        return xmlDocument;
    }

    public IEnumerable<NodeBase> GetRootNodes()
    {
        int ChildCount = GetXMLDOM().DocumentElement.ChildNodes.Count;
        yield return new NodeBase(GetXMLDOM().DocumentElement.Name, null, null, ChildCount);
    }
    public IEnumerable<NodeBase> GetChildNodes(NodeBase node)
    {
        XmlNode parentNode = GetXMLDOM().SelectSingleNode(".//" + node.Identity);
        foreach (XmlNode cNode in parentNode)
        {
            int ChildCount = cNode.ChildNodes.Count;
            yield return new NodeBase(cNode.Name, node.Identity, cNode.Value, ChildCount);
        }
    }



}
\$\endgroup\$
3
  • \$\begingroup\$ @SurenGalstyan Why did you again change the title of your question to a non-descriptive one? "C# Developer Interview Test" doesn't tell us anything relevant. As stated in the very first comment: "write a title that summarizes what your code does" \$\endgroup\$
    – BCdotWEB
    Aug 3, 2015 at 11:24
  • \$\begingroup\$ @BCdotWEB In such cases you should apply a rollback :) I see you have the necessary reputation to do this. In case of a rollback-war you should flag for moderator attention. They have the tools to deal with things like this. \$\endgroup\$
    – Vogel612
    Aug 3, 2015 at 11:33
  • \$\begingroup\$ @BCdotWeb, I just want to figure out the reason, why already 2 days no answer and no comment. Just have seen another question with same request as mine link. So I just want to check out how title can improve reaction to my question. The result +20 views in 30 minutes. However no answers. Only you guys!!! Now I am sure that the problem in my question. Maybe it's too long, too bad, too boring or something else. Next time I will try to be more interesting. \$\endgroup\$
    – s_galstyan
    Aug 3, 2015 at 20:30

1 Answer 1

4
\$\begingroup\$
  1. The usual whitespace/egyptian brackets mumbo-jumbo.
    NodeBase nodeBase = new NodeBase() ;
    try{
        nodeBase.Identity = directory.Name;
        nodeBase.ParentIdentity = directory.Parent.FullName;

Should be:

    NodeBase nodeBase = new NodeBase();

    try
    {
        nodeBase.Identity = directory.Name;
        nodeBase.ParentIdentity = directory.Parent.FullName;

Etc.

  1. Did you mean for the internal classes?

    class FileSystemTreeSource : ITreeSource
    class SQLTreeSource : ITreeSource
    public class XMLTreeSource : ITreeSource
    

Notice that XMLTreeSource is the only one that is public.

If you did want them to be internal, I suggest adding the internal keyword in front of them to be explicit about it.

  1. Naming nitpicks:
    1. XMLTreeSource should be XmlTreeSource;
    2. GetXMLDOM should be GetXmlDom;
    3. SQLTreeSource should be SqlTreeSource;
    4. etc.

This is to fall in line with the best-practices of PascalCasing.

  1. Your SQL should be parameterized queries instead. As it stands you are very open to SQL injection. (What would happen if I specified a tableName of Table; DROP TABLE Customers;? Not good things, I suggest.)

  2. Usually I tend to favour immutability over mutability.

public string Identity { get; set; }
public string ParentIdentity { get; set; }
public string Content { get; set; }
public int ChildCount { get; set; }

Those set actions should be private.

Unless you absolutely have a need for mutability, you should try to favour immutability. It leaves you less vulnerable to problems with data validation, et al.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your answer. Concerning point 4, as far as know there is no clear way to set Table Name in SQL command with parameters. Am I wrong? \$\endgroup\$
    – s_galstyan
    Sep 3, 2015 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.