NameTransformer Documentation

Jun 7, 2011 at 8:09 PM
Edited Jun 7, 2011 at 8:23 PM

Name transformation is based on rules that use regular expression pattern-matching. When a transformation is executed, all registered rules are evaluated in sequence until a transformation can be applied. Any remaining rules, which may or may not have applied, will be ignored. Because of this "short-circuit" evaluation behavior, the most general rules need to be evaluated last. To allow for application-specific custom rules to be added at run-time, the default rules are added in increasing order of specificity (i.e. the most general rules are added to the rule list first). The rules are then evaluated in a LIFO (last in first out) queue. This ensures that application-specific custom rules are always evaluated first.

Custom rules are added by calling the AddRule() method of the NameTransformer object maintained by the ViewLocator and ViewModelLocator classes. Both classes reference their own separate static instance of NameTransformer, so each class maintains its own set of rules.

The calling convention is as follows:

public void AddRule(string replacePattern, IEnumerable<string> replaceValueList, string globalFilterPattern = null)
  • replacePattern: a regular expression pattern for replacing all or parts of the input string
  • replaceValueList: a collection of strings to apply to the replacePattern
  • globalFilterPattern: a regular expression pattern used for determining if rule should be evaluated. Optional.

Or:

public void AddRule(string replacePattern, string replaceValue, string globalFilterPattern = null)
  • replacePattern: a regular expression pattern for replacing all or parts of the input string
  • replaceValue: a string to apply to the replacePattern
  • globalFilterPattern: a regular expression pattern used for determining if rule should be evaluated. Optional.

 

To show how this method is used, we can take a look at one of the built-in rules added by the ViewLocator class:

NameTransformer.AddRule("Model$", string.Empty);

This transformation rule looks for the substring "Model" terminating the ViewModel name and strips out that substring (i.e. replace with string.Empty or "null string"). The "$" in the first argument indicates that the pattern must match at the end of the source string. If "Model" exists anywhere else, the pattern is not matched. Because this call didn't include the optional "globalFilterPattern" argument, this rule applies to all ViewModel names.

This rule yields the following results:

Input

Result

MainViewModel

MainView

ModelAirplaneViewModel

ModelAirplaneView

CustomerViewModelBase

CustomerViewModelBase

The corresponding built-in rule added by the ViewModelLocator is:

NameTransformer.AddRule(@"(?<fullname>^.*$)", @"${fullname}Model");

This rule takes whatever the input is and adds "Model" to the end. This rule uses a regular expression capture group, which is extremely useful in complex transformations. The "replacePattern" assigns the full name of the View to a capture group called "fullname" and the "replaceValue" transforms it into <fullname> "Model".

To demonstrate how the "globalFilterPattern" applies, we can take a look at two other built-in rules for the ViewModelLocator:

//Check for <Namespace>.<BaseName>View construct
NameTransformer.AddRule
    (
        @"(?<namespace>(.*\.)*)(?<basename>[A-Za-z_]\w*)(?<suffix>View$)",
        new[] {
            @"${namespace}${basename}ViewModel",
            @"${namespace}${basename}",
            @"${namespace}I${basename}ViewModel",
            @"${namespace}I${basename}"
        },
        @"(.*\.)*[A-Za-z_]\w*View$"
    );
 
//Check for <Namespace>.Views.<BaseName>View construct
NameTransformer.AddRule
    (
        @"(?<namespace>(.*\.)*)Views\.(?<basename>[A-Za-z_]\w*)(?<suffix>View$)",
        new[] {
            @"${namespace}ViewModels.${basename}ViewModel",
            @"${namespace}ViewModels.${basename}",
            @"${namespace}ViewModels.I${basename}ViewModel",
            @"${namespace}ViewModels.I${basename}"
        },
        @"(.*\.)*Views\.[A-Za-z_]\w*View$"
    );

The "globalFilterPattern" arguments for the two calls are identical with the exception of the addition of "Views\." to the argument in the second method call. This indicates that the rule should be applied only if the namespace name terminates with "Views." (dot inclusive). If the pattern is matched, the result is an array of ViewModel names with namespace terminating with "ViewModels.".

The first rule, which echoes back the original namespace unchanged, would cover all other cases. As mentioned earlier, the least specific rule is added first. It covers the fall-through case when the namespace doesn't end with "Views.".

to be continued...

Jun 7, 2011 at 8:16 PM
Edited Jun 7, 2011 at 8:22 PM

When adding custom application-specific transformation rules, the following replace pattern should prove quite useful. The replace pattern takes a fully-qualified ViewModel name and breaks it into capture groups that should cover almost any transformation:

(?<nsfull>((?<nsroot>[A-Za-z_]\w*\.)(?<nsstem>([A-Za-z_]\w*\.)*))?(?<nsleaf>[A-Za-z_]\w*\.))?(?<basename>[A-Za-z_]\w*)(?<suffix>ViewModel$)

For example, adding the following rule:

ViewLocator.NameTransformer.AddRule
(
    @"(?<nsfull>((?<nsroot>[A-Za-z_]\w*\.)(?<nsstem>([A-Za-z_]\w*\.)*))?(?<nsleaf>[A-Za-z_]\w*\.))?(?<basename>[A-Za-z_]\w*)(?<suffix>ViewModel$)",
    @"nsfull=${nsfull}, nsroot=${nsroot}, nsstem=${nsstem}, nsleaf=${nsleaf}, basename=${basename}, suffix=${suffix}"
);

will yield the following results:

 

Input

Result

MainViewModel

nsfull=, nsroot=, nsstem=, nsleaf=, basename=Main, suffix=ViewModel

ViewModels.MainViewModel

nsfull=ViewModels., nsroot=, nsstem=, nsleaf=ViewModels., basename=Main, suffix=ViewModel

Root.ViewModels.MainViewModel

nsfull=Root.ViewModels., nsroot=Root., nsstem=, nsleaf=ViewModels., basename=Main, suffix=ViewModel

Root.Stem.ViewModels.MainViewModel 

nsfull=Root.Stem.ViewModels., nsroot=Root., nsstem=Stem., nsleaf=ViewModels., basename=Main, suffix=ViewModel

Notes:

The same replace pattern above can be used for the ViewModelLocator by changing the "ViewModel$" to "View$".

You wouldn't ever construct a replace value like in the example above since it would yield an illegal type name. It's merely a replace value that will echo back all of the capture groups for demonstration purposes.

You might notice that the capture groups are not mutually exclusive. Capture groups can be nested as in the example such that "nsfull" captures a full namespace while "nsroot", "nsstem", and "nsleaf" capture individual components of that namespace. You would use the individual components if you needed to "swap out" any one of the individual components.

The capture group "suffix" in the example above does a pattern match on names that end with "ViewModels". The main purpose of this capture group isn't so that it could be used as part of the transformation, since the purpose of the ViewLocator is to resolve a View name. The main reason to have this capture group is to prevent the substring "ViewModels" from being captured in the "basename" group, which in most cases would be part of the string transformation.

Coordinator
Jun 7, 2011 at 8:52 PM

Awesome! Thanks. I'll get this into the official docs soon.

Coordinator
Jun 7, 2011 at 9:14 PM

Ok. I added this to the official docs. Thanks again! This really helps out. REALLY.

Jun 7, 2011 at 9:37 PM

No problem.

BTW, don't forget about that pending pull request. ;)

Also, do you have an opinion about the two intentionally omitted transforms as I noted here?:  http://caliburnmicro.codeplex.com/discussions/259959

Coordinator
Jun 8, 2011 at 4:41 PM

I saw it....I'm just taking a CM coding week off :)