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!