locked
Defferent behaviour between sandbox and none sandbox RRS feed

  • Question

  • Hi all

    I have a strange issue updating an invoicedetail entity. I developed a plugin that sets some 5 attributes (money) on a invoicedetail and the id property. Then I do an update using the IServiceProvider interface. As long as the plugin is registered in sandbox mode everything works fine. But as soon I change the plugin isolation mode to "none" (none sandbox) it givs me the error: The given key was not present in the dictionary.

    What does that mean. What attribute does CRM expect that is nessessary in none sandbox mode but not in sandbox mode?

    Thanks for you hints
    Thomas


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    Wednesday, May 9, 2012 10:13 AM

Answers

  • Hi Thomas,

    This is not a bug: You cannot make any assumptions about when CRM will create a new instance of your plugin class and when it will reuse the same object. In Sandbox it will always create a new object (most likely) but not when it is outside of sandbox. Therefore, you are not allowed to have those global properties:

    private IOrganizationService organizationService;
           
    private IServiceProvider serviceProvider;
           
    private ITracingService tracingService;
           
    private IPluginExecutionContext context;

    You need to reset those properties every time that your plugin executes in your Execute method, not in the constructor (basically, there is no use in aching those).

    I noticed you are setting the ServiceProvider every time in your Execute method. You need to do the same with the context, tracingService and organizationService, otherwise your plugin will execute a second time but use the context from the previous operation!

    That should solve your problem


    Gonzalo | gonzaloruizcrm.blogspot.com

    Thursday, May 10, 2012 3:17 PM
    Moderator
  • Hi Gonzalo

    Thanks for your replay! Just resetting this variables in the Execute() method may not be enough. Imagine the following situation:

    Somewhere in the middle of your plugin code you will make a call to the crm that executes the plugin again. In the Execute() method you reset the variables und do some other stuff. When the thread returns to the point where the first plugin code was paused to execute the secund, the variables (context, orgService...) will have changed. Then the plugin continues whit a different context. That may cause errors.

    The only solution that I can imagine at the moment and that I successfully testetd in sandbox as well as in none-sandbox mode is to make sure that you always create a new instance of you plugin class. So every execution has always its very own execution context. This can be achieved by the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Sdk.Discovery;
    using Microsoft.Xrm.Sdk.Linq;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Xrm.Sdk.Metadata;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.XmlNamespaces;
    
    namespace TestPlugin
    {
        public class Plugin : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                InternalPlugin internalPlugin = new InternalPlugin();
                internalPlugin.Execute(serviceProvider);
            }
        }
    
        internal class InternalPlugin
        {
            private IServiceProvider serviceProvider;
            private IPluginExecutionContext context;
            private IOrganizationService organizationService;
            private ITracingService tracingService;
    
            public void Execute(IServiceProvider serviceProvider)
            {
                this.serviceProvider = serviceProvider;
                this.context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                this.organizationService = serviceFactory.CreateOrganizationService(context.UserId);
                tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
                PlayTheMusic();
            }
    
            private void PlayTheMusic()
            {
                ...
            }
        }
    }


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    • Marked as answer by Flück Thursday, May 10, 2012 7:25 PM
    Thursday, May 10, 2012 6:59 PM

All replies

  • Hello,

    Have you tried to debug this plugin? I think that debug will give you an answer.


    Microsoft CRM Freelancer

    My blog (english)
    Мой блог (русскоязычный)
    Follow Andriy on Twitter

    Wednesday, May 9, 2012 10:25 AM
    Moderator
  • Hello Fluck:

    There is difference b/w sandbox plug-in and none sandbox plug-in.

    The difference is in there permissions to perform task. I am explain it below. It should help you.

    MS CRM 2011 and MS CRM Online support the execution of plug-ins in an isolated environment. In this isolated environment, also known as a sandbox, a plug-in can make use of the full power of the MS CRM SDK to access the organization Web service like accessing to the file system, system event log, certain network protocols, registry, and more is prevented in the sandbox. However, sandbox plug-ins do have access to external endpoints like the Windows Azure cloud.

    Code within the Sandbox runs in partial-trust mode. Windows Workflow Foundation requires a full-trust environment in order to function properly. But, in order to provide any plug-in functionality within CRM Online, Microsoft has restricted that all custom code to the Sandbox for stability and security purposes.


    Saurabh Gupta, MS CRM 2011 Software Development Engineer

    Wednesday, May 9, 2012 10:40 AM
  • After some platform tracing it turned out, that the plugin framework does not provide the pre and post images although they are defined by the plugin registration tool.

    Situation when in sandbox mode:

    1. invoicedetail gets updated
    2. Plugin on update of invoicedetail fires
    3. Plugin logic updates the invoicedetail again on some attributes
    4. Plguin on update of invoicedetail fires again
    5. Plugin logic does nothing and the execution sequence has finished.

    Situation when in "none sandbox" mode (fulltrust):

    1. invoicedetail gets updated
    2. Plugin on update of invoicedetail fires
    3. Plugin logic updates the invoicedetail again on some attributes
    4. Plguin on update of invoicedetail fires again but does not provide the defined pre and post images
    5. Plugin logic fails becouse the pre and post images are not present

    The system runs on rollup 6. Does anyone know if there ist a known issu concerning these pre and post images?

    Thanks a lot!


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    Wednesday, May 9, 2012 1:17 PM
  • The pre and post images should work the exact same way whether you are in sandbox or not. I support Andrii's suggestion to debug the plugin and figure out where the images are and why they would not be there. If the images are only present in sandbox mode then that would be a bug you could report to Microsoft.

    Between sandbox and full-trust, are you just updating the isolation level of the assembly? (without any modifications to the steps/images?)


    Gonzalo | gonzaloruizcrm.blogspot.com

    Wednesday, May 9, 2012 6:51 PM
    Moderator
  • Agree with both Andrii and Gonzalo.

    If you have configured the Pre and Post Images for this plugin, it should be working in both sandbox or non-sandbox environment.

    I suspect this could be a bug in the Plugin Registration Tool. Can you try removing the Step and re-register it again?


    Dimaz Pramudya - CRM Developer - CSG (Melbourne) www.xrmbits.com http://twitter.com/xrmbits

    Wednesday, May 9, 2012 8:41 PM
  • After some more testing and development of an easy to understand test plugin, it now turned out that the unavailability of the pre and post images was just a sideeffect of an other strange behaviour that I think it is a bug. The following code is registered as a synchronous post-update step whit pre and post images on the invoicedetail entity:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Sdk.Discovery;
    using Microsoft.Xrm.Sdk.Linq;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Xrm.Sdk.Metadata;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.XmlNamespaces;
    
    namespace TestPlugin
    {
        public class DebugPlugin : IPlugin
        {
            private IOrganizationService organizationService;
            private IServiceProvider serviceProvider;
            private ITracingService tracingService;
            private IPluginExecutionContext context;
    
            #region Properties
            public IOrganizationService OrganizationService
            {
                get
                {
                    if (organizationService == null)
                    {
                        IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                        organizationService = serviceFactory.CreateOrganizationService(Context.UserId);
                    }
                    if (organizationService == null) Trace("organizationService == null");
                    return organizationService;
                }
            }
    
            public IPluginExecutionContext Context
            {
                get
                {
                    if (context == null)
                        context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                    if (context == null) Trace("context == null");
    
                    return context;
                }
            }
    
            public ITracingService TracingService
            {
                get
                {
                    if (tracingService == null)
                    {
                        tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
                    }
    
                    return tracingService;
                }
            }
            #endregion
    
            public void Execute(IServiceProvider serviceProvider)
            {
                this.serviceProvider = serviceProvider;
    
                Trace("Execute called");
                if (Context.PrimaryEntityName == "invoicedetail")
                {
                    Trace("getting post entity...");
                    Entity postEntity = Context.PostEntityImages["post"];
                    Trace("post entity: "+postEntity.LogicalName+" "+postEntity.Id.ToString());
                    Entity entityToUpdate = new Entity
                    {
                        LogicalName = postEntity.LogicalName,
                        Id = postEntity.Id,
                        Attributes = new AttributeCollection()
                    };
                    Trace("getting current tax...");
                    Money currentTax = new Money(0);
                    if (postEntity.Attributes.Contains("tax"))
                    {
                        currentTax = (Money)postEntity["tax"];
                    }
                    Trace("current tax: " + currentTax.Value);
                    if (currentTax.Value < 6)
                    {
                        Money newTax = new Money(currentTax.Value + 1);
                        Trace("new tax: " + newTax.Value);
                        entityToUpdate.Attributes.Add(new KeyValuePair<string, object>("tax", newTax));
                        Trace("updating entity...");
                        OrganizationService.Update(entityToUpdate);
                    }
                    else
                    {
                        Trace("nothin to do!");
                    }
                }
            }
    
            public void Trace(string message)
            {
                if (TracingService != null)
                {
                    TracingService.Trace(Context.Depth + ": " + message);
                }
            }
        }
    }
    

    When I change the tax attribute to 1 or 2 and hit the save button everything works fine and the plugin is executed as many times until the tax value is 6 or higher. This works only as long as the the plugin runs in sandbox mode.
    If the plugin registration is set to none-sandbox mode then the plugin execution runs into an infinite loop. The trace protocol is the following:

    Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: This workflow job was canceled because the workflow that started it included an infinite loop. Correct the workflow logic and try again. For information about workflow logic, see Help.Detail: 
    <OrganizationServiceFault xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/xrm/2011/Contracts">
      <ErrorCode>-2147204734</ErrorCode>
      <ErrorDetails xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
      <Message>This workflow job was canceled because the workflow that started it included an infinite loop. Correct the workflow logic and try again. For information about workflow logic, see Help.</Message>
      <Timestamp>2012-05-10T11:39:18.672152Z</Timestamp>
      <InnerFault i:nil="true" />
      <TraceText>
    [TestPlugin: TestPlugin.DebugPlugin]
    [f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    1: Execute called
    1: getting post entity...
    1: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    1: getting current tax...
    1: current tax: 5.0000
    1: new tax: 6.0000
    1: updating entity...
    2: Execute called
    2: getting post entity...
    2: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    2: getting current tax...
    2: current tax: 5.0000
    2: new tax: 6.0000
    2: updating entity...
    3: Execute called
    3: getting post entity...
    3: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    3: getting current tax...
    3: current tax: 5.0000
    3: new tax: 6.0000
    3: updating entity...
    4: Execute called
    4: getting post entity...
    4: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    4: getting current tax...
    4: current tax: 5.0000
    4: new tax: 6.0000
    4: updating entity...
    5: Execute called
    5: getting post entity...
    5: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    5: getting current tax...
    5: current tax: 5.0000
    5: new tax: 6.0000
    5: updating entity...
    6: Execute called
    6: getting post entity...
    6: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    6: getting current tax...
    6: current tax: 5.0000
    6: new tax: 6.0000
    6: updating entity...
    7: Execute called
    7: getting post entity...
    7: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    7: getting current tax...
    7: current tax: 5.0000
    7: new tax: 6.0000
    7: updating entity...
    8: Execute called
    8: getting post entity...
    8: post entity: invoicedetail 1e316b71-859a-e111-b5fa-005056890001
    8: getting current tax...
    8: current tax: 5.0000
    8: new tax: 6.0000
    8: updating entity...
    
    	[TestPlugin: TestPlugin.DebugPlugin]
    	[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    	
    		[TestPlugin: TestPlugin.DebugPlugin]
    		[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    		
    			[TestPlugin: TestPlugin.DebugPlugin]
    			[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    			
    				[TestPlugin: TestPlugin.DebugPlugin]
    				[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    				
    					[TestPlugin: TestPlugin.DebugPlugin]
    					[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    					
    						[TestPlugin: TestPlugin.DebugPlugin]
    						[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    						
    							[TestPlugin: TestPlugin.DebugPlugin]
    							[f3e59004-e799-e111-b5fa-005056890001: TestPlugin.DebugPlugin: Update of invoicedetail]
    							
    </TraceText>
    </OrganizationServiceFault>
    
    

    As you can see, althoug the tax value is alsways increased and updated, the current tax value is always set to 5. I think this is because the Context is never updated and always returns the same instance of the pre entity image as when it was created the very first time.

    That means that the none-sandbox environment always keeps an instance of the plugin class alive and only call the method Execute(). But the sandbox mode always creates a new instance of the plugin class for every execution.

    You can proof this by adding the following code at the top of the above Execute() method:

    if (AppDomain.CurrentDomain.IsFullyTrusted) 
    { 
            context = null;
    }
    Can anyone confirm this as a bug?

    Thanks
    Thomas


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    Thursday, May 10, 2012 2:57 PM
  • Hi Thomas,

    This is not a bug: You cannot make any assumptions about when CRM will create a new instance of your plugin class and when it will reuse the same object. In Sandbox it will always create a new object (most likely) but not when it is outside of sandbox. Therefore, you are not allowed to have those global properties:

    private IOrganizationService organizationService;
           
    private IServiceProvider serviceProvider;
           
    private ITracingService tracingService;
           
    private IPluginExecutionContext context;

    You need to reset those properties every time that your plugin executes in your Execute method, not in the constructor (basically, there is no use in aching those).

    I noticed you are setting the ServiceProvider every time in your Execute method. You need to do the same with the context, tracingService and organizationService, otherwise your plugin will execute a second time but use the context from the previous operation!

    That should solve your problem


    Gonzalo | gonzaloruizcrm.blogspot.com

    Thursday, May 10, 2012 3:17 PM
    Moderator
  • Hi Gonzalo

    Thanks for your replay! Just resetting this variables in the Execute() method may not be enough. Imagine the following situation:

    Somewhere in the middle of your plugin code you will make a call to the crm that executes the plugin again. In the Execute() method you reset the variables und do some other stuff. When the thread returns to the point where the first plugin code was paused to execute the secund, the variables (context, orgService...) will have changed. Then the plugin continues whit a different context. That may cause errors.

    The only solution that I can imagine at the moment and that I successfully testetd in sandbox as well as in none-sandbox mode is to make sure that you always create a new instance of you plugin class. So every execution has always its very own execution context. This can be achieved by the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Sdk.Discovery;
    using Microsoft.Xrm.Sdk.Linq;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Xrm.Sdk.Metadata;
    using Microsoft.Xrm.Sdk.Query;
    using Microsoft.Xrm.Sdk.XmlNamespaces;
    
    namespace TestPlugin
    {
        public class Plugin : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                InternalPlugin internalPlugin = new InternalPlugin();
                internalPlugin.Execute(serviceProvider);
            }
        }
    
        internal class InternalPlugin
        {
            private IServiceProvider serviceProvider;
            private IPluginExecutionContext context;
            private IOrganizationService organizationService;
            private ITracingService tracingService;
    
            public void Execute(IServiceProvider serviceProvider)
            {
                this.serviceProvider = serviceProvider;
                this.context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                this.organizationService = serviceFactory.CreateOrganizationService(context.UserId);
                tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
                PlayTheMusic();
            }
    
            private void PlayTheMusic()
            {
                ...
            }
        }
    }


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    • Marked as answer by Flück Thursday, May 10, 2012 7:25 PM
    Thursday, May 10, 2012 6:59 PM
  • Hi Thomas,

    The plugin framework in CRM is designed so that you always get the context from the serviceProvider that is passed to the Execute method. Even if you make a call back to CRM which will trigger another plugin (or the same plugin), that other plugin needs to get the context that is passed in the serviceProvider of the execute method as well.

    Your solution above would work, but it would also work if you make it simpler : do not use class-level variables. Simply in your execute method, get the context and organization service and make those variables on the scope of your method. That would also work but makes the code simpler.


    Gonzalo | gonzaloruizcrm.blogspot.com

    Thursday, May 10, 2012 7:20 PM
    Moderator
  • Hi Gonzalo

    Your right. That would also work. But in a more complex scenario such as mine this would not be practival. But it would absolutely work.

    Thanks for all your help guys
    Special thank to Gonzalo

    Cheeers
    Thomas


    Thomas Flückiger Flückiger Informatik http://www.f-in.ch

    Thursday, May 10, 2012 7:25 PM