Tag Archives: plugin

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
        }
    }
}

Plug-In auf Close Message eines Incidents – wo ist die Incident Id?

Wird ein Plug-In auf die “Close” Message eines Incidents registriert, wird das Plug-In gefeuert, sobald der Incident geschlossen wird. Die “PrimaryEntityId” des “IPluginExecutionContext” ist jedoch leer (Guid.Empty). Wie erhalte ich die Id des Incidents? Die Id des Incidents, welches die “Close” Message ausgelöst hat erhält man wie folgt:

var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var incidentResolutionEntity = (Entity)context.InputParameters["IncidentResolution"];
Guid incidentId = ((EntityReference)incidentResolutionEntity.Attributes["incidentid"]).Id;

Externe Daten in CRM Grid einbinden

Mithilfe eines RetrieveMultiple Plug-Ins lässt sich die EntityCollection welche als Resultat zurückgegeben wird manipulieren. Das kann nützlich sein um z.B. Entitäten mit Informationen anzureichern oder Daten von einem Fremdsystem direkt im CRM anzuzeigen.

Das Plug-In wird auf der RetrieveMultiple Message registriert. Als Eventing Pipeline Stage wird “Post-Operation” gewählt. Zu diesem Zeitpunkt enthält der Output Parameter “BusinessEntityCollection” bereits alle Entitäten die zurückgegeben werden. Diese Collection lässt sich nun einfach manipulieren. Der folgende Code zeigt, wie eine eigene Dummy Entität hinzugefügt werden kann.

public class Class1 : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        var outputEntities = context.OutputParameters["BusinessEntityCollection"] as EntityCollection;

        var entity = new Entity("account");
        entity.Attributes.Add("name", "test");
        entity.Attributes.Add("new_link", "http://google.ch");

        outputEntities.Entities.Add(entity);
    }
}

Das nachfolgende Bild zeigt, wie das Plug-In registriert werden muss.

ManipulateEntityCollection