Open sandboxFocusImprove this doc

Coping with Several Aspects on the Same Target

As the team learns aspect-oriented programming and starts adding more aspects to projects, the chance increases that several aspects are added to the same element of code. This could be a major source of trouble if PostSharp did not provide a robust framework to detect and prevent conflicts between aspects:

  • Most aspects need to be ordered. For instance, an authorization aspect must be executed before a caching aspect.

  • Even if some aspects do not require a specific order, it is good to have them applied in a predictable order. Otherwise, some code that works today may break tomorrow just because aspects were applied in a different order.

  • Some aspects conflict: they cannot be on the same target, or not in a given order. For instance, it does not make sense to persist an object using two different aspects: one would persist to the database, the other to the registry.

  • Some aspects require other aspects to be applied. For instance, an aspect changing the mouse pointer to an hourglass requires the method to execute asynchronously, otherwise the pointer shape will never be updated.

PostSharp addresses these issues by making it possible to add dependencies between aspects. The aspect dependency framework is implemented in the namespace PostSharp.Aspects.Dependencies.

Note

The aspect dependency framework is not related to the notion of dependency injection.

Aspect dependency custom attributes

You can express dependencies of an aspect by annotating the aspect class with custom attributes derived from the type AspectDependencyAttribute. Several derived types are available; every type matches other aspects according to different criteria.

Types of aspect dependency custom attributes

Attribute type Description
AspectTypeDependencyAttribute This custom attribute expresses a dependency with a well-known aspect class.
AspectRoleDependencyAttribute This custom attribute expresses a dependency with any aspect classes enrolled in a given role. Its dual is ProvideAspectRoleAttribute: this custom attribute enrolls an aspect class into a role. A role is simply a string. Whenever possible, consider using one of the roles defined in the class StandardRoles.
AspectEffectDependencyAttribute This custom attribute expresses a dependency with any aspect that has a specific effect on the source code or the control flow. Effects are represented as a string, whose valid values are listed in the type StandardEffects. Effects are provisioned by the aspect weaver on the basis of a rough analysis of what the aspect may do; aspect developers cannot assign new effects to aspects. However, they can waive effects by using the custom attribute WaiveAspectEffectAttribute, on the aspect class or on an advice method. For instance, an aspect developer can specify that a trace attribute has no effect at all; this aspect will commute with any other aspect (see below).

All these custom attributes have similar structure and members. The first parameter of their constructor, of type AspectDependencyAction, determines the kind of dependency relationship added between the current aspect and the aspects matched by the custom attribute.

PostSharp supports the following kinds of relationships:

Kinds of aspect dependency relationships

Action Description
Order The dependency expresses an order relationship. The second constructor parameter of the custom attribute, of type AspectDependencyPosition (with values Before or After), must be specified. The custom attribute determines the position of the current aspect with respect to matched aspects.
Require The dependency expresses a requirement. PostSharp will issue a compile-time error if the requirement is not satisfied for any target of the current aspect. The second constructor parameter of the custom attribute, of type AspectDependencyPosition, is optional. If specified, the current aspect must be before or after the matching aspect.
Conflict The dependency expresses a conflict. PostSharp will issue a compile-time error if any aspect matching the dependency rule is present on any target of the current aspect. The second constructor parameter of the custom attribute, of type AspectDependencyPosition, is optional. If specified, an error is issued only if the current aspect is before or after a matching aspect: with After, the error is issued when a matching aspect runs before the current aspect.
Commute The dependency specifies that the current aspect is commutable with any matching aspect. When aspects are commutable, PostSharp does not issue any warning if they are not strongly ordered.

Custom attribute types and values of the enumeration AspectDependencyAction are orthogonal; they can be freely combined.

Examples

Using role-based dependencies

The following code shows how three aspects can be ordered without having explicit knowledge of each other. Each aspect provides a different role, and defines dependencies with respect to other roles.

[PSerializable]
[ProvideAspectRole( StandardRoles.Threading )]
[AspectRoleDependency( AspectDependencyAction.Order, AspectDependencyPosition.Before, "UI" )]
public sealed class BackgroundAttribute : MethodInterceptionAspect
{
    public override void OnInvoke( MethodInterceptionArgs args )
    {
        Console.WriteLine( "Background: running the method on a background thread." );
        Task.Run( args.Proceed ).Wait();
    }
}

[PSerializable]
[ProvideAspectRole( StandardRoles.ExceptionHandling )]
[AspectRoleDependency( AspectDependencyAction.Order, AspectDependencyPosition.After, StandardRoles.Threading )]
[AspectRoleDependency( AspectDependencyAction.Order, AspectDependencyPosition.After, "UI" )]
public sealed class ExceptionDialogAttribute : OnExceptionAspect
{
    public override void OnException( MethodExecutionArgs args )
    {
        Console.WriteLine( $"ExceptionDialog: showing the error '{args.Exception.Message}'." );
        args.FlowBehavior = FlowBehavior.Continue;
    }
}

[PSerializable]
[ProvideAspectRole( "UI" )]
public sealed class StatusTextAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args )
    {
        Console.WriteLine( "StatusText: Working..." );
    }

    public override void OnExit( MethodExecutionArgs args )
    {
        Console.WriteLine( "StatusText: Ready." );
    }
}

When the three aspects are applied to a method that throws an exception, BackgroundAttribute runs first, then StatusTextAttribute, and ExceptionDialogAttribute handles the exception inside the other two aspects:

Background: running the method on a background thread.
StatusText: Working...
Saving the document.
ExceptionDialog: showing the error 'The disk is full.'.
StatusText: Ready.

Using effect-based dependencies

The following code shows how to prevent an authorization aspect from being executed after an aspect that may change the control flow and skip the execution of the method, such as a caching aspect. Then, it shows how the aspect BackgroundAttribute can opt out of this effect, because the aspect developer knows that this aspect does not skip the execution of the method, but only defers it.

Both aspects use advice methods. AuthorizationAttribute declares a conflict positioned After: it conflicts with any aspect that changes the control flow and runs before it. BackgroundAttribute intercepts the method, so the weaver gives it the effect ChangeControlFlow, and the custom attribute WaiveAspectEffectAttribute removes it.

[PSerializable]
[AspectEffectDependency( AspectDependencyAction.Conflict, AspectDependencyPosition.After,
                         StandardEffects.ChangeControlFlow )]
public sealed class AuthorizationAttribute : MethodLevelAspect
{
    [OnMethodEntryAdvice]
    [SelfPointcut]
    public static void OnEntry( [DeclarationName] string methodName )
    {
        Console.WriteLine( $"Authorization: checking the permission to call {methodName}." );
    }
}

[PSerializable]
[WaiveAspectEffect( StandardEffects.ChangeControlFlow )]
public sealed class BackgroundAttribute : MethodLevelAspect
{
    [OnMethodInvokeAdvice]
    [SelfPointcut]
    public static void OnInvoke( [This] object instance, [Binding] IMethodBinding binding, [Arguments] Arguments arguments,
                                 [DeclarationName] string methodName )
    {
        Console.WriteLine( $"Background: running {methodName} on a background thread." );
        Task.Run( () => binding.Invoke( ref instance, arguments ) ).Wait();
    }
}

BackgroundAttribute runs before AuthorizationAttribute because of their AspectPriority. Without WaiveAspectEffectAttribute, the build fails with the error PS0112, which says that AuthorizationAttribute cannot be preceded by BackgroundAttribute. Because BackgroundAttribute waives the effect, both aspects can be applied to the same method in this order:

Background: running Generate on a background thread.
Authorization: checking the permission to call Generate.
Generating the report.

Deferring ordering to aspect users

By adding dependencies to the aspect class, the aspect developer specifies the order of execution of aspects in a fully static way. The same order is used for every element of code to which aspects apply. Most of the time, this behavior is desirable, but there may be situations where we want to defer ordering to users of our aspects.

Aspect users can influence the order of execution of an aspect by setting the aspect property AspectPriority, typically when using the aspect custom attribute (the same property is available in the configuration object as AspectPriority, see Aspect Configuration).

Setting the AspectPriority property on an aspect adds an ordering dependency between this aspect and all other aspects where the same property has been set. Therefore, aspect priorities complement, and do not replace, other ordering dependencies. The aspect developer may specify only the vital aspect dependencies (that is, under-specify aspect ordering), and let the aspect user complete the ordering with priorities.

Caution

Do not confuse the property AspectPriority with AttributePriority. The latter determines the order in which several custom attributes of the same type are processed by the MulticastAttribute engine. The former determines the order in which the aspects are executed at run time.

Adding dependencies to third-party aspects

If you are using aspects provided by several third-party vendors who do not know about each other, you may need to solve conflicts on your own.

You can do that by adding any custom attribute derived from AspectDependencyAttribute at assembly level, and using the property TargetType to specify to which aspect class the dependency applies.

Here is an example:

[assembly: AspectTypeDependency( AspectDependencyAction.Order, AspectDependencyPosition.Before,
typeof(Vendor1.TraceAspect), TargetType = typeof(Vendor2.ExceptionHandlingAspect) )]

The dependency applies to Vendor2.ExceptionHandlingAspect, the aspect type given by TargetType. Therefore, Vendor2.ExceptionHandlingAspect is positioned before Vendor1.TraceAspect: it runs outside of the trace aspect, and handles the exception after the trace aspect has written its messages:

Vendor1.TraceAspect: entering PlaceOrder.
Vendor1.TraceAspect: leaving PlaceOrder.
Vendor2.ExceptionHandlingAspect: handling 'The product is out of stock.'.

See Also

Reference

PostSharp.Aspects.Dependencies