2
votes

I have a CRM plugin registered on Create (synchronous, post-operation) of a custom entity that performs some actions, and I want the Create operation to succeed in spite of errors in the plugin. For performance reasons, I also want the plugin to fire immediately when a record is created, so making the plugin asynchronous is undesirable. I've implemented this by doing something like the following:

public class FooPlugin : IPlugin
{
    public FooPlugin(string unsecureInfo, string secureInfo) { }

    public void Execute(IServiceProvider serviceProvider)
    {
        try
        {
            // Boilerplate
            var context = (IPluginExecutionContext) serviceProvider.GetService(typeof (IPluginExecutionContext));
            var serviceFactory = (IOrganizationServiceFactory) serviceProvider.GetService(typeof (IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // Additional validation omitted
            var targetEntity = (Entity) context.InputParameters["Target"];

            UpdateFrobber(service, (EntityReference)targetEntity["new_frobberid"]);
            CreateFollowUpFlibber(service, targetEntity);
            CloseTheEntity(service, targetEntity);
        }
        catch (Exception ex)
        {
            // Send an email but do not re-throw the exception 
            // because we don't want a failure to roll-back the transaction.
            try
            {
                SendEmailForException(ex, context);
            }
            catch { }
        }
    }
}

However, when an error occurs (e.g. in UpdateFrobber(...)), the service client receives this exception:

System.ServiceModel.FaultException`1[Microsoft.Xrm.Sdk.OrganizationServiceFault]: 
There is no active transaction. This error is usually caused by custom plug-ins
that ignore errors from service calls and continue processing.

Server stack trace: 
   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ref ProxyRpc rpc)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref MessageData msgData, Int32 type)
   at Microsoft.Xrm.Sdk.IOrganizationService.Create(Entity entity)
   at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.CreateCore(Entity entity)
   at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.Create(Entity entity)
   at Microsoft.Xrm.Client.Services.OrganizationService.<>c__DisplayClassd.<Create>b__c(IOrganizationService s)
   at Microsoft.Xrm.Client.Services.OrganizationService.InnerOrganizationService.UsingService(Func`2 action)
   at Microsoft.Xrm.Client.Services.OrganizationService.Create(Entity entity)
   at MyClientCode() in MyClientCode.cs: line 100

My guess is that this happens because UpdateFrobber(...) uses the IOrganizationService instance derived from the plugin, so any CRM service calls that it makes participate in the same transaction as the plugin, and if those "child" operations fail, it causes the entire transaction to rollback. Is this correct? Is there a "safe" way to ignore an error from a "child" operation in a synchronous plugin? Perhaps a way of instantiating an IOrganizationService instance that doesn't re-use the plugin's context?

In case it's relevant, we're running CRM 2013, on-premises.

3
Caleb are you sure that a plug-in is not executed after that? I mean that your method triggers another plugin that errors, if it's synchronous then the plugin you are looking at fill fail as a result. To prevent that to happen you can move the child plugin to a post stage Asynchronous, or you can manage the exception in the child plugin.Mauro De Biasio

3 Answers

3
votes

You cannot ignore unhandled exceptions from child plugins when your plugin is participating in a database transaction.

However, when your plugin is operating On Premise in partial trusted mode, you can actually create a OrganizationServiceProxy instance of your own and use that to access CRM. Be sure you reference the server your plugin is executing on to avoid "double hop" problems.

0
votes

If really needed, I would create an ExecuteMultipleRequest with ContinueOnError = true, for your email you could just check the ExecuteMultipleResponse...

But it looks a bit overkill.

0
votes

You can catch exceptions if running in async mode. Be sure to verify your mode when catching the exception.

Sample Code:

try
{
    ExecuteTransactionResponse response = 
        (ExecuteTransactionResponse)service.Execute(exMultReq);
}

catch (Exception ex)
{
    errored = true;
    if (context.Mode == 0) //0 sync, 1 Async. 
        throw new InvalidPluginExecutionException(
            $"Execute Multiple Transaction 
            Failed.\n{ex.Message}\n{innermessage}", ex);
}

if(errored == true)
{
    //Do more stuff to handle it, such as Log the failure.
}

It is not possible to do so for a synchronous plugin.

A more detailed summary, explaining the execution mode and use case can be found on my blog: https://helpfulbit.com/handling-exceptions-in-plugins/

Cheers.