Container (WIP) - almost ready to be merged into master

This commit is contained in:
Daniele Teti 2024-03-28 23:57:59 +01:00
parent 1920249eb9
commit 6142c2d929
13 changed files with 447 additions and 141 deletions

View File

@ -57,6 +57,7 @@ end;
function TMyController.GetPeople2(OtherPeopleService: IPeopleService): TObjectList<TPerson>;
begin
LogI('PeopleService in GetPeople2: ' + IntToHex(NativeUInt(Pointer(OtherPeopleService))));
Result := OtherPeopleService.GetAll;
end;
@ -82,6 +83,7 @@ begin
inherited Create;
Assert(PeopleService <> nil, 'PeopleService not injected');
fPeopleService := PeopleService;
LogI('PeopleService in constructor: ' + IntToHex(NativeUInt(Pointer(PeopleService))));
end;

View File

@ -16,19 +16,19 @@ type
implementation
uses
MVCFramework.Logger;
MVCFramework.Logger, System.SysUtils;
{ TConnectionService }
constructor TConnectionService.Create;
begin
inherited;
LogI('Service ' + ClassName + ' created');
LogI('Service ' + ClassName + ' created [' + IntToHex(NativeUInt(Pointer(Self))) + ']');
end;
destructor TConnectionService.Destroy;
begin
LogI('Service ' + ClassName + ' destroyed');
LogI('Service ' + ClassName + ' destroyed [' + IntToHex(NativeUInt(Pointer(Self))) + ']');
inherited;
end;

View File

@ -19,18 +19,21 @@ type
implementation
uses
System.SysUtils {IntToHex};
{ TPeopleService }
constructor TPeopleService.Create(ConnectionService: IConnectionService);
begin
inherited Create;
fConnService := ConnectionService;
LogI('Service ' + ClassName + ' created');
LogI('Service ' + ClassName + ' created [' + IntToHex(NativeUInt(Pointer(Self))) + ']');
end;
destructor TPeopleService.Destroy;
begin
LogI('Service ' + ClassName + ' destroyed');
LogI('Service ' + ClassName + ' destroyed [' + IntToHex(NativeUInt(Pointer(Self))) + ']');
inherited;
end;

View File

@ -0,0 +1,21 @@
unit Services.RegistrationU;
interface
uses
MVCFramework.Container;
procedure RegisterServices(Container: IMVCServiceContainer);
implementation
uses
Services.PeopleU, Services.InterfacesU, Services.ConnectionU;
procedure RegisterServices(Container: IMVCServiceContainer);
begin
Container.RegisterType(TPeopleService, IPeopleService);
Container.RegisterType(TConnectionService, IConnectionService, '', TRegistrationType.SingletonPerRequest)
end;
end.

View File

@ -21,7 +21,8 @@ uses
Entities in '..\commons\Entities.pas',
MVCFramework.Router in '..\..\sources\MVCFramework.Router.pas',
MVCFramework.Container in '..\..\sources\MVCFramework.Container.pas',
Services.ConnectionU in 'Services.ConnectionU.pas';
Services.ConnectionU in 'Services.ConnectionU.pas',
Services.RegistrationU in 'Services.RegistrationU.pas';
{$R *.res}
@ -75,9 +76,8 @@ begin
.Build();
end);
DefaultMVCServiceContainer.RegisterType(TPeopleService, IPeopleService);
DefaultMVCServiceContainer.RegisterType(TConnectionService, IConnectionService);
DefaultMVCServiceContainer.Build();
RegisterServices(DefaultMVCServiceContainer);
DefaultMVCServiceContainer.Build;
if dotEnv.Env('dmvc.profiler.enabled', false) then
begin

View File

@ -126,6 +126,7 @@
<DCCReference Include="..\..\sources\MVCFramework.Router.pas"/>
<DCCReference Include="..\..\sources\MVCFramework.Container.pas"/>
<DCCReference Include="Services.ConnectionU.pas"/>
<DCCReference Include="Services.RegistrationU.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>

View File

@ -7,21 +7,22 @@ uses
type
{$SCOPEDENUMS ON}
TRegistrationType = (Transient, Singleton, SingletonPerRequest);
TClassOfInterfacedObject = class of TInterfacedObject;
TRegistrationType = (rtTransient, rtSingleton, rtSingletonPerRequest);
IMVCServiceContainerResolver = interface
['{2C920EC2-001F-40BE-9911-43A65077CADD}']
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
end;
IMVCServiceContainer = interface
['{1BB3F4A8-DDA1-4526-981C-A0BF877CFFD5}']
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = rtTransient): IMVCServiceContainer; overload;
function Re vsolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface;
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = TRegistrationType.Transient): IMVCServiceContainer; overload;
procedure Build();
end;
IMVCServiceContainerEx = interface
['{2C920EC2-001F-40BE-9911-43A65077CADD}']
function ResolveEx(const aTypeInfo: PTypeInfo; const aName: string; out ServiceKey: String; out RegType: TRegistrationType): IInterface; overload;
end;
MVCInjectAttribute = class(TCustomAttribute)
private
fServiceName: String;
@ -37,6 +38,9 @@ type
function DefaultMVCServiceContainer: IMVCServiceContainer;
function NewMVCServiceContainer: IMVCServiceContainer;
function NewServiceContainerResolver: IMVCServiceContainerResolver; overload;
function NewServiceContainerResolver(Container: IMVCServiceContainer): IMVCServiceContainerResolver; overload;
implementation
@ -44,6 +48,12 @@ uses
MVCFramework.Rtti.Utils;
type
IMVCServiceInternalResolver = interface
['{81527509-BA94-48C1-A030-E26F1FC9BFF5}']
function Resolve(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface;
function ResolveEx(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string; out ServiceKey: String; out RegType: TRegistrationType): IInterface; overload;
end;
TRegistration = class
Intf: TGUID;
Clazz: TClassOfInterfacedObject;
@ -51,37 +61,47 @@ type
Instance: IInterface;
RegistrationType: TRegistrationType;
end;
TMVCServiceContainer = class(TInterfacedObject, IMVCServiceContainer)
TMVCServiceContainer = class(TInterfacedObject, IMVCServiceContainer, IMVCServiceInternalResolver)
private
fBuilt: Boolean;
fRegistry: TObjectDictionary<string, TRegistration>;
function CreateServiceWithDependencies(const ServiceClass: TClassOfInterfacedObject;
function CreateServiceWithDependencies(
const ServiceContainerResolver: IMVCServiceContainerResolver;
const ServiceClass: TClassOfInterfacedObject;
const ConstructorMethod: TRttiMethod): TInterfacedObject;
protected
class function GetKey(const aGUID: TGUID; const aName: String): String;
constructor Create; virtual;
destructor Destroy; override;
class var fInstance: IMVCServiceContainer;
public
class function Instance: IMVCServiceContainer;
class constructor Create;
class destructor Destroy;
function RegisterType<TImpl: TInterfacedObject>(const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = rtTransient): IMVCServiceContainer; overload;
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = rtTransient): IMVCServiceContainer; overload;
function Resolve<TIntf: IInterface>(const aName: string = ''): TIntf; overload;
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
function ResolveEx(const aTypeInfo: PTypeInfo; const aName: string; out ServiceKey: String; out RegType: TRegistrationType): IInterface; overload;
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = TRegistrationType.Transient): IMVCServiceContainer; overload;
function Resolve(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
function ResolveEx(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string; out ServiceKey: String; out RegType: TRegistrationType): IInterface; overload;
procedure Build();
end;
TMVCServiceContainerAdapter = class(TInterfacedObject, IMVCServiceContainerEx)
TMVCServiceContainerAdapter = class(TInterfacedObject, IMVCServiceContainerResolver)
private
fCachedServices: TDictionary<String, IInterface>;
fContainer: IMVCServiceInternalResolver;
protected
function ResolveEx(const aTypeInfo: PTypeInfo; const aName: string; out ServiceKey: string; out RegType: TRegistrationType): IInterface;
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
public
constructor Create(Container: IMVCServiceContainer);
destructor Destroy; override;
end;
var
gDefaultMVCServiceContainer: IMVCServiceContainer = nil;
gLock: TObject = nil;
{ TMVCServiceContainer }
function TMVCServiceContainer.CreateServiceWithDependencies(const ServiceClass: TClassOfInterfacedObject;
function TMVCServiceContainer.CreateServiceWithDependencies(
const ServiceContainerResolver: IMVCServiceContainerResolver;
const ServiceClass: TClassOfInterfacedObject;
const ConstructorMethod: TRttiMethod): TInterfacedObject;
var
lActionFormalParams: TArray<TRttiParameter>;
@ -97,7 +117,10 @@ begin
begin
for I := 0 to Length(lActionFormalParams) - 1 do
begin
lIntf := Resolve(lActionFormalParams[I].ParamType.Handle);
if ServiceContainerResolver = nil then
lIntf := Resolve(nil, lActionFormalParams[I].ParamType.Handle)
else
lIntf := ServiceContainerResolver.Resolve(lActionFormalParams[I].ParamType.Handle);
if not Supports(lIntf, lActionFormalParams[I].ParamType.Handle.TypeData.GUID, lOutIntf) then
begin
raise EMVCContainerError.CreateFmt('Cannot inject parameter %s: %s into constructor of %s', [
@ -131,21 +154,11 @@ begin
inherited;
end;
class destructor TMVCServiceContainer.Destroy;
begin
fInstance := nil;
end;
class function TMVCServiceContainer.GetKey(const aGUID: TGUID; const aName: String): String;
begin
Result := aGUID.ToString + '_' + aName;
end;
class function TMVCServiceContainer.Instance: IMVCServiceContainer;
begin
Result := fInstance;
end;
function TMVCServiceContainer.RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID;
const aName: string; const aRegType: TRegistrationType): IMVCServiceContainer;
var
@ -174,12 +187,7 @@ begin
Result := Self;
end;
function TMVCServiceContainer.RegisterType<TImpl>(const aInterface: TGUID; const aName: string; const aRegType: TRegistrationType): IMVCServiceContainer;
begin
Result := RegisterType(TImpl, aInterface, aName, aRegType);
end;
function TMVCServiceContainer.Resolve(const aTypeInfo: PTypeInfo; const aName: string): IInterface;
function TMVCServiceContainer.Resolve(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string): IInterface;
var
lReg: TRegistration;
lTypeInfo: PTypeInfo;
@ -198,13 +206,13 @@ begin
lType := lReg.RttiType;
case lReg.RegistrationType of
rtTransient:
TRegistrationType.Transient, TRegistrationType.SingletonPerRequest:
begin
lService := CreateServiceWithDependencies(lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
Supports(lService, lTypeInfo.TypeData.GUID, Result);
end;
rtSingleton:
TRegistrationType.Singleton:
begin
if lReg.Instance = nil then
begin
@ -212,7 +220,7 @@ begin
try
if lReg.Instance = nil then
begin
lService := CreateServiceWithDependencies(lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
Supports(lService, lTypeInfo.TypeData.GUID, lReg.Instance)
end;
finally
@ -226,12 +234,7 @@ begin
end;
end;
function TMVCServiceContainer.Resolve<TIntf>(const aName: string): TIntf;
begin
Result := Resolve(TypeInfo(TIntf), aName);
end;
function TMVCServiceContainer.ResolveEx(const aTypeInfo: PTypeInfo; const aName: string;
function TMVCServiceContainer.ResolveEx(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string;
out ServiceKey: String; out RegType: TRegistrationType): IInterface;
var
lReg: TRegistration;
@ -255,14 +258,14 @@ begin
RegType := lReg.RegistrationType;
ServiceKey := lServiceKey;
case lReg.RegistrationType of
rtTransient, rtSingletonPerRequest:
TRegistrationType.Transient, TRegistrationType.SingletonPerRequest:
begin
lService := CreateServiceWithDependencies(lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
Supports(lService, lTypeInfo.TypeData.GUID, Result);
{rtSingletonPerRequest is destroyed by the adapter owned by Context}
end;
rtSingleton:
TRegistrationType.Singleton:
begin
if lReg.Instance = nil then
begin
@ -270,7 +273,7 @@ begin
try
if lReg.Instance = nil then
begin
lService := CreateServiceWithDependencies(lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
Supports(lService, lTypeInfo.TypeData.GUID, lReg.Instance)
end;
finally
@ -289,16 +292,29 @@ begin
fBuilt := True;
end;
class constructor TMVCServiceContainer.Create;
begin
fInstance := TMVCServiceContainer.Create;
end;
function DefaultMVCServiceContainer: IMVCServiceContainer;
begin
Result := TMVCServiceContainer.fInstance;
if gDefaultMVCServiceContainer = nil then
begin
TMonitor.Enter(gLock);
try
if gDefaultMVCServiceContainer = nil then
begin
gDefaultMVCServiceContainer := TMVCServiceContainer.Create;
end;
finally
TMonitor.Exit(gLock);
end;
end;
Result := gDefaultMVCServiceContainer;
end;
function NewMVCServiceContainer: IMVCServiceContainer;
begin
Result := TMVCServiceContainer.Create;
end;
{ MVCInjectAttribute }
constructor MVCInjectAttribute.Create(ServiceName: String);
@ -307,4 +323,60 @@ begin
fServiceName := ServiceName;
end;
{ TMVCServiceContainerAdapter }
constructor TMVCServiceContainerAdapter.Create(Container: IMVCServiceContainer);
begin
inherited Create;
fCachedServices := TDictionary<String, IInterface>.Create;
fContainer := Container as IMVCServiceInternalResolver;
end;
destructor TMVCServiceContainerAdapter.Destroy;
begin
fCachedServices.Free;
inherited;
end;
function TMVCServiceContainerAdapter.Resolve(const aTypeInfo: PTypeInfo; const aName: string): IInterface;
var
lKey: string;
lIntf: IInterface;
lRegType: TRegistrationType;
begin
lKey := TMVCServiceContainer.GetKey(aTypeInfo.TypeData.GUID, aName);
if fCachedServices.TryGetValue(lKey, lIntf) then
begin
Supports(lIntf, aTypeInfo.TypeData.GUID, Result);
end
else
begin
Result := fContainer.ResolveEx(Self, aTypeInfo, aName, lKey, lRegType);
if lRegType = TRegistrationType.SingletonPerRequest then
begin
fCachedServices.Add(lKey, Result);
end;
end;
end;
function NewServiceContainerResolver: IMVCServiceContainerResolver;
begin
Result := TMVCServiceContainerAdapter.Create(DefaultMVCServiceContainer);
end;
function NewServiceContainerResolver(Container: IMVCServiceContainer) : IMVCServiceContainerResolver;
begin
Result := TMVCServiceContainerAdapter.Create(Container);
end;
initialization
gLock := TObject.Create;
finalization
gLock.Free;
end.

View File

@ -277,7 +277,7 @@ begin
// select the constructor with the most mumber of parameters
if not Assigned(FControllerCreateAction) then
begin
FControllerInjectableConstructor := TRttiUtils.GetFirstDeclaredConstructor(LRttiType);
FControllerInjectableConstructor := TRttiUtils.GetConstructorWithAttribute<MVCInjectAttribute>(LRttiType);
end;
// end - select the constructor with the most mumber of parameters

View File

@ -533,7 +533,7 @@ type
fWebSession: TMVCWebSession;
fData: TMVCStringDictionary;
fIntfObject: IInterface;
FServiceContainerRef: IMVCServiceContainer;
fServiceContainerResolver: IMVCServiceContainerResolver;
function GetWebSession: TMVCWebSession;
function GetLoggedUser: TUser;
function GetParamsTable: TMVCRequestParamsTable;
@ -550,7 +550,7 @@ type
const ASessionTimeout: Integer): TMVCWebSession;
function GetData: TMVCStringDictionary;
public
constructor Create(const AServiceContainer: IMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse;
constructor Create(const AServiceContainerResolver: IMVCServiceContainerResolver; const ARequest: TWebRequest; const AResponse: TWebResponse;
const AConfig: TMVCConfig; const ASerializers: TDictionary<string, IMVCSerializer>);
destructor Destroy; override;
@ -572,6 +572,7 @@ type
property CustomIntfObject: IInterface read GetIntfObject write SetIntfObject;
property ParamsTable: TMVCRequestParamsTable read GetParamsTable write SetParamsTable;
property ActionQualifiedName: String read fActionQualifiedName;
property ServiceContainerResolver: IMVCServiceContainerResolver read fServiceContainerResolver;
end;
TMVCJSONRPCExceptionErrorInfo = record
@ -981,7 +982,6 @@ type
fSavedOnBeforeDispatch: THTTPMethodEvent;
fOnException: TMVCExceptionHandlerProc;
fOnRouterLog: TMVCRouterLogHandlerProc;
fServiceContainer: IMVCServiceContainer;
fWebContextCreateEvent: TWebContextCreateEvent;
fWebContextDestroyEvent: TWebContextDestroyEvent;
procedure FillActualParamsForAction(const ASelectedController: TMVCController;
@ -1020,7 +1020,10 @@ type
const AResponse: TWebResponse); virtual;
function ExecuteAction(const ASender: TObject; const ARequest: TWebRequest;
const AResponse: TWebResponse): Boolean; virtual;
function CreateControllerWithDependencies(const ControllerClass: TMVCControllerClazz; const ConstructorMethod: TRttiMethod): TMVCController;
function CreateControllerWithDependencies(
const Context: TWebContext;
const ControllerClass: TMVCControllerClazz;
const ConstructorMethod: TRttiMethod): TMVCController;
public
class function GetCurrentSession(const ASessionId: string;
const ARaiseExceptionIfExpired: Boolean = True): TMVCWebSession; static;
@ -1066,7 +1069,6 @@ type
property ApplicationSession: TWebApplicationSession read FApplicationSession
write FApplicationSession;
property OnRouterLog: TMVCRouterLogHandlerProc read fOnRouterLog write fOnRouterLog;
property ServiceContainer: IMVCServiceContainer read fServiceContainer;
end;
[MVCNameCase(ncLowerCase)]
@ -2109,11 +2111,11 @@ begin
raise EMVCException.Create('Session already bounded for this request');
end;
constructor TWebContext.Create(const AServiceContainer: IMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse;
constructor TWebContext.Create(const AServiceContainerResolver: IMVCServiceContainerResolver; const ARequest: TWebRequest; const AResponse: TWebResponse;
const AConfig: TMVCConfig; const ASerializers: TDictionary<string, IMVCSerializer>);
begin
inherited Create;
FServiceContainerRef := AServiceContainer;
FServiceContainerResolver := AServiceContainerResolver;
FIsSessionStarted := False;
FSessionMustBeClose := False;
FWebSession := nil;
@ -2494,7 +2496,6 @@ begin
FSerializers := TDictionary<string, IMVCSerializer>.Create;
FMiddlewares := TList<IMVCMiddleware>.Create;
FControllers := TObjectList<TMVCControllerDelegate>.Create(True);
fServiceContainer := DefaultMVCServiceContainer;
FApplicationSession := nil;
FSavedOnBeforeDispatch := nil;
WebRequestHandler.CacheConnections := True;
@ -2538,7 +2539,9 @@ end;
//end;
//
function TMVCEngine.CreateControllerWithDependencies(const ControllerClass: TMVCControllerClazz;
function TMVCEngine.CreateControllerWithDependencies(
const Context: TWebContext;
const ControllerClass: TMVCControllerClazz;
const ConstructorMethod: TRttiMethod): TMVCController;
var
lActionFormalParams: TArray<TRttiParameter>;
@ -2564,7 +2567,7 @@ begin
begin
raise EMVCException.CreateFmt('Parameter "%s" is not an interface type', [lActionFormalParams[i].ToString]);
end;
lIntf := fServiceContainer.Resolve(lActionFormalParams[I].ParamType.Handle, lServiceName);
lIntf := Context.ServiceContainerResolver.Resolve(lActionFormalParams[I].ParamType.Handle, lServiceName);
Supports(lIntf, lActionFormalParams[I].ParamType.Handle.TypeData.GUID, lOutIntf);
TValue.Make(@lOutIntf, lActionFormalParams[I].ParamType.Handle, lActualParams[I]);
end;
@ -2598,7 +2601,6 @@ begin
fSerializers.Free;
fMiddlewares.Free;
fControllers.Free;
fServiceContainer := nil;
inherited Destroy;
end;
@ -2658,7 +2660,7 @@ begin
{$ENDIF}
lParamsTable := TMVCRequestParamsTable.Create;
try
lContext := TWebContext.Create(fServiceContainer, ARequest, AResponse, FConfig, FSerializers);
lContext := TWebContext.Create(NewServiceContainerResolver, ARequest, AResponse, FConfig, FSerializers);
try
DefineDefaultResponseHeaders(lContext);
DoWebContextCreateEvent(lContext);
@ -2685,7 +2687,10 @@ begin
end
else if lRouter.ControllerInjectableConstructor <> nil then
begin
lSelectedController := CreateControllerWithDependencies(lRouter.ControllerClazz, lRouter.ControllerInjectableConstructor);
lSelectedController := CreateControllerWithDependencies(
lContext,
lRouter.ControllerClazz,
lRouter.ControllerInjectableConstructor);
end
else
begin
@ -3147,7 +3152,7 @@ begin
lInjectAttribute) then
begin
Inc(lAttributeInjectedParamCount, 1);
lIntf := Self.ServiceContainer.Resolve(AActionFormalParams[I].ParamType.Handle, lInjectAttribute.ServiceName);
lIntf := AContext.ServiceContainerResolver.Resolve(AActionFormalParams[I].ParamType.Handle, lInjectAttribute.ServiceName);
Supports(lIntf, AActionFormalParams[I].ParamType.Handle.TypeData.GUID, lOutIntf);
TValue.Make(@lOutIntf, AActionFormalParams[I].ParamType.Handle, AActualParams[I]);
end

View File

@ -77,7 +77,8 @@ uses
ObjectPoolTestU in 'ObjectPoolTestU.pas',
MVCFramework.DotEnv.Parser in '..\..\..\sources\MVCFramework.DotEnv.Parser.pas',
MVCFramework.DotEnv in '..\..\..\sources\MVCFramework.DotEnv.pas',
MVCFramework.Injector in '..\..\..\sources\MVCFramework.Injector.pas';
InjectorTestU in 'InjectorTestU.pas',
MVCFramework.Container in '..\..\..\sources\MVCFramework.Container.pas';
{$R *.RES}

View File

@ -274,7 +274,8 @@
<DCCReference Include="ObjectPoolTestU.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.DotEnv.Parser.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.DotEnv.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.Injector.pas"/>
<DCCReference Include="InjectorTestU.pas"/>
<DCCReference Include="..\..\..\sources\MVCFramework.Container.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>

View File

@ -288,16 +288,6 @@ type
procedure TestInLineComments;
end;
[TestFixture]
TTestInjector = class(TObject)
public
[Test]
procedure TestTransient;
[Test]
procedure TestUnknownService;
end;
implementation
{$WARN SYMBOL_DEPRECATED OFF}
@ -321,7 +311,7 @@ uses
TestServerControllerU, System.Classes,
MVCFramework.DuckTyping, System.IOUtils, MVCFramework.SystemJSONUtils,
IdGlobal, System.TypInfo, System.Types, Winapi.Windows, MVCFramework.DotEnv,
MVCFramework.DotEnv.Parser, MVCFramework.Injector;
MVCFramework.DotEnv.Parser;
var
JWT_SECRET_KEY_TEST: string = 'myk3y';
@ -2405,50 +2395,7 @@ begin
end;
end;
{ TTestInjector }
procedure TTestInjector.TestTransient;
begin
var lCont := TMVCServiceContainer.Create;
try
lCont.RegisterType<TMyService>(IMyInterface1);
lCont.RegisterType<TMyService>(IMyInterface2, '', rtSingleton);
var l0 := lCont.Resolve<IMyInterface1>;
var l1 := lCont.Resolve<IMyInterface1>;
Assert.AreNotEqual(l0, l1);
var l2 := lCont.Resolve<IMyInterface2>;
var l3 := lCont.Resolve<IMyInterface2>;
Assert.AreEqual(l2, l3);
finally
lCont.Free;
end;
end;
procedure TTestInjector.TestUnknownService;
begin
var lCont := TMVCServiceContainer.Create;
try
Assert.WillRaise(
procedure
begin
lCont.RegisterType<TMyService2>(IMyInterface2);
end, TMVCServiceContainer.EMVCUnknownService);
Assert.WillRaise(
procedure
begin
lCont.RegisterType<TTestJWT>(IMyInterface2);
end, TMVCServiceContainer.EMVCUnknownService);
Assert.WillRaise(
procedure
begin
lCont.RegisterType<TMyService2>(IMVCSerializer);
end, TMVCServiceContainer.EMVCUnknownService);
finally
lCont.Free;
end;
end;
initialization
@ -2461,7 +2408,6 @@ TDUnitX.RegisterTestFixture(TTestCryptUtils);
TDUnitX.RegisterTestFixture(TTestLRUCache);
TDUnitX.RegisterTestFixture(TTestDotEnv);
TDUnitX.RegisterTestFixture(TTestDotEnvParser);
TDUnitX.RegisterTestFixture(TTestInjector);
finalization

View File

@ -0,0 +1,254 @@
// ***************************************************************************
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// The Initial Developer of the Original Code is Vivaticket S.p.A. https://www.vivaticket.com/
// The code has been fully donated to the DMVCFramework community without any charge nor rights.
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit InjectorTestU;
interface
uses
DUnitX.TestFramework, MVCFramework.Container,
MVCFramework.Serializer.JsonDataObjects;
type
[TestFixture]
TTestContainer = class
public
[Test]
procedure TestNotBuiltContainer;
[Test]
procedure TestUnknownService;
[Test]
procedure TestTransient;
[Test]
procedure TestSingleton;
[Test]
procedure TestSingletonPerRequest;
[Test]
procedure TestCascadeConstructorInjection;
end;
IServiceA = interface
['{B6C5EAD8-9008-4200-BF33-E3DE5C8A2320}']
end;
IServiceB = interface
['{8418244D-8AEC-4567-A21E-3F4ECD07E227}']
end;
IServiceC = interface
['{A9E5FD77-87FD-4C9C-91BA-79556252DAAD}']
function GetServiceA: IServiceA;
function GetServiceB: IServiceB;
end;
TServiceA = class(TInterfacedObject, IServiceA)
end;
TServiceB = class(TInterfacedObject, IServiceB)
end;
TServiceAB = class(TInterfacedObject, IServiceA, IServiceB)
end;
TServiceC = class(TInterfacedObject, IServiceC)
private
fServiceA: IServiceA;
fServiceB: IServiceB;
protected
function GetServiceA: IServiceA;
function GetServiceB: IServiceB;
public
constructor Create(ServiceA: IServiceA; ServiceB: IServiceB);
end;
implementation
uses
System.Generics.Collections, MVCFramework.IntfObjectPool, System.SysUtils, System.Classes, SyncObjs,
MVCFramework.Serializer.Intf;
{ TTestContainer }
procedure TTestContainer.TestCascadeConstructorInjection;
begin
var lCont := NewMVCServiceContainer;
lCont.RegisterType(TServiceA, IServiceA);
lCont.RegisterType(TServiceB, IServiceB, '', TRegistrationType.SingletonPerRequest);
lCont.RegisterType(TServiceC, IServiceC);
lCont.Build;
// 1° "request"
var lResolver := NewServiceContainerResolver(lCont);
var l0 := lResolver.Resolve(TypeInfo(IServiceC)) as IServiceC;
Assert.IsNotNull(l0.GetServiceA);
Assert.IsNotNull(l0.GetServiceB);
// resolve another "IServiceC" in the same request - ServiceB is rtSingletonPerRequest
var l01 := lResolver.Resolve(TypeInfo(IServiceC)) as IServiceC;
Assert.IsNotNull(l0.GetServiceA);
Assert.IsNotNull(l0.GetServiceB);
Assert.AreNotEqual(l0.GetServiceA, l01.GetServiceA);
Assert.AreEqual(l0.GetServiceB, l01.GetServiceB);
// 2° "request"
lResolver := NewServiceContainerResolver(lCont);
var l1 := lResolver.Resolve(TypeInfo(IServiceC)) as IServiceC;
Assert.IsNotNull(l0.GetServiceA);
Assert.IsNotNull(l0.GetServiceB);
Assert.AreNotEqual(l0.GetServiceA, l1.GetServiceA);
Assert.AreNotEqual(l0.GetServiceB, l1.GetServiceB);
end;
procedure TTestContainer.TestNotBuiltContainer;
begin
var lCont := NewMVCServiceContainer;
lCont.RegisterType(TServiceA, IServiceA);
var lResolver := NewServiceContainerResolver(lCont);
Assert.WillRaise(
procedure
begin
var l0 := lResolver.Resolve(TypeInfo(IServiceA));
end, EMVCContainerError);
end;
procedure TTestContainer.TestSingleton;
begin
var lCont := NewMVCServiceContainer;
lCont.RegisterType(TServiceA, IServiceA, '', TRegistrationType.Singleton);
lCont.RegisterType(TServiceA, IServiceA, 'Svc1', TRegistrationType.Singleton);
lCont.Build;
// 1° Request
var lResolver := NewServiceContainerResolver(lCont);
var l0 := lResolver.Resolve(TypeInfo(IServiceA));
var l1 := lResolver.Resolve(TypeInfo(IServiceA));
Assert.AreEqual(l0, l1);
var l2 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
var l3 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
Assert.AreEqual(l2, l3);
// 2° Request
lResolver := NewServiceContainerResolver(lCont);
var l10 := lResolver.Resolve(TypeInfo(IServiceA));
var l11 := lResolver.Resolve(TypeInfo(IServiceA));
Assert.AreEqual(l10, l11);
Assert.AreEqual(l0, l10);
Assert.AreEqual(l1, l11);
end;
procedure TTestContainer.TestSingletonPerRequest;
begin
var lCont := NewMVCServiceContainer
.RegisterType(TServiceA, IServiceA, '', TRegistrationType.SingletonPerRequest)
.RegisterType(TServiceA, IServiceA, 'Svc1', TRegistrationType.SingletonPerRequest);
lCont.Build;
// 1° "request"
var lResolver := NewServiceContainerResolver(lCont);
var l0 := lResolver.Resolve(TypeInfo(IServiceA));
var l1 := lResolver.Resolve(TypeInfo(IServiceA));
Assert.AreEqual(l0, l1);
var l2 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
var l3 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
Assert.AreEqual(l2, l3);
// 2° "request"
lResolver := NewServiceContainerResolver(lCont);
var l00 := lResolver.Resolve(TypeInfo(IServiceA));
var l10 := lResolver.Resolve(TypeInfo(IServiceA));
Assert.AreEqual(l00, l10);
Assert.AreNotEqual(l0, l00);
Assert.AreNotEqual(l1, l10);
end;
procedure TTestContainer.TestTransient;
begin
var lCont := NewMVCServiceContainer;
lCont.RegisterType(TServiceA, IServiceA);
lCont.RegisterType(TServiceA, IServiceA, 'Svc1');
lCont.Build;
var lResolver := NewServiceContainerResolver(lCont);
var l0 := lResolver.Resolve(TypeInfo(IServiceA));
var l1 := lResolver.Resolve(TypeInfo(IServiceA));
Assert.AreNotEqual(l0, l1);
var l2 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
var l3 := lResolver.Resolve(TypeInfo(IServiceA), 'Svc1');
Assert.AreNotEqual(l2, l3);
end;
procedure TTestContainer.TestUnknownService;
begin
var lCont := NewMVCServiceContainer;
Assert.WillRaise(
procedure
begin
lCont.RegisterType(TServiceA, IServiceB);
end, EMVCContainerErrorUnknownService);
Assert.WillRaise(
procedure
begin
lCont.RegisterType(TMVCJsonDataObjectsSerializer, IServiceB);
end, EMVCContainerErrorUnknownService);
Assert.WillRaise(
procedure
begin
lCont.RegisterType(TServiceA, IMVCSerializer);
end, EMVCContainerErrorUnknownService);
end;
{ TServiceC }
constructor TServiceC.Create(ServiceA: IServiceA; ServiceB: IServiceB);
begin
inherited Create;
fServiceA := ServiceA;
fServiceB := ServiceB;
end;
function TServiceC.GetServiceA: IServiceA;
begin
Result := fServiceA;
end;
function TServiceC.GetServiceB: IServiceB;
begin
Result := fServiceB;
end;
initialization
TDUnitX.RegisterTestFixture(TTestContainer);
end.