In this post I will attempt to explain why and when IsActive method is needed in DAC and graph extensions, and how it can simplify the developer's life and improves performance of your code. The post is largely based on the articles from Acumatica Help Portal:
Let's start with a short reminder of what the IsActive method is. It is a special public static method that is declared in graph and DAC extensions and is recognized by the Acumatica platform. The method returns bool and doesn't have any parameters. It looks like this:
public class MyGraphExtension : PXGraphExtension<MyGraph> { public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.myFeature>(); ... }
public class MyDacExtension : PXCacheExtension<MyDac> { public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.myFeature>(); ... }
The platform calls IsActive method during the graph initialization when it initializes graph and DAC extensions to determine if the extension is active and should be initialized. If there is no IsActive method, then the extension is considered to be constantly active.
IsActive Purpose and Usage Scenarios
The goal of the IsActive mechanism is to allow developers to write simple logic to enable their extensions only based on some simple condition. The most frequent use case is to check if some feature is enabled on the Acumatica site. The IsActive mechanism allows to achieve this with a very simple code:
public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.myFeature>();
This greatly simplifies the implementation of a new feature with a graph and DAC extensions which is a recommended approach to the design of new features.
Before the introduction of the IsActive mechanism extensions were constantly active. The developers of a new Acumatica feature had to emulate the behavior of a disabled extension by constantly checking the feature they were implementing to see if it is enabled. So, their code looked something like this:
public class SOOrderEntrySomeFeatureExt : PXGraphExtension<SOOrderEntry> { public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.someFeature>();
... public PXAction<SOOrder> myAction;
PXUIField(DisplayName = "My Action", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)] PXButton] public virtual IEnumerable MyAction(PXAdapter adapter) { if (!PXAccess.FeatureInstalled<FeaturesSet.someFeature>()) return adapter.Get();
// the action logic ...
return adapter.Get(); }
public override void Initialize() { base.Initialize();
public virtual void SOOrder_OrderNbr_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) { if (!PXAccess.FeatureInstalled<FeaturesSet.someFeature>()) return;
//... the event handler implementation }
// All other event handlers also have to check if feature is enabled with a call to PXAccess.FeatureInstalled<FeaturesSet.someFeature>() }
As you can see in the code above, there was a lot of boilerplate code. Developers had to constantly check if the feature implemented by the extension was enabled in:
Every graph event handler. This was required to prevent the execution of the feature logic when it was disabled.
In the graph extension's Initialize method or primary DAC's row selected event. Developers had to hide actions added by the extension if it was disabled.
In action delegate, to not execute any logic.
In addition, If the graph extension declared an action or view delegate override, developers had to call the base delegate, if the feature was disabled. And there probably were other places, I just named a few places I know.
The IsActive mechanism eliminated any need in this boilerplate code and made the development of extensions tied to some particular feature much simpler.
IsActive Performance Improvements
Now let’s talk about the performance improvements made by IsActive. I believe there are three types of changes that can be introduced by extensions and affect the performance of a screen graph:
New DAC fields added by DAC extensions
New graph event handlers, actions, and views brought by graph extensions.
Overrides of existing graph members declared in graph extensions
Perhaps, I missed something but IMO these are the three most significant categories that are affected by the IsActive mechanism. Let's consider them one by one.
New DAC Fields
New DAC fields from a DAC extension will be added to the corresponding graph cache to the Fields collection . There are many mechanisms in the Acumatica core that cycle over all cache fields in the Fields collection. For instance, update of the row has to cycle over all cache fields, see all registered handlers for the field update event, both from attributes and from graph with its graph extensions. Cycles made for fields from DAC extensions that should not be active are just wasted.
If the DAC extension is disabled via IsActive mechanism, then its fields just won't be added to the cache fields. Having less fields means doing less iterations in cycles over all cache fields. In addition, no graph event handlers would be registered for the fields from the disabled DAC extension.
New Graph Event Handlers, Actions and Views
For new graph event handlers everything is similar to the previous section. Each graph event handler of an active graph extension will be executed when the corresponding graph event is raised.
Previously, I have already described that before the introduction of the IsActive mechanism extension developers had to constantly check the feature they were implementing to see if it is enabled. The frequent execution of calls to PXAccess.FeatureInstalled API consumed some resources. The IsActive mechanism eliminated the need in such calls which improved the extension's performance.
It's the same for new graph actions and views . If graph extension is disabled via IsActive mechanism, then its actions and views won't be initialized and added to graph actions and views collections. This reduces the graph's memory usage. In addition, no code is required to configure the visibility of extension's actions depending on the feature status (enabled/disabled). No extra code to run means better performance.
Overrides of Existing Graph Members
The last case is when a graph extension declares overrides of the members from its base graph and graph extensions. In the section "IsActive purpose and usage scenarios" I described how previously developers had to check in overrides if the feature was active, and call the base method, it the feature was disabled. Obviously, this has a performance cost. Graph extensions disabled via IsActive mechanism do not pay this cost.
As you know, Acumatica customization engine combines overrides of graph members (such as graph event handlers, action and view delegates, PXOverride methods) into a queue of methods. Disabled graph extensions do not contribute anything to these queues. Therefore, there is no need to check in the override if the feature is active, the override just won't be called for a disabled extension.
Acuminator Support for IsActive Method
IsActive mechanism is important for DAC and graph extensions. Therefore, is is recognized and supported by Acuminator in several ways.
PX1016 Diagnostic
Many people have encountered the PX1016 diagnostic that displays a warning if there is no IsActive method in a DAC or graph extension.
The diagnostic display warnings only for a particular kind of extensions. I would informally describe them as "normal concrete extensions". By "concrete" I mean extensions that are not used as a base class for another extension, and specify a concrete graph type that they are extending. "Normal" means that the extension is used for extension's "normal" purpose - extension of business logic.
In addition to the static code analysis Acuminator also displays IsActive method in the Acuminator Code Map. You can open Code Map window in Visual Studio: "Extensions" -> "Acuminator" -> "Code Map".
"Extensions" -> "Acuminator" -> "Code Map"
I personally recommend you to dock the Code Map window to the side. It is designed to work in pair with a code editor. In the window you can see Acumatica specific graph extension members including the IsActive method. If you hover your mouse over it, you can even see the IsActive method body in a tooltip:
IsActive in Acuminator Code Map
Constantly Active Extensions
So far I have described only extensions that sometimes have to be disabled. Usually, such extensions' activity is tied to the activity of some Acumatica feature.
Still, not every DAC/Graph extension falls into this category. There are many extensions that just customize the core functionality and should be constantly active.
It is perfectly fine to use constantly active extensions if you need them.
For such extensions you don't need to declare IsActive method at all. In other words, the IsActive mechanism should be used only when your extension sometimes should be disabled.
I would like to explain the statement above more. Sometimes, people see Acuminator PX1016 warning, or read info about the importance of the IsActive method in the documentation, and make a false assumption that IsActive is a mandatory part of the extension. To comply with this false assumption they decide to declare a trivial IsActive method in their constantly active extensions like this:
public static bool IsActive() => true;
Please do not do this! If you have an Acuminator warning, please suppress it instead!
If your extension needs to be always active then you don't have a need for IsActive method at all. The IsActive method also has its own small cost. The platform has to find and call it via reflection at least once during the graph initialization to ensure that the extension should be enabled and added to the graph. Therefore, for a constantly active extension the performance will be slightly better, if there is no IsActive method at all than if there is a trivial one.
Acuminator recognizes this scenario. The PX1016 documentation also contains this recommendation. When you generate a suppression comment via Acuminator for PX1016 diagnostic, Acuminator will automatically add the default justification to it:
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
Compare this with how the suppression comment is usually generated with the justification placeholder:
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod eJustification]
Disabling Graph Extensions Hierarchy
Some features may affect multiple Acumatica screens. Some examples: tax calculation, multi-currency support. Such features are also implemented with graph extensions. Usually, the core logic of the feature goes into the base extension class which usually is abstract and has a generic type parameter representing graph type:
public abstract class SomeFeatureGraphExtBase<TGraph> : PXGraphExtension<TGraph> where TGraph : PXGraph { // Core logic }
The base extension class may provide some extension points that can be overridden in the derived extensions, but it is not necessary. Still, in order to extend a particular graph we need to declare a concrete derived extension:
public class SomeFeatureAPInvoiceEntryExt : SomeFeatureGraphExtBase<APInvoiceEntry> {}
You will need to introduce such graph extensions for every graph that you wish to extend with your feature.
Now, suppose that you want to disable your extensions if your feature is disabled on Acumatica site. The naïve approach would be to declare the IsActive method inside the base extension class:
public abstract class SomeFeatureGraphExtBase<TGraph> : PXGraphExtension<TGraph> where TGraph : PXGraph { public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.someFeature>();
// Core logic }
Unfortunately, this won't work. The IsActive method currently disables only the extension in which it is declared, there is no lookup in the base classes. Therefore, you will need to declare IsActive method in all derived graph extensions which is rather tedious.
Still, you can at least reuse the logic of the IsActive check. You can introduce a protected static IsActiveBase method in the base extension and then call it from the IsActive method in each derived extension. It should look like this:
public abstract class SomeFeatureGraphExtBase<TGraph> : PXGraphExtension<TGraph> where TGraph : PXGraph { protected static bool IsActiveBase() => PXAccess.FeatureInstalled<FeaturesSet.someFeature>();
// Core logic }
public class SomeFeatureAPInvoiceEntryExt : SomeFeatureGraphExtBase<APInvoiceEntry> { public static bool IsActive() => IsActiveBase(); }
IsActiveForGraph Method
Finally, I would like to mention the less known IsActiveForGraph<TGraph> method. It allows you to exercise more granular control and enable extensions only for a particular graph:
public static bool IsActiveForGraph<TGraph>() => typeof(TGraph) == typeof(ARInvoiceEntry);
This can be useful in one particular scenario where you have a graph hierarchy.
Graph extensions applied to a base graph are also applied to all graphs derived from it. Sometimes, you may wish to avoid this, or to apply graph extension only to some of the derived graphs. The IsActive method won't help you with this scenario, you will need IsActiveForGraph<TGraph> method to do it.
The IsActiveForGraph<TGraph> method is very similar to the IsActive method. It has the same limitation:
You cannot create graphs inside IsActiveForGraph method because it can lead to deadlocks!
It is also checked by the Acuminator PX1056 diagnostic although it is not displayed in Code Map yet.
You can also declare in your graph extension both IsActive and IsActiveForGraph methods:
public class MyFeatureARInvoiceEntryExt : PXGraphExtension<ARInvoiceEntry> { public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.myFeature>();
public static bool IsActiveForGraph<TGraph>() where Graph : PXGraph // Constraint is not obligatory { // Check the type of the graph here to disable extension for derived graphs (assuming that we need it) return typeof(TGraph) == typeof(ARInvoiceEntry); } }