Как реализовать независимую от языка обработку ошибок с использованием c # .Net типобезопасных файлов ресурсов?

#c# #.net #error-handling #resources #multilingual

#c# #.net #обработка ошибок #Ресурсы #многоязычный

Вопрос:

Проблема: я пытаюсь реализовать независимую от языка обработку ошибок, используя.Файлы сетевых ресурсов. Сообщения об ошибках должны отображаться на языке пользовательского интерфейса и могут регистрироваться на другом языке журнала. В сообщения об ошибках может быть вставлено содержимое переменных (String.Формат). Различные ошибки могут накапливаться, например, при проверке объектов. Затем накопленные ошибки будут регистрироваться и передаваться в пользовательский интерфейс.

Фреймворк: я хочу использовать файлы resx ресурсов и редактор ресурсов Visual Studio, потому что языковые ресурсы компилируются в класс, который обеспечивает типобезопасный доступ к ключам в коде. (тест проверит, есть ли у каждого языка перевод для каждого ключа)

Реализация: ошибки должны передаваться вверх в объекте error. Я пробовал разные реализации, но у каждой из них есть недостаток, и мне интересно, есть ли лучшее решение или способ преодолеть недостатки. Приведенные ниже коды упрощены, чтобы донести суть.

1) Создайте два сообщения, пользовательский интерфейс и сообщение журнала при создании ошибки

Для создания сообщений я могу вызвать метод getString ResourceManager с пользовательским интерфейсом и культурой ведения журнала и сохранить эти сообщения в объекте error. Например.

     ResourceManager rm = new ResourceManager(typeof(ErrorText));
    
    if (!isValid) {
         string key = Resources.ErrorText.MaximumTitleLength
         string uiError = String.Format(GetString(key, uiCulture), MaxSeperatorLength.ToString());
         string logError = String.Format(GetString(key, logCulture), MaxSeperatorLength.ToString());
         Error err = new Error (uiError, logError);
    }
 

Недостатки: основным недостатком является то, что классу домена приходится иметь дело с проблемой языка, нарушая разделение проблем. Код загрязнен кодом языка. Языковые варианты ограничены двумя. Решение о языке не может быть отложено на более поздний срок, и если языки журнала и пользовательского интерфейса идентичны, строки сохраняются дважды.

2) Передайте только ключ и тип ресурса

     if (!isValid) {
         Error err = new Error (resType: typeof(Resources.ErrorText),
                                key: nameof(Resources.ErrorText.MaximumTitleLength), 
                                params: MaxSeperatorLength.ToString());
    }
 

err.GetMessage(CultureInfo) вернет сообщение на нужном языке.

Недостатки: ключи в скомпилированном классе ресурсов являются свойствами типа string . C # не позволяет передавать ссылку на свойство класса. Поэтому я использую nameof(), чтобы компилятор мог проверять тип ключа. В классе ошибок ключ сохраняется как строка, доступная только для чтения, чтобы избежать случайных изменений. nameof() возвращает только имя ключа, но не имя класса. Поэтому классу Error требуется другой параметр для типа ресурса, а также имя сборки, если файл ресурсов находится в другой сборке. Необязательные заводские методы для специальных классов ошибок, таких как PersonError, могут иметь имя типа ресурса внутри класса, что упрощает вызовы. Но ничто не мешает вызывающей стороне использовать ключ из другого ключа ресурса, поскольку имя типа ресурса и ключ разделены.

Вопрос: Есть ли лучший вариант для передачи ключа, включая тип ресурса, типобезопасным способом? Есть ли способ обеспечить, чтобы параметр resType был типом ресурса? Похоже, что созданный класс не имеет интерфейса, который можно было бы использовать в качестве селектора. Я подумал о том, чтобы инкапсулировать используемые классы ресурсов в собственные статические классы, которые содержат ссылку на конкретный файл ресурсов, а затем использовать этот класс в качестве параметра для resType .

3) Передайте ключ как лямбда-выражение

     if (!isValid) {
        Error err = new Error (key: () => Resources.ErrorText.MaximumTitleLength,
                               params: MaxSeperatorLength.ToString());
    }
 

Преимущество лямбда-выражения заключается в том, что ключ и тип ресурса передаются в одном аргументе.

Недостатки: может быть передан любой лямбда-код, который выдает строку. Невозможно принудительно использовать ключ ресурса. Когда лямбда-выражение будет вызвано в потоке метода GetErrorMessage.currentThread.CurrentUICulture используется для определения используемой языковой версии. Таким образом, язык потока должен быть сохранен, установлен на запрошенный язык, а затем возвращен к сохраненному.

Вопрос: Есть ли какой-либо способ ограничить лямбда-выражение ключом ресурса? Я не очень хорошо знаком с параметрами lambda. Есть ли способ получить с правой стороны переданные лямбда-ресурсы.ErrorText.MaximumTitleLength» имя ключа и тип ресурса, позволяющие получать перевод с помощью вызова getString(CultureInfo) через ResourceManager.

Общий вопрос: Есть ли лучший способ достичь целей многоязычных и типобезопасных сообщений об ошибках?

РЕДАКТИРОВАТЬ: я нашел некоторый ответ на вопрос № 3. Если я обрабатываю ключ в классе ошибок как лямбда-выражение вместо делегата, у меня есть доступ к свойству Body . Теперь я могу получить (с проверкой на наличие нулей):

 resource key as string -> key.Body.Member.Name
resource Type as string -> key.Body.Member.DeclaringType.FullName
assembly -> key.Body.Member.DeclaringType.Assembly
 

С этими значениями я могу читать файлы ресурсов на нужном языке.

Ответ №1:

В итоге я использовал лямбда-выражения. Это позволяет мне отложить решение о языке и отображать ошибки на разных языках.

Для гибкости и уменьшения количества текстов ошибок я определил распространенные тексты ошибок с помощью заполнителей. Например. запись в файле ресурсов на английском языке выглядит следующим образом:

 Max_Char_Length_Exceeded -> "{0} is too long. The maximum length is {1} characters."
 

Кроме того, я создал статические методы для класса сообщений об ошибках, чтобы обеспечить правильные параметры во время компиляции, также используя лямбда-выражения для перевода имен полей.

 public static ErrorMessage MaxCharLengthExceeded(Expression<Func<string>> fieldKey, int maxLength)
{
  return new ErrorMessage(() => ErrorText.Max_Char_Length_Exceeded, fieldKey, maxLength.ToString());
}