Плагин CRM: настраиваемое исключение в песочнице

У меня есть плагин Dynamics CRM 2013, работающий в песочнице.

Этот код имеет следующий настраиваемый класс исключения:

    [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)
        {
        }
    }

Когда это исключение выбрасывается в плагине, это приводит к общему окну ошибки без каких-либо подробностей в файле журнала:

введите описание изображения здесь

Необработанное исключение: System.ServiceModel.FaultException`1 [[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version = 6.0.0.0, Culture = нейтральный, PublicKeyToken = 31bf3856ad364e35]]: System.Runtime.Serialization .SerializationException: в Microsoft Dynamics CRM произошла ошибка. Справочный номер для администраторов или службы поддержки: # 1355B4E4 Подробная информация: -2147220970 CallStack в Microsoft.Crm.Application.Platform.ServiceCommands.PlatformCommand.XrmExecuteInternal () в Microsoft.Crm.Application.Platform.ServiceCommands.CreateCommand. Application.Platform.EntityProxy.Create (Boolean performDuplicateCheck, Guid auditingTransactionId) в Microsoft.Crm.Application.Platform.EntityProxy.Create (Boolean performDuplicateCheck) в Microsoft.Crm.Application.Platform.EntityProxy.CreveDownload (Boolean performDuplicateCheck), StringCreveDublication.Platform.EntityProxy. ) в Microsoft.Crm.Application.WebServices.InlineEdit.CommandBase.UpdateEntity (сущность сущности, логическое извлечение) в Microsoft.Crm.Application.WebServices.InlineEdit.SaveCommand.ExecuteCommand (String commandXml) в Microsoft.Crmb.Application. .CommandBase.Execute (String commandXml) System.Runtime.Serialization.SerializationException: Microsoft D ynamics CRM произошла ошибка. Номер ссылки для администраторов или службы поддержки: # 1355B4E4 2014-04-06T02: 04: 30.0972001Z [Demo.DemoPlugin: Demo.DemoPlugin.BasicCrmPlugin] [d86b89ab-f1bc-e311-9408-000c29254b18: Demo.DemoPlugin.BasicCrm ]

Просмотр журнала трассировки CRM показывает следующее:

System.Runtime.Serialization.SerializationException: введите Demo.Helpers.PluginValidationException в сборке Demo.DemoPlugin, Version = 1.0.0.0, Culture = нейтральный, PublicKeyToken = fbb51ba1e588d276 не помечен как сериализуемый. на Microsoft.Crm.Sandbox.SandboxAppDomainHelper.Execute (IServiceEndpointNotificationService serviceBusService, IOrganizationServiceFactory organizationServiceFactory, струнного pluginTypeName, струнного pluginConfiguration, струнного pluginSecureConfig, IPluginExecutionContext RequestContext) в 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)

На основании некоторого чтения я не считаю, что это ошибка - скорее, это потому, что пользовательские классы Exception не являются надежными по своей природе, начиная с .NET 4 (я использую .NET 4.5).

Кто-нибудь знает, как создать собственный класс исключения, который будет работать с песочницей CRM. Я использую собственный класс исключений, потому что я перехватываю ошибки и мне нужно различать InvalidPluginExecutionException исключение, вызванное неправильной регистрацией подключаемого модуля.

ОБНОВЛЕНО 8 апреля 2014 г.

Вот код в плагине, который перехватывает исключения, со значительным упрощением для помещения его в 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);                    
        }

person Nicknow    schedule 06.04.2014    source источник
comment
У меня аналогичная проблема. Удалось ли вам решить эту проблему?   -  person Daryl    schedule 22.12.2015
comment
По результатам моего тестирования реализация Exception.StackTrace по умолчанию выполняет своего рода сериализацию, которая предотвращает доступ к трассировке стека, если исключение не является стандартным типом (т. Е. Настраиваемым исключением). Вы видели нечто подобное?   -  person Daryl    schedule 22.12.2015


Ответы (2)


После некоторого дополнительного тестирования я смог определить следующее:

По-видимому, вы можете получить доступ к трассировке стека для исключений, только если это сделано явно. При генерировании исключения из изолированного плагина трассировка стека исключения на самом деле не отображается, пока это исключение является тем, о котором платформа CRM «знает» (не уверен, что он делает, здесь, но я предполагаю, что это смотрит на тип исключения и по-разному обрабатывает разные типы). Если тип неизвестен, это приводит к тому, что CRM пытается сериализовать исключение, которое не разрешено в a, потому что оно использует отражение (почему оно должно быть сериализовано, не уверен).

Вот пример плагина с некоторыми примерами, которые работали, а некоторые нет:

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
{

}

Итак, чтобы ответить на ваш вопрос: я написал обработчик исключений, чтобы иметь возможность обходить внутренние исключения и определять, допустимо ли его выбросить:

/// <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,");
    }
}

Это может быть вызвано из вашего самого верхнего try catch в вашем плагине следующим образом:

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;
    }
}

Я считаю, что это настоящая ошибка, и я открыл заявку в службу поддержки Microsoft, мы посмотрим, согласны ли они ...

Обновление !!

Я создал заявку в Microsoft: (не уверен, что означают эти числа, но они были в теме и, надеюсь, будут полезны для кого-то в будущем: REG: 115122213520585 SRXCAP: 1318824373ID). Они подтвердили, что пользовательские исключения не поддерживаются в CRM для изолированных плагинов.

Пожалуйста, проголосуйте за этот Connect Ticket чтобы Microsoft исправила это или, по крайней мере, справилась с этим лучше!

person Daryl    schedule 22.12.2015
comment
Вау, спасибо, что поработали над этим. В то время я закончил тем, что отказался от модели «исключений», которая в то время создавала вонючий код. Я перешел к другим проектам и никогда не возвращался к ним, но это всегда было в моей голове. Я собираюсь потратить некоторое время на то, чтобы реализовать это со своей стороны в эти выходные. Еще раз спасибо за то, что приложили усилия и поделились этим здесь! - person Nicknow; 30.12.2015
comment
@Nicknow Я создал простой оператор Assert для плагинов, который утверждает, что все необходимые атрибуты существуют в пре-объекте. Он генерирует настраиваемое исключение, что является стандартным передовым опытом для C #. Надеюсь, люди проголосуют за Connect Ticket, и Microsoft сделает нашу жизнь лучше! - person Daryl; 05.01.2016

Я не думаю, что то, что вы хотите, возможно, если вы хотите, чтобы сообщение об ошибке появилось в этом диалоговом окне, вы должны выбросить InvalidPluginExecutionException.

Обработка исключений в подключаемых модулях

Для синхронных подключаемых модулей можно дополнительно отобразить настраиваемое сообщение об ошибке в диалоговом окне ошибки веб-приложения, если надстройка выдает исключение InvalidPluginExecutionException со строкой настраиваемого сообщения в качестве значения свойства сообщения

Рекомендуется, чтобы подключаемые модули передавали обратно на платформу только InvalidPluginExecutionException.

В качестве стороны, я бы не стал утруждать себя проверкой регистрации в самом плагине, это ситуация, которая не имеет большого смысла в 2013 году. Еще в CRM 4, где плагины приходилось регистрировать вручную, в этом был определенный смысл. Теперь у нас есть решения, правильная регистрация - это задача разработки и тестирования, а не проверка во время выполнения.

person James Wood    schedule 07.04.2014
comment
Мне не нужно сообщение в диалоговом окне ошибки, мне нужно настраиваемое исключение, чтобы я мог catch обработать его - если оно вернется на платформу, код выдаст InvalidPluginExecutionException. Я полностью не согласен с отказом от проверки в коде - любой плагин, который не прошел проверку, не пройдет проверку кода. Несмотря на то, что плагины теперь находятся в решениях, их регистрацию можно изменить - даже в управляемом решении - с помощью инструмента регистрации плагинов. Не проверять правильность выполнения - плохая практика, т. К. Проверка стоит недорого, но устранение неполадок может обойтись очень дорого. - person Nicknow; 08.04.2014
comment
Кроме того, настраиваемое исключение отлично работает в режиме без песочницы. Мой код перехватывает исключение, записывает сведения в службу трассировки (или другую службу ведения журнала, если она настроена) и, если необходимо, выдает InvalidPluginExecutionException с удобным для пользователя сообщением о поддержке нескольких языков. - person Nicknow; 08.04.2014
comment
Когда вы говорите: «Мой код перехватывает исключение», где выполняется этот код? - person James Wood; 08.04.2014
comment
Код, перехватывающий исключение, находится в плагине. Я обновил свой вопрос, чтобы показать пример. - person Nicknow; 09.04.2014
comment
У меня самого было немного горя с настраиваемыми исключениями - вы можете фактически писать настраиваемые исключения, если поймаете его в конце и убедитесь, что выброшенный тип - InvalidPluginExecutionException ... но я вижу, что у вас есть журнал. бывший); ... Некоторое время назад у меня был опыт с обновлением во вторник, которое не позволяло мне получить доступ к информации стека сгенерированного исключения при работе в режиме песочницы. Когда вы устанавливаете KB2872041 на сервер OnPremise, он ломает xxxxxxxx. Этот патч предотвращает использование System.Diagnostics.Trace в плагине. - person Travis Sharp; 11.04.2014
comment
@TravisSharp ... итак ... Нет трассировки стека в режиме песочницы? - person Daryl; 22.12.2015
comment
@ Дэрил, я считаю, что это и есть влияние. System.Diagnostics вызовет ошибки, см. KB2872041 - person Travis Sharp; 30.12.2015