Dependency Injection in Acumatica ERP

Recently I had a chance to work with Dependency Injection in Acumatica ERP. I want to share how it works and how one can use it.

Let’s assume that we have a REST Service, which we want to inject into Sales Order Entry and work with it using Actions.

First, we need to create interfaces that we will use for injecting the implementations into the Sales Order Entry.

public interface IRestServiceProvider
{
    string URL { get; set; }
    object[] GetRecords();
    bool CreateRecord(object rec);
    bool UpdateRecord(object rec);
    bool DeleteRecord(object rec);
}

public interface IRestServiceConfiguration
{
    void Configure(IRestServiceProvider provider);
}

Now we can add the implementations of the interfaces :

public class RestServiceConfiguration : IRestServiceConfiguration
{
    public void Configure(IRestServiceProvider provider)
    {
        provider.URL = "TEST_URL";
    }
}

and

public class RestServiceProvider : IRestServiceProvider
{
    public RestServiceProvider(IRestServiceConfiguration serviceConfiguration)
    {
        serviceConfiguration.Configure(this);
    }

    public string URL { get; set; }

    public bool CreateRecord(object rec)
    {
        throw new NotImplementedException();
    }

    public bool DeleteRecord(object rec)
    {
        throw new NotImplementedException();
    }

    public object[] GetRecords()
    {
        throw new NotImplementedException();
    }

    public bool UpdateRecord(object rec)
    {
        throw new NotImplementedException();
    }
 }

The final step will be to register our implementations with Autofac, which is done by defining an Autofac Module and overriding the Load method:

public class ServiceRegistrator : Module
{
    protected override void Load(ContainerBuilder builder)
    {

        //We need to register our implementations with Autofac.
        builder.RegisterType<RestServiceConfiguration>().As<IRestServiceConfiguration>();
        builder.RegisterType<RestServiceProvider>().As<IRestServiceProvider>();
    }
}

Now we can inject our types into the Sales Order Entry in a very simple way using InjectDependencyAttribute and property:

public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{

    [InjectDependency]
    public IRestServiceProvider ServiceProvider { get; set; }

    public PXAction<SOOrder> doRestRequest;
    [PXButton(CommitChanges = true)]
    [PXUIField(DisplayName ="Do Request")]
    protected IEnumerable DoRestRequest(PXAdapter adapter)
    {
        if(ServiceProvider!=null)
        {
            throw new PXException("Dependency Injection Worked! URL : {0}",ServiceProvider.URL);
        }
        else
        {
            throw new PXException("Something Went wrong");
        }
    }
}

As a result of our hard work we will get the following message:

The interesting part here is that we will find the below description if we check the InjectDependecyAttribute:

The attribute that is used in the <see cref=”T:PX.Data.PXGraph” />, <see cref=”T:PX.Data.PXAction” />, or <see cref=”T:PX.Data.PXEventSubscriberAttribute” />-derived classes to create the properties that need to be injected via dependency injection.

And as we all remember PXGraphExtension<T> is not derived from PXGraph, so technically our code shouldn’t work, but it is working and let’s understand why.

Let’s start by reviewing the code of the PXGraph.CreateInstance<T>() method of the PXGraph class. Acumatica ERP development best practices require us to use that method for graph object creation and Acumatica ERP itself is using it for correct initialization of the graph.

I am using dnSpy for reverse engineering Acumatica’s Source codes from DLLs, but you can use whatever disassembler that you want.

Below is the full code of the internal method called behind the scene in the  PXGraph.CreateInstance<T>():

internal static PXGraph CreateInstance(Type graphType, string prefix)
{
    if (graphType == null)
    {
        throw new ArgumentNullException("graphType");
    }
    if (!typeof(PXGraph).IsAssignableFrom(graphType))
    {
        throw new ArgumentException(string.Format("The type '{0}' must inherit the PX.Data.PXGraph type.", graphType.FullName), "graphType");
    }
    if (graphType.GetConstructor(new Type[0]) == null)
    {
        throw new ArgumentException(string.Format("The type '{0}' must contain a default constructor.", graphType.FullName), "graphType");
    }
    string customizedTypeFullName = CustomizedTypeManager.GetCustomizedTypeFullName(graphType);
    Type type = graphType;
    PXGraph result;
    try
    {
        if (customizedTypeFullName != graphType.FullName)
        {
            type = (PXBuildManager.GetType(customizedTypeFullName, false) ?? graphType);
        }
        Type type2 = PXGraph.b(type).Wrapper ?? type;
        Type graphInstanceCreating = PXGraph.GraphInstanceCreating;
        try
        {
            PXGraph.GraphStatePrefix = prefix;
            PXGraph.e = true;
            PXGraph.GraphInstanceCreating = type2;
            PXGraph pxgraph = (PXGraph)Activator.CreateInstance(type2);
            InjectMethods.InjectDependencies(pxgraph, graphType, prefix);
            IGraphWithInitialization graphWithInitialization = pxgraph as IGraphWithInitialization;
            if (graphWithInitialization != null)
            {
                graphWithInitialization.Initialize();
            }
            if (pxgraph.Extensions != null && pxgraph.Extensions.Length != 0)
            {
                PXGraphExtension[] extensions = pxgraph.Extensions;
                for (int i = 0; i < extensions.Length; i++)
                {
                    extensions[i].Initialize();
                }
            }
            try
            {
                if (PXDatabase.Provider is PXDatabaseProviderBase)
                {
                    pxgraph._VeryFirstTimeStamp = ((PXDatabaseProviderBase)PXDatabase.Provider).selectTimestamp().Item1;
                }
            }
            catch
            {
            }
            result = pxgraph;
        }
        finally
        {
            PXGraph.GraphStatePrefix = "";
            PXGraph.e = false;
            PXGraph.GraphInstanceCreating = graphInstanceCreating;
        }
    }
    catch (TargetInvocationException exception)
    {
        throw PXException.ExtractInner(exception);
    }
    return result;
}

The interesting part for us is the one below:

PXGraph pxgraph = (PXGraph)Activator.CreateInstance(type2);
InjectMethods.InjectDependencies(pxgraph, graphType, prefix);

As you can see Acumatica ERP is using InjectMethod.InjectDependencies method for Dependency Injection. Let’s see what is happening inside that method.

internal static void InjectDependencies(PXGraph graph, Type graphType, string prefix)
{
    if (!InjectMethods.b(graph))
    {
        if (graph.Extensions == null)
	{
	    return;
	}
	if (!graph.Extensions.Any((PXGraphExtension e) => InjectMethods.b(e)))
	{
	    return;
        }
    }
    IDependencyInjector dependencyInjector = CompositionRoot.GetDependencyInjector();
    if (dependencyInjector == null)
    {
	return;
    }
    dependencyInjector.InjectDependencies(graph, graphType, prefix);
}

Now we need to understand what is returned by CompositionRoot.GetDependencyInjector() 

Below is the code of that method:

internal static IDependencyInjector GetDependencyInjector()
{
    IDependencyInjector dependencyInjector;
    if ((dependencyInjector = CompositionRoot.InjectorSlot.GetInjector(LifetimeScopeHelper.GetLifetimeScope())) == null)
    {
        dependencyInjector = (ServiceLocator.IsLocationProviderSet ? LazyInitializer.EnsureInitialized<IDependencyInjector>(ref CompositionRoot.a, new Func<IDependencyInjector>(ServiceLocator.Current.GetInstance<IDependencyInjector>)) : null);
    }
    IDependencyInjector result;
    if ((result = dependencyInjector) == null)
    {
        result = CompositionRoot.b;
    }
    return result;
}

In short, this method is resolving the implementation of the IDependencyInjector interface.

If you check the PX.Data.ServiceRegistration class which represents an implementation of the Autofac’s Module class 

namespace PX.Data
{
    public class ServiceRegistration : Module
    {

        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<PropertyDependencyInjector>().As<IDependencyInjector>().InstancePerLifetimeScope().PreserveExistingDefaults<PropertyDependencyInjector, ConcreteReflectionActivatorData, SingleRegistrationStyle>();
            builder.RegisterType<DataScreenFactory>().As<IDataScreenFactory>().SingleInstance();
            builder.Register((IComponentContext c) => new ServerCompressionHandler(WebConfig.GetInt("CompressionThreshold", 0), new ICompressor[]
            {
                new GZipCompressor(),
                new DeflateCompressor()
            })).As<ServerCompressionHandler>();

You can find the below lines which means that we need to check the PropertyDependencyInjector class to understand how the InjectPropertyAttribute is working with PXGraphExtensions.

builder.RegisterType<PropertyDependencyInjector>().As<IDependencyInjector>().InstancePerLifetimeScope().PreserveExistingDefaults<PropertyDependencyInjector, ConcreteReflectionActivatorData, SingleRegistrationStyle>();

Remember that thedependencyInjector.InjectDependencies method is invoked in the InjectMethod.InjectDependencies and PXGraph.CreateInstance method is calling InjectMethod.InjectDependencies on the created graph instance.

Below is the code of that method from PropertyDependencyInjector class, which is the implementation of the IDependencyInjector interface.

public void InjectDependencies(PXGraph graph, Type graphType, string prefix)
	{
		IEnumerable<PropertyInfo> injectableProperties = PropertyDependencyInjector.PropertyInjector<InjectDependencyAttribute>.GetInjectableProperties(graph);
		if (injectableProperties != null)
		{
			injectableProperties.ForEach(delegate(PropertyInfo p)
			{
				PropertyDependencyInjector.PropertyInjector<InjectDependencyAttribute>.InjectProperty(this._componentContext, graph, this.GetGraphParameters(graph), p);
			});
		}
		if (graph.Extensions != null)
		{
			PXGraphExtension[] extensions = graph.Extensions;
			for (int i = 0; i < extensions.Length; i++)
			{
				PXGraphExtension extension = extensions[i];
				IEnumerable<PropertyInfo> injectableProperties2 = PropertyDependencyInjector.PropertyInjector<InjectDependencyAttribute>.GetInjectableProperties(extension);
				if (injectableProperties2 != null)
				{
					injectableProperties2.ForEach(delegate(PropertyInfo p)
					{
						PropertyDependencyInjector.PropertyInjector<InjectDependencyAttribute>.InjectProperty(this._componentContext, extension, this.GetGraphExtensionParameters(graph, extension), p);
					});
				}
			}
		}
	}

As we can see from the code of this method after injecting the properties of the PXGraph, the method is checking if the graph has extensions, and if it does,the method is injecting the properties of them.

1 Reply to “Dependency Injection in Acumatica ERP”

  1. This will not work if you add Module in plain customization code. Module as all other plugins can only be used in external dll.

Leave a Reply

Your email address will not be published. Required fields are marked *