mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
Container (WIP) - almost ready to be merged into master
This commit is contained in:
parent
1920249eb9
commit
6142c2d929
@ -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;
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
21
samples/services_injection/Services.RegistrationU.pas
Normal file
21
samples/services_injection/Services.RegistrationU.pas
Normal 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.
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
254
unittests/general/Several/InjectorTestU.pas
Normal file
254
unittests/general/Several/InjectorTestU.pas
Normal 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.
|
Loading…
Reference in New Issue
Block a user