Share via


Développer les implémentations IPlugin en mode sans état

Catégorie : Conception, performance

Risque potentiel : élevé

Symptômes

Les membres des classes qui implémentent IPlugin interface sont exposés à des problèmes potentiels de sécurité de thread qui peuvent conduire à :

  • Des données incohérentes
  • Des exécution de plug-ins plus lentes

Conseils

Lorsque vous implémentez IPlugin, n’utilisez pas les champs et les propriétés du membre et écrivez la méthode Execute comme une opération sans état. Toutes les informations sur l’état de l’invocation doivent être consultées dans le contexte d’exécution uniquement.

Ne tentez pas de stocker les données d’état d’exécution dans les champs ou les propriétés du membre pour les utiliser lors de l’invocation du plug-in actuel ou suivant, à moins que ces données soient obtenues à partir du paramètre de configuration fourni au constructeur surchargé.

N’utilisez pas de code qui est enregistré dans les événements AppDomain. La logique du plug-in ne doit pas se baser sur les événements ou les propriétés AppDomain, car la mise en œuvre interne de l’infrastructure du plug-in peut modifier le comportement d’exécution à tout moment. L’enregistrement dans les événements AppDomain peut entraîner des échecs même si le code fonctionnait à un moment donné.

Les membres en lecture seule, statiques, et constants sont sûrs pour le thread de manière intrinsèque et peuvent également être utilisés de manière fiable dans une classe de plug-in. Les exemples suivants expliquent comment maintenir des plug-ins sûrs pour le thread :

Membres de champ constants

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

Le stockage de données de configuration attribuées ou définies dans le fabricant de plug-ins de classe

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

Implémentation de la méthode sans état

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

Schémas problématiques

Avertissement

Ces schémas doivent être évités.

Attribution d’un membre de champ de classe de plug-in lors de l’exécution du plug-in

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

Configuration d’un membre de propriété de classe de plug-in lors de l’exécution du plug-in

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

Informations supplémentaires

Une fois que Microsoft Dataverse a instancié la classe du plug-in, la plate-forme met en cache cette instance de plug-in pour des raisons de performances. Dataverse gère la durée de la mise en cache d’une instance de plug-in. Certaines opérations, comme modifier les propriétés d’enregistrement d’un plug-in, déclenchent une notification à la plateforme pour actualiser le cache. Dans ces scénarios, le plug-in est réinitialisé.

Comme la plateforme met en cache les instances de la classe du plug-in, le constructeur n’est pas appelé pour chaque invocation d’exécution du plug-in. Pour cette raison, les mises en œuvre d’IPlugin ne doivent pas dépendre de la chronologie des opérations dans le constructeur, hormis pour obtenir des données de configuration statiques.

Une autre raison pour laquelle les IPlugins doivent être sans état est que plusieurs threads système peuvent exécuter la même instance de plug-in partagée simultanément. Cet anti-modèle expose les membres des classes qui implémentent IPlugin à des problèmes potentiels de sécurité de thread, qui peuvent conduire à une incohérence des données ou à des problèmes de performances.

Voir aussi

Écrire un plug-in

Notes

Pouvez-vous nous indiquer vos préférences de langue pour la documentation ? Répondez à un court questionnaire. (veuillez noter que ce questionnaire est en anglais)

Le questionnaire vous prendra environ sept minutes. Aucune donnée personnelle n’est collectée (déclaration de confidentialité).