Dynamics 365 for Operations (AX 7) tries to get rid of the ugly concept of “customizations”, when developers directly change somebody else’s code, which has a plenty of negative consequences. Most importantly, installing a new version isn’t easy, because it requires dealing with conflicts in code.
There are several ways how to make a class extensible without requiring changes in the class itself. In certain cases, you can make a specialization of a base class, add additional behavior or modify the current behavior by overriding methods. It’s easy to do and understand, but there is a catch: you have to control the place where the class is instantiated, to provide your class instead of the original one. For example, if you have code like this,
IComponent c = new ComponentBase();
it will always create an instance of ComponentBase and nothing else. You need something smarter.
The solution is dependency injection – things like the construction of specific types are taken out from the class using them. The logic is only aware of an interface or base class and doesn’t care about what exact type it is, therefore you can start using another type and it will all work.
For example, you can provide the dependency in a constructor:
class MainLogic { IComponent component; public void new(IComponent _c) { component = _c; } }
The class doesn’t refer to ComponentBase or any other specific type; it will work with any class implementing IComponent interface and it’s up to the calling code to provide a suitable type.
If you don’t do anything else, you might end up with the same problem as before. There may be a piece of code constructing ComponentBase directly and passing it to the constructor, which you would have to replace by changing the code:
new MainLogic(new BaseClass());
You need to stop constructing such instances by yourself – leave it to a special piece of logic that knows which types to use: an Inversion of Control (IoC) container.
In Dynamics 365 for Operations, you can do something similar with plugins, but it’s quite cumbersome. Let me demonstrate how easy it can be to use an IoC container, namely Autofac in C#. I think it should be inspiration for X++.
First of all, let’s make a base class with some default implementation:
public class ComponentBase { public virtual int GetValue() { return 42; } }
and another class (overriding the method) that we want to use instead of the base class.
class ComponentCustom : ComponentBase { public override int GetValue() { return 1337; } }
Using an interface would be better, but because most code in X++ doesn’t utilize interfaces, this is closer to reality.
Then let’s have a class calling a method of the component and using the value (in this case simply showing it in console).
class MainLogic { public ComponentBase Component { get; set; } internal void DoStuff() { Console.WriteLine($"The value is {Component.GetValue()}"); } }
In this case, I’m not getting the component in constructor – I’m using a property instead, which the calling code (such as an IoC container) will have to set.
Before actually calling the logic, we have to set up our IoC container. Here is the code for Autofac:
var builder = new ContainerBuilder(); builder.RegisterType<ComponentCustom>().As<ComponentBase>(); builder.RegisterType<MainLogic>().PropertiesAutowired(); IContainer container = builder.Build();
The most interesting is the second line, where we instruct the container to use ComponentCustom in place of ComponentBase.
Then we can use container’s Resolve method to get an instance of MainLogic and execute DoStuff():
MainLogic mainLogic = container.Resolve<MainLogic>(); mainLogic.DoStuff();
The result is, as expected, is the value from ComponentCustom .
IoC containers allow you to get rid of instantiating components by yourself, therefore there is nothing like new ComponentBase() that you would have to change by overlayering; you just need a way to call RegisterType() of the builder. AX could offer an event for this purpose, where each extension would register its logic. Or there could be attributes similar to those used for declarative event handler subscriptions. IoC containers even allow setting type mapping in configuration files, therefore you can easily change type to use without touching code at all.
It would still require changes to the application, but it would usually mean merely replacing a constructor call with Resolve(). The current alternative, plugins, requires much more work and therefore it’s unlikely to be widely adopted for this purpose (to be fair, it’s not intended for this).
Because X++ doesn’t support properties, the implementation of my example would be more involved in current X++, but that’s just an implementation detail. And it may be extra motivation for introducing properties to X++.
Although I focused on extensiblity in this blog post, the loose coupling you can achieve with dependency injection has many other benefits, such as simplifying isolation for unit testing.
Note that IoC containers can do much more than what I’ve demonstrated here and there are many different solutions to choose from.
Just for the sake of completeness, X++ plugins are based on Managed Extensibility Framework.
You inspired me not to give up in Ax to have an IoC container and I experimented to create one. I will be happy of any feedback on my experiment:
https://enricoariel.blogspot.com/2019/08/inversion-of-control-framework-for.html