dependency injection - rezanid/xrmtools GitHub Wiki

Xrm Tools provides a simple, fast, and reflection-free Dependency Injection (DI) system for Dataverse plugins. It achieves this via code generation and attributes, ensuring that no reflection is needed at runtime and keeping plugin execution efficient.

Why use Xrm Tools’ DI model? Some key advantages include:

  • No Reflection – The injected assignments are done via generated code, so there are no runtime Type lookups or Invoke calls.
  • High Performance – Dependencies are provided through direct property setters or via constructor calls, making it as fast as hand-written wiring.
  • Explicit and Design-Time – You declare dependencies with attributes, and the wiring is generated at design time. This makes it clear which services are needed and avoids hidden magic.
  • Opt-in – Dependency injection is optional. If you want to use it, you call the injection method in your code. You can still choose not to use DI for certain plugins or parts of code.

How It Works

With Xrm Tools, you define dependencies by marking them in your plugin code, and the extension generates an InjectDependencies method that knows how to resolve and instantiate those dependencies. This method only has one parameter of type IServiceProvider that needs to be passed by your code. You can get the value from the Execute method of your plugin. To use this:

  • In your plugin class (or its base class), mark any service or object you need (or that in turn needs other dependencies) with the [Dependency] attribute on its property. The property should be a settable non-private property. Example:

    public partial class MyPlugin : IPlugin
    {
        [Dependency] 
        public ITracingService TracingService { get; set; }
    
        public void Execute(IServiceProvider serviceProvider)
        {
            // This method is generated by Xrm Tools when you save the plugin file
            InjectDependencies(serviceProvider);
            // Now TracingService (and any other [Dependency] properties) are initialized
            TracingService.Trace("MyPlugin Execute started.");
            // ... rest of plugin logic ...
        }
    }
  • You can also mark constructors of your classes with [DependencyConstructor] if the class has multiple constructors and you want Xrm Tools to use a specific one when instantiating that dependency. If you don’t specify, the first public constructor is chosen by default. If a dependency class only has one public constructor, you don’t need to annotate it.

  • If your plugin class (or a base class) already provides an instance of a dependency that should be reused, use [DependencyProvider] on that property. This tells the generator “this property already has a value, don’t attempt to create it, just use it.” For example, you might have a base plugin class that creates a tracing service upfront; by marking it as [DependencyProvider], any [Dependency] requiring an ITracingService will use that existing instance.

  • When the plugin runs, you need to call the generated injection method at the beginning of the execution. In the example above, InjectDependencies(serviceProvider) is called inside Execute. Xrm Tools generates this method as part of the partial class (you’ll see it in the .Generated.cs file once you set up code generation for the plugin). Calling it will:

    • Create new instances of classes for each [Dependency] property.
    • For each dependency, if that dependency itself has its own [Dependency] properties or its constructor has parameters, those will be resolved too (recursive injection).
    • If a [Dependency] property’s type matches a [DependencyProvider] property already present, it will use the provided one instead of creating a new instance.
    • All of this happens in a straightforward, pre-compiled sequence of calls.

Some notes and best practices for the DI system:

  • The generated injection code uses only static, compile-time knowledge. There is no runtime scanning of types; this makes it very fast.
  • You can mix property injection and constructor injection as needed. For instance, a dependency class might require an IServiceProvider in its constructor (which Xrm Tools will pass) and also have a [Dependency] property that needs filling after construction.
  • Marking a base class property with [DependencyProvider] is a convenient way to supply common services (like a cached HttpClient or a pre-initialized BusinessLogicService) to all derived plugins.
  • If you need special logic for resolving a particular type, you can customize the DI code generation template (this is an advanced scenario covered in Extensibility under Advanced Topics).
  • Constructor injection is not supported in the Plugin class or its base class. The reason this is not supported is to keep the plugins stateless. It is not guaranteed in Power Platform to instantiate plugins in every request and they might get cached and reused for multiple requests even between multiple users. To read more about best practices make you sure you have read Best practices and guidance regarding plug-in and workflow development for Microsoft Dataverse

DI Attributes Summary

DependencyAttribute

  • [Dependency] – Mark a property in your plugin class or a dependency class that should be injected. Xrm Tools will generate code to set this property. The property must have a setter (and not be private).
  • [DependencyConstructor] – (Optional) Mark a constructor of a dependency class to instruct Xrm Tools to use that constructor when creating the object. Use this when the class has multiple public constructors and you want to ensure a specific one is chosen (e.g., one that takes certain dependencies). If not used, the first public constructor is assumed.
  • [DependencyProvider] – Mark a property in your plugin class (or base class) that already has an instance assigned (or can be readily assigned) which should be used as a dependency for other classes. For example, in a base plugin you might do TracingService = new LocalTracingService(provider) and mark TracingService with [DependencyProvider]. Then any [Dependency] ITracingService in other classes will reuse this instance instead of creating a new one.

Example

public abstract class BasePlugin : IPlugin
{
    // Provide a tracing service to all derived plugins
    [DependencyProvider]
    public ITracingService Tracer { get; private set; }

    public void Execute(IServiceProvider serviceProvider)
    {
        // Initialize the base-provided tracer
        Tracer = (ITracingService) serviceProvider.GetService(typeof(ITracingService));
        // Let derived plugin implement ExecuteInternal
        ExecuteInternal(serviceProvider);
    }

    protected abstract void ExecuteInternal(IServiceProvider serviceProvider);
}

public class SamplePlugin : BasePlugin
{
    // This service depends on ITracingService in its constructor
    [Dependency] 
    public ICustomService CustomService { get; set; }

    protected override void ExecuteInternal(IServiceProvider serviceProvider)
    {
        // InjectDependencies is generated to resolve CustomService (and its dependencies)
        InjectDependencies(serviceProvider);
        // Now CustomService is initialized and its constructor received the tracer from base
        CustomService.RunBusinessLogic();
    }
}

In this scenario:

  • BasePlugin provides Tracer with [DependencyProvider].
  • SamplePlugin declares a [Dependency] on ICustomService. Xrm Tools will generate an InjectDependencies method in SamplePlugin.Generated.cs that creates CustomService. If CustomService’s constructor accepts an ITracingService, Xrm Tools will pass in the Tracer instance from the base class (thanks to it being a [DependencyProvider]). This happens seamlessly in the generated code.

Read Next

⚠️ **GitHub.com Fallback** ⚠️