简体   繁体   中英

How to implement language independent error handling using c# .Net ressource files type-safe?

Problem: I'm trying to implement a language independent error handling using.Net resource files. Error messages must be displayed in the UI language and may be logged in a different log language. Error messages might have the content of variables inserted (String.Format). Different errors can be accumulated for example when validating objects. The accumulated errors will then be logged and passed to the UI.

Framework: I want to use Resource resx files and the Visual Studio resource editor, because the language resources are compiled into a class which allows type-safe access to the keys in code. (a test will check if each language has a translation for each key)

Implementation: The errors should be passed upwards in an error object. I tried different implementations, but each one has a drawback and I wonder if there is a better solution or a way to overcome the drawbacks. The codes below are simplified to get the point across.

1) Create two messages, UI and LOG message at error creation

To create the messages I can call the GetString method of the ResourceManager with the UI and the LOG Culture and save those messages in the error object. Eg

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

Drawbacks: The main drawback is that the domain class has to deal with the language concern, violating the separation of concerns. The code is polluted with the language code. The language variations are limited to two. The language decision can't be delayed to later and if log and UI languages are identical, the strings are saved twice.

2) Pass just the key and resource type

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

err.GetMessage(CultureInfo) will then return the message in the desired language.

Drawbacks: The keys in the compiled resource class are properties of type string. C# doesn't allow to pass on a reference to a class property. Therefore I use nameof() to have the compiler be able to check the key type. In the error class the key is saved as a read-only string, to avoid accidental changes. nameof() only returns the key name, but not the class name. Therefore the Error class needs another parameter for the resource type and also the assembly name if the resource file is in a different assembly. Optional factory methods for special Error classes like PersonError can have the resource type name internally to the class, making the calls simpler. But nothing stops the caller from using a key from a different resource key, since the resource type name and the key are split.

Question: Is there any better option to pass the key including the resource type in a type-safe way? Is there a way to enforce that the resType parameter is a resource type? It seems that the created class has no interface that could be used as a selector. I thought of encapsulating the used resource classes in an own static classes that hold the reference to the concrete resource file and then use this class as a parameter for resType.

3) Pass the key as a lambda

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

The benefit of a lambda is that the key and the resource type are passed in one argument.

Drawbacks: Any lambda that produces a string can be passed. It can't be enforced that a resource key is used. When the lambda will be invoked in the GetErrorMessage method Thread.CurrentThread.CurrentUICulture is used to decide which language version is used. So the thread culture has to be saved, set to the requested language, and then returned to the saved one.

Question: Is there any method to limit the lambda to a resource key? I'm not very familiar with lambda options. Is there a way to get from the right side of the passed lambda "Resources.ErrorText.MaximumTitleLength" the key name and the resource type to allow obtaining the translation via GetString(CultureInfo) call via the ResourceManager.

General Question: Is there a better way to accomplish the goals of multi-language and type-safe error messages?

EDIT: I found some answer to question #3. If I treat the key in the Error class as a LambdaExpression instead of a delegate I have access to the Body property. Now I can get (with checking for Nulls):

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

With those values I can read the resource files in the desired language.

I ended up using the LambdaExpressions. This allows me to defer the language decision and have the errors displayed in various languages.

For flexibility and reduced amount of error texts I defined common error texts with placeholders. Eg an entry in the english resource file looks like:

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

Additionally, I created static methods for the error message class to ensure correct parameters at compile time, also using lambda expressions for field name translations.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM