Sunday, September 30, 2012

How to save web site configuration data in Xml file instead of web.config.

We often use web.config file to keep the site configuration data. In this case, there is draw back since it need to restart the application when we change web.config file everytimes, and it don't affect the current running transaction. There is a simple way to solve it out that is XML file to save web site configuration data when we don't want to use Database for that. Perhaps we should consider what would happen if we use XML file for sensitive data concerned with security risk. Anyway, let's start here.
First, create xml file "site-config.xml" in "App_Data" folder as below:
<?xml version="1.0"?>
<SiteSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SiteName>Aspmemo.net</SiteName>
  <SiteVersion>1.0</SiteVersion>
  <SiteEmailAddress>sithuwin@aspmemo.net</SiteEmailAddress>
</SiteSettings>
Second, create "SiteSettings.cs" as follows:
using System.IO;
using System.Text;
using System.Xml.Serialization;

public class SiteSettings
{
    private const string XmlConfigFile = "~/App_Data/site-config.xml";
    private string _siteName;
    private string _siteVersion;
    private string _siteEmail;

    public string SiteName
    {
        get
        {
            return _siteName;
        }
        set
        {
            lock (this)
            {
                _siteName = value;
            }
        }
    }
    public string SiteVersion
    {
        get
        {
            return _siteVersion;
        }
        set
        {
            lock (this)
            {
                _siteVersion = value;
            }
        }
    }
    public string SiteEmailAddress
    {
        get
        {
            return _siteEmail;
        }
        set
        {
            lock (this)
            {
                _siteEmail = value;
            }
        }
    }
    public string SiteEmailFromField
    {
        get
        {
            return String.Format("{0} <{1}>", _siteName, _siteEmail);
        }
    }
    public static SiteSettings LoadFromConfiguration()
    {
        SiteSettings s = LoadFromXml();

        if (s == null)
        {
            s = new SiteSettings();
            //s.StorePhotosInDatabase = false;
            //s.ServerPhotoUploadDirectory = "Upload";
            s.SiteName = "Aspmemo";
            s.SiteVersion = "1.0";
            s.SiteEmailAddress = "sithuwin@aspmemo.net";
            SaveToXml(s);

        }
        return s;
    }
    public static SiteSettings GetSharedSettings()
    {
        return SiteHttpApplication.SiteApplicationSettings;
    }
    public static bool UpdateSettings(SiteSettings newSettings)
    {
        // write settings to code or db
        // update Application-wide settings, only over-writing settings that users should edit
        lock (SiteHttpApplication.SiteApplicationSettings)
        {
            // Site Name
            SiteHttpApplication.SiteApplicationSettings.SiteName = newSettings.SiteName;
            // Site Version
            SiteHttpApplication.SiteApplicationSettings.SiteVersion = newSettings.SiteVersion;
            // Contact Email Address for Site
            SiteHttpApplication.SiteApplicationSettings.SiteEmailAddress = newSettings.SiteEmailAddress;
            // Serialize to Xml Config File
            return SaveToXml(SiteHttpApplication.SiteApplicationSettings);
        }
    }
    private static SiteSettings LoadFromXml()
    {
        SiteSettings settings = null;
        HttpContext context = HttpContext.Current;
        if (context != null)
        {
            string configPath = context.Server.MapPath(XmlConfigFile);
            XmlSerializer xml = null;
            FileStream fs = null;
            bool success = false;
            int numAttempts = 0;

            while (!success && numAttempts < 2)
            {
                try
                {
                    numAttempts++;
                    xml = new XmlSerializer(typeof(SiteSettings));
                    fs = new FileStream(configPath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                    settings = xml.Deserialize(fs) as SiteSettings;
                    success = true;
                }
                catch (Exception x)
                {
                    // if an exception is thrown, there might have been a sharing violation;
                    // we wait and try again (max: two attempts)
                    success = false;
                    System.Threading.Thread.Sleep(1000);
                    if (numAttempts == 2)
                        throw new Exception("The Site Configuration could not be loaded.", x);
                }
            }
            if (fs != null)
                fs.Close();
        }
        return settings;
    }
    public string GetXml()
    {
        StringBuilder result = new StringBuilder();
        StringWriter s = new StringWriter(result);
        try
        {
            XmlSerializer xml = new XmlSerializer(typeof(SiteSettings));
            xml.Serialize(s, this);
        }
        finally
        {
            s.Close();
        }
        return result.ToString();
    }
    private static bool SaveToXml(SiteSettings settings)
    {
        if (settings == null)
            return false;
        HttpContext context = HttpContext.Current;
        if (context == null)
            return false;

        string configPath = context.Server.MapPath(XmlConfigFile);
        XmlSerializer xml = null;
        System.IO.FileStream fs = null;
        bool success = false;
        int numAttempts = 0;

        while (!success && numAttempts < 2)
        {
            try
            {
                numAttempts++;
                xml = new XmlSerializer(typeof(SiteSettings));
                fs = new FileStream(configPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
                xml.Serialize(fs, settings);
                success = true;
            }
            catch
            {
                // if an exception is thrown, there might have been a sharing violation;
                // we wait and try again (max: two attempts)
                success = false;
                System.Threading.Thread.Sleep(1000);
            }
        }
        if (fs != null)
            fs.Close();
        return success;
    }
}
Third, create "SiteHttpApplication.cs" as the custom base class for applications:
public class SiteHttpApplication : HttpApplication
{
 private static object _settingsLock = new object();
 private static SiteSettings _settings;

 public static SiteSettings SiteApplicationSettings
 {
  get
  {
   if (_settings == null)
    SiteApplicationSettings = SiteSettings.LoadFromConfiguration();
   return _settings;
  }
  set
  {
   if (value == null)
    throw new ArgumentNullException("SiteApplicationSettings cannot be set to null");
   lock (_settingsLock)
   {
    _settings = value;
   }
  }
 }
 public void Application_Start(Object sender, EventArgs e)
 {
  // set-up Settings
  SiteHttpApplication.SiteApplicationSettings = SiteSettings.LoadFromConfiguration();
 }
}
Then, create our "SiteHttpApplication" object in "Global.asax" file.
void Application_Start(object sender, EventArgs e)
{
 // Code that runs on application startup
 SiteHttpApplication siteHttp = new SiteHttpApplication();
 siteHttp.Application_Start(sender, e);
}
Finally, create UI for accessing the xml data as below:
<form id="form1" runat="server">
<div>
    <asp:Panel ID="UpdateErrorPanel" runat="server" Visible="False" EnableViewState="False">
        <p>
            <strong>There was an error writing to the Settings file<br />
                App_Data\site-config.xml.</strong><br />
            Please make sure that the ASP.NET process has write-access to the file.</p>
        <p>
            The textbox below contains the XML equivalent of the Site Settings you saved. You
            can copy it and update the file manually.</p>
        <asp:TextBox ID="SettingsXmlTextBox" runat="server" TextMode="multiLine" Rows="10"
            Width="100%"></asp:TextBox>
    </asp:Panel>
    <fieldset>
        <asp:FormView ID="SettingsFormView" runat="server" DataSourceID="SettingsDataSource"
            DefaultMode="Edit">
            <EditItemTemplate>
                SiteName:
                <asp:TextBox ID="SiteNameTextBox" runat="server" Text='<%# Bind("SiteName") %>' />
                <br />
                SiteVersion:
                <asp:TextBox ID="SiteVersionTextBox" runat="server" Text='<%# Bind("SiteVersion") %>' />
                <br />
                SiteEmailAddress:
                <asp:TextBox ID="SiteEmailAddressTextBox" runat="server" Text='<%# Bind("SiteEmailAddress") %>' />
                <br />
                <br />
                <asp:Button ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update"
                    Text="Update" />
                &nbsp;<asp:Button ID="UpdateCancelButton" runat="server" CausesValidation="False"
                    CommandName="Cancel" Text="Cancel" />
            </EditItemTemplate>
        </asp:FormView>
        <asp:ObjectDataSource ID="SettingsDataSource" runat="server" TypeName="SiteSettings"
            SelectMethod="GetSharedSettings" UpdateMethod="UpdateSettings" OnUpdated="SettingsDataSource_Updated"
            DataObjectTypeName="SiteSettings"></asp:ObjectDataSource>
    </fieldset>
</div>
</form>
UI will appear as below:
In code-behind, it need to handle the unsuccessful action in OnUpdated event of ObjectDataSource.
protected void SettingsDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
      try
      {
            // the UpdateSettings methods returns a boolean success value
            bool updateSuccess = false;
            if (e.ReturnValue != null && e.ReturnValue is bool)
                updateSuccess = (bool)e.ReturnValue;

            if (!updateSuccess)
            {
                 // we failed to save the settings to the settings file
                 // but we can output its Xml equivalent for manual saving:
                 SiteSettings s = SiteSettings.GetSharedSettings();
                 SettingsXmlTextBox.Text = s.GetXml();
                 UpdateErrorPanel.Visible = true;
            }
       }
       catch (Exception ex)
       {
                
       }
}
That's it. Run the application and see the result. Right now, you can access the site configuration data on the fly and can update immediately.


If you got error such as "The type [Class] is ambiguous.", Please see here.

No comments:

Post a Comment