Plug-Ins und Instanzvariablen

Ein Plug-In für CRM implementiert das Interface “IPlugin” welches die Methode “public void Execute(IServiceProvider serviceProvider)” vorschreibt.
Diese Methode wird aufgerufen sobald das Plug-In ausgeführt wird und enthält die Logik. Aber wann wird die Plug-In Klasse instantiiert?

Auf MSDN ist im Artikel Write a plugin-in folgender Text, in einem mit “Important” markierten Abschnitt, zu finden:

For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in’s Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables or attempt to store any data in member variables for use during the next plug-in invocation unless that data was obtained from the configuration parameter provided to the constructor. Changes to a plug-ins registration will cause the plug-in to be re-initialized.

Zusammengefasst heisst dass ein Plug-In wird einmal instantiiert und danach wird nur noch jeweils die Execut Methode ausgeführt.
Aus diesem Grund sollten keine Instanzvariablen verwendet werden.

Folgendes sollte also nicht gemacht werden.
Das Plug-In wird trotzdem funktionieren, es resultiert aber in einem unerwartetem Verhalten.

public class MyPlugin : IPlugin
{
    private ITracingService tracingService;
    private IPluginExecutionContext context;
    private Entity entity;

    public void Execute(IServiceProvider serviceProvider)
    {
        tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        entity = (Entity)context.InputParameters["Target"];

        DoThis();
        DoThat();
    }

    private void DoThis()
    {
        // Do something (using tracingService, context and entity)
    }

    private void DoThat()
    {
        // Do something (using tracingService, context and entity)
    }
}

Der Code muss wie folgt geschrieben werden, damit er erwartungsgemäss funktioniert.

public class MyPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var entity = (Entity)context.InputParameters["Target"];

        DoThis(tracingService, context, entity);
        DoThat(tracingService, context, entity);
    }

    private void DoThis(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
    {
        // Do something
    }

    private void DoThat(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
    {
        // Do something
    }
}

Diese Lösung ist natürlich suboptimal und führt zu unleserlichem Code.
Eine bessere Option stellt die Verwendung einer privaten Klasse dar um die Logik zu kapseln. Somit können Instanzvariablen verwendet werden. Am besten werden gleich alle Abhängigkeiten zum Plug-In abstrahiert. D.h. anstatt den IPluginExecutionContext gebe ich nur die benötigten Eigenschaften über den Konstruktor von “PluginLogic” weiter. Somit lässt sich der Code auch ausserhalb eines Plug-Ins ausführen und/oder mit Unit Tests testen.

public class MyPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var entity = (Entity)context.InputParameters["Target"];

        var pluginLogic = new PluginLogic(tracingService, context, entity);
        pluginLogic.Run();
    }

    private class PluginLogic
    {
        private readonly ITracingService tracingService;
        private readonly IPluginExecutionContext context;
        private readonly Entity currentEntity;

        public PluginLogic(ITracingService tracingService, IPluginExecutionContext context, Entity currentEntity)
        {
            this.tracingService = tracingService;
            this.context = context;
            this.currentEntity = currentEntity;
        }

        public void Run()
        {
            DoThis();
            DoThat();
        }

        private void DoThis()
        {
            // Do something
        }

        private void DoThat()
        {
            // Do something
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *