Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CRM Plugin: Custom Exception in Sandbox

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);                    
        }
like image 338
Nicknow Avatar asked Apr 06 '14 02:04

Nicknow


People also ask

Can we register for Premise plugin in sandbox mode?

Plug-ins registered in the sandbox must be stored in the database regardless of the Dynamics 365 Customer Engagement (on-premises) deployment (on-premises, IFD, or Online).

What is sandbox Mode in CRM?

A sandbox environment is any non-production environment of Microsoft Dataverse. Isolated from production, a sandbox environment is the place to safely develop and test application changes with low risk.

How will you show custom formatted synchronous plugin exception error to the user?

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.

What are sandbox plugins?

The Sandbox plugin enables Developers to test changes in their apps by pointing them to a sandbox environment. It provides a simple UI to set and modify the URL to a development host that acts as a sandbox directly on the desktop, which prevents you from entering potentially long and complicated URLs inside your app.


2 Answers

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!

like image 172
Daryl Avatar answered Sep 20 '22 11:09

Daryl


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.

like image 35
James Wood Avatar answered Sep 18 '22 11:09

James Wood