Code overriding
What is code overriding?
Code overriding represents the redefinition of Neos Studio server code. It allows a child module to add code to run before, after, or instead of the code of one of its parent modules.
Note
The redefinition is based on the Decorator design pattern. It is strongly advised to understand its principle before tackling the rest of the article.
How to override server code in Neos Studio?
Each time you create server code in Neos Studio, a C# class is generated.
If we take the example of a validation rule on a Customer entity in a Sales module (see previous article on metadata overriding) that checks the CompanyName length, the generated class would look like this:
modules\Sales\businessAssembly\Domain\ValidationRules\Customer\CompanyNameCheck.cs:
namespace MyApplication.Sales.Domain.CustomerValidationRules
{
public class CompanyNameCheck : ValidationRule<Customer>, ICompanyNameCheck
{
/// <inheritdoc/>
public override IValidationRuleResult Execute()
{
if (Item.CompanyName.Length < 5)
{
return Error(Resources.Sales.CompanyNameCheckError);
}
return Success();
}
}
}
To redefine this class in another module, as for overriding metadata, you must first select a working module (CarRental in our example) and active Allow override.
This causes an Add button to appear above the Neos code editor to the right of the zone allowing to select the business assembly.
When clicking on this button, a popup is displayed to validate the choice of the business assembly in which to create the class. When pressin Ok, the overriding generated class appears on the screen and should look like this:
namespace MyApplication.CarRental.Domain.CustomerValidationRules
{
public class CompanyNameCheck : AsyncValidationRule<Customer>, ICompanyNameCheck
{
private readonly ICompanyNameCheck _baseRule;
private readonly INeosLogger<ICompanyNameCheck> _logger;
public CompanyNameCheck(ICompanyNameCheck baseRule, INeosLogger<ICompanyNameCheck> logger)
: base(baseRule)
{
_baseRule = baseRule;
_logger = logger;
}
/// <inheritdoc/>
public async override Task<IValidationRuleResult> ExecuteAsync()
{
// Calling the base code. You can add code before and/or after or remove the call but you cannot remove the dependency.
IValidationRuleResult baseResult = await _baseRule.ExecuteAsync();
if (!baseResult.IsSuccess)
{
return baseResult;
}
_logger.LogWarning("CompanyNameCheck failed");
return Error("<Put your error message here>");
}
}
}
Note
The C# files are not created and cannot be edited in Visual Studio until you save once.
Some important points that can be noted when reading this code:
- Note the implementation of the Decorator design pattern with the injection of the base implementation into the constructor via an interface and the call to the base implementation in the
ExecuteAsyncmethod. - The redefinition being an independent class of the basic implementation, it can have its own dependencies. Adding a dependency in the base implementation will not impact the redefinition and vice versa.
- Since the redefinition is responsible for calling the base implementation, it has the freedom to execute code before and / or after it.
- The redefinition having no dependency on the base implementation, writing unit tests stays as simple as if there was no redefinition.
The code can then be modified, for example, to add an additional constraint on CompanyName :
namespace DemoFormation.CarRental.Domain.CustomerValidationRules
{
/// <inheritdoc/>
public class CompanyNameCheck : AsyncValidationRule<Customer>, ICompanyNameCheck
{
private readonly ICompanyNameCheck _baseRule;
public CompanyNameCheck(ICompanyNameCheck baseRule) : base(baseRule)
{
_baseRule = baseRule;
}
public async override Task<IValidationRuleResult> ExecuteAsync()
{
// Calling the base code. You can add code before and/or after or remove the call but you cannot remove the dependency.
IValidationRuleResult baseResult = await _baseRule.ExecuteAsync();
if (!baseResult.IsSuccess)
{
return baseResult;
}
// Additional constraint
if (!Item.CompanyName.StartsWith("R"))
{
return Error(Resources.CarRental.CompanyNameCheckError);
}
return Success();
}
}
}
The same principle applies for all types of server code.
Is it possible to completely replace the base code?
Yes, just don't call the base implementation. However, you cannot remove the injection of the base implementation in the constructor as the Scrutor library used for the implementation of the Decorator design pattern considers this as an error and throws an error at runtime.
How to see the base code and the code of the different redefinitions?
It is possible to switch between the base code and its redefinitions by using the business assembly selection dropdown at the top of the Neos Studio code editor.
How to delete a redefinition?
You have to use the Remove button at the top right of the code editor. This button only deletes the selected class and its unit test class if it exists.
Note
The C# files containing the code are not deleted until saving.
Why are redefinitions always asynchronous?
An override must call the base implementation. This basic implementation can be asynchronous and even if it is not at present, it might become asynchonous in the future. Forcing overrides to be asynchronous gives the assurance that their code will always compile.
Is it normal that an event rule calls a base implementation that does not exist?
When you create a validation rule or a server method, you choose its a name and the code you write becomes the base implementation. Therefore, you must not call a base implementation that will never exist.
For event rules, the principle is a little different. You link them to an event and in some cases, it is possible that someone else links another event rule to the same event in a parent module.
For this reason, even without enabling redefinition, you will end up with a call to a base implementation when:
- You edit an entity event rule that is not stored in the
Domainlayer assembly of the entity module. - You edit an entity view event rule that is not stored in the the
Applicationlayer assembly of the module of the entity view.
Warning
We strongly discourage you from removing the call to the base implementation in an event rule.
Note
When generating, if the base event rule does not exist, an empty implementation is generated for it.