Question

I have a Dynamics CRM 2013 plugin executing in the Sandbox.

This code has the following custom exception class:

    [Serializable]
    public class PluginValidationException : Exception
    {
        public PluginValidationException()
        {
        }

        protected PluginValidationException(SerializationInfo info, StreamingContext context) 
            : base(info, context)
        {            
        }

        public PluginValidationException(string message)
            : base(message)
        {
        }

        public PluginValidationException(string message, Exception inner)
            : base(message, inner)
        {
        }
    }

When this exception is thrown in the plugin it results in a generic error window, with no details in the log file:

enter image description here

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: System.Runtime.Serialization.SerializationException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #1355B4E4Detail: -2147220970 CallStack at Microsoft.Crm.Application.Platform.ServiceCommands.PlatformCommand.XrmExecuteInternal() at Microsoft.Crm.Application.Platform.ServiceCommands.CreateCommand.Execute() at Microsoft.Crm.Application.Platform.EntityProxy.Create(Boolean performDuplicateCheck, Guid auditingTransactionId) at Microsoft.Crm.Application.Platform.EntityProxy.Create(Boolean performDuplicateCheck) at Microsoft.Crm.Application.Platform.EntityProxy.CreateAndRetrieve(String[] columnSet, Boolean performDuplicateCheck) at Microsoft.Crm.Application.WebServices.InlineEdit.CommandBase.UpdateEntity(Entity entity, Boolean retrieve) at Microsoft.Crm.Application.WebServices.InlineEdit.SaveCommand.ExecuteCommand(String commandXml) at Microsoft.Crm.Application.WebServices.InlineEdit.CommandBase.Execute(String commandXml) System.Runtime.Serialization.SerializationException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #1355B4E4 2014-04-06T02:04:30.0972001Z [Demo.DemoPlugin: Demo.DemoPlugin.BasicCrmPlugin] [d86b89ab-f1bc-e311-9408-000c29254b18: Demo.DemoPlugin.BasicCrmPlugin: Create of contact]

Looking at the CRM trace log shows the following:

System.Runtime.Serialization.SerializationException: Type 'Demo.Helpers.PluginValidationException' in assembly 'Demo.DemoPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fbb51ba1e588d276' is not marked as serializable. at Microsoft.Crm.Sandbox.SandboxAppDomainHelper.Execute(IServiceEndpointNotificationService serviceBusService, IOrganizationServiceFactory organizationServiceFactory, String pluginTypeName, String pluginConfiguration, String pluginSecureConfig, IPluginExecutionContext requestContext) at Microsoft.Crm.Sandbox.SandboxWorker.Execute(SandboxCallInfo callInfo, SandboxPluginExecutionContext requestContext, Guid pluginAssemblyId, Int32 sourceHash, String assemblyName, Guid pluginTypeId, String pluginTypeName, String pluginConfiguration, String pluginSecureConfig, SandboxRequestCounter& workerCounter)

I do not, based on some reading, believe this is a bug - rather it is because custom Exception classes are not, inherently trusted as of .NET 4 (I'm using .NET 4.5.)

Does anyone know how to make a custom exception class that will work with the CRM Sandbox. I'm using a custom exception class because I catch errors and need to distinguish between an InvalidPluginExecutionException an exception caused because the plug-in is incorrectly registered.

UPDATED Apr 08 2014

Here is the code in the plugin that catches the exceptions, with significant simplification for putting it on Stackoverflow:

        try
        {
            //TODO: Prevalidation Logic
            ValidatePluginExecution(crmContext, logging, out keyName);
            //TODO: Postvalidation Logic
        }
        catch (PluginValidationException ex)
        {
            //TODO: Specific logging for Plugin Validation Exception
            throw new InvalidPluginExecutionException("Did Not Validate");                    
        }
        catch (InvalidPluginExecutionException ex)
        {
            logging.Write("InvalidPluginExectionException at Plugin Validation");                    
            throw;
        }
        catch (Exception ex)
        {
            logging.Write("Unhandled Exeception During Plugin Validation Operation");
            logging.Write(ex);
            throw new InvalidPluginExecutionException("Error.  Download Log and submit to the Help Desk.", ex);                    
        }
Was it helpful?

Solution

After some extra testing, this is what I was able to determine:

Apparently, you can access the Stack Trace for exceptions, only if explicitly done so. When throwing an exception from a sandboxed plugin, the stack trace of the exception is not actually being displayed as long as the exception is one that the CRM platform "knows about" (Not sure what it is doing, here but I'm guessing it is looking at the type of the exception, and handling different types in different ways). If the type is unknown, it results in CRM attempting to be serialize the exception which is not allowed in a because it uses reflection (why it has to be serialized, not sure).

Here is an example Plugin with some examples that worked, and some that didn't:

public class TestPlugin: IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        try
        {
            OtherMethod();
        }
        catch (Exception ex)
        {
            var trace = (ITracingService)serviceProvider.GetService(typeof (ITracingService));
            trace.Trace("Throwing Plugin");
            // Doesn't work
            throw new InvalidPluginExecutionException("Error ", ex);
        }
    }

    // Works:
    //public void Execute(IServiceProvider serviceProvider)
    //{
        //try
        //{
            //OtherMethod();
        //}
        //catch (Exception ex)
        //{
            //var trace = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            //trace.Trace("Throwing Plugin");
            //throw new InvalidPluginExecutionException("Error " + ex);
        //}
    //}

    // Doesn't Work:
    //public void Execute(IServiceProvider serviceProvider)
    //{
    //    try
    //    {
    //        OtherMethod();
    //    }
    //    catch (Exception ex)
    //    {
    //        throw;
    //    }
    //}

    // Doesn't Work:
    //public void Execute(IServiceProvider serviceProvider)
    //{
    //    try
    //    {
    //        OtherMethod();
    //    }
    //    catch (Exception ex)
    //    {
    //        throw new InvalidPluginExecutionException("Error", ex);
    //    }
    //}

    public void OtherMethod()
    {
        throw new MyException();
    }
}

public class MyException : Exception
{

}

So to answer your question: I wrote an exception handler to be able to walk the inner exceptions and determine if it is valid to be thrown:

/// <summary>
/// Exception Handler For Exceptions when executing in Sandbox Isolation Mode
/// </summary>
public class ExceptionHandler
{
    /// <summary>
    /// Determines whether the given exception can be thrown in sandbox mode.
    /// Throws a Safe if it can't
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    /// <exception cref="InvalidPluginExecutionException"></exception>
    /// <exception cref="Exception"></exception>
    public static bool CanThrow(Exception ex)
    {
        var exceptionRootTypeIsValid = IsValidToBeThrown(ex);
        var canThrow = exceptionRootTypeIsValid;
        var innerException = ex.InnerException;

        // While the Exception Types are still valid to be thrown, loop through all inner exceptions, checking for validity
        while (canThrow && innerException != null)
        {
            if (IsValidToBeThrown(ex))
            {
                innerException = innerException.InnerException;
            }
            else
            {
                canThrow = false;
            }
        }

        if (canThrow)
        {
            return true;
        }

        var exceptionMessage = ex.Message +
                                   (ex.InnerException == null
                                       ? string.Empty
                                       : " Inner Exception: " + ex.InnerException.ToStringWithCallStack());

        // ReSharper disable once InvertIf - I like it better this way
        if (exceptionRootTypeIsValid)
        {
            // Attempt to throw the exact Exception Type, with the 
            var ctor = ex.GetType().GetConstructor(new[] { typeof(string) });
            if (ctor != null)
            {
                throw (Exception) ctor.Invoke(new object[] { exceptionMessage });
            }
        }

        throw new Exception(exceptionMessage);
    }

    /// <summary>
    /// Determines whether the specified ex is valid to be thrown.
    /// Current best guess is that it is not 
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    private static bool IsValidToBeThrown(Exception ex)
    {
        var assembly = ex.GetType().Assembly.FullName.ToLower();
        return assembly.StartsWith("mscorlib,") || assembly.StartsWith("microsoft.xrm.sdk,");
    }
}

This can be called from your uppermost try catch in your plugin like so:

catch (InvalidPluginExecutionException ex)
{
    context.LogException(ex);
    // This error is already being thrown from the plugin, just throw
    if (context.PluginExecutionContext.IsolationMode == (int) IsolationMode.Sandbox)
    {
        if (Sandbox.ExceptionHandler.CanThrow(ex))
        {
            throw;
        }
    }
    else
    {
        throw;
    }
}
catch (Exception ex)
{
    // Unexpected Exception occurred, log exception then wrap and throw new exception
    context.LogException(ex);
    ex = new InvalidPluginExecutionException(ex.Message, ex);
    if (context.PluginExecutionContext.IsolationMode == (int)IsolationMode.Sandbox)
    {
        if (Sandbox.ExceptionHandler.CanThrow(ex))
        {
            // ReSharper disable once PossibleIntendedRethrow - Wrap the exception in an InvalidPluginExecutionException
            throw ex;
        }
    }
    else
    {
        // ReSharper disable once PossibleIntendedRethrow - Wrap the exception in an InvalidPluginExecutionException
        throw ex;
    }
}

I believe this is an actual error, and I have opened up support ticket with Microsoft, we shall see if they agree...

Update!!

I created a ticket with Microsoft: (not sure what these numbers mean, but they were in the subject and hopefully will be beneficial for someone in the future: REG:115122213520585 SRXCAP:1318824373ID). They did confirm that Custom Exceptions were not supported in CRM for Sandboxed Plugins.

Please up-vote this Connect Ticket to have Microsoft fix this or at least handle it better!

OTHER TIPS

I don't think what you want is possible, if you want a message in that error dialog you have to throw InvalidPluginExecutionException.

Handle Exceptions in Plug-Ins

For synchronous plug-ins, you can optionally display a custom error message in the error dialog of the web application by having your plug-in throw an InvalidPluginExecutionException exception with the custom message string as the exception Message property value

It is recommended that plug-ins only pass an InvalidPluginExecutionException back to the platform.

As a side, I wouldn't bother checking the registration in the plugin itself, its a situation which doesn't make a lot of sense in 2013. Back in CRM 4 where plugins had to be manually registered there was some sense in it. Now we have solutions, the correct registration is a development and testing task - not a run time check.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top