Open sandboxFocusImprove this doc

Example: String normalization

This project contains very simple aspects that implement several cases of string normalization:

  • Trimming,
  • Upper-casing,
  • UTF normalization.

They transform the code as follows:

Source Code



1namespace Metalama.Samples.NormalizeStrings;
2
3public class Class1
4{


5    // Applied on a property.
6    [Trim] public string FirstName { get; set; } = "Borek";

7
8    // Applied on a field.
9    [Normalize] public string LastName = "Stavitel";











10
11    // Applied on a parameter - nullable.
12    public void SetCountry( [ToUpperCase] string? country )
13    {
14        Console.WriteLine($"Country: {country}");
15    }
16}
Transformed Code
1using System;
2using System.Globalization;
3
4namespace Metalama.Samples.NormalizeStrings;
5
6public class Class1
7{
8private string _firstName = "Borek";
9
10    // Applied on a property.
11    [Trim] public string FirstName { get { return this._firstName; } set { value = value.Trim(); this._firstName = value; } }
12    private string _lastName = "Stavitel";
13
14    [Normalize]
15    public string LastName
16    {
17        get
18        {
19            return this._lastName;
20        }
21        set
22        {
23            value = value.Normalize();
24            this._lastName = value;
25        }
26    }
27
28    // Applied on a parameter - nullable.
29    public void SetCountry( [ToUpperCase] string? country )
30{country = country?.ToUpper(CultureInfo.CurrentCulture);
31        Console.WriteLine($"Country: {country}");
32}}

Base aspect class

We created a base class that shares the common behaviors for all aspects:

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Eligibility;
4
5namespace Metalama.Samples.NormalizeStrings;
6
7public abstract class StringContractAspect : ContractAspect
8{
9    public override void BuildEligibility( IEligibilityBuilder<IFieldOrPropertyOrIndexer> builder )
10        => builder.Type().MustBe( typeof(string) );
11
12    public override void BuildEligibility( IEligibilityBuilder<IParameter> builder )
13        => builder.Type().MustBe( typeof(string) );
14
15    [CompileTime]
16    protected static bool IsAppliedToNullableString()
17    {
18        var type = meta.Target.Declaration switch
19        {
20            IFieldOrProperty fieldOrProperty => fieldOrProperty.Type,
21            IParameter parameter => parameter.Type,
22            _ => throw new InvalidOperationException()
23        };
24
25        return type.IsNullable == true;
26    }
27}

This class derives from ContractAspect because this base class conveniently supports fields, properties and parameters at the same type. As its name does not suggest, this class can be used to modify the value, not just to validate it.

The StringContractAspect class overrides BuildEligibility to limit the eligibility of the aspects to fields, properties and parameters of type string. For details regarding eligibility, see Defining the eligibility of aspects.

It also defines a helper method to determine whether the field, property or parameter is nullable.

Concrete aspect classes

With this base class, the concrete implementations are almost trivial. The only difficulty is that the nullable and non-nullable cases must be handled separately.

Let's look at the implementation of the Trim aspect:

1using Metalama.Framework.Aspects;
2
3namespace Metalama.Samples.NormalizeStrings;
4
5[RunTimeOrCompileTime]
6public sealed class TrimAttribute : StringContractAspect
7{
8    public override void Validate( dynamic? value )
9    {
10        if ( IsAppliedToNullableString() )
11        {
12            value = ((string?) value)?.Trim();
13        }
14        else
15        {
16            value = ((string) value!).Trim();
17        }
18    }
19}