How to avoid passing a context reference among classes
Asked Answered
E

5

5

Dynamics CRM 2011 on premise. (But this problem exists in many situations away from Dynamics CRM.)

CRM plugins have an entry point:

void IPlugin.Execute (IServiceProvider serviceProvider)

(http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.iplugin.execute.aspx)

serviceProvider is a reference to the plugin execution context. Anything useful that a plugin does requires accessing serviceProvider, or a member of it.

Some plugins are large and complex and contain several classes. For example, I'm working on a plugin that has a class which is instantiated multiple times. This class needs to use serviceProvider.

One way to get access to serviceProvider from all the classes that need it would be to add a property to all those classes and then to set that property. Or to add properties for the parts of serviceProvider that each class needs. Either of these approaches would result in lots of duplicate code.

Another approach would be to have a global variable in the scope of the thread. However, according to http://msdn.microsoft.com/en-us/library/cc151102.aspx one "should not use global class variables in plug-ins."

So what is the best way to have access to serviceProvider without passing it around everywhere?

P.S. If an example helps, serviceProvider provides access to a logging object. I want almost every class to log. I don't want to pass a reference to the logging object to every class.

Elisavetgrad answered 1/10, 2013 at 15:5 Comment(2)
I too had same problem, but I not found any solutions.Technocracy
I am now doing a lot of Android. In this too it is necessary to pass context parameters all over the place.Elisavetgrad
H
2

There are multiple things I would worry about in this design request (not that it's bad, just that one should be aware of, and anticipate).

  1. IOrganizationService is not multi-thread safe. I'm assuming that other aspects of the IServiceProvider are not as well.
  2. Testing things at an IServiceProvider level is much more complicated due to the additional properties that have to be mocked
  3. You'd need a method for handle logging if you ever decided to call logic that is currently in your plugin, outside of the plugin (e.g. a command line service).

If you don't want to be passing the object around everywhere, the simple solution is to create a static property on some class that you can set it upon plugin execution, and then access from anywhere.

Of course now you have to handle issue #1 from above, so it'd have to be a singleton manager of some sort, that would probably use the current thread's id to set and retrieve the value for that thread. That way if the plugin is fired twice, you could retrieve the correct context based on your currently executing thread. (Edit Rather than some funky thread id lookup dictionary, @shambulator's ThreadStatic property should work)

For issue #2, I wouldn't be storing the IServiceProvider as is, but split up it's different properties (e.g. IPluginExecutionContext, IOrganizationService, etc)

For issue #3, it might make sense to store an action or a function in your manager rather than the object values themselves. For example, if rather than storing the IPluginExecutionContext, store a func that accepts a string to log and uses the IPlurginExeuctionContext to log. This allows other code to setup it's own logging, without being dependent on executing from within a plugin.

Halcomb answered 1/10, 2013 at 16:54 Comment(0)
J
7

That's not quite what the warning in the documentation is getting at. The IServiceProvider isn't a global variable in this context; it's a method parameter, and so each invocation of Execute gets its own provider.

For improved performance, Microsoft Dynamics CRM caches plug-in instances. The plug-in's Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. In addition, multiple threads could be running the plug-in at the same time. All per invocation state information is stored in the context. This means that you should not use global class variables in plug-ins [Emphasis mine].

There's nothing wrong with passing objects from the context to helper classes which need them. The warning advises against storing something in a field ("class variable") on the plugin class itself, which may affect a subsequent call to Execute on the same instance, or cause concurrency problems if Execute is called by multiple threads on the same instance simultaneously.

Of course, this "globalness" has to be considered transitively. If you store anything in either the plugin class or in a helper class in any way that multiple calls to Execute can access (using fields on the plugin class or statics on either plugin or helper classes, for example), you leave yourself open to the same problem.

As a separate consideration, I would write the helper classes involved to accept types as specific to their function as possible - down to the level of individual entities - rather than the entire IServiceProvider. It's much easier to test a class which needs only an EntityReference than one which needs to have an entire IServiceProvider and IPluginExecutionContext mocked up.


On global variables vs injecting values required by classes

You're right, this is something that comes up everywhere in object-oriented code. Take a look at these two implementations:

public class CustomEntityFrubber
{
    public CustomEntityFrubber(IOrganizationService service, Guid entityIdToFrub)
    {
        this.service = service;
        this.entityId = entityIdToFrub;
    }

    public void FrubTheEntity()
    {
        // Do something with service and entityId.
    }

    private readonly IOrganizationService service;
    private readonly Guid entityId;
}

// Initialised by the plugin's Execute method.
public static class GlobalPluginParameters
{
    public static IOrganizationService Service
    {
        get { return service; }
        set { service = value; }
    }

    public static Guid EntityIdToFrub
    {
        get { return entityId; }
        set { entityId = value; }
    }

    [ThreadStatic]
    private static IOrganizationService service;

    [ThreadStatic]
    private static Guid entityId;
}

public class CustomEntityFrubber
{
    public FrubTheEntity()
    {
        // Do something with the members on GlobalPluginParameters.
    }
}

So assume you've implemented something like the second approach, and now you have a bunch of classes using GlobalPluginParameters. Everything is going fine until you discover that one of them is occasionally failing because it needs an instance of IOrganizationService obtained by calling CreateOrganizationService(null), so it accesses CRM as the system user rather than the calling user (who doesn't always have the required privileges).

Fixing the second approach requires you to add another field to your growing list of global variables, remembering to make it ThreadStatic to avoid concurrency problems, then changing the code of CustomEntityFrubber to use the new SystemService property. You have tight coupling between all these classes.

Not only that, all these global variables hang around between plugin invocations. If your code has a bug that somehow bypasses the assignment of GlobalPluginParameters.EntityIdToFrub, suddenly your plugin is inexplicably operating on data that wasn't passed to it by the current call to Execute.

It's also not obvious exactly which of these global variables the CustomEntityFrubber requires, unless you read its code. Multiply that by however many helper classes you have, and maintaining this code starts to become a headache. "Now, does this object need me to have set Guid1 or Guid2 before I call it?" On top of that, the class itself can't be sure that some other code won't go and change the values of global variables it was relying on.

If you used the first approach, you simply pass in a different value to the CustomEntityFrubber constructor, with no further code changes needed. Furthermore, there's no stale data hanging around. The constructor makes it obvious which dependencies the class has, and once it has them, it can be sure that they don't change except in ways they were designed for.

Jovitajovitah answered 1/10, 2013 at 15:35 Comment(6)
But it's silly to be passing references to the same things to every object. In most applications/plugins there are a few things that it's pragmatic to make available to everything. Otherwise you end up with the same load of properties on most classes and the same assignments over and over again.Elisavetgrad
I know serviceProvider is not a global var. I've rephrased my question to try to better represent my understanding.Elisavetgrad
The biggest advantage of passing these different objects about is, as shambulator mentioned, testing/mocking. Also, you don't have to pass the Service Provider to everything, just the objects you need. For example, 90% of the time you'll only require the Organization Service and the Tracing Service. Once you've abstracted to 1 layer of classes, and if you've got a good object model/pattern in place, you'll probably stop passing the Organization Service around as well. And you'll even stop passing the Tracing Service around depending on what level of tracing you require deeper in the stack.Clinton
"There's nothing wrong" - yes there is - it's tedious. I'm finding that it's necessary a lot in Android too.Elisavetgrad
@Elisavetgrad See above, "this is something that comes up everywhere in object-oriented code".Jovitajovitah
Yes, there is a balance to be struck. Until you've directly experienced the benefits of writing decoupled code though, I think it can be difficult to appreciate the headaches spared later in return for a little more tedium now.Jovitajovitah
V
3

As you say, you shouldn't put a member variable on the plugin since instances are cached and reused between requests by the plugin pipeline.

The approach I take is to create a class that perform the task you need and pass a modified LocalPluginContext (making it a public class) provided by the Developer Toolkit (http://msdn.microsoft.com/en-us/library/hh372957.aspx) on the constructor. Your class then can store the instance for the purposes of executing it's work just in the same way you would with any other piece of code. You are essentially de-coupling from the restrictions imposed by the Plugin framework. This approach also makes it easier to unit test since you only need to provide the execution context to your class rather than mocking the entire plugin pipeline.

It's worth noting that there is a bug in the automatically generated Plugin.cs class in the Developer Toolkit where it doesn't set the ServiceProvider property - At the end of the constructor of the LocalPluginContext add the line:

this.ServiceProvider = serviceProvider;

I have seen some implementations of an IoC approach in Plugins - but IMHO it makes the plugin code way too complex. I'd recommend making your plugins lean and simple to avoid threading/performance issues.

Vaccination answered 1/10, 2013 at 21:3 Comment(0)
H
2

There are multiple things I would worry about in this design request (not that it's bad, just that one should be aware of, and anticipate).

  1. IOrganizationService is not multi-thread safe. I'm assuming that other aspects of the IServiceProvider are not as well.
  2. Testing things at an IServiceProvider level is much more complicated due to the additional properties that have to be mocked
  3. You'd need a method for handle logging if you ever decided to call logic that is currently in your plugin, outside of the plugin (e.g. a command line service).

If you don't want to be passing the object around everywhere, the simple solution is to create a static property on some class that you can set it upon plugin execution, and then access from anywhere.

Of course now you have to handle issue #1 from above, so it'd have to be a singleton manager of some sort, that would probably use the current thread's id to set and retrieve the value for that thread. That way if the plugin is fired twice, you could retrieve the correct context based on your currently executing thread. (Edit Rather than some funky thread id lookup dictionary, @shambulator's ThreadStatic property should work)

For issue #2, I wouldn't be storing the IServiceProvider as is, but split up it's different properties (e.g. IPluginExecutionContext, IOrganizationService, etc)

For issue #3, it might make sense to store an action or a function in your manager rather than the object values themselves. For example, if rather than storing the IPluginExecutionContext, store a func that accepts a string to log and uses the IPlurginExeuctionContext to log. This allows other code to setup it's own logging, without being dependent on executing from within a plugin.

Halcomb answered 1/10, 2013 at 16:54 Comment(0)
D
0

I haven't made any of these plugins myself, but I would treat the IServiceProvider like an I/O device.

Get the data you need from it and convert that data to format that suits your plugin. Use the transformed data to set up the other classes. Get the the output from the other classes and then translate back to terms the IServiceProvider can understand and use.

Your input and output are dependent on the IServiceProvider, but the processing doesn't have to be.

Debidebilitate answered 1/10, 2013 at 16:13 Comment(1)
That's a nice way to look at interacting with serviceProvider but you don't address how to avoid passing the same load of properties around all my classes.Elisavetgrad
E
-1

From Eduardo Avaria at http://social.microsoft.com/Forums/en-US/f433fafa-aff7-493d-8ff7-5868c09a9a9b/how-to-avoid-passing-a-context-reference-among-classes

Well, as someone at SO already told you, the global variables restriction is there cause the plugin won't instantiate again if it's called within the same context (the object context and probably other environmental conditions), so any custom global variable would be shared between that instances, but since the context will be the same, there's no problem in assigning it to a global variable if you want to share it between a lot of classes.

Anyways, I'd rather pass the context on the constructors and share it have a little more control over it, but that's just me.

Elisavetgrad answered 2/10, 2013 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.