Is It Possible to Access Introduced/Weaved Interfaces and Members by PostSharp During Build Time?
Asked Answered
D

1

1

I am designing a scenario where two PostSharp aspects are working with each other. I have one aspect (FirstAspect in the code below) that is meant to introduce an interface, and then another aspect (SecondAspect in the code below) is supposed to work with the interface that was introduced by the first aspect.

However, it does not seem that the interface that is introduced by the first aspect is ever available to the second aspect.

Here is the code that I am currently working with:

public class Tests
{
    [Fact]
    public void Verify()
    {
        // Not really all that significant as the current code does not compile correctly:
        var sut = new MyClass();
        Assert.True( sut is IInterface );
    }

    public interface IInterface
    {
        void HelloWorld();
    }

    [IntroduceInterface( typeof(IInterface) )]
    public class FirstAspect : InstanceLevelAspect, IInterface, IAspectProvider
    {
        public void HelloWorld() {}

        public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
        {
            // Implementing IAspectProvider appears to ensure this aspect is processed first.
            // This may be a bug.
            // Please see: http://support.sharpcrafters.com/discussions/problems/3365-runtimeinitialize-does-not-follow-ordering-rules#comment_40824072
            // for more information.
            yield break;
        }
    }

    [AspectTypeDependency( AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(FirstAspect) )]
    public class SecondAspect : InstanceLevelAspect, IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects( object targetElement )
        {
            var type = (Type)targetElement;
            if ( !typeof(IInterface).GetTypeInfo().IsAssignableFrom( type ) )
            {
                // This is currently being thrown, as MyClass does not implement 
                // IInterface when the AppDomain is first loaded and initialized:
                throw new InvalidOperationException( $"Does not implement {typeof(IInterface)}" );
            }

            // How to access the weaved elements from FirstAspect? ...

            yield break;
        }
    }

    [FirstAspect, SecondAspect]
    class MyClass {}
}

When I build, the InvalidOperationException in the SecondAspect.ProvideAspects is thrown, as the interface that was introduced by FirstAspect is not available to SecondAspect at the time the call is made. That is, even though the interface has been weaved into the MyClass type, the type as it stands within the current AppDomain as loaded is not marked as having the interface implemented.

What I am looking for is the ability to access and locate all known and weaved interfaces and members on a target element during build time.

I looked into ReflectionSearch, and this is close to what I am looking for, but it does not appear to account for weaved elements at the time calls into this API are made. For instance, making a call to ReflectionSearch.GetMembersOfType does not yield the expected IInterface.HelloWorld on MyClass (which is introduced by FirstAspect in the example above).

Is there another API I should be using to access introduced/weaved elements by PostSharp during build-time? Is this even possible?

Direful answered 25/9, 2016 at 15:43 Comment(4)
PostSharp aspects see only original types and methods, there is no way how to see weaved interfaces on build time. What do you want to do with the introduced interface in SecondAspect?Ake
Thanks for your reply @JakubLinhart. I am wanting to ensure that a particular interface is applied to my class, and then once that interface is applied, apply any number of aspects to it as I normally would. Sounds like I need to figure out another way of doing this?Direful
What about checking presence of FirstAspect custom attributes besieds check for IInterface? Or do you want to apply any aspect to introduced methods as well?Ake
Sorry for the delay here @JakubLinhart I just got back from vacation. :) To be sure here, I am expecting a particular interface method to be implemented by the target class that I want to apply the aspect to. That is, the interface is a dependency of the aspect. If that interface is not implemented on the target class, I want to implement it with a default implementation (represented by FirstAspect above), and then apply the aspect to the method that the interface introduces (whether it exists or was introduced, represented by SecondAspect above). Please let me know if that makes sense!Direful
G
1

So this question looks a little old, but I have a similar issue which I still need an answer to (which is: how do I introduce an attribute to an introduced method without applying the attribute to the implementation and copying it). That said, I may have to ask my own question, as there are some common steps for the pattern you're asking about which may solve your dilemma, but do not solve mine. It looks like you've already experimented with some of this, but for the sake of others that come along, I'll detail it out.

In short, don't use "reflection" types to specify aspect dependency. PostSharp provides attributes which you can use to require aspects to be applied or to require specific order (See: Coping with Several Aspects on the Same Target for an overview), as well as a method for importing members already provided by other aspects (This StackOverflow Answer, while not marked, is the correct answer to that user's question and also shows a way to use the ImportMemberAttribute together with aspect dependency). The ImportMemberAttribute is capable of importing members from other aspects as long as the order is correct; the IsRequired property on this attribute will cause a build error if the member does not exist and was not introduced by an aspect.

Now, if you want your second aspect to be able to apply to all classes that implement an interface, whether the interface was applied by your first aspect or not, you would set the AspectTypeDependencyAttribute with AspectDependencyAction.Order, but not set an AspectTypeDependencyAttribute with AspectDependencyAction.Required; if you only want the second aspect to apply to targets advised by the first then you can apply multiple dependency attributes to specify both the requirement and the order and no Aspect Provider would be required (the above answer also shows an alternative implementation applying Advice to multiple Pointcuts in a single Aspect). Similarly, if you want your first aspect to always require your second aspect you can apply an additional AspectTypeDependencyAttribute to specify requirement in the other direction (i.e. if both require the other you want the requirement specified on both).

Aspect "Priority" can also be used to determine the order aspects are applied, although whenever possible you should use the dependencies instead because they also server as contract documentation.

This is all assuming you don't actually need to use an Aspect Provider (since your comments imply it was done for ordering). You would not want one Aspect Provider to depend on the results of another Aspect Provider (this would violate separation of concerns), you would instead have a single Aspect Provider yield multiple aspects for each target. You can, however, use AspectTypeDependencyAttribute on Aspect Providers as well so, for instance, you can have a Type Level Aspect Provider that orders after a type level aspect that introduces an interface and then in the provider you can loop through methods on the type and inject aspects that depend on the first aspect (e.g. an interface introduction follower by a provider that applies method interception advice to members that can now call methods introduced by the first aspect).

Hope that clears things up for you (or, given the time since the question was asked, anyone else that runs into this issue). Some of this information may also be outdated or inaccurate (for all I know, it may now be possible to detect injected interfaces on the target types passed to aspect providers, under some or any condition), but I believe the patterns expressed are still the preferred practice proposed by PostSharp.

Grefe answered 25/5, 2017 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.