It's possible to register a class with a parameter expected to be passed from the point of creation?
I know it can be done something like this:
GlobalContainer.RegisterType<TUserProcessor>.Implements<IUserUpgrader>.
AsTransient.DelegateTo(
function: TUserProcessor
begin
Result := TUserProcessor.Create(GetCurrentUser);
end
);
But there the parameters are binded to the execution context where the container gets registered and not where the object get's intantiated.
Something like this it's possible for example?
GlobalContainer.Resolve<IMathService>([FCurrentUser]);
I know some peoble advocate to have very simple constructors, but there are times when a constructor parameter looks clearly the way to go:
The object constructed needs the object parameter to work, so the reference must be satisfied. The parameter also makes that constraint much more obvious looking at the class.
You can assign the reference in a method or property and raise and exception in every other method if you try to use the object without first making the assignment.. I don't like writing this type of code it's simply a waste of time, just use the constructor parameter and check there. Less code, the better IMO.
Also the object being passed it's local to the object that constructs the new object using the container (for example a Transaction object) and has some state (it's not a new object that I can get with the container).
I have added resolver overrides as in Unity.
So you can write:
program Demo;
{$APPTYPE CONSOLE}
uses
Classes,
Spring.Container,
Spring.Container.Resolvers;
type
IUserUpgrader = interface
['{ADC36759-6E40-417D-B6F7-5DCADF8B9C07}']
end;
TUser = class(TObject);
TUserProcessor = class(TInterfacedObject, IUserUpgrader)
public
constructor Create(AUser: TUser);
end;
constructor TUserProcessor.Create(AUser: TUser);
begin
Writeln('called constructor with passed user');
end;
begin
GlobalContainer.RegisterType<TUserProcessor>.Implements<IUserUpgrader>;
GlobalContainer.Build;
GlobalContainer.Resolve<IUserUpgrader>(
TOrderedParametersOverride.Create([TUser.Create]));
GlobalContainer.Resolve<IUserUpgrader>(
TParameterOverride.Create('AUser', TUser.Create));
Readln;
end.
Edit 17.09.2018:
This changed quite some while ago and Resolve is accepting parameters via array of TValue
. You can either pass values directly there which then have to match exactly the parameter list in the ctor. For only partially filling the parameters you can use TNamedValue
or TTypedValue
from Spring.pas
Anyway I suggest using factories to be injected and consumed by your code and not reaching into the container (global one or not) Resolve method.
You could check out this thread:
https://forums.embarcadero.com/message.jspa?messageID=440741
I've done an implementation on this in the framework and use it in my code. If you want it, I'll send it to you. Keep in mind that this is not how it might be done when and if they add functionality like this in the codebase. Baoquan has my code.
This implementation also handles if you try to resolve without all needed parameters, and gives you an exception showing the missing parameters.
You use it like this:
ServiceLocator.GetService<ISomeObj>(ServiceParameters['paramname1',value1]['paramname2',value2]..);
Value is stored as TValue and can be anything.
Registering the parameter with your class is done with attributes like this:
private
[ContainerParameter('paramname1')]
FYourValue1: ...;
[ContainerParameter('paramname2')]
FYourValue2: ..;
You can also do
[ContainerParameter]
FYourValue1:..
But then your parameter have 'FYourValue1' as name.