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.

Saturday, September 15, 2012

How to read image properties (Metadata) in asp.net

Some image files contain metadata that you can read to determine features of the image. For example, a digital photograph might contain metadata that you can read to determine the make, model of the camera used to capture the image and Date taken. With GDI+, you can read existing metadata, and you can also write new metadata to image files. This tutorial show you how to take the image date taken in asp.net.
Image Property Date Taken
 Copy the following server control to <form> tag.
 <asp:Label ID="label1" runat="server" Text =""></asp:Label>
In code-behind, look like as below
protected void Page_Load(object sender, EventArgs e)
{
    string path = @"G:\test.jpg"; //your image physical path here
    label1.Text = TakenDate(System.Drawing.Image.FromFile(path)).ToString();

}
public DateTime TakenDate(System.Drawing.Image img)
{
    DateTime dt = DateTime.MinValue;
    try
    {
        int DateTakenValue = 0x9003; // 36867
        if (img != null && img.PropertyIdList.Contains(DateTakenValue))
        {
            string dateTakenTag = System.Text.Encoding.UTF8.GetString(img.GetPropertyItem(DateTakenValue).Value);
            string[] parts = dateTakenTag.Split(':', ' ');
            int year = int.Parse(parts[0]);
            int month = int.Parse(parts[1]);
            int day = int.Parse(parts[2]);
            int hour = int.Parse(parts[3]);
            int minute = int.Parse(parts[4]);
            int second = int.Parse(parts[5]);
            dt = new DateTime(year, month, day, hour, minute, second);
        }
    }
    catch (Exception ex)
    {
    }
    return dt;
}
That's it. Run the application and see the image-date-taken in page.
More detail-how to read Image Metadata

Sunday, September 2, 2012

How to show Confirm Navigation using onbeforeunload event in javascript


I think, you have an experience as if you see "Confirm Navigation" popup when you close Browser. Let's say, when you clicks other link or close browser in Facebook before pressing "Post" button during post something, you will get the following confirm dialog.
Confirm Navigation in FB
This example sohow you how to use the onbeforeunload event to ask users whether they want to remain on the current document or refresh the page again. When the user press F5 or attempts to close the window, the onbeforeunload event fires on the body and a dialog box displays. If the user chooses OK, the document refresh again or closes the window; if the user chooses Cancel, the document remains the same.
Copy the following script to <head> tag.
<script type="text/javascript">
document.onkeydown = getKeyCode;
var keyCode = 0;
window.onbeforeunload = confirmExit;
function confirmExit() {
    // F5 is 116.
    if (keyCode == 116) 
    {
        keyCode = 0;
        // For F5
        return "If you have made any changes to the fields without clicking the Save button, " +
        "Your changes will be lost. Are you sure you want to exit this page?";
    }
    else
    {
        // For close browser
        return "You have attempted to leave this page." +
        "Are you sure you want to exit this page?";
    }
}
function getKeyCode(e) {
    if (window.event) {
        e = window.event;
        keyCode = e.keyCode;
    }
    else {
        keyCode = e.which;
    }
}
</script> 
That's it. When you close the browser or press F5, it show the "Confirm Navigation" popup with the different message. Now, it don't work in IE 7. If you know the root cause, please drop a line. Thank!