Sunday 15 April 2012

CRUD operation on XML file


In sometimes, we may develop some projects which will not have much interaction with database operations. It may require little bit of data to be stored in database and to start some operation and updated in sometimes. So in such times, we might decide not to go for any heavy databases such as SQL Server, Oracle etc., instead we can use some text, XML format of files to keep track of records.

I had written an article on When, Where and Why XML used for understanding the XML usage. Even it is a small post, it provide some important information about XML such as the XML can be used as light database file which can be used for storage operation.

In this post, I am planning to take XML files and show a CRUD (Create Read, Update and Delete) operation using employee information.

Before going to the implementation, the use case of this employee screen will be as below.
  1. Need an Employee maintenance screen which should have list of employees (GridView) and an entry screen to enter the employee information.
  2. Initially the page should load with list of employees fetching from the XML file.
  3. User can add a new employee by filling in the entry screen and press Save button. The information must be added in the XML file and show in the GridView.
  4. User can update existing employee information by selecting the employee record. By selecting a record, the information must be filled in the entry screen for modification. The user can change the details and press Save button.
  5. User can delete an existing record by selecting Delete link in the employee record. On selection of the Delete link, the employee record must be deleted from the XML file and refresh the GridView.
As you can see in the use case, this is the basic functionality which everyone will do on databases. So let us take this requirement and implement using XML file as back end.

The structure of XML file

The structure of XML file which store the employee information will be
<?xml version="1.0" encoding="utf-8"?>
<Employees>
  <Employee>
    <Id>1</Id>
    <EmployeeId>EMP001</EmployeeId>
    <Name>Thirumalai</Name>
    <Role>Architect</Role>
    <Gender>Male</Gender>
    <DOB>29/Feb/1980</DOB>
    <DOJ>27/Apr/2011</DOJ>
  </Employee>
  <Employee>
    <Id>2</Id>
    <EmployeeId>EMP002</EmployeeId>
    <Name>Rajaram</Name>
    <Role>DeliveryManager</Role>
    <Gender>Male</Gender>
    <DOB>04/Apr/1972</DOB>
    <DOJ>11/Apr/2005</DOJ>
  </Employee>
</Employees>

Note:
  1. In this example, I am creating the XML file if the file does not exist. So it is not required to have XML file in the specified directory.
  2. The directory path where the XML file will be stored must be updated in the Web.config file as DatabasePath under appSettings.
    <appSettings>
      <add key="DataBasePath" value="D:\Blog\CRUDonXML\CRUDonXML\Database\" />
    </appSettings>

Employee entity class

The Employee entity class contains the attributes of employee information.
public class Employee
{
    public long Id { get; set; }
    public string EmployeeId { get; set; }
    public string Name { get; set; }
    public Role Role { get; set; }
    public Gender Gender { get; set; }
    public DateTime DOB { get; set; }
    public DateTime DOJ { get; set; }
}
I also have two enum types, which specifies of Gender and Role of the employee.

Data Access Layer (EmployeeDAL.cs)

The EmployeeDAL class contains the logic carried out on XML file. I have defined the following methods for each operation. The comments in each method explains the usage of the same.
/// <summary>
/// Class for handling Employee data operation logic
/// </summary>
public class EmployeeDAL
{
    string strFileName = string.Empty;
    public EmployeeDAL()
    {
        // Getting the XML file name at the initialization
        strFileName = ConfigurationManager.AppSettings["DataBasePath"] + "Employee.xml";
    }
    /// <summary>
    /// This method is used for creating a new employee information in XML file
    /// </summary>
    /// <param name="employee">employee object</param>
    /// <returns>True - Success, False - Failure</returns>
    public bool Create(Employee employee)
    {
        try
        {
            // Checking if the file exist
            if (!File.Exists(strFileName))
            {
                // If file does not exist in the database path, create and store an empty Employees node
                XmlTextWriter textWritter = new XmlTextWriter(strFileName, null);
                textWritter.WriteStartDocument();
                textWritter.WriteStartElement("Employees");
                textWritter.WriteEndElement();
                textWritter.Close();
            }

            // Create the XML docment by loading the file
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(strFileName);

            // Creating Employee node
            XmlElement subNode = xmlDoc.CreateElement("Employee");

            // Getting the maximum Id based on the XML data already stored
            string strId = CommonMethods.GetMaxValue(xmlDoc, "Employees" + "/" + "Employee" + "/" + "Id").ToString();

            // Adding Id column. Auto generated column
            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "Id", strId));
            xmlDoc.DocumentElement.AppendChild(subNode);

            // Adding Employee column. Note - The employee Id is build automatically. No input required
            string strEmployeeId = "EMP" + (Convert.ToInt32(strId) + 1).ToString().PadLeft(3, '0');
            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "EmployeeId", strEmployeeId));
            xmlDoc.DocumentElement.AppendChild(subNode);

            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "Name", employee.Name));
            xmlDoc.DocumentElement.AppendChild(subNode);

            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "Role", employee.Role.ToString()));
            xmlDoc.DocumentElement.AppendChild(subNode);

            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "Gender", employee.Gender.ToString()));
            xmlDoc.DocumentElement.AppendChild(subNode);

            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "DOB", employee.DOB.ToString("dd/MMM/yyyy")));
            xmlDoc.DocumentElement.AppendChild(subNode);

            subNode.AppendChild(CommonMethods.CreateXMLElement(xmlDoc, "DOJ", employee.DOJ.ToString("dd/MMM/yyyy")));
            xmlDoc.DocumentElement.AppendChild(subNode);

            // Saving the file after adding the new employee node
            xmlDoc.Save(strFileName);

            return true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// This method is used for updating an existing employee information
    /// </summary>
    /// <param name="employee">Employee object</param>
    /// <returns>True - Success, False - Failure</returns>
    public bool Update(Employee employee)
    {

        try
        {
            if (File.Exists(strFileName))
            {
                XmlDocument objXmlDocument = new XmlDocument();
                objXmlDocument.Load(strFileName);

                // Getting a particular Employee by selecting using Xpath query
                XmlNode node = objXmlDocument.SelectSingleNode("//Employee[Id='" + employee.Id + "']");

                if (node != null)
                {
                    // Assigining all the values
                    node.SelectNodes("EmployeeId").Item(0).FirstChild.Value = employee.EmployeeId;
                    node.SelectNodes("Name").Item(0).FirstChild.Value = employee.Name;
                    node.SelectNodes("Role").Item(0).FirstChild.Value = employee.Role.ToString();
                    node.SelectNodes("Gender").Item(0).FirstChild.Value = employee.Gender.ToString();
                    node.SelectNodes("DOB").Item(0).FirstChild.Value = employee.DOB.ToString("dd/MMM/yyyy");
                    node.SelectNodes("DOJ").Item(0).FirstChild.Value = employee.DOJ.ToString("dd/MMM/yyyy");
                }
                // Saving the file
                objXmlDocument.Save(strFileName);

                return true;
            }
            else
            {
                Exception ex = new Exception("Database file does not exist in the folder");
                throw ex;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    /// <summary>
    /// Thie method is used for delete an existing employee information from XML file
    /// </summary>
    /// <param name="id">Id of the employee</param>
    /// <returns>True - Success, False - Failure</returns>
    public bool Delete(string id)
    {

        try
        {
            if (File.Exists(strFileName))
            {
                XmlDocument objXmlDocument = new XmlDocument();
                objXmlDocument.Load(strFileName);

                XmlNode node = objXmlDocument.SelectSingleNode("//Employee[Id='" + id + "']");

                if (node != null)
                {
                    objXmlDocument.ChildNodes[1].RemoveChild(node);
                }
                objXmlDocument.Save(strFileName);

                return true;
            }
            else
            {
                Exception ex = new Exception("Database file does not exist in the folder");
                throw ex;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// This method is for getting all the employee details and return as List collection
    /// </summary>
    /// <returns>Employees in IList</returns>
    public IList<Employee> GetAll()
    {
        try
        {
            if (File.Exists(strFileName))
            {
                // Loading the file into XPath document
                XPathDocument doc = new XPathDocument(strFileName);
                XPathNavigator nav = doc.CreateNavigator();

                XPathExpression exp = nav.Compile("/Employees/Employee"); // Getting all employees

                // Sorting the records by Employee Id
                exp.AddSort("EmployeeId", System.Xml.XPath.XmlSortOrder.Ascending, System.Xml.XPath.XmlCaseOrder.None, "", System.Xml.XPath.XmlDataType.Text);

                XPathNodeIterator iterator = nav.Select(exp);
                IList<Employee> objEmployees = new List<Employee>();

                while (iterator.MoveNext())
                {
                    XPathNavigator nav2 = iterator.Current.Clone();

                    Employee objEmployee = new Employee();
                    objEmployee.Id = Convert.ToInt64(nav2.Select("//Employee").Current.SelectSingleNode("Id").InnerXml);
                    objEmployee.EmployeeId = nav2.Select("//Employee").Current.SelectSingleNode("EmployeeId").InnerXml;
                    objEmployee.Name = nav2.Select("//Employee").Current.SelectSingleNode("Name").InnerXml;
                    objEmployee.Role = (Role)Enum.Parse(typeof(Role), nav2.Select("//Employee").Current.SelectSingleNode("Role").InnerXml);
                    objEmployee.Gender = (Gender)Enum.Parse(typeof(Gender), nav2.Select("//Employee").Current.SelectSingleNode("Gender").InnerXml);
                    objEmployee.DOB = Convert.ToDateTime(nav2.Select("//Employee").Current.SelectSingleNode("DOB").InnerXml);
                    objEmployee.DOJ = Convert.ToDateTime(nav2.Select("//Employee").Current.SelectSingleNode("DOJ").InnerXml);

                    objEmployees.Add(objEmployee);
                }
                return objEmployees;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return null;
    }

    /// <summary>
    /// This method is used to fetch a particular employee details
    /// </summary>
    /// <param name="id">Id of the employee</param>
    /// <returns>Employee object</returns>
    public Employee Get(string id)
    {
        try
        {
            if (File.Exists(strFileName))
            {
                Employee objEmployee = new Employee();

                XPathDocument doc = new XPathDocument(strFileName);
                XPathNavigator nav = doc.CreateNavigator();
                XPathNodeIterator iterator;

                iterator = nav.Select("//Employee[Id='" + id + "']");

                while (iterator.MoveNext())
                {
                    XPathNavigator nav2 = iterator.Current.Clone();

                    objEmployee.Id = Convert.ToInt64(nav2.Select("//Employee").Current.SelectSingleNode("Id").InnerXml);
                    objEmployee.EmployeeId = nav2.Select("//Employee").Current.SelectSingleNode("EmployeeId").InnerXml;
                    objEmployee.Name = nav2.Select("//Employee").Current.SelectSingleNode("Name").InnerXml;
                    objEmployee.Role = (Role)Enum.Parse(typeof(Role), nav2.Select("//Employee").Current.SelectSingleNode("Role").InnerXml);
                    objEmployee.Gender = (Gender)Enum.Parse(typeof(Gender), nav2.Select("//Employee").Current.SelectSingleNode("Gender").InnerXml);
                    objEmployee.DOB = Convert.ToDateTime(nav2.Select("//Employee").Current.SelectSingleNode("DOB").InnerXml);
                    objEmployee.DOJ = Convert.ToDateTime(nav2.Select("//Employee").Current.SelectSingleNode("DOJ").InnerXml);
                }
                return objEmployee;
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return null;
    }
}
I am using some methods which are used for doing common operations such as CreateXMLElement, GetMaxValue as used in the previous shown code.
public class CommonMethods
{
    public static XmlElement CreateXMLElement(XmlDocument xmlDoc, string name, string value)
    {
        XmlElement xmlElement = xmlDoc.CreateElement(name);
        XmlText xmlText = xmlDoc.CreateTextNode(value);
        xmlElement.AppendChild(xmlText);
        return xmlElement;
    }
    public static int GetMaxValue(XmlDocument xmlDoc, string nodeNameToSearch)
    {
        int intMaxValue = 0;
        XmlNodeList nodelist = xmlDoc.SelectNodes(nodeNameToSearch);
        foreach (XmlNode node in nodelist)
        {
            if (Convert.ToInt32(node.InnerText) > intMaxValue)
            {
                intMaxValue = Convert.ToInt32(node.InnerText);
            }
        }
        return (intMaxValue + 1);
    }
}
The User Interface

The user interface is simple as we do in normal way. The ASPX script file will be
<style type="text/css">
.Message
{
    font-size:14px;
    font-weight:bold;
    color:Red;
    text-align:left;
}
</style>
<div>
    <table>
        <tr>
            <td>Employee Id</td>
            <td><asp:TextBox ID="txtEmployeeId" runat="server" Width="120px" ReadOnly="true"></asp:TextBox> </td>
            <td style="width:100px"></td>
            <td>DOB</td>
            <td>
                <asp:TextBox ID="txtDOB" runat="server" Width="150px"></asp:TextBox>
                <asp:ImageButton runat="Server" ID="ImageDOB" ImageUrl="~/Images/Calendar_scheduleHS.png" AlternateText="Click to show calendar" /><br />
                <AjaxToolkit:CalendarExtender ID="CalendarExtenderDOB" runat="server" TargetControlID="txtDOB" PopupButtonID="ImageDOB" Format="MMMM d, yyyy" />
                <asp:RequiredFieldValidator ID="valDOB" runat="server" ErrorMessage="DOB cannot be Empty" Display="None" ControlToValidate="txtDOB" ValidationGroup="Save"></asp:RequiredFieldValidator>
            </td>
        </tr>
        <tr>
            <td>Name</td>
            <td>
                <asp:TextBox ID="txtName" runat="server" Width="250px"></asp:TextBox>
                <asp:RequiredFieldValidator ID="valName" runat="server" ErrorMessage="Name cannot be Empty" Display="None" ControlToValidate="txtName" ValidationGroup="Save"></asp:RequiredFieldValidator>
            </td>
            <td></td>
            <td>DOJ</td>
            <td>
                <asp:TextBox ID="txtDOJ" runat="server" Width="150px"></asp:TextBox>
                <asp:ImageButton runat="Server" ID="ImageDOJ" ImageUrl="~/Images/Calendar_scheduleHS.png" AlternateText="Click to show calendar" /><br />
                <AjaxToolkit:CalendarExtender ID="CalendarExtenderDOJ" runat="server" TargetControlID="txtDOJ" PopupButtonID="ImageDOJ" Format="MMMM d, yyyy" />
                <asp:RequiredFieldValidator ID="valDOJ" runat="server" ErrorMessage="DOJ cannot be Empty" Display="None" ControlToValidate="txtDOJ" ValidationGroup="Save"></asp:RequiredFieldValidator>
            </td>
        </tr>
        <tr>
            <td>Gender</td>
            <td>
                <asp:DropDownList ID="DDLGender" runat="server" Width="260px">
                </asp:DropDownList>
                <asp:RequiredFieldValidator ID="valGender" runat="server" ErrorMessage="Role cannot be Empty" Display="None" ControlToValidate="DDLGender" ValidationGroup="Save"></asp:RequiredFieldValidator>
            </td>
            <td></td>
            <td>Role</td>
            <td>
                <asp:DropDownList ID="DDLRole" runat="server" Width="260px">
                </asp:DropDownList>
                <asp:RequiredFieldValidator ID="valRole" runat="server" ErrorMessage="Role cannot be Empty" Display="None" ControlToValidate="DDLRole" ValidationGroup="Save"></asp:RequiredFieldValidator>
            </td>
        </tr>
        <tr>
            <td colspan="5" style="text-align:center">
                <asp:Button ID="btnSave" runat="server" Width="100px" Text="Save" onclick="btnSave_Click" ValidationGroup="Save" />
                <asp:Button ID="btnClear" runat="server" Width="100px" Text="Clear" onclick="btnClear_Click" />
            </td>
        </tr>
        <tr>
            <td colspan="5">
                <asp:Label runat="server" ID="lblMessage" Text="" CssClass="Message"></asp:Label>
                <asp:ValidationSummary ID="valSummary" ValidationGroup="Save" runat="server" CssClass="Message" DisplayMode="List" ShowSummary="true" />
                <asp:HiddenField ID="hndId" runat="server" Value="" />
            </td>
        </tr>
    </table>
</div>
<div>
    <asp:GridView ID="grdViewEmployee" runat="server"
        AllowPaging="True" AutoGenerateColumns="False" TabIndex="1"
        DataKeyNames="EmployeeID" Width="100%" BackColor="White" GridLines="Vertical"
        CellPadding="3" BorderStyle="None" BorderWidth="1px" BorderColor="#999999" 
        OnRowDataBound="grdViewEmployee_RowDataBound"
        OnPageIndexChanging="grdViewEmployee_PageIndexChanging"
        onselectedindexchanging="grdViewEmployee_SelectedIndexChanging" 
        onrowdeleting="grdViewEmployee_RowDeleting">
        <Columns>
            <asp:CommandField ShowSelectButton="True" HeaderText="Select" ItemStyle-HorizontalAlign="Center" />
            <asp:CommandField ShowDeleteButton="True" HeaderText="Delete" ItemStyle-HorizontalAlign="Center" />
            <asp:BoundField DataField="Id" HeaderText="Id" />
            <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID" />
            <asp:BoundField DataField="Name" HeaderText="Name" />
            <asp:BoundField DataField="Role" HeaderText="Role" />
            <asp:BoundField DataField="Gender" HeaderText="Gender" />
            <asp:TemplateField HeaderText="DOB" SortExpression="DOB">
                <ItemTemplate >
                    <asp:Label ID="lblDOB" Text='<%# Convert.ToDateTime(Eval("DOB")).ToString("dd-MMM-yyyy") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="DOJ" SortExpression="DOJ">
                <ItemTemplate >
                    <asp:Label ID="lblDOJ" Text='<%# Convert.ToDateTime(Eval("DOJ")).ToString("dd-MMM-yyyy") %>' runat="server"></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
        <RowStyle BackColor="#EEEEEE" ForeColor="Black" />
        <FooterStyle BackColor="#CCCCCC" ForeColor="Black" />
        <PagerStyle BackColor="#999999" ForeColor="Black" HorizontalAlign="Right" />
        <SelectedRowStyle BackColor="#008A8C" Font-Bold="true" ForeColor="White" />
        <HeaderStyle BackColor="#000084" Font-Bold="True" ForeColor="White" />
        <AlternatingRowStyle BackColor="#DCDCDC" />
    </asp:GridView>
</div>
The code behind
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            FillRoleDropdown();
            FillGenderDropdown();
            BindGrid();
            ClearScreen();
        }
    }
    private void BindGrid()
    {
        EmployeeDAL objEmployeeDAL = new EmployeeDAL();
        grdViewEmployee.DataSource = objEmployeeDAL.GetAll();
        grdViewEmployee.DataBind();
    }

    private void FillGenderDropdown()
    {
        DDLGender.Items.Clear();
        foreach (var value in Enum.GetNames(typeof(Gender)))
            DDLGender.Items.Add(new ListItem(value.ToString()));
        DDLGender.SelectedIndex = -1;
    }

    private void FillRoleDropdown()
    {
        DDLRole.Items.Clear();
        foreach (var value in Enum.GetNames(typeof(Role)))
            DDLRole.Items.Add(new ListItem(value.ToString()));
        DDLRole.SelectedIndex = -1;
    }

    protected void grdViewEmployee_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            e.Row.Cells[1].Attributes.Add("onclick", "return confirm('Are you sure want to delete?')");
        }
    }

    protected void grdViewEmployee_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
        grdViewEmployee.PageIndex = e.NewPageIndex;
        grdViewEmployee.SelectedIndex = -1;
        BindGrid();
    }

    protected void grdViewEmployee_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
    {
        hndId.Value = grdViewEmployee.Rows[e.NewSelectedIndex].Cells[2].Text.Trim();
        if (hndId.Value.Length > 0)
        {
            EmployeeDAL objEmployeeDAL = new EmployeeDAL();
            Employee employee = objEmployeeDAL.Get(hndId.Value);

            txtEmployeeId.Text = employee.EmployeeId;
            txtName.Text = employee.Name;
            DDLRole.SelectedValue = employee.Role.ToString();
            DDLGender.SelectedValue = employee.Gender.ToString();
            txtDOB.Text = employee.DOB.ToString("dd-MMM-yyyy");
            txtDOJ.Text = employee.DOJ.ToString("dd-MMM-yyyy");
            lblMessage.Text = "";
        }
    }

    protected void grdViewEmployee_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
        try
        {
            string strId = grdViewEmployee.Rows[e.RowIndex].Cells[2].Text.Trim();
            if (strId.Length > 0)
            {
                EmployeeDAL objEmployeeDAL = new EmployeeDAL();
                objEmployeeDAL.Delete(strId);

                ClearScreen();
                BindGrid();
                lblMessage.Text = "Record deleted successfully";
            }
        }
        catch (Exception ex)
        {
            lblMessage.Text = "There is an error occured while processing the request. Please verify the code!";
        }
    }

    protected void btnSave_Click(object sender, EventArgs e)
    {
        try
        {
            if (!Page.IsValid) return;

            Employee employee = new Employee();
            employee.EmployeeId = txtEmployeeId.Text;
            employee.Name = txtName.Text;
            employee.Role = (Role)Enum.Parse(typeof(Role), DDLRole.SelectedValue.ToString());
            employee.Gender = (Gender)Enum.Parse(typeof(Gender), DDLGender.SelectedValue.ToString());
            employee.DOB = Convert.ToDateTime(txtDOB.Text);
            employee.DOJ = Convert.ToDateTime(txtDOJ.Text);
            employee.Id = Convert.ToInt64(hndId.Value);

            EmployeeDAL objEmployeeDAL = new EmployeeDAL();
            if ((hndId.Value.Trim().Length > 0) && 
                (Convert.ToInt64(hndId.Value) > 0))
                objEmployeeDAL.Update(employee);
            else
                objEmployeeDAL.Create(employee);

            ClearScreen();
            BindGrid();
            lblMessage.Text = "Record saved successfully";
        }
        catch (Exception ex)
        {
            lblMessage.Text = "There is an error occured while processing the request. Please verify the code!";
        }
    }

    protected void btnClear_Click(object sender, EventArgs e)
    {
        ClearScreen();
    }

    private void ClearScreen()
    {
        txtEmployeeId.Text = "Auto Generated";
        txtName.Text = "";
        DDLRole.SelectedIndex = -1;
        DDLGender.SelectedIndex = -1;
        txtDOB.Text = "";
        txtDOJ.Text = "";
        hndId.Value = "0";
    }
}
The output of the implementation will be as below







Download the working example of the source code in C# here and in VB here.


2 Responses to “CRUD operation on XML file”

  • chaitu says:
    19 August 2013 at 09:23

    superb article

  • Anonymous says:
    12 June 2014 at 21:16

    You have only two(2) level deep XML stack for which You write hundred of lines of code. On top of that Your code is capable to serve only the structure from the example and nothing else.

Post a Comment