65.9K
CodeProject 正在变化。 阅读更多。
Home

又一个核心实用组件 - 应用中的日志记录、异常处理、配置和本地化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (7投票s)

2015 年 6 月 12 日

CPOL

5分钟阅读

viewsIcon

15678

downloadIcon

216

核心实用组件 - 本地化、配置、日志记录和异常处理。

引言

在启动新的软件项目时,我们需要能够执行本地化、配置以及日志记录和异常处理等核心实用组件的代码。

本文简要介绍了这些组件在一个集成应用中的应用。为了简化,我删除了大部分注释,以便代码尽可能清晰。

本地化

本地化(Localization)通常是指将产品或服务适应特定语言、文化和期望的本地“外观和感觉”的过程,即使其能够支持您的应用程序的多语言。微软使用不同的本地化机制,但这并非我在示例源代码中实现的。

我在这里展示的是如何在运行时以编程方式切换用户界面语言。因此,要使用示例演示,您需要安装以下三种语言的键盘:英语(美国)、阿拉伯语(埃及)和德语(德国),如下图所示。

好吧,让我们从一个例子开始。假设您为一个拥有许多不同国籍同事的跨国企业工作,您希望让用户轻松地在用户界面中使用他们自己的母语,以及他们的默认键盘布局。那么您就可以构建一个示例如下图片的样本窗体。

此外,还需要在解决方案的所有项目中实现完整的本地化,包括向用户显示的警告和错误消息、日志记录活动等,如下图所示。

为了简化,我将展示如何在字典中设置语言,但通常情况下会使用外部实体,如数据库表、CSV 文件或文本文件来存储同一消息在不同语言中的引用。

以下代码初始化一个字典,并设置其在英语、阿拉伯语和德语三种语言下的消息值。

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

以下代码根据枚举切换语言。有关更多详细信息,请参阅源代码。

        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];

        }

 

配置

要利用 VS 中的配置功能,您需要在应用程序引用中添加对 System.Configuration 的引用,如下图所示。

对于示例项目,添加 App.Config 文件并按如下方式设置。

<?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>

好了,我在一个名为 ConfigHandler 的独立类中提供了足够多的方法,该类实现了 IDisposable 接口。

该类在初始化时有两个构造函数重载。以下演示了如何保存配置值。我们必须始终检查可能出现的 null 或空值,并且不要忘记刷新 ConfigurationManager 部分,以便在应用程序生命周期内使用保存的数据。

        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);
            }
        }

以下演示了如何通过键获取配置值。您会注意到,默认情况下,节名称为“appSettings”,但如果您愿意,也可以是任何其他节。

        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);
            }
        }

 

异常处理

对于异常处理,我区分了以下两种不同的异常机制:

  • 首先,构建一个自定义窗体来接收来自任何线程的应用程序异常。这种机制使您能够完全控制您的应用程序并捕获粗心的代码异常。

  • 其次,一种通用的成功、警告和错误消息框机制,基于 Microsoft MessageBox 组件,可以根据用户界面的从右到左/从左到右的要求改变其布局,正如您在代码中将看到的。

关于第一点,我创建了三个静态方法来显示自定义线程异常窗体,用于处理 AppDomain 未捕获的异常和其他用户界面异常。请回看静态类“Program”。

第二种机制是构建一个 ExceptionHandler 类,使您能够根据您的需求显示 MessageBox 对话框。

以下代码展示了如何生成 RTL/LTR MessageBox 对话框,并用您喜欢的标题和内容变量填充它。

        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);
        }

以下方法根据枚举显示正确的 MessageBox。

        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);
        }

下图显示了自定义 MessageBox 的实际效果。

日志记录

日志记录是识别您的应用程序是否按计划运行的重要因素。对于日志处理,我选择将日志记录到配置文件中指定的专用文件夹中。当然,您可以使用事件查看器或数据库表来保存日志,但在示例代码中,我使用了文件日志记录。

以下代码创建了一个日志文件夹,并且为了简化,我没有考虑创建文件夹时可能面临的安全问题,以及创建的文件夹是在同一台机器上还是在网络驱动器上等等。

        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);
            }
        }

以下代码根据枚举创建日志文件。嗯,您创建三个日志文件:一个用于错误,一个用于数据,第三个用于活动监控日志。

        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);
            }
        }

同时,也基于枚举,您可以在需要时写入任何日志文件,正如您在下面的 WriteLogLine 方法中所看到的。

        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);
            }
        }

 

结论

任何有价值的应用程序都必须具备本地化、配置、日志记录和异常处理机制等核心实用组件。这些组件必须具备足够的标准来丰富您的应用程序,并且必须基于足够的面向对象技术,以便您可以依赖它们。

© . All rights reserved.