Overview of New FxCop Globalization Rules


Now that we know how FxCop works and what we can expect from the FxCop Globalization rules, let's look at writing our own FxCop Globalization rules. We cover this subject in two phases. First, we look at the rules from a usage point of view. If you're just interested in adding some new globalization rules to your library of rules and you don't want to know how they work, you'll need to read only this section. Second, we look at how each rule is implemented. From this, you will learn how to write your own FxCop rules.

I have grouped the rules into three categories:

  • Resource rules

  • Type/Resource rules

  • Instruction rules

The categories are relevant from the point of view of developing the rules, so if you intend only to consume them, then this grouping won't provide you with any benefit. Resource rules are rules that analyze the resources in an assembly. An example is:

  • Control characters embedded in resource string

Type/Resource rules are rules that analyze the resources in an assembly for a given type. They are:

  • Form.Language must be (Default)

  • Form.Localizable must be true

  • Label.AutoSize must be true

Instruction rules are rules that analyze the instructions in an assembly. They are:

  • CultureInfo must not be constructed from LCID

  • CultureInfo not provided by provider

  • DateTime.ToString() should not use a culture specific format

  • Dialog culture dictated by operating system

  • Dialog culture dictated by .NET Framework

  • Do not use literal strings

  • RegionInfo must not be constructed from LCID

  • ResourceManager not provided by provider

  • Resource string missing from fallback assembly

  • Thread not provided by THReadFactory

Resource Rules

"Control characters embedded in resource string"

This simple rule walks through all of the resource string values in an assembly, looking for strings that contain embedded control characters. For example:

 You have 5 deliveries\nPress any key 


This string has a new line control character ("\n") in the string. For a discussion of the pros and cons of embedding control characters in resource strings, see the section entitled "Embedded Control Characters" in Chapter 8. You need to decide for yourself who should have control over control characters in your strings: the developers or the translator. If you opt for the former, you should apply this rule. If you opt for the latter, you should not apply this rule.

Type/Resource Rules

"Form.Language must be (Default)"

This rule catches forms in which the Language property has been set to something other than "(Default)". This rule doesn't correspond to a runtime problem because the form that is displayed is dependent upon CultureInfo.CurrentUICulture. Instead, this is a development problem. What has mostly likely happened here is that a developer has opened a form in Visual Studio and has needed to do some work on a specific culture. So the developer changed the form's language to that culture, performed the work, and then saved the form. So far, there is no problem. As usual, the problem arises during the maintenance phase. The next programmer to open the form won't necessarily check that the form's language is already "(Default)". Visual Studio 2005 is less prone to this problem than Visual Studio 2003 because it provides visual feedback of the selected culture in the Form Designer tab's text. Visual Studio allows certain changes, such as moving and resizing controls, and changing various properties, to refer specifically to the selected culture version of the form; thus, the developer is unwittingly changing only a single culture, not all cultures. Maybe I am too paranoid and this situation won't ever happen, but that's why I like FxCopI can write rules that will never be needed 99.9 percent of the time, but I can be certain that they will always be applied.

"Form.Localizable must be true"

Here's a rather fundamental rule. This rule checks that the Localizable property of the form has been set to true. If it hasn't been set to true, it won't be possible to localize it. I would say that's quite fundamental, but as an application grows in size, it is sometimes possible to forget even the basics.

At the time of writing, however, this rule is appropriate only for forms developed for the .NET Framework 1.1, not for forms developed for the .NET Framework 2.0. The reason for this is that the storage of the Form.Localizable property changed in the .NET Framework 2.0. Here's how Form.Localizable is represented in a .NET Framework 1.1 resx file:

 <data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 


Here's the same again, but for a .NET Framework 2.0 resx file:

 <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> 


The difference is that in the .NET Framework 2.0 Form.Localizable is considered to be metadata instead of data. At the time of writing, FxCop does not support the reading of metadata resources.

"Label.AutoSize must be true"

This rule checks that the AutoSize property of System.Windows.Forms' Label controls has been set to TRue. This applies more to .NET Framework 1.1 applications than .NET Framework 2.0 applications because Visual Studio 2005 sets Label.AutoSize to true for all new Labels. Clearly, this rule is dependent upon whether you feel that Labels should be auto-sized. Refer to the "AutoSize" section of Chapter 8 for the discussion of the pros and cons of setting Label.AutoSize to true.

Instruction Rules

"CultureInfo must not be constructed from LCID" and "RegionInfo must not be constructed from LCID"

The "CultureInfo must not be constructed from LCID" rule checks that the CultureInfo or CultureInfoEx constructors (or the CultureInfo.GetCultureInfo or CultureInfoProvider.GetCultureInfo methods) are not used with integers (integers are locale identifiers, LCIDs). The idea is that you should construct new CultureInfo/CultureInfoEx objects using a name (e.g. "en-US") and not an LCID. The reasoning behind this is that supplementary custom cultures (in the .NET Framework 2.0) all share the same LCID (0x1000, 4096), which is invalid as an LCID used to construct a new CultureInfo. To be sure that code works as well with custom cultures as it does with regular cultures, the code should use culture names. See Chapter 11, "Custom Cultures," for more information. There is an exception to this rule, and that is when constructing a culture for an alternate sort order. For example:

 CultureInfo cultureInfo = new CultureInfo(0x000040A); 


This code creates the Spanish (Spain) culture using the alternate sort order. The rule accepts literal LCIDs as valid but instead protects against integers derived from an expression. In the .NET Framework 1.1, this exception is essential because cultures that use alternate sort orders can be constructed only using LCIDs. However, the .NET Framework 2.0 allows cultures that use alternate sort orders to be constructed using strings (e.g., "es-ES_tradnl"), so the need for the exception is minimized.

The "RegionInfo must not be constructed from LCID" rule is almost identical, with the exceptions that it checks for RegionInfo constructors and it doesn't check for calls to some RegionInfo equivalent to CultureInfoProvider.GetCultureInfo because there is no equivalent. The rule itself, however, is possibly more important because the following pattern often occurs:

 CultureInfo cultureInfo = new CultureInfo("en-US"); RegionInfo regionInfo = new RegionInfo(cultureInfo.LCID); 


To work with custom cultures, the second line should be changed to:

 RegionInfo regionInfo = new RegionInfo(cultureInfo.Name); 


"CultureInfo not provided by Provider"

In some scenarios, it is useful to ensure that all CultureInfo objects are created using a provider or factory instead of directly using the CultureInfo constructor. Two of these situations are discussed in Chapter 6 for updating out-of-date culture information and also for extending CultureInfo objects with additional globalization information. This rule enforces that all objects are created using the Globalization. CultureInfoProvider.GetCultureInfo method, so instead of writing this:

 CultureInfo cultureInfo = new CultureInfo("en-US"); 


you should write this:

 CultureInfo cultureInfo =     Globalization.CultureInfoProvider.GetCultureInfo("en-US"); 


"DateTime.ToString() should not use a culture specific format"

This rule is a cousin of the "Specify IFormatProvider" rule. It checks that the format string passed to DateTime.ToString() is not a culture-specific format. This is not the same as checking that an IFormatProvider is passed. Here's an example that breaks the rule:

 DateTime dateTime = new DateTime(2005, 1, 5); string dateTimeString = dateTime.ToString("MM/dd/yyyy"); 


The "MM/dd/yyyy" format is culture-specific. A date displayed in this format in Germany would not be interpreted as the same date displayed in the U.S. If you include an IFormatProvider, it changes only the formatting of the string; the order is dictated by the string parameter. So the following amendment changes only the slashes to periods:

 string dateTimeString =     dateTime.ToString("MM/dd/yyyy", new CultureInfo("de-DE")); 


All of the single-character string format parameters (e.g., "d", "D") are quite acceptable because they are simply shorthand for a culture-sensitive setting. For example, DateTime.ToString("D") is the same as DateTime.ToLongDateString(), which is culture sensitive. These single-character formats are not raised as an error.

"Dialog culture dictated by operating system" and "Dialog culture dictated by .NET Framework"

These two rules provide reminders about various Windows Forms dialogs. Recall from Chapter 4, "Windows Forms Specifics," that many of the .NET Framework controls (e.g., OpenFileDialog) take their localized resources from the operating system and that one control takes its localized resources from the .NET Framework Language Pack. If you have written an application that allows the user to change the UI culture of your application (either through the "Regional and Language Options" or through a manual approach), the change will affect all of the resources over which your application has control, but it won't affect these dialogs because they are not influenced by your application's settings. As a consequence, your application's user interface will have a split personality.

"Do not use literal strings"

This rule is related to the "Do not pass literals as localized parameters" rule. The latter rule aims for correctness, and the number of false positives that it generates is very low. Necessarily, its scope is narrow. This new rule has a much wider approach and generates a significant number of false positives. I start by explaining the rule and then I talk about how we can overcome the large number of false positives.

The "Do not use literal strings" rule mindlessly ploughs through assemblies looking for strings. It ignores all strings in the InitializeComponent method of Form classes, as these will already have been handled by setting Form.Localizable to true (and the "Form.Localizable must be true" rule ensures that it is true). It also ignores all strings that are parameters to:

  • Exception constructors (because these are handled by other rules)

  • ResourceManager constructors (because these are never localizable)

  • ResourceManagerProvider constructors (because these are never localizable)

  • DateTime.ToString() (because these are never localizable)

  • Debug.WriteLine (because these are used for debugging)

  • TRace.WriteLine (because these are used for debugging)

It also ignores strings that are single characters (after all of the white space has been removed). The result is a "suspect" string. The first time you run this rule, all suspect strings will be reported. They will also be written to a file called Literal-Strings.xml, which will be used later. The list will include a significant number of false positives. The developer should then look at each error and decide which are genuine reports and fix them (by putting the string in a resource). This leaves us with a whole bunch of false positives. We need a way to say that these are okay and that they shouldn't be reported on again. For this purpose, I wrote the Literal String Manager. When you start the Literal String Manager, it looks for the Literal-Strings.xml file and displays it like this:

Figure 13.7. Literal String Manager Showing Suspect Strings


The idea is that you go down the list accepting all of the strings that you don't want to see again (it's a bit like a spam filter for localizability). The list of accepted strings is written to IgnoredLiteralStrings.xml. This file is read by the "Do not pass literal strings" rule so that you don't get told about these false positives the next time you run FxCop.

As such, this rule represents a fine balance between the benefit of being told about strings that you have forgotten to make localizable and the additional effort of maintaining the list of ignored literal strings. My experience of this is that it catches many more strings than I want to admit to, and, as such, I have to live with the inconvenience of keeping the ignored list up-to-date.

"ResourceManager not provided by provider"

This rule is useful if you write custom resource managers (see Chapter 12, "Custom Resource Managers"). It checks to see if a resource manager is created from the ResourceManager class or a descendant of it. The approach recommended in Chapter 12 is to create all resource managers using the ResourceManagerProvider class written in the same chapter. The ResourceManagerProvider class, therefore, does to ResourceManagers what the ADO.NET 2 DbProviderFactories class does to connection classes; it provides a level of indirection between the application and the exact ResourceManager class that is used. This rule ensures that developers stick to the approach of using the ResourceManagerProvider class and don't stray back into creating resource managers using an explicitly named class.

"Resource string missing from fallback assembly"

This rule checks that all resource names used in ResourceManager.GetString() exist in an equivalent resource in the fallback assembly. In this code snippet, the Resource-Manager.GetString() method is being called with a resource name of "Hello":

 string hello = resourceManager.GetString("Hello"); 


This rule looks through the corresponding resource for a key called "Hello", and if the key doesn't exist, the rule identifies it as an error. It makes no difference whether you use strongly-typed resources or not, as the strongly-typed resources simply map a property name onto a string and, hence, will be checked in the same way. Hence, this rule epitomises the value of FxCop; recall my maxim from the beginning of the chapter, "Anything a developer has to remember to do, they will eventually forget to do." One of the things that a developer has to remember to do is to add the keys that they use to the fallback resource. My experience is that this is very easily forgotten, and this rule exists to catch those times.

Of course, this does raise the question of how the rule finds the correct resource to locate the key. Herein lies a bit of an assumption. The assumption is that you have adhered to the naming convention suggested in Chapter 3, "An Introduction to Internationalization." To recap briefly, I suggested that each source file have a corresponding resource file with the same name, but suffixed with "Resources". So "Form1.cs" would normally have an associated resource file "Form1.resx", which is maintained by Visual Studio; your own resources under your own control would go in a file called "Form1Resources.resx". If this convention is maintained, the rule will be able to find the associated resource. If the convention is not maintained, you will need to modify the rule to locate the associated resource.

"Thread not provided by ThreadFactory"

This rule enforces the recommendation made in Chapter 3 that threads should be created using a thread factory instead of the System.Threading.Thread constructor. This ensures that all new threads have their CurrentCulture and Current UICulture properties set, so instead of writing this:

 Thread thread = new Thread(new ThreadStart(Work)); 


developers should write this:

 Thread thread = ThreadFactory.CreateThread(new ThreadStart(Work)); 





.NET Internationalization(c) The Developer's Guide to Building Global Windows and Web Applications
.NET Internationalization: The Developers Guide to Building Global Windows and Web Applications
ISBN: 0321341384
EAN: 2147483647
Year: 2006
Pages: 213

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net