Click here to Skip to main content
15,884,176 members
Articles / Desktop Programming / Win32

Yet another Core Utility Component - Logging, Exception Handling, Configuration and Localization in one App

Rate me:
Please Sign up or sign in to vote.
4.47/5 (7 votes)
11 Jun 2015CPOL5 min read 14.9K   189   15   1
Core utility components - Localization, Configuration, Logging, and Exception Handling.

Introduction

Intending to initiate new software project, we face the need for a code that performs the core utility components such as Localization, Configuration, and Logging and Exception Handling mechanisms.

This article describes in brief each of these components in one contained app. For simplicity, I’ve removed most of over comments to make the code clear as much as possible.

Localization

The term Localization generally means the process of adapting a product or service to a particular language, culture, and desired local "look-and-feel" i.e. among of it the ability to have more than one language for your application. Microsoft uses different mechanisms for localization, but this is not what I’ve implemented in the sample source code.

What I’ve done here is showing you how to programmatically switch the user interface language at run time. So, to use the sample demo, you’ll need to install the three languages’ Keyboards English-US, Arabic-EG, and German-DE as shown in the below figure.

Image 1

Well, let’s start by an example, suppose that you work for a multinational enterprise that has lots of co-workers from different nationalities and you want to make it easy for the user to have its own mother tongue language in the UI in addition to his default keyboard layout, then you build a sample form such as the following images:

Image 2

Image 3

Image 4

In addition, it is required to have full localization in all items across the solution including the warnings and error messages shown to the user, the logging activities and so on such as the following image

Image 5

For simplicity, I’ll show how to set the languages in a dictionary but the normal case is to use external entity such as a database table or csv or text file to have the reference of the same message in different languages.

The following code initialize a dictionary and sets its messaging values in three languages; English, Arabic and German.

C#
        private void InitDictionary()
        {
            eDictionary = new Dictionary<string, string="">>
            {
                {
                    "Text",
                    new List<string> {"Login Details ...", "بيانات الدخول ...", "Login-Daten ..."}
                },
                {
                    "_sIsNullOrEmptyMsg",
                    new List<string> 
                    {"Argument can't be Null or Empty", 
                        "المعامل لا يمكن أن يكون فارغ أو عديم القيمة", 
                        "Dem kann nicht null oder leer sein"
                    }
                },
                {
                    "_sUserNameMsg",
                    new List<string> 
                    {"The [User Name] value can't be Null or Empty ...", 
                        "إسم المستخدم لا يمكن أن يكون فارغ أو عديم القيمة",
                        "Die [Benutzername] kann nicht null oder leer sein ..."
                    }
                },
                {
                    "_sUserPasswordMsg",
                    new List<string> 
                    {"The [Password] value can't be Null or Empty ...", 
                        "كلمة السر لا يمكن أن يكون فارغ أو عديم القيمة",
                        "Die [Kennwort] kann nicht null oder leer sein ..."
                    }
                },
                {
                    "_sOkayMsg",
                    new List<string> 
                    {"Very good, everything went Okay ...", 
                        "هايل ، كل شيء مر على ما يرام",
                        "Sehr gut, ging alles in Ordnung ..."
                    }
                },
                {
                    "lblUserName",
                    new List<string> {"User Name", "إسم المستخدم", "Benutzername"}
                },
                {
                    "lblPassword",
                    new List<string> {"Password", "كلمة السر", "Kennwort"}
                },
                {
                    "btnOK",
                    new List<string> {"OK", "موافق", "ja"}
                },
                {
                    "btnClose",
                    new List<string> {"Cancel", "خروج", "kündigen"}
                },
                {
                    "btnGenerateException",
                    new List<string> {"Generate Exception", "توليد إعتراض", "Erzeugen Ausnahme"}
                }
            };
        }
</string></string></string></string></string></string></string></string></string></string></string,>

The following code switches the language based on enumeration. For more details, please refer to the source code.

C#
private void InitMessagesLanguage(LanguageName languageName)
{
    int iIndex = 0;

    switch (languageName)
    {
        case LanguageName.English:
            iIndex = 0;
            break;
        case LanguageName.Arabic:
            iIndex = 1;
            break;
        case LanguageName.German:
            iIndex = 2;
            break;
    }

    Text = eDictionary["Text"][iIndex];

    _sIsNullOrEmptyMsg = eDictionary["_sIsNullOrEmptyMsg"][iIndex];
    _sUserNameMsg = eDictionary["_sUserNameMsg"][iIndex];
    _sUserPasswordMsg = eDictionary["_sUserPasswordMsg"][iIndex];
    _sOkayMsg = eDictionary["_sOkayMsg"][iIndex];

    lblUserName.Text = eDictionary["lblUserName"][iIndex];
    lblPassword.Text = eDictionary["lblPassword"][iIndex];

    btnOK.Text = eDictionary["btnOK"][iIndex];
    btnClose.Text = eDictionary["btnClose"][iIndex];

    btnGenerateException.Text = eDictionary["btnGenerateException"][iIndex];

}

 

Configuration

To benefits from the Configuration feature in VS, you need to add a reference to System.Configuration in your app references as shown in the following figure.

Image 6

For the sample project, add App.Config file and set it as following.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <appSettings>

    <add key="Global.Setting.AppLang" value="US" />

    <add key="Global.Setting.LoggingFolderName" value="LogFolder" />

    <add key="Global.Setting.LoggingFolderParentPath" value="c:\" />

    <add key="Global.Setting.EnableLoggingAppFlow" value="True" />

    <add key="Global.Setting.EnableLoggingData" value="True" />

    <add key="Global.Setting.EnableLoggingErrors" value="True" />

  </appSettings>

</configuration>

Well, I’ve given you enough number of methods in a separated class called ConfigHandler that implements the IDisposable interface.

The class when initiated has two overloads constructor methods. The following demonstrates how to save a configuration value. We must always check for possible null or empty values and do not forget to refresh the ConfigurationManager section to be able to use the saved data during the application lifetime.

C#
public bool SaveSectionSettingsValueByKey(string sKey, string sKeyValue, string sSection = "appSettings")
{
    try
    {
        if (string.IsNullOrEmpty(sKey) || string.IsNullOrEmpty(sKeyValue) || string.IsNullOrEmpty(sSection))
            throw new ArgumentOutOfRangeException(_sKeyValueSectionMsg);

        AppSettingsSection section = (AppSettingsSection)_config.GetSection(sSection);

        section.Settings[sKey].Value = sKeyValue;

        //Save the configuration file.
        _config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(sSection);

        //Op done successfully
        return true;
    }
    catch (Exception ex)
    {
        //bubble error.
        throw new Exception(_sSaveSectionSettingsValueByKeyMsg, ex);
    }
}

The following demonstrates how to get a configuration value by a key. You’ll notice that the section name by default is equal to "appSettings" but it could be any other section as well if you like.

C#
public string GetSectionSettingsValueByKey(string sKey, string sSection = "appSettings")
{
    try
    {
        if (string.IsNullOrEmpty(sKey) || string.IsNullOrEmpty(sSection))
            throw new ArgumentOutOfRangeException(_sKeySectionMsg);

        AppSettingsSection section = (AppSettingsSection)_config.GetSection(sSection);

        return section.Settings[sKey].Value;
    }
    catch (Exception ex)
    {
        //bubble error.
        throw new Exception(_sGetSectionSettingsValueByKeyMsg, ex);
    }
}

 

Exception Handling

For exception handling, I’ve differentiated among two different exception mechanisms as below:

  • First, building a custom form to receive application exception from any thread if any. Such mechanism enables you to fully control your application and hunt careless code exceptions.

  • Second, a generic Success, Warning, and Error Message Box mechanism based on Microsoft MessageBox component that can change its layout based on the Right-to-Left/Left-to-Right requirement of the user interface as you shall see in code

For the first point, I’ve created three static methods to show the custom Thread Exception form for AppDomain unhandled exceptions and other user interface exceptions. Please refer back to the static class “Program”.

Image 7

The second mechanism is to build an ExceptionHandler class that enables you to show MessageBox dialogs based on your requirements.

The following code shows how to generates RTL/LTR MessageBox dialog and fill it with the header and content variables as you like.

C#
private void ShowMessageBox(string sOutPut, string sHeading, MessageBoxIcon messageBoxIcon = MessageBoxIcon.Information, bool bRightToLeftLayout = false)
{
    if (bRightToLeftLayout)
        MessageBox.Show(sOutPut, sHeading, MessageBoxButtons.OK, messageBoxIcon, MessageBoxDefaultButton.Button1,
                MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign);
    else
        MessageBox.Show(sOutPut, sHeading, MessageBoxButtons.OK, messageBoxIcon, MessageBoxDefaultButton.Button1);
}

And the following method shows the right MessageBox based on enumeration

C#
public void ShowException(string sExceptionMessage, OperationResult opResult)
{
    string currOpStatus = String.Empty;
    string sCharSeparatorLine = UtilityHandler.DrawSeparatorLine();
    MessageBoxIcon messageBoxIcon = MessageBoxIcon.Information;

    switch (opResult)
    {
        case OperationResult.Success:
            currOpStatus = _sSuccessMsg;
            _sOutput = _sOutputSuccessMsg + Environment.NewLine + sCharSeparatorLine + Environment.NewLine;
            _sOutput += sExceptionMessage + Environment.NewLine;
            _sOutput += sCharSeparatorLine;
            messageBoxIcon = MessageBoxIcon.Information;
            break;
        case OperationResult.Warning:
            currOpStatus = _sWarningMsg;
            _sOutput = _sOutputWarningMsg + Environment.NewLine + sCharSeparatorLine + Environment.NewLine;
            _sOutput += sExceptionMessage + Environment.NewLine;
            _sOutput += sCharSeparatorLine;
            messageBoxIcon = MessageBoxIcon.Warning;
            break;
        case OperationResult.Errors:
            currOpStatus = _sErrorMsg;
            _sOutput = _sOutputErrorMsg + Environment.NewLine + sCharSeparatorLine + Environment.NewLine;
            _sOutput += sExceptionMessage + Environment.NewLine;
            _sOutput += sCharSeparatorLine;
            messageBoxIcon = MessageBoxIcon.Error;
            break;
    }

    ShowMessageBox(_sOutput, currOpStatus, messageBoxIcon, _bRightToLeftLayout);
}

The following figure shows the custom MessageBox in action.

Image 8

Logging

Logging is an important factor for identifying whether your application flows as you planned to or not. For logging handling, I’ve chosen to log to a file in a dedicated folder identified from the app configuration file. Of course, you can use Event Log or database table to save logging but in the sample code I’ve used to log to a file.

The following code creates a log folder and also for simplicity, I did not take into account the security issues that I may face while creating a folder and whether the created folder is on the same machine or on a network drive etc.

C#
public bool CreateLogFolder(string sLoggingFolderParentFullPath = "Default", string sLogFolderName = "LogFolder")
{
    if (string.IsNullOrEmpty(sLogFolderName))
        throw new ArgumentOutOfRangeException("LogFolderName", _sCantBeNullOrEmptyMsg);

    if (sLoggingFolderParentFullPath.ToLower() == "default")
    {
        sLoggingFolderParentFullPath = Environment.CurrentDirectory;
    }

    try
    {
        if (!(Directory.Exists(Path.Combine(sLoggingFolderParentFullPath, sLogFolderName))))
        {
            Directory.CreateDirectory(Path.Combine(sLoggingFolderParentFullPath, sLogFolderName));
        }

        using (ConfigHandler configHandler = new ConfigHandler())
        {
            configHandler.SaveSectionSettingsValueByKey(UtilityHandler.SLoggingFolderNameKey, sLogFolderName);
            configHandler.SaveSectionSettingsValueByKey(UtilityHandler.SLoggingFolderParentPathKey, sLoggingFolderParentFullPath);
        }

        return true;
    }
    catch (Exception ex)
    {
        //bubble error.
        throw new Exception(_sCreateLogFolderMsg, ex);
    }
}

The following code creates log file based on enumeration. Well, you create three log files, one for Errors, one for Data, and the third for Activity monitoring logging.

C#
public bool CreateLogFileInstance(LogFileType logFileType, bool bOverwriteCurrent = false)
{
    if (!GetLoggingEnableDisableStatus(logFileType))
        return false;   //Logging is Forbidden, do not do anything

    try
    {
        string sToBeWritten = UtilityHandler.DrawSeparatorLine(99, "*") + Environment.NewLine;

        switch (logFileType)
        {
            case LogFileType.Error:
                sToBeWritten += _sCreatingNewMsg + _sErrorLogMsg + _sInstanceAtMsg + DateTime.Now.ToString();
                break;
            case LogFileType.Logs:
                sToBeWritten += _sCreatingNewMsg + _sActivityLogMsg + _sInstanceAtMsg + DateTime.Now.ToString();
                break;
            case LogFileType.Data:
                sToBeWritten += _sCreatingNewMsg + _sDataLogMsg + _sInstanceAtMsg + DateTime.Now.ToString();
                break;
            default:
                sToBeWritten += _sCreatingNewMsg + _sGenericLogMsg + _sInstanceAtMsg + DateTime.Now.ToString();
                break;
        }

        sToBeWritten += Environment.NewLine + UtilityHandler.DrawSeparatorLine(99, "*") + Environment.NewLine;

        string strLoggingFileName = GetLoggingFileName(logFileType);
        string strLoggingFileNameWithPath = Path.Combine(_strLoggingFolderFullPath, strLoggingFileName);

        if (bOverwriteCurrent)
        {
            if (File.Exists(strLoggingFileNameWithPath))
                File.Delete(strLoggingFileNameWithPath);    //remove the file.
        }

        //Establish a Logger file.
        using (FileStream fStream = new FileStream(strLoggingFileNameWithPath, FileMode.Append, FileAccess.Write, FileShare.None))
        using (StreamWriter swLogWritter = new StreamWriter(fStream, Encoding.UTF8))
            swLogWritter.WriteLine(sToBeWritten);

        return true;
    }
    catch (Exception ex)
    {
        //bubble error.
        throw new Exception(_sCreateLogFileInstance01Msg, ex);
    }
}

In the same time, based on enumerations as well, you can write to any of the log files when needed as you can see in the below WriteLogLine method.

C#
public bool WriteLogLine(LogFileType logFileType, string sToBeWritten)
{
    if (!GetLoggingEnableDisableStatus(logFileType))
        return false;   //Logging is Forbidden, do not do anything

    string strLogFileName = GetLoggingFileName(logFileType);
    string strLoggingFileNameWithPath = Path.Combine(_strLoggingFolderFullPath, strLogFileName);

    try
    {
        //Create a Logger file if it does not exist.
        if (!File.Exists(strLoggingFileNameWithPath))
            CreateLogFileInstance(logFileType);

        using (FileStream fStream = new FileStream(strLoggingFileNameWithPath, FileMode.Append, FileAccess.Write, FileShare.None))
        using (StreamWriter swLogWritter = new StreamWriter(fStream, Encoding.UTF8))
            swLogWritter.WriteLine(sToBeWritten);

        return true;
    }
    catch (Exception ex)
    {
        //bubble error.
        throw new Exception(_sWriteLogLine01Msg, ex);
    }
}

 

Conclusion

Any valuable application has to have core utility components such as Localization, Configuration, Logging, and Exception Handling mechanisms. These components must be built with enough standards to enrich your application and they must be based on adequate OO techniques that you can rely on.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Freelancer
Egypt Egypt
I'm a Lead .NET consultant with large experience in Database (SQL server, Oracle, MySQL), Web Services, WCF & REST and I'm excellent coder of C# with over 15 years of international commercial working experience by working in different environments and countries such as Egypt, UK, Qatar and Bahrain.

Comments and Discussions

 
GeneralMy Vote of 5 Pin
aarif moh shaikh12-Jun-15 1:27
professionalaarif moh shaikh12-Jun-15 1:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.