Welcome to Manicprogrammer Sign in | Join | Help

Continuing the Presenter Saga - Plugins

As I stated in my last post, we're defining a lot of stuff for NMVP release 1.0.0. One of our main concerns right now is how to structure the presenters, so I took two of my really smart friends and had a heated discussion with them about it (don't we love those? lol).

So far we are leaning on a Plugin + Decorator structure, but before explaining let me state what were the premises for this problem:

  1. The presenter must be easily composable, in a sense that the user of the framework must easily add another feature to the presenter without having to change other parts of his application.
  2. There must be compile-time support, at least for the operations that the user of the framework is invoking, like Log or something like that.
  3. It should be REALLY easy creating new plug-ins, since we want the community to contribute in this, so we are creating a plug-in repository in the Stormwind Community site. This way if you love Framework XYZ and want it integrated with NMVP, just write a presenter plug-in for it and there you go. Then you share it with the community.
  4. This structure should offer you guidance, in a sense that it should drive you to use best practices all along, and not the other way around. One major concern for me is people placing business rules inside plug-ins. That's not what they're for, at all.

Given those premises we started debating. So far we've reached a structure that really appeals to me since it answers all those concerns.

We'll be using a structure similar to this one:

[PersistentPresenter]
[ValidatingPresenter]
[LogEnabledPresenter]
public class CustomerDetailsPresenter : Presenter<ICustomerDetails>
{
    public CustomerDetailsPresenter(ICustomerDetails view)
    {
        this.View = view;
    }
    
    public void Save(){
        //Here we do our thing calling the Log plugin.
        //Notice that this log we are using is redundant, since
        //the log plug-in would probably plug in the NMVP
        //lifecycle and log the event OnBeforeSave anyway.
        this.GetPlugin<LogEnabledPresenter>().Log("Beginning to save...");
        //perform saving of data.
        this.GetPlugin<LogEnabledPresenter>().Log("Completed saving.");
    }
    
    public void Validate(){
        //performs validation. Please advise that this method may be empty
        //since a lot of the validation can be performed automatically
        //if you decorate your view with Validation Everywhere attributes.
    }
}

Now let me explain this. Let's begin with the decorating attributes.

Why decorate the presenter with those? Well, this gives the NMVP engine the opportunity to mess with those plug-ins. The engine will instantiate them, place them in a collection, fill their properties with the needed values and hook them to the engine's event lifecycle. This way if your plug-in only executes logic that's related to the life cycle, all you need to do in order to use it is placing the attribute. Very AOP hehehe.

Ok, now let's get to the Save and Validate methods (I trust you get what the constructor is if you used the NMVP framework already).

Those methods are the only flaw with this model as of today. They are not compile-time friendly. If you called the method SaveMe() it would never be called by the engine. I know we are trading compile-time safety for a lot of extensibility, and that's a cool trade-off, but I wish we had some way to minimize that. Notice, once again that if you omit the method, the consequences will de dictated by the plug-in. So for our sample, if I omitted the Validate method, everything would be fine, since it's not Required, but if I omitted the Save method the engine would throw an exception since it's a required method (what would the Save plug-in do without a Save method?).

So now let's talk plug-in authoring (neat name huh?). There are three types of plug-ins: input, output or mixed. Those may not be the best names, and we'll probably evolve them along the line, but this is what they mean:

  1. Input Plugins - Plugins that Receive behavior through conventioned methods like Validate() or Save().
  2. Output Plugins - Plugins that offer methods for the user of the plugin, like Log() or ExecuteRule("CurrencyConversion").
  3. Mixed - Plugins that feature both input and output behavior.

So we'll see samples of each. First an output plugin.

public class LogEnabledPresenter : PluginBase
{
    protected override OnBeforeInit(EventArgs ev){
        Log("Before Init of the presenter " 
            + this.Presenter.GetType().FullName);
    }
    protected override OnAfterInit(EventArgs ev){
        Log("After Init of the presenter " 
            + this.Presenter.GetType().FullName);
    }
    //...
    //you get the idea.
    
    public void Log(string message){
        //Logs the message.
    }
}

As you can see this plug-in participates actively in the NMVP Lifecycle (I'll post about it). This is really cool in that plugin authors can interact with the NMVP Lifecycle to provide bundled functionality for users of the plugin.

RequiredMethod("Save")]
public class PersistentPresenter : PluginBase
{
    protected override OnCustomEvent(string eventName, EventArgs ev){
        if (eventName.Equals("Save",
                             StringComparison.OrdinalIgnoreCase)){
            //Code that calls the Save method on the presenter.
        }
    }
    
    public void RaiseOnBeforeSave(EventArgs ev){
        //Raises the custom event OnBeforeSave.
    }
    public void RaiseOnAfterSave(EventArgs ev){
        //Raises the custom event OnAfterSave.
    }
    public void RaiseOnSaveCancelled(EventArgs ev){
        //Raises the custom event OnSaveCancelled.
    }
}

Since saving will now be an optional part of the NMVP Lifecycle, there won't be typed support for the events that it provides (in this case OnBeforeSave, OnAfterSave and OnSaveCancelled). So one issue I see arising now is how to propagate those events right now, since in composite NMVP you'd raise these events in the HostPresenter and expect them to be fired in every single presenter in the correct order.

Right now my best guess is something like:

HostPresenter.RaiseEvent(typeof(PersistentPresenter), 
                            "OnBeforeSave", EventArgs);

There are some issues here:

  1. The Event name is not typed.
  2. The syntax isn't very friendly.

So one of my main concerns is how to help Plugin Authors encapsulate this logic in their Plugins so users could call:

HostPresenter.GetPlugin<PersistentPresenter>().Save()

Even though this does not seem to improve a lot on syntax, it does in type-safe access, since the logic for calling the custom event is encapsulated in the Plugin. What would get done in that line above is that every single presenter with a PersistentPresenter attribute would have their Save method called in the following order:

  1. OnBeforeSave raised in the PersistentPresenter plugin, so if the presenter subscribed to that it would get called.
  2. Method Save would get called.
  3. OnAfterSave raised in the PersistentPresenter plugin, so if the presenter subscribed to that it would get called.
  4. OnSaveCancelled if the Save method throws. This event would be bubbled to the HostPresenter one. So if one subscribes to one of those, it would get called.

As you can see a lot of functionality is bundled here, and once again, all the user of the framework must do is call on HostPresenter to do the trick. This is the NMVP philosophy, make our lives easier, not harder. Even our lives as plugin authors (I just can't get enough of that lol).

At this point I really need help from the community as to the definition of what a presenter should behave. Get involved. Each person counts. Make a difference. Help us help you!

#112

Published Saturday, August 18, 2007 12:30 PM by heynemann

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments


Enter the text you see in the image:

Leave a Comment

(required) 
required 
(required)