Monday, November 30, 2009

Updating a resource file.

Recently one of our client had this requirement where they wanted to customize the texts appearing in the website as and when needed i.e. suppose they have a heading text in the login page which says “Login” and at some point they would like to change the text to something like “Login using email and password” without tinkering with the code. This is just an e.g., the client wanted each and every text appearing in the UI to be customizable.

The above requirement can be easily achieved using resource files. The text which are displayed can be placed in the resource files and whenever there is a change in the text just editing the resource file will take care of displaying the new text. Now you might be thinking what is the big deal in this, yes there is no rocket science involved in editing a resource file, just open any text editor and make the necessary modification. Asking the client to do that will be too much in the asking. As resource files are stored as XML, any syntax problem can bring down the whole application. So the next obvious solution is to provide a web page to edit the existing resource files.

By itself .NET doesn’t provide any class to edit and update resource files. There are classes to read and create resource files but nothing as such to update a resource files. If you want to simply read the content of a resource file you can make use of “System.Resources.ResourceReader” class and if you want to dynamically create a resource file then make use of “System.Resource.Resources.ResourceWriter” class. One of the disadvantages of using these classes is that they don’t have facility to fetch the comment part of a resource item.

My problem was not reading or creation of resource file but updating the content. Also I had to update the comments as well. So some googling helped me and gave me hints. People suggested that, treat a resource file as an XML and use the XML classes to read/update the resource file. With this hint and little bit of LINQ to XML I found the solution. Lets dive into the solution.

Reading resource file using LINQ

Following is the HTML content for displaying the resource file content.

<asp:GridView ID="gvResourceEditor" runat="server" AutoGenerateColumns="False" OnRowCancelingEdit="gvResourceEditor_RowCancelingEdit"
            OnRowEditing="gvResourceEditor_RowEditing" OnRowUpdating="gvResourceEditor_RowUpdating"            CellPadding="0" BorderWidth="0px" Width="100%" meta:resourcekey="gvResourceEditorResource1">
            <Columns>
                <asp:TemplateField HeaderText="Key" meta:resourcekey="TemplateFieldResource1">
                    <EditItemTemplate>
                        <asp:Label ID="lblKey" runat="server" Text='<%# Eval("Key") %>' meta:resourcekey="lblKeyResource1"></asp:Label>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="lblKey" runat="server" Text='<%# Eval("Key") %>' meta:resourcekey="lblKeyResource2"></asp:Label>
                    </ItemTemplate>
                    <ItemStyle CssClass="gridText tenXPadding bottomBorder rightBorder" Wrap="True" VerticalAlign="Top"
                        Width="10%" />
                </asp:TemplateField>
                <asp:TemplateField HeaderText="TEXT" meta:resourcekey="TemplateFieldResource2">
                    <HeaderStyle BorderWidth="0" BorderColor="White" BorderStyle="None" />
                    <EditItemTemplate>
                        <asp:TextBox ID="txtValue" TextMode="MultiLine" CssClass="textBoxEnabled" runat="server"
                            Text='<%# Bind("Value") %>' Width="100%" Height="150px" meta:resourcekey="txtValueResource1"></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="lblValue" runat="server" Text='<%# Bind("Value") %>' meta:resourcekey="lblValueResource1"></asp:Label>
                    </ItemTemplate>
                    <ItemStyle CssClass="gridText tenXPadding rightBorder bottomBorder" Wrap="True" Width="35%"
                        VerticalAlign="Top" />
                </asp:TemplateField>
                <asp:TemplateField HeaderText="COMMENTS" meta:resourcekey="TemplateFieldResource3">
                    <HeaderStyle BorderWidth="0" BorderColor="White" BorderStyle="None" />
                    <EditItemTemplate>
                        <asp:TextBox ID="txtComment" TextMode="MultiLine" CssClass="textBoxEnabled" runat="server"
                            Text='<%# Bind("Comment") %>' Width="100%" Height="150px" meta:resourcekey="txtDescriptionResource1"></asp:TextBox>
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Label ID="lblDescription" runat="server" Text='<%# Bind("Comment") %>' meta:resourcekey="lblDescriptionResource1"></asp:Label>
                    </ItemTemplate>
                    <ItemStyle CssClass="gridText tenXPadding rightBorder bottomBorder" Wrap="True" Width="35%"
                        VerticalAlign="Top" />
                </asp:TemplateField>
                <asp:CommandField ButtonType="Button" ItemStyle-CssClass="tenXPadding bottomBorder"
                    ShowEditButton="True" ControlStyle-CssClass="buttonEnabled" ItemStyle-VerticalAlign="Top"
                    HeaderStyle-BorderColor="White" HeaderStyle-BorderWidth="0" HeaderStyle-BorderStyle="None"
                    ItemStyle-Width="20%" meta:resourcekey="CommandFieldResource1">
                    <ControlStyle CssClass="buttonEnabled"></ControlStyle>
                    <HeaderStyle BorderColor="White" BorderWidth="0px" BorderStyle="None"></HeaderStyle>
                    <ItemStyle VerticalAlign="Top" CssClass="tenXPadding bottomBorder" Width="20%"></ItemStyle>
                </asp:CommandField>
            </Columns>
        </asp:GridView>

In the above code you can see there is a GridView control which has its columns bound to various properties like “Key”, “Value” and “Comment”. This is a simple grid with “Edit”, “Update” and “Cancel” facility. In the above HTML make sure you have the “Key” field. “Key” field is used to search the resource file for the record to be updated. Make sure the “Key” field is not editable.

In the Page_Load event in the code behind the following code is used to read the content of a resource file and data bind the same to a GridView control.

protected void Page_Load(object sender, EventArgs e)
{
   if (!IsPostBack)
     LoadData();
}

private void LoadData()
{
   string path = Server.MapPath("App_LocalResources/Default.aspx.resx");
   //Loading the XML file using the Load method of XElement.
   XElement resourceElements = XElement.Load(path);
   //LINQ to load the resource items
   gvResourceEditor.DataSource = (from resElem in resourceElements.Elements("data")
       select new
       {
           Key = resElem.Attribute("name").Value,
           Value = HttpUtility.HtmlEncode(resElem.Element("value").Value),
           Comment = resElem.Element("comment") != null ?
                HttpUtility.HtmlEncode(resElem.Element("comment").Value) : string.Empty
       });
    gvResourceEditor.DataBind();
}

In the above code you can see we are making use of XML to LINQ to read the contents from a resource file (Default.aspx.resx). Once we have loaded the resource file using the Load method of XElement class, in the next line we are making use of LINQ and creating anonymous types and assigning the same as DataSource to the GridView control.

With that simple code we have read the contents from a resource file and displayed the same in a GridView with facility to “Edit”, “Update” and “Cancel”. Now lets see how to update a particular value in a resource file.

Updating a resource file

In the “RowUpdating” event of the GridView control we are doing the following to update the resource file.

protected void gvResourceEditor_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    string theFileName = Server.MapPath("App_LocalResources/Default.aspx.resx");
    Label lblKey = gvResourceEditor.Rows[e.RowIndex].FindControl("lblKey") as Label;
    TextBox txtValue = gvResourceEditor.Rows[e.RowIndex].FindControl("txtValue") as TextBox;
    TextBox txtComment = gvResourceEditor.Rows[e.RowIndex].FindControl("txtComment") as TextBox;
    //Loading the Resource file as an XML.
    XDocument xDoc = XDocument.Load(theFileName);
    //Filtering out key which matches currently updated record.
    var elementToBeEdited = from xle in xDoc.Element("root").Elements("data") where xle.Attribute("name").Value == lblKey.Text select xle;

    if (elementToBeEdited != null && elementToBeEdited.Count() > 0)
    {
        //Updating the new value
        elementToBeEdited.First().SetElementValue("value", HttpUtility.HtmlDecode(txtValue.Text));

        //Checking if there is a comment node, if no then adding a new comment node.
        if (elementToBeEdited.First().Element("comment") == null && !string.IsNullOrEmpty(txtComment.Text))
        {
            elementToBeEdited.First().Add(new XElement("comment", HttpUtility.HtmlDecode(txtComment.Text)));
        }
        else if (elementToBeEdited.First().Element("comment") != null && !string.IsNullOrEmpty(txtComment.Text))
        {
            //If comment node is present then updating the comment node.
            elementToBeEdited.First().SetElementValue("comment", HttpUtility.HtmlDecode(txtComment.Text));
        }
        //Finally saving the Resource file.
        xDoc.Save(theFileName);               
    }

    gvResourceEditor.EditIndex = -1;
    LoadData();
}

In the above code, first we are finding controls holding the values and then we are loading the resource file into the XDoc object. Once the resource file is loaded, in the next line we are making use of LINQ statement to select the element whose name attribute matches the “Key” field of the GridView. Once the element to be edited is filtered next we are updating the “value” node with the newly updated value. The value node in the resource file holds the text.

Then in the next line we are checking whether there is a comment node available, if not we are creating a new comment node by calling the “Add” method and passing a new “XElement” object as the parameter. If there is a comment node then we are updating the comment by calling the “SetElementValue” method and passing the necessary text as argument. Finally the updated resource file is saved by calling the “Save” method of the XDoc class.

Isn’t it so simple to update the resource file. With XML to LINQ the code has become terse and very easy. So with this you have seen how to read and update a resource file using LINQ.

Note: If you are updating a resource file in a live system then keep in mind that all the logged in users session will expire and they will be logged out.

Try to konw more…

Sandeep