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.
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:
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
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.
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>Colourised in 82ms
The following code switches the language based on enumeration. For more details, please refer to the source code.
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];
}
Colourised in 23ms
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.
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.
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;
_config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(sSection);
return true;
}
catch (Exception ex)
{
throw new Exception(_sSaveSectionSettingsValueByKeyMsg, ex);
}
}
Colourised in 11ms
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.
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)
{
throw new Exception(_sGetSectionSettingsValueByKeyMsg, ex);
}
}
Colourised in 8ms
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”.
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.
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);
}
Colourised in 6ms
And the following method shows the right MessageBox based on enumeration
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);
}
Colourised in 28ms
The following figure shows the custom MessageBox in action.
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.
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)
{
throw new Exception(_sCreateLogFolderMsg, ex);
}
}
Colourised in 20ms
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.
public bool CreateLogFileInstance(LogFileType logFileType, bool bOverwriteCurrent = false)
{
if (!GetLoggingEnableDisableStatus(logFileType))
return false;
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); }
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)
{
throw new Exception(_sCreateLogFileInstance01Msg, ex);
}
}
Colourised in 37ms
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.
public bool WriteLogLine(LogFileType logFileType, string sToBeWritten)
{
if (!GetLoggingEnableDisableStatus(logFileType))
return false;
string strLogFileName = GetLoggingFileName(logFileType);
string strLoggingFileNameWithPath = Path.Combine(_strLoggingFolderFullPath, strLogFileName);
try
{
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)
{
throw new Exception(_sWriteLogLine01Msg, ex);
}
}
Colourised in 16ms
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.