Register a Custom Component
This article will explain how you can register your own implementations of various interfaces to change the behavior of the platform. You most likely ended up here after reading another article on a common extension point. This article is for you if you've already made some code you want to register, or you are interested in learning how extension in Ucommerce works in a basic way.
The behavior of the platform can be changed in any way you'd like. This means that if you are not happy with how a piece of business logic works - for example pricing, tax or similar, you can change the behavior of the platform so it behaves the way you want. This is possible throughout the entire platform. And to make things even easier, the recipe is more or less always the same.
The recipe starts with an IOC container (powered by Castle Windsor) where all business logic is encapsulated in a .NET type and registered via configuration files. We refer to this concept as components, and should be considered building blocks to the platform. So you need to think of this concept as you changing the behavior via code, rather than re-compiling the platform. An example of this could for example be how tax is calculated.
Once you have written your desired change to the platform, it is time to register it. Your code ended up in a dll file output in the bin folder, and will be encapsulated in a .NET type which has 3 pieces of information:
- Assembly (dll file)
- Namespace
- Type name
So consider this dummy extension really quick to get an idea. In this
using Ucommerce; using Ucommerce.EntitiesV2; namespace UcommerceCodeSamples.ExtendingUcommerce { public class MyTaxService : Ucommerce.Catalog.ITaxService { public Money CalculateTax(Product product, PriceGroup priceGroup, Money unitPrice) { return new Money(0, "EUR"); } public Money CalculateTax(PriceGroup priceGroup, Money amount) { return new Money(0, "EUR"); } public decimal CalculateTax(decimal amount, decimal taxRate) { return 0; } } }
In the example above we have:
- Namespace is
UcommerceCodeSamples.ExtendingUcommerce
- Type name (class name) is
MyTaxService
- Assembly we can't see from the example above but typically the first section of your namespace unless you explic changed it when you created the class library so in this case
UcommerceCodeSamples
This makes up the actual type we want to register, and referenced in .NET it will look like this:
Namespace.Typename, Assemblyname
which in our example above becomes
UcommerceCodeSamples.ExtendingUcommerce.MyTaxService, UcommerceCodeSamples
Component registration 101
When you register a component you do it in an XML file placed under the apps folder in Ucommerce. If you haven't created one before, take a look at the structure under Ucommerce/Apps
.
Here you'll find lots of examples of foldername\configuration\configuration.config
Create your own folder with your own configuration.config file placed in it. Ucommerce will load them up recursively in alphabetic order. The order of the files are important, but more of that later.
Once you have created one, it should be empty looking like this:
<configuration> <components> </components> </configuration>
We are now ready to create an actual component.
To do so, You'll need to specify 3 things:
- An Id of your component
- The service that your component exposes - usually an interface, but can also be a class
- The specific type of the implementation of your component - always a class like the example above
When registering a component, use the following format:
<component id="Id of your component" service="interface you are implementing" type="the type you created" />
This would end up with the following component for our example above. You need to put that in your configuration file under the components
tag.
<component id="TaxService" service="Ucommerce.Catalog.ITaxService, Ucommerce" type="UcommerceCodeSamples.ExtendingUcommerce.MyTaxService, UcommerceCodeSamples" />
The final content of your config file looks like this:
<configuration> <components> <component id="TaxService" service="Ucommerce.Catalog.ITaxService, Ucommerce" type="UcommerceCodeSamples.ExtendingUcommerce.MyTaxService, UcommerceCodeSamples" /> </components> </configuration>
Once you have done this, restart the application and your type is now active in the IOC container. But we are not done understanding this just yet.
Overriding a Default Component
When you register a component like we just did, we typically change the behavior of the platform which means that there's also a default implementation in place somewhere that we need to replace. For the ITaxService implementation it happens to look like this:
<component id="TaxService" service="Ucommerce.Catalog.ITaxService, Ucommerce" type="Ucommerce.Catalog.TaxService, Ucommerce"/>
All we have to do to replace this component is reuse the id of the component - in this case TaxService
. There can only be one component with the same Id which means it is unique. If the IOC container stumble upon a component with the same ID, the last component wins. So in this case the order of which the configuration files are loaded is very important.
Notice that the ID is the same as the default registration, but the type now points to UcommerceCodeSamples.ExtendingUcommerce.MyTaxService, UcommerceCodeSamples
and thus we have changed how tax is calculated in the platform.
This effectively redirects all calls to your implementation and can be done for any components in Ucommerce that ships out of the box.
Please note that if you do not reuse the same id, the two implementations of ITaxService
will live side by side, but your component will never be picked up, as the IOC container picks the first implementation it finds, which will always be the OOTB components.
Constructor Injected Components
Components can be composed using other components. What this means is that you can create highly focused components, which do just one thing and later on "click" them together to solve a task together.
Constructor injection is a way to achieve this without the components knowing that they're operating in an injected environment.
Consider a pipeline task: The task is part of the overall framework of Ucommerce and as such is injected into the pipeline executing it.
The pipeline task might need other components to complete its job; these can be injected just like the pipeline task itself is. You might need access to other components shipped with Ucommerce such as the ITaxService
discussed above or entirely new ones created just for the occasion:
public class MyPipelineTask : IPipelineTask<PurchaseOrder> { private readonly ITaxService _taxService; private readonly IMyNewComponent _myNewComponent; public MyPipelineTask( ITaxService taxService, IMyNewComponent myNewComponent) { // priceService is provided automatically to MyPipelineTask // because it's registered with Ucommerce _taxService = taxService; // Works for entirely new components too _myNewComponent = myNewComponent; } public PipelineExecutionResult Execute(PurchaseOrder subject) { // Do some price calculation return PipelineExecutionResult.Success; } }
As you can see from the example MyPipelineTask
receives both the existing ITaxService
and the completely new component IMyNewComponent
and can use the instances to perform its job.
You can read more about Constructor Injection, Property Injection, and more advanced injection in this article
Getting and Using Registered Components
Typically when you are writing your components you just have to constructor inject your components into your code. But this only counts for classes that you already inject. If you are working in Web API or in your MNVC controllers or similar, you have to ask the IOC to give it to you.
When you need to use any given service in Ucommerce you can resolve them from ObjectFactory
- either by Id for an exact implementation of a service or by service/interface for any given implementation.
You can also get a list of all implementations for a specific service/interface.
Fetching services is possible by using the static methods on ObjectFactory
. To use it, you'll need a reference to UCommerce.Infrastructure.dll
in your solution.
Ucommerce.Infrastructure.ObjectFactory
Description
Object factory class.
Methods
AddChildContainer
-
Description
Adds a child container to the current Castle Windsor container. Components registered in the child container will override the ones from the parent. -
Arguments
Castle.Windsor.IWindsorContainer
childContainer
- Return type
Void
Resolve
-
Arguments
- This method is called without any arguments
- Return type
Ucommerce.Infrastructure.T
Resolve
-
Arguments
Type
type
- Return type
Object
GetServiceIdsFor
-
Arguments
- This method is called without any arguments
- Return type
IEnumerable<string>
RegisteredServicesFor
- Description
Gets ids for the registered services. -
Arguments
Type
serviceType
- Return type
IEnumerable<string>
GetRegisteredComponentsFor
- Description
Gets full information for the registered components of a single service. -
Arguments
Type
serviceType
- Return type
List<Castle.Core.ComponentModel>
Resolve
-
Arguments
string
id
- Return type
Ucommerce.Infrastructure.T
Resolve
-
Arguments
Type
typestring
id
- Return type
Object
ResolveAll
-
Arguments
- This method is called without any arguments
- Return type
IList<Ucommerce.Infrastructure.T>
ResolveAll
-
Arguments
Type
type
- Return type
IList<Object>
Instance
- Return type
Ucommerce.Infrastructure.ObjectFactory
Quick Note on Pipelines
If you are looking to modify a pipeline component, please refer to the "Configure the Pipeline" section in Create Pipeline Task article.