Big Disappointment: PIAB not applicable to WCSF ViewPresenter Pattern .. !

Topics: Enterprise Library Core, General discussion, Policy Injection Application Block
Jun 15, 2008 at 10:23 AM

hi,

I have been spending hours trying to get PIAB (policy injection) to work on my WCSF View-Presenter recipe generated project.

What I try to do is to wrap my presenter class by PolicyInjection so it can be interceptable and do necessary AOP-like works.

First, in the xml configuration, I bind a policy to the presenter class's method OnViewLoaded().

Below is where I begin:

[CreateNew]
public MyViewPresenter Presenter
{
    get
    {
        return this._presenter;
    }
    set
    {
        if (value == null)
            throw new ArgumentNullException("value");

        this._presenter = value;    //Replace this line with few TRY scenarios to do Policy injection wrapping (see below next ..)
        this._presenter.View = this;        
    }
}

Try 1:
this._presenter = (MyViewPresenter)PolicyInjection.Wrap<IPresenter>(value);

Result: Compile Error !

Try 2:
IPresenter proxy = PolicyInjection.Wrap<IPresenter>(this._presenter);
this._presenter = (MyViewPresenter)proxy;

Result: Runtime Error: Unable to cast transparent proxy to type ..

Try 3:
this._presenter = PolicyInjection.Wrap<MyViewPresenter>(this._presenter);

Result: Runtime Error: The type MyViewPresenter is not interceptable ..

Try 4:
PolicyInjection.Wrap<IPresenter>(this._presenter);
this._presenter.OnViewLoaded();

Result: Compiled OK. But no policy invoked when called OnViewLoaded().

Try 5:
IPresenter proxy = PolicyInjection.Wrap<IPresenter>(this._presenter);
proxy.OnViewLoaded();

Result: Working .. Policy invoked successfully.

I tried many possible combinations. So far, only Try 5 is working. However, policy can only be invoked by the locally declared instance (proxy) only. Once exited the function. The next time the presenter class function OnViewLoaded() is called everytime, its binded policy will never invoke. That means another failed !

My conclusion:
Obviously, the EntLib team did a great job in creating the impressive PIAB for AOP-like work. But only if it can intercept and adopt themselves in the WCSF recipe generated View-Presenter project.

Please correct me if I am wrong and if there is a way to get PIAB working on WCSF VP project ...

Otherwise, with AOP emphasis, I will have to think twice if I want to continue staying with P&P framework and to consider other alternative AOP framework like castlewindsor or spring.net. (p/s: I hope not as my system is fully on P&P framework currently).

Elvin

Jun 15, 2008 at 3:46 PM
Further exploration on PIAB has make me understand better.

The following 2 scenarios will explain why:

Try Scenario 1:
public class TestPresenter() : IPresenter
{
    [Tag("Log")]
    public void Method1()
    {
    }

    [Tag("Log")]
    public void Method2()
    {
    }
}

For the above, I try to execute it by calling below:
    IPresenter proxy = PolicyInjection.Wrap<IPresenter>(this._presenter);
    proxy.Method1();
    proxy.Method2();

Result: Both method 1 and 2 will be intercepted by the log policy successfully.

Try Scenario 2:
But if try a different approach by replacing Method1() above with:
    [Tag("Log")]
    public void Method1()
    {
        Method2();
    }

and then call Method1 by calling below:
    IPresenter proxy = PolicyInjection.Wrap<IPresenter>(this._presenter);
    proxy.Method1();

Result: Both method 1 and 2 will be called but log policy will only intercept Method 1 (and never 2).

The above test results hint that PIAB is very limited AOP-like functionality. Only the entry level method (Method1() as in above example) will be intercepted, all methods calls subsequently from within Method1() will not be intercepted.

So, if currently I want to comfortably ensure all methods inside the presenter class are intercepted for logging and exception handling, I have to forget it, PIAB cannot do it .. 

Hope someone can prove me wrong again.. if not, will just have to feel really disappointed with PIAB.

Elvin

Jun 16, 2008 at 6:02 PM

Hi Elvin,

PIAB default interceptor has some specific requirements about which classes can be intercepted, and from what you've shown here it looks like the classes generated by WCSF do not fulfill these requirements. I'm not familiar with WCSF, but would it be possible to either change the base class for MyViewPresenter to MarshallByRefObject or define an interface with your presenter's methods to make it a candidate to PIAB's interception? It's not clear from your code samples whether any of these alternatives would work.

Because of the way PIAB works, you need to invoke the intercepted methods through the reference to a new object returned by PolicyInjection; the original reference is not updated, and that's why your tries #4 and #5 don't work as you expect. You need to set the property to the reference returned by PolicyInjection so calls will be intercepted, like #3 if MyVewPresenter inherited from MBRO.

Keep in mind PIAB is not AOP in the pointcuts-and-weaving sense; the interceptions only apply to external calls before and after executing the original method as your experiments prove. Here's an in-depth post from Ed Jeziersky about PIAB's internals that might help deciding whether PIAB is the tool you need.

Fernando

Dec 25, 2009 at 10:09 AM
Edited Dec 25, 2009 at 10:14 AM

Hi Elvin,

I had the same task to apply a policy to a presenter and I did it successfully. May be my post will be very late for you, but anyway I want to help you and may be others ... Ok, lets start.

I have used the way from Try #5 because for Presenters classes it is the easiest solution. According the documentation in order to apply policy to a class, that class must be derived from MarshallByRefObject class or implements some interface. In our case a Presenter class is not derived from from MarshallByRefObject class. So the only way for us (without change code of WCSF) is to use the interface approach.

1. So, first we have to do is to declare interface for out Presenter class with the methods to which we want to apply a policy:

public interface ILoginPresenter
{
    void OnViewInitialized();
    void OnViewLoaded();
    void OnLoginButtonClicked();
}

2. Implement our interface in Presenter class:

public class LoginPresenter : Presenter<ILoginView>, ILoginPresenter
{
    // ...
        
    public LoginPresenter(/*...*/)
    {
         // ...
    }

     // OnViewInitialized (optional) - already presents in Presenter class. 
     // OnViewLoaded (optional) - already presents in Presenter class. 

     public void OnLoginButtonClicked()
     {
         // ...
     }
}        

3. Change presenter field type from LoginPresenter to ILoginPresenter, because we have to use policy returned object instead of not wrapped one.

4. Initialize that field with Policy Injection Application Block.

// First initialize real class.
LoginPresenter presenter = value;
presenter.View = this;

// Apply policy.
ipresenter = PolicyInjection.Wrap<ILoginPresenter>(presenter);

5. Invoke methods of the presenter only through ILoginPresenter interface.

public partial class LoginView : Page, ILoginView
{
    // Use ILoginPresenter instead of LoginPresenter class.
    private ILoginPresenter ipresenter;

    [CreateNew]
    public LoginPresenter Presenter
    {
        set
        {
            if (value == null) {
                throw new ArgumentNullException("value");
            }

            // First initialize real class.
            LoginPresenter presenter = value;
            presenter.View = this;

            // Apply policy.
            ipresenter = PolicyInjection.Wrap<ILoginPresenter>(presenter);
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack) {
            // All access is done through ILoginPresenter interface.
            ipresenter.OnViewInitialized();
        }

        // All access is done through ILoginPresenter interface.
        ipresenter.OnViewLoaded();
    }

    protected void loginDetailsView_ItemCommand(object sender, DetailsViewCommandEventArgs e)
    {
        if (!IsValid) {
            return;
        }

        if (e.CommandName == "Login") {
            // All access is done through ILoginPresenter interface.
            ipresenter.OnLoginButtonClicked();
        }
    }      
}

Enjoy programming!