2.3 Tu Primer Plugin: Hello World

Escribe tu primer plugin funcional con código de ejemplo

Ha llegado el momento que estabas esperando: escribir código que realmente haga algo. En esta lección crearemos tu primer plugin funcional, uno que puedas registrar y ver ejecutándose en tu entorno de Dataverse.

Objetivos de aprendizaje

  • Escribir un plugin funcional desde cero
  • Entender cada línea de código y por qué está ahí
  • Implementar logging efectivo con ITracingService
  • Aprender a validar y rechazar operaciones

El plugin más simple del mundo

Empecemos con algo básico: un plugin que simplemente registra información sobre la operación que lo disparó. No hace nada útil en términos de negocio, pero te permite verificar que todo el flujo funciona.


using System;
using Microsoft.Xrm.Sdk;

namespace Contoso.CRM.Plugins
{
    public class HelloWorldPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Paso 1: Obtener el servicio de tracing
            ITracingService trace = (ITracingService)
                serviceProvider.GetService(typeof(ITracingService));
            
            trace.Trace("HelloWorldPlugin: Iniciando ejecución");
            
            // Paso 2: Obtener el contexto de ejecución
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            
            // Paso 3: Registrar información sobre la operación
            trace.Trace($"Mensaje: {context.MessageName}");
            trace.Trace($"Entidad: {context.PrimaryEntityName}");
            trace.Trace($"Id del registro: {context.PrimaryEntityId}");
            trace.Trace($"Stage: {context.Stage}");
            trace.Trace($"Depth: {context.Depth}");
            trace.Trace($"Usuario que inició: {context.InitiatingUserId}");
            
            trace.Trace("HelloWorldPlugin: Ejecución completada");
        }
    }
}

Analicemos cada parte de este código.

Los using statements

Solo necesitamos dos namespaces para un plugin básico: System para tipos básicos como Guid y Exception, y Microsoft.Xrm.Sdk que contiene todo lo relacionado con plugins.

La interfaz IPlugin

Toda clase que quiera ser un plugin debe implementar IPlugin. Esta interfaz define un único método: Execute(IServiceProvider serviceProvider). No hay constructores especiales, no hay otros métodos. Todo ocurre en Execute.

El IServiceProvider

El parámetro serviceProvider es tu puerta de acceso a todo lo que Dataverse te proporciona. No contiene los servicios directamente, pero sabe cómo obtenerlos. Usamos GetService(typeof(T)) para pedir cada servicio que necesitamos.

El ITracingService

Lo primero que hago siempre es obtener el servicio de tracing. Si algo falla en mi plugin, quiero tener logs que me digan dónde y por qué. Los traces se acumulan durante la ejecución y se pueden ver en el Plugin Trace Log o en los mensajes de error si el plugin falla.

El IPluginExecutionContext

El contexto contiene toda la información sobre la operación actual. En el ejemplo anterior logueo las propiedades más importantes, pero hay muchas más que veremos a lo largo del curso.


Un plugin que hace algo útil: validación

El ejemplo anterior es bueno para verificar que el flujo funciona, pero no aporta valor de negocio. Vamos a crear algo más práctico: un plugin que valida que las cuentas tengan un número de teléfono antes de poder crearse.


using System;
using Microsoft.Xrm.Sdk;

namespace Contoso.CRM.Plugins.Account
{
    /// <summary>
    /// Valida que las cuentas tengan número de teléfono.
    /// Registro: Create on account, Pre-validation, Synchronous
    /// </summary>
    public class RequirePhoneOnAccountPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtener servicios
            ITracingService trace = (ITracingService)
                serviceProvider.GetService(typeof(ITracingService));
            
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            
            trace.Trace("RequirePhoneOnAccountPlugin: Inicio");
            
            // Validar que estamos en el contexto correcto
            if (context.MessageName != "Create")
            {
                trace.Trace("No es Create, saliendo");
                return;
            }
            
            if (context.PrimaryEntityName != "account")
            {
                trace.Trace("No es account, saliendo");
                return;
            }
            
            // Obtener el Target
            if (!context.InputParameters.Contains("Target"))
            {
                trace.Trace("No hay Target, saliendo");
                return;
            }
            
            Entity target = (Entity)context.InputParameters["Target"];
            trace.Trace($"Target obtenido: {target.LogicalName}");
            
            // Obtener el teléfono
            string telefono = target.GetAttributeValue<string>("telephone1");
            trace.Trace($"Teléfono: '{telefono ?? "(vacío)"}'");
            
            // Validar
            if (string.IsNullOrWhiteSpace(telefono))
            {
                throw new InvalidPluginExecutionException(
                    "El número de teléfono es obligatorio para crear una cuenta. " +
                    "Por favor, complete el campo 'Teléfono principal'.");
            }
            
            trace.Trace("Validación pasada, teléfono presente");
            trace.Trace("RequirePhoneOnAccountPlugin: Fin");
        }
    }
}

Validaciones de seguridad al principio

Observa las validaciones al inicio: verifico MessageName, PrimaryEntityName, y la existencia del Target. ¿Por qué? Porque aunque registres el plugin solo para Create de account, es buena práctica validar.

En el futuro, alguien podría cambiar el registro por error. O podrías copiar el código a otro plugin y olvidar ajustar algo. Estas validaciones actúan como red de seguridad.

GetAttributeValue: acceso seguro a campos

En lugar de acceder al campo directamente con target["telephone1"], uso GetAttributeValue<string>. Este método devuelve null si el campo no existe, en lugar de lanzar una excepción. Es más seguro para campos opcionales.

InvalidPluginExecutionException: la forma correcta de rechazar

Cuando quieres cancelar una operación y mostrar un mensaje al usuario, lanzas InvalidPluginExecutionException. Esta excepción especial le dice a Dataverse: "rechaza esta operación y muestra este mensaje al usuario".

El mensaje que proporcionas aparece directamente en la interfaz, así que debe ser claro y orientado al usuario, no técnico. "El número de teléfono es obligatorio" es mucho mejor que "NullReferenceException en línea 42".

Consejo práctico: Los mensajes de InvalidPluginExecutionException se muestran al usuario final. Escríbelos como si estuvieras hablando directamente con ellos. Evita jerga técnica y ofrece orientación sobre cómo resolver el problema.

Un plugin que modifica datos

Las validaciones son útiles, pero a veces quieres que el plugin haga algo con los datos. Aquí tienes un ejemplo que genera un código de cliente automáticamente:


using System;
using Microsoft.Xrm.Sdk;

namespace Contoso.CRM.Plugins.Account
{
    /// <summary>
    /// Genera un código de cliente automático.
    /// Registro: Create on account, Pre-operation, Synchronous
    /// </summary>
    public class GenerateAccountCodePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService trace = (ITracingService)
                serviceProvider.GetService(typeof(ITracingService));
            
            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));
            
            trace.Trace("GenerateAccountCodePlugin: Inicio");
            
            // Validaciones
            if (context.MessageName != "Create") return;
            if (context.PrimaryEntityName != "account") return;
            if (!context.InputParameters.Contains("Target")) return;
            
            Entity target = (Entity)context.InputParameters["Target"];
            
            // Verificar si ya tiene código (por si viene de importación)
            string codigoExistente = target.GetAttributeValue<string>("accountnumber");
            if (!string.IsNullOrWhiteSpace(codigoExistente))
            {
                trace.Trace($"Ya tiene código: {codigoExistente}, no generamos");
                return;
            }
            
            // Generar nuevo código
            // En un caso real, consultarías el último número usado
            string timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
            string codigo = $"ACC-{timestamp}";
            
            trace.Trace($"Código generado: {codigo}");
            
            // Modificar el Target para incluir el código
            target["accountnumber"] = codigo;
            
            trace.Trace("GenerateAccountCodePlugin: Fin");
        }
    }
}

Modificando el Target

El punto clave de este plugin está en la línea target["accountnumber"] = codigo. En Pre-operation, el objeto target es el que Dataverse va a guardar. Cualquier modificación que hagas aquí se refleja en el registro final.

No necesitas llamar a ningún método especial para guardar. El sistema se encarga automáticamente cuando el pipeline continúa.

Verificando datos existentes

Antes de generar el código, verifico si ya tiene uno. Esto es importante para escenarios de importación de datos: si alguien importa cuentas desde un sistema externo que ya tienen códigos, no quieres sobrescribirlos.


Compilando y verificando

Una vez que tienes tu plugin escrito, compila el proyecto con Ctrl+Shift+B o Build → Build Solution.

Verifica que:

La compilación termina sin errores. Si ves errores, lee los mensajes cuidadosamente. Los más comunes son: falta el paquete NuGet de CoreAssemblies, o usas alguna característica de .NET que no existe en Framework 4.6.2.

El archivo DLL existe en la carpeta bin/Debug (o bin/Release si compilas en Release). Este es el archivo que subirás a Dataverse.

El assembly está firmado. Puedes verificarlo con el comando sn -vf TuProyecto.dll desde Developer Command Prompt.


Próximos pasos

Ahora tienes un plugin compilado y listo. En la siguiente lección veremos cómo usar el Plugin Registration Tool para registrarlo en tu entorno de desarrollo y verlo en acción.


Puntos clave

  • Los plugins implementan IPlugin con un único método Execute()
  • Siempre obtén ITracingService primero para tener logs disponibles
  • Valida MessageName, entidad y existencia del Target al inicio
  • Usa GetAttributeValue<T> para acceso seguro a campos
  • InvalidPluginExecutionException cancela la operación y muestra mensaje al usuario
  • En Pre-operation, modificar el Target afecta el registro que se guarda
  • Compila y verifica que el DLL existe y está firmado antes de intentar registrar

Para profundizar

Inicia sesión e inscríbete para guardar tu progreso.
En este curso
¿Te ha resultado útil?