Skip to main content
Solved

Service added using Dependency Injection always returns null


MichaelShirk
Captain II
Forum|alt.badge.img+2

This is my first time using Dependency Injection in a customization project.
I’m able to use it as expected in a few different places, but in this particular class, when I try to call a method from the service, a null reference exception is thrown. 
I have this service injected in a different class and it’s not throwing the error.


Any help would be appreciated as I’m new to this!
 

 

Best answer by snikomarov36

@MichaelShirk after re-reading your initial question I think I know what is the problem. I haven’t noticed first time that you are injecting not in graph but in a normal C# type - your data service.

You see, the InjectDependency mechanism supports DI injection only in graphs, graph extensions, Acumatica attributes derived from PXEventSubscriberAttribute, and custom graph views and actions. You can’t inject into a regular C# type with this mechanism.

On the other hand, all is not lost. On the contrary, you can use normal DI constructor injection with Autofac framework. You can read more here, basically, you can scroll to the constructor injection:
https://www.devleader.ca/2023/08/22/dependency-injection-how-to-start-with-autofac-the-easy-way/

You just need to add a parameter to your data service , so it would look like this:

public class SWZohoDataService : ISWZohoDataService
{
   // you can keep graph instance if you need it, 
   // although it may be cleaner to create a dedicated graph type without members
   // for the data service.
   private readonly PXGraph _graph;

   // No need for the attribute, also no need to make this a property
   private readonly ISWZohoAuthService _authService;

   public SWZohoDataService(ISWZohoAuthService authService)
   {
      // parameter will be provided by Autofac during the component resolution
      _authService = authService;
      _graph = PXGraph.CreateInstance<PXGraph>();
   }
}

 

View original
Did this topic help you find an answer to your question?

snikomarov36
Acumatica Employee
Forum|alt.badge.img

Hi @MichaelShirk.

You wrote that the service is not null in another type.
Do I understand correctly that your service is registered in the DI?

If yes, then a bit more details are needed. Could you please provide details on how the GetInventoryCount method throwing the exception is called?
I’m particularly interested in how the graph instance on which the GetInventoryCount method is called.


snikomarov36
Acumatica Employee
Forum|alt.badge.img

Some explanation behind my questions.

Acumatica property injection mechanism via InjectDependency attribute has some limitations.
The DI services are injected by the runtime only if you create the graph via a factory PXGraph.CreateInstance<TGraph> method.

Graphs created via a direct constructor call have a lot of things uninitialized including injected services and graph extensions. This is the reason why it is forbidden to create graphs via direct constructor calls. There is even an Acuminator diagnostic to check for that:
https://github.com/Acumatica/Acuminator/blob/dev/docs/diagnostics/PX1001.md

This doesn’t look like your case but it’s also worth mentioning that you cannot use DI services in the graph constructor. They are not initialized at that moment. DI services are injected only after the execution of the graph constructor.

I would start with checking for violations of the limitations I have described above.


MichaelShirk
Captain II
Forum|alt.badge.img+2

Hi @snikomarov36 ,
Thank you for responding.

“The DI services are injected by the runtime only if you create the graph via a factory PXGraph.CreateInstance<TGraph> method.”

That may be the issue, because I’ve injected the authService into the dataService, which is an implementation class and is not instantiated using PXGraph.CreateInstance<TGraph>.
However, I’m not sure what the best way is to access this method other than injecting the authService.


Would it be better practice to create a new() instance of the authService implementation class in the constructor of the dataService Implementation and use it to access the method instead of injecting the interface property as a service? 

The DataService and AuthService are registered with Autofac.

The Graph Extension that calls the GetInventoryCount method.

The AuthService is injected to provide an OAuth token to send in the request header.

The AuthService implementation.

 

Thank you!


snikomarov36
Acumatica Employee
Forum|alt.badge.img

@MichaelShirk after re-reading your initial question I think I know what is the problem. I haven’t noticed first time that you are injecting not in graph but in a normal C# type - your data service.

You see, the InjectDependency mechanism supports DI injection only in graphs, graph extensions, Acumatica attributes derived from PXEventSubscriberAttribute, and custom graph views and actions. You can’t inject into a regular C# type with this mechanism.

On the other hand, all is not lost. On the contrary, you can use normal DI constructor injection with Autofac framework. You can read more here, basically, you can scroll to the constructor injection:
https://www.devleader.ca/2023/08/22/dependency-injection-how-to-start-with-autofac-the-easy-way/

You just need to add a parameter to your data service , so it would look like this:

public class SWZohoDataService : ISWZohoDataService
{
   // you can keep graph instance if you need it, 
   // although it may be cleaner to create a dedicated graph type without members
   // for the data service.
   private readonly PXGraph _graph;

   // No need for the attribute, also no need to make this a property
   private readonly ISWZohoAuthService _authService;

   public SWZohoDataService(ISWZohoAuthService authService)
   {
      // parameter will be provided by Autofac during the component resolution
      _authService = authService;
      _graph = PXGraph.CreateInstance<PXGraph>();
   }
}

 


snikomarov36
Acumatica Employee
Forum|alt.badge.img

@MichaelShirk, in addition, I would think about the instance scopes of your registrations:

Instance scope is a configuration of your service registration that determines how the instance will be shared between all requests to Autofac for it. It would be also good for you to understand how Autofac manages lifetimes of the registered services. You can read more here:

In your case, you did not specify the instance scope. This means that the default scope will be used which is Instance per Dependency:
https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-dependency

Every request for the auth and data services will receive a new instance which is far from perfect from the performance POV, especially for the graph creation that your services do in the constructor.

On the other hands, graphs live only during the web request, so your data service should not keep a reference to stale graphs. I would guess, that registering your services with an Instance per Request scope should be done:
https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings