Entwicklung von IPlugin-Implementierungen als zustandslose Systeme

Kategorie: Design, Leistung

Potentielles Risiko: Hoch

Symptome

Mitglieder von Klassen, die die IPlugin interface implementieren, sind potenziellen Thread-Sicherheitsproblemen ausgesetzt, die zu folgenden führen können:

  • Dateninkonsistenzen
  • Langsamere Plugin-Ausführungen

Anweisungen

Verwenden Sie bei der Implementierung von IPlugin keine Memberfelder und -eigenschaften und schreiben Sie die Methode Execute als zustandslose Operation. Alle Informationen über den Zustand pro Aufruf sollten nur über den Ausführungskontext abgerufen werden.

Versuchen Sie nicht, Ausführungszustandsdaten in Elementfeldern oder Eigenschaften zu speichern, die während des aktuellen oder nächsten Plugin-Aufrufs verwendet werden, es sei denn, diese Daten wurden aus dem Konfigurationsparameter gewonnen, der dem überladenen Konstruktor zur Verfügung gestellt wurde.

Verwenden Sie keinen Code, der sich an AppDomain-Ereignissen registriert. Die Plugin-Logik sollte sich nicht auf AppDomain-Ereignisse oder -Eigenschaften verlassen, da die interne Implementierung der Plugin-Infrastruktur das Ausführungsverhalten zu jedem Zeitpunkt ändern kann. Die Registrierung zu AppDomain-Ereignissen kann zu Fehlern führen, auch wenn der Code zu einem bestimmten Zeitpunkt funktionierte.

Leserechte, statische und konstante Elemente sind von Natur aus threadsicher und können auch innerhalb einer Plugin-Klasse zuverlässig verwendet werden. Im Folgenden finden Sie einige Beispiele, wie Sie threadsichere Plug-Ins pflegen können:

Konstante Feldmitglieder

public class Valid_ClassConstantMember : IPlugin
{
   public const string validConstantMember = "Plugin registration not valid for {0} message.";

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

      if (context.MessageName.ToLower() != "create")
            throw new InvalidPluginExecutionException(String.Format(Valid_ClassConstantMember.validConstantMember, context.MessageName));
   }
}

Speichern von Konfigurationsdaten, die im Plugin-Klassenkonstruktor zugewiesen oder gesetzt wurden.

public class Valid_ClassFieldConfigMember : IPlugin
{
   private string validConfigField;

   public Valid_ClassFieldConfigMember(string unsecure, string secure)
   {
      this.validConfigField = String.IsNullOrEmpty(secure)
            ? unsecure
            : secure;
   }

   public void Execute(IServiceProvider serviceProvider)
   {
      if (!String.IsNullOrEmpty(this.validConfigField))
      {
            var message = ValidHelperMethod();
      }
   }

   private string ValidHelperMethod()
   {
      return String.Format("{0} is the config value.", this.validConfigField);
   }
}

Zustandslose Methodenimplementierung

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

      if (ValidMemberMethod(context))
      {
            //Then continue with execution
      }
   }

   private bool ValidMemberMethod(IPluginExecutionContext context)
   {
      if (context.MessageName.ToLower() == "create")
            return true;
      else
            return false;
   }
}

Problematische Muster

Warnung

Diese Muster sollten vermieden werden.

Zuweisung von Feldmitgliedern der Plugin-Klasse während der Ausführung des Plugins

public class Violation_ClassAssignFieldMember : IPlugin
{
   //The instance member used in multiple violation patterns
   internal IOrganizationService service = null;
   internal IPluginExecutionContext context = null;

   public void Execute(IServiceProvider serviceProvider)
   {
      this.context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
      var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

      //The violation
      this.service = factory.CreateOrganizationService(this.context.UserId);

      //Invoke another violation in method member
      AccessViolationProperties();
   }

   private void AccessViolationProperties()
   {
      //Accessing the context and service fields exposes this IPlugin implementation to thread-safety issues
      var entity = new Entity("task");
      entity["regardingid"] = new EntityReference(this.context.PrimaryEntityName, this.context.PrimaryEntityId);

      var id = this.service.Create(entity);
   }
}

Einstellen des Eigenschaftselements der Plugin-Klasse während der Ausführung des Plugins

public class Violation_ClassAssignPropertyMember : IPlugin
{
   //The instance member used in multiple violation patterns
   internal IOrganizationService Service { get; set; }
   internal IPluginExecutionContext Context { get; set; }

   public void Execute(IServiceProvider serviceProvider)
   {
      this.Context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
      var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

      //The violation
      this.Service = factory.CreateOrganizationService(context.UserId);

      //Invoke another violation in method member
      AccessViolationProperties();
   }

   private void AccessViolationProperties()
   {
      //Accessing the Context and Service properties exposes this IPlugin implementation to thread-safety issues
      var entity = new Entity("task");
      entity["regardingid"] = new EntityReference(this.Context.PrimaryEntityName, this.Context.PrimaryEntityId);

      var id = this.Service.Create(entity);
   }
}

Weitere Informationen

Nachdem Microsoft Dataverse die Plugin-Klasse instanziiert hat, speichert die Plattform diese Plugin-Instanz aus Performancegründen zwischen. Dataverse verwaltet die Zeitspanne, in der eine Plug-Instanz im Cache gehalten wird. Bestimmte Vorgänge, wie das Ändern der Registrierungseigenschaften eines Plug-Ins, lösen eine Benachrichtigung an die Plattform aus, um den Cache zu aktualisieren. In diesen Szenarien wird das Plug-In neu initialisiert.

Da die Plattform Plugin-Klasseninstanzen zwischenspeichert, wird der Konstruktor nicht bei jedem Aufruf der Plugin-Ausführung aufgerufen. Aus diesem Grund sollten IPlugin-Implementierungen nicht vom Zeitpunkt der Operationen im Konstruktor abhängen, abgesehen vom Erhalten statischer Konfigurationsdaten.

Ein weiterer Grund, warum IPlugins zustandslos sein sollten, ist, dass mehrere System-Threads die gleiche, gemeinsam genutzte Plugin-Instanz gleichzeitig ausführen können. Dieses Anti-Muster öffnet Mitglieder von Klassen, die IPlugin implementieren, für potenzielle Thread-Sicherheitsprobleme, was zu Dateninkonsistenz oder Leistungsproblemen führen kann.

Siehe auch

Schreiben eines Plug-Ins

Hinweis

Können Sie uns Ihre Präferenzen für die Dokumentationssprache mitteilen? Nehmen Sie an einer kurzen Umfrage teil. (Beachten Sie, dass diese Umfrage auf Englisch ist.)

Die Umfrage dauert etwa sieben Minuten. Es werden keine personenbezogenen Daten erhoben. (Datenschutzbestimmungen).