Desarrollar implementaciones de IPlugin como sin estado

Categoría: diseño, rendimiento

Riesgo potencial: elevado

Síntomas

Los miembros de clases que implementan IPlugin interface se exponen a problemas potenciales de seguridad de los subprocesos que pueden llevar a:

  • Incoherencias de datos
  • Ejecuciones de complementos más lentas

Instrucciones

Cuando implemente IPlugin, no utilice los campos y las propiedades del miembro y escriba el método Execute como una operación sin estado. Se debe acceder a toda la información por estado de invocación a través del contexto de ejecución únicamente.

No intente almacenar los datos de estado de ejecución en campos o propiedades de miembros para su uso durante la invocación del complemento siguiente, a menos que esos datos se hayan obtenido del parámetro de configuración proporcionado al constructor sobrecargado.

No use código que se registre en eventos AppDomain. La lógica de complementos no debe basarse en ningún evento o propiedad de AppDomain, ya que la implementación interna de la infraestructura dek complemento puede cambiar el comportamiento de la ejecución en cualquier momento. Registrar a eventos AppDomain puede causar errores incluso si el código funcionó en algún momento.

Los miembros de sólo lectura, estáticos y constantes son intrínsecamente seguros para los subprocesos y también pueden utilizarse de forma fiable dentro de una clase de complementos. A continuación se ofrecen algunos ejemplos sobre cómo mantener complementos seguros para los subprocesos:

Miembros de un campo constante

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

Datos de configuración de almacenamiento asignados o establecidos en el constructor de clases de complementos

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

Implementación de método sin estado

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

Patrones problemáticos

Advertencia

Estos patrones deben evitarse.

Asignación de miembros de clase de complemento durante la ejecución del complemento

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

Configuración de la propiedad de clase de complemento durante la ejecución del complemento

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

Información adicional

Después de que Microsoft Dataverse cree una instancia de la clase del complemento, la plataforma almacena en memoria caché esa instancia del complemento por razones de rendimiento. Dataverse administra el tiempo en que la instancia del complemento se retiene en memoria caché. Algunas operaciones, como cambiar las propiedades de registro del complemento, desencadenarán una notificación para la plataforma de actualizar la memoria caché. En estos escenarios, el complemento se reinicializará.

Puesto que la plataforma almacena en caché instancias de la clase del complemento, no se llama al constructor para cada invocación de ejecución de complemento. Por este motivo, las implementaciones de IPlugin no deberían depender de la temporización de las operaciones en el constructor aparte de la obtención de datos de configuración estáticos.

Otra razón por la que IPlugins no debería tener estado es que los múltiples subprocesos del sistema pueden ejecutar la misma estancia compartida de complemento de forma simultánea. Este anti patrón expone a los miembros de clases que implementan IPlugin a problemas potenciales de seguridad de los subprocesos que pueden llevar a incoherencia o problemas de rendimiento.

Consulte también

Escribir un complemento

Nota

¿Puede indicarnos sus preferencias de idioma de documentación? Realice una breve encuesta. (tenga en cuenta que esta encuesta está en inglés)

La encuesta durará unos siete minutos. No se recopilan datos personales (declaración de privacidad).