🎯 Objetivos de aprendizaje
- Manejar excepciones correctamente en plugins
- Usar InvalidPluginExecutionException para cancelar operaciones
- Implementar validaciones de negocio efectivas
- Entender el comportamiento de rollback
📚 InvalidPluginExecutionException
La InvalidPluginExecutionException es la forma correcta de cancelar una operación y mostrar un mensaje al usuario:
// Lanzar excepción para cancelar operación
throw new InvalidPluginExecutionException(
"No se puede procesar: el cliente tiene crédito bloqueado. " +
"Contacte al departamento de finanzas.");
Opciones del constructor
// Solo mensaje
throw new InvalidPluginExecutionException("Mensaje de error");
// Mensaje con inner exception (para diagnóstico)
catch (Exception ex)
{
throw new InvalidPluginExecutionException(
"Error procesando el pedido", ex);
}
// Con código de error personalizado
throw new InvalidPluginExecutionException(
OperationStatus.Failed,
12345, // Tu código de error personalizado
"Error de procesamiento");
⚡ Patrón de validación completo
public class ValidacionPedidoPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var trace = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
try
{
Entity target = (Entity)context.InputParameters["Target"];
// Lista de errores
List<string> errores = new List<string>();
// Validación 1: Campo requerido
if (string.IsNullOrEmpty(target.GetAttributeValue<string>("name")))
{
errores.Add("El nombre del pedido es obligatorio");
}
// Validación 2: Valor mínimo
decimal monto = target.GetAttributeValue<Money>("totalamount")?.Value ?? 0;
if (monto < 100)
{
errores.Add("El monto mínimo del pedido es $100");
}
// Validación 3: Consulta a BD
EntityReference clienteRef = target.GetAttributeValue<EntityReference>("customerid");
if (clienteRef != null)
{
Entity cliente = service.Retrieve(clienteRef.LogicalName, clienteRef.Id,
new ColumnSet("creditonhold"));
if (cliente.GetAttributeValue<bool>("creditonhold"))
{
errores.Add("El cliente tiene el crédito bloqueado");
}
}
// Si hay errores, lanzar excepción con todos
if (errores.Count > 0)
{
string mensajeCompleto = string.Join("\n• ", errores);
throw new InvalidPluginExecutionException(
$"No se puede crear el pedido:\n• {mensajeCompleto}");
}
trace.Trace("Todas las validaciones pasaron");
}
catch (InvalidPluginExecutionException)
{
throw; // Re-throw validaciones de negocio
}
catch (Exception ex)
{
trace.Trace($"Error inesperado: {ex}");
throw new InvalidPluginExecutionException(
$"Error procesando el pedido: {ex.Message}", ex);
}
}
}
🔄 Rollback automático
| Stage | Rollback si falla | Comportamiento |
|---|---|---|
| Pre-validation (10) | N/A | No hay transacción aún, la operación simplemente no continúa |
| Pre-operation (20) | ✅ Sí | Todo lo dentro de la transacción se revierte |
| Post-operation Sync (40) | ✅ Sí | La operación principal Y acciones del plugin se revierten |
| Post-operation Async | ❌ No | La operación ya se confirmó, el plugin falla independientemente |
// Ejemplo: Plugin en Post-operation síncrono
public void Execute(IServiceProvider serviceProvider)
{
// ... setup ...
// Esta cuenta ya "existe" en la BD (dentro de la transacción)
Guid nuevaCuentaId = context.PrimaryEntityId;
// Crear contacto relacionado
Entity contacto = new Entity("contact");
contacto["parentcustomerid"] = new EntityReference("account", nuevaCuentaId);
contacto["lastname"] = "Gerente";
Guid contactoId = service.Create(contacto); // Se crea dentro de la transacción
// Si AQUÍ lanzamos excepción...
throw new InvalidPluginExecutionException("Fallo intencional");
// RESULTADO: Ni la cuenta ni el contacto existirán
// Todo se revierte porque estamos dentro de la transacción
}
⚠️ Errores comunes
// ❌ MAL: Tragarse excepciones
try
{
service.Create(registro);
}
catch (Exception ex)
{
trace.Trace($"Error: {ex.Message}");
// SIN throw - el plugin termina "exitosamente" pero la operación falló silenciosamente
}
// ❌ MAL: Lanzar Exception genérica
throw new Exception("Error"); // No es user-friendly
// ✅ BIEN: Re-throw o lanzar InvalidPluginExecutionException
try
{
service.Create(registro);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException(
$"No se pudo crear el registro: {ex.Detail.Message}", ex);
}
📝 Resumen
- Usa
InvalidPluginExecutionExceptionpara cancelar operaciones - El mensaje se muestra al usuario - hazlo claro y útil
- Acumula errores y muestra todos juntos si hay múltiples
- Rollback es automático en Pre-op y Post-op síncrono
- En async, la operación principal ya se confirmó
- Nunca "tragues" excepciones sin re-throw