From b592963a78324546f9b53de7b5620e6e907603ba Mon Sep 17 00:00:00 2001 From: Daniele Teti Date: Mon, 25 Mar 2024 00:15:50 +0100 Subject: [PATCH] ServiceContainer (wip) --- .../services_injection/MainControllerU.pas | 5 +- .../Services.ConnectionU.pas | 40 +++++ .../Services.InterfacesU.pas | 5 + .../services_injection/Services.PeopleU.pas | 13 +- samples/services_injection/WebModuleU.pas | 2 - .../services_injection.delphilsp.json | 1 - .../services_injection/services_injection.dpr | 7 +- .../services_injection.dproj | 1 + sources/MVCFramework.Container.pas | 150 +++++++++++++++--- sources/MVCFramework.Router.pas | 12 +- sources/MVCFramework.Rtti.Utils.pas | 18 +++ sources/MVCFramework.pas | 18 +-- 12 files changed, 217 insertions(+), 55 deletions(-) create mode 100644 samples/services_injection/Services.ConnectionU.pas delete mode 100644 samples/services_injection/services_injection.delphilsp.json diff --git a/samples/services_injection/MainControllerU.pas b/samples/services_injection/MainControllerU.pas index b0922780..5f79b22a 100644 --- a/samples/services_injection/MainControllerU.pas +++ b/samples/services_injection/MainControllerU.pas @@ -3,8 +3,9 @@ unit MainControllerU; interface uses - MVCFramework, MVCFramework.Commons, MVCFramework.Serializer.Commons, System.Generics.Collections, Services.InterfacesU, - Entities; + MVCFramework, MVCFramework.Commons, MVCFramework.Serializer.Commons, + System.Generics.Collections, Services.InterfacesU, + Entities, MVCFramework.Container; type [MVCPath('/api')] diff --git a/samples/services_injection/Services.ConnectionU.pas b/samples/services_injection/Services.ConnectionU.pas new file mode 100644 index 00000000..30da5811 --- /dev/null +++ b/samples/services_injection/Services.ConnectionU.pas @@ -0,0 +1,40 @@ +unit Services.ConnectionU; + +interface + +uses Services.InterfacesU; + +type + TConnectionService = class(TInterfacedObject, IConnectionService) + protected + function GetConnectionName: string; + public + constructor Create; virtual; + destructor Destroy; override; + end; + +implementation + +uses + MVCFramework.Logger; + +{ TConnectionService } + +constructor TConnectionService.Create; +begin + inherited; + LogI('TConnectionService.Create'); +end; + +destructor TConnectionService.Destroy; +begin + LogI('TConnectionService.Destroy'); + inherited; +end; + +function TConnectionService.GetConnectionName: string; +begin + Result := 'MyDemoConnection'; +end; + +end. diff --git a/samples/services_injection/Services.InterfacesU.pas b/samples/services_injection/Services.InterfacesU.pas index 135b068b..0762265e 100644 --- a/samples/services_injection/Services.InterfacesU.pas +++ b/samples/services_injection/Services.InterfacesU.pas @@ -11,6 +11,11 @@ type function GetAll: TObjectList; end; + IConnectionService = interface + ['{146C21A5-07E8-456D-8E6D-A72820BD17AA}'] + function GetConnectionName: String; + end; + implementation end. diff --git a/samples/services_injection/Services.PeopleU.pas b/samples/services_injection/Services.PeopleU.pas index 2d0dd2f9..c7301fad 100644 --- a/samples/services_injection/Services.PeopleU.pas +++ b/samples/services_injection/Services.PeopleU.pas @@ -3,12 +3,16 @@ unit Services.PeopleU; interface uses - System.Generics.Collections, Entities, Services.InterfacesU, MVCFramework.Logger; + System.Generics.Collections, Entities, Services.InterfacesU, MVCFramework.Logger, + MVCFramework; type TPeopleService = class(TInterfacedObject, IPeopleService) + private + fConnService: IConnectionService; public - constructor Create; virtual; + [MVCInject] + constructor Create(ConnectionService: IConnectionService); virtual; destructor Destroy; override; function GetAll: TObjectList; end; @@ -17,9 +21,10 @@ implementation { TPeopleService } -constructor TPeopleService.Create; +constructor TPeopleService.Create(ConnectionService: IConnectionService); begin - inherited; + inherited Create; + fConnService := ConnectionService; LogI('Service ' + ClassName + ' created'); end; diff --git a/samples/services_injection/WebModuleU.pas b/samples/services_injection/WebModuleU.pas index 7df02925..30c900e0 100644 --- a/samples/services_injection/WebModuleU.pas +++ b/samples/services_injection/WebModuleU.pas @@ -70,10 +70,8 @@ begin Config[TMVCConfigKey.MaxRequestSize] := dotEnv.Env('dmvc.max_request_size', IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE)); end); FMVC.AddController(TMyController); - FMVC.ServiceContainer.RegisterType(IPeopleService); - // Analytics middleware generates a csv log, useful to do traffic analysis //FMVC.AddMiddleware(TMVCAnalyticsMiddleware.Create(GetAnalyticsDefaultLogger)); diff --git a/samples/services_injection/services_injection.delphilsp.json b/samples/services_injection/services_injection.delphilsp.json deleted file mode 100644 index 191aab3e..00000000 --- a/samples/services_injection/services_injection.delphilsp.json +++ /dev/null @@ -1 +0,0 @@ -{ "settings": { "project": "file:///C%3A/DEV/dmvcframework/samples/services_injection/services_injection.dpr", "dllname": "dcc32290.dll", "dccOptions": "-$O- -$W+ -$R+ -$Q+ --no-config -Q -TX.exe -AGenerics.Collections=System.Generics.Collections;Generics.Defaults=System.Generics.Defaults;WinTypes=Winapi.Windows;WinProcs=Winapi.Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE -DDEBUG;;FRAMEWORK_VCL -E.\\Win32\\Debug -I\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\debug\";C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\release\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win32\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\DEV\\dmvcframework\\sources;C:\\DEV\\dmvcframework\\lib\\dmustache;C:\\DEV\\dmvcframework\\lib\\loggerpro;C:\\DEV\\dmvcframework\\lib\\swagdoc\\source;C:\\DEV\\dmvcframework\\contrib;\"C:\\Users\\Daniele Teti\\AppData\\Local\\Programs\\TestInsight\\Source\";C:\\DLib\\smcmpnt\\SOURCES;C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework\\contrib;C:\\DLib\\sempare-delphi-template-engine\\src;C:\\DEV\\dmvcframework\\lib\\crosssocket;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Net;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Utils;C:\\DEV\\loggerpro -LEC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Bpl -LNC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp -NU.\\Win32\\Debug -NSWinapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell; -OC:\\DEV\\dmvcframework;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\release\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win32\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\DEV\\dmvcframework\\sources;C:\\DEV\\dmvcframework\\lib\\dmustache;C:\\DEV\\dmvcframework\\lib\\loggerpro;C:\\DEV\\dmvcframework\\lib\\swagdoc\\source;C:\\DEV\\dmvcframework\\contrib;\"C:\\Users\\Daniele Teti\\AppData\\Local\\Programs\\TestInsight\\Source\";C:\\DLib\\smcmpnt\\SOURCES;C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework\\contrib;C:\\DLib\\sempare-delphi-template-engine\\src;C:\\DEV\\dmvcframework\\lib\\crosssocket;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Net;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Utils;C:\\DEV\\loggerpro -RC:\\DEV\\dmvcframework;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\release\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win32\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\DEV\\dmvcframework\\sources;C:\\DEV\\dmvcframework\\lib\\dmustache;C:\\DEV\\dmvcframework\\lib\\loggerpro;C:\\DEV\\dmvcframework\\lib\\swagdoc\\source;C:\\DEV\\dmvcframework\\contrib;\"C:\\Users\\Daniele Teti\\AppData\\Local\\Programs\\TestInsight\\Source\";C:\\DLib\\smcmpnt\\SOURCES;C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework\\contrib;C:\\DLib\\sempare-delphi-template-engine\\src;C:\\DEV\\dmvcframework\\lib\\crosssocket;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Net;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Utils;C:\\DEV\\loggerpro -U\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\debug\";C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\lib\\Win32\\release\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\";\"C:\\Users\\Daniele Teti\\Documents\\Embarcadero\\Studio\\23.0\\Imports\\Win32\";\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\Imports\";C:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp;\"c:\\program files (x86)\\embarcadero\\studio\\23.0\\include\";C:\\DEV\\dmvcframework\\sources;C:\\DEV\\dmvcframework\\lib\\dmustache;C:\\DEV\\dmvcframework\\lib\\loggerpro;C:\\DEV\\dmvcframework\\lib\\swagdoc\\source;C:\\DEV\\dmvcframework\\contrib;\"C:\\Users\\Daniele Teti\\AppData\\Local\\Programs\\TestInsight\\Source\";C:\\DLib\\smcmpnt\\SOURCES;C:\\DLib\\spring4d\\Library\\Delphi12\\Win32\\Debug;C:\\DEV\\dmvcframework\\contrib;C:\\DLib\\sempare-delphi-template-engine\\src;C:\\DEV\\dmvcframework\\lib\\crosssocket;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Net;C:\\DEV\\dmvcframework\\lib\\crosssocket\\Utils;C:\\DEV\\loggerpro -V -VN -NBC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\Dcp -NHC:\\Users\\Public\\Documents\\Embarcadero\\Studio\\23.0\\hpp\\Win32 -NO.\\Win32\\Debug -LU" , "projectFiles":[ { "name": "MainControllerU", "file": "file:///C%3A/DEV/dmvcframework/samples/services_injection/MainControllerU.pas" }, { "name": "WebModuleU", "file": "file:///C%3A/DEV/dmvcframework/samples/services_injection/WebModuleU.pas" }, { "name": "Services.PeopleU", "file": "file:///C%3A/DEV/dmvcframework/samples/services_injection/Services.PeopleU.pas" }, { "name": "Services.InterfacesU", "file": "file:///C%3A/DEV/dmvcframework/samples/services_injection/Services.InterfacesU.pas" }, { "name": "Entities", "file": "file:///C%3A/DEV/dmvcframework/samples/commons/Entities.pas" }, { "name": "MVCFramework.Router", "file": "file:///C%3A/DEV/dmvcframework/sources/MVCFramework.Router.pas" }, { "name": "MVCFramework.Injector", "file": "file:///C%3A/DEV/dmvcframework/sources/MVCFramework.Injector.pas" } ] , "includeDCUsInUsesCompletion": true, "enableKeyWordCompletion": true, "browsingPaths": [ "file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/OCX/Servers","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/VCL","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/RTL/SYS","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/win","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/win/winrt","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/ToolsAPI","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/IBX","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Internet","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/PROPERTY%20EDITORS","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/soap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/SOURCE/XML","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/Core","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/System","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Indy10/Protocols","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/fmx","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/components","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/engine","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/databinding/graph","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/ado","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/cloud","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/dbx","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/dsnap","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/vclctrls","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap/connectors","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/datasnap/proxygen","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DataExplorer","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/Common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/Common/dunit","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/Common","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/DUnitProject","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/DUnitWizard/Source/DelphiExperts/DUnitProject/dunit","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/src","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/tests","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Experts","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indy/abstraction","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indy/implementation","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/indyimpl","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Property%20Editors/Indy10","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/soap/wsdlimporter","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/Visualizers","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/XMLReporting","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnit/Contrib/XPGen","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/rest","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/firedac","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/tethering","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/DUnitX","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/data/ems","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/rtl/net","file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/source/FlatBox2D","file:///C%3A/DLib/spring4d/Source","file:///C%3A/DLib/spring4d/Source/Base","file:///C%3A/DLib/spring4d/Source/Base/Collections","file:///C%3A/DLib/spring4d/Source/Base/Logging","file:///C%3A/DLib/spring4d/Source/Base/Patches","file:///C%3A/DLib/spring4d/Source/Core/Container","file:///C%3A/DLib/spring4d/Source/Core/Interception","file:///C%3A/DLib/spring4d/Source/Core/Logging","file:///C%3A/DLib/spring4d/Source/Core/Mocking","file:///C%3A/DLib/spring4d/Source/Core/Services","file:///C%3A/DLib/spring4d/Source/Extensions/Cryptography","file:///C%3A/DLib/spring4d/Source/Extensions/Utils" ] , "CommonAppData": "file:///C%3A/Users/Daniele%20Teti/AppData/Roaming/Embarcadero/BDS/23.0/" , "Templates": "file:///c%3A/program%20files%20%28x86%29/embarcadero/studio/23.0/ObjRepos/" } } \ No newline at end of file diff --git a/samples/services_injection/services_injection.dpr b/samples/services_injection/services_injection.dpr index 76524d29..029f2ece 100644 --- a/samples/services_injection/services_injection.dpr +++ b/samples/services_injection/services_injection.dpr @@ -20,7 +20,8 @@ uses Services.InterfacesU in 'Services.InterfacesU.pas', Entities in '..\commons\Entities.pas', MVCFramework.Router in '..\..\sources\MVCFramework.Router.pas', - MVCFramework.Container in '..\..\sources\MVCFramework.Container.pas'; + MVCFramework.Container in '..\..\sources\MVCFramework.Container.pas', + Services.ConnectionU in 'Services.ConnectionU.pas'; {$R *.res} @@ -79,6 +80,10 @@ begin .Build(); //uses the executable folder to look for .env* files end); + DefaultServiceContainer.RegisterType(TPeopleService, IPeopleService); + DefaultServiceContainer.RegisterType(TConnectionService, IConnectionService); + DefaultServiceContainer.Build(); + WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024); if dotEnv.Env('dmvc.profiler.enabled', false) then diff --git a/samples/services_injection/services_injection.dproj b/samples/services_injection/services_injection.dproj index 7eb83449..30d21c9f 100644 --- a/samples/services_injection/services_injection.dproj +++ b/samples/services_injection/services_injection.dproj @@ -125,6 +125,7 @@ + Base diff --git a/sources/MVCFramework.Container.pas b/sources/MVCFramework.Container.pas index d14de0e9..c34a0de0 100644 --- a/sources/MVCFramework.Container.pas +++ b/sources/MVCFramework.Container.pas @@ -5,27 +5,57 @@ interface uses System.Generics.Collections, System.Rtti, System.SysUtils, System.TypInfo; + type - TMVCServiceContainer = class - private - type - TRegistrationType = (rtTransient, rtSingleton {, rtSingletonPerThread}); - TRegistration = class - Intf: TGUID; - Clazz: TClass; - Instance: IInterface; - RegistrationType: TRegistrationType; - end; + TClassOfInterfacedObject = class of TInterfacedObject; + TRegistrationType = (rtTransient, rtSingleton {, rtSingletonPerThread}); + 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 Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''; const aParams: TArray = nil): IInterface; + procedure Build(); + end; + + MVCInjectAttribute = class(TCustomAttribute) + + end; + + + + function DefaultServiceContainer: IMVCServiceContainer; + +implementation + +uses + MVCFramework.Rtti.Utils; + +type + TRegistration = class + Intf: TGUID; + Clazz: TClassOfInterfacedObject; + Instance: IInterface; + RegistrationType: TRegistrationType; + end; + TMVCServiceContainer = class(TInterfacedObject, IMVCServiceContainer) private + fBuilt: Boolean; fRegistry: TObjectDictionary; + function CreateServiceWithDependencies(const ServiceClass: TClassOfInterfacedObject; + const ConstructorMethod: TRttiMethod): TInterfacedObject; protected function GetKey(const aGUID: TGUID; const aName: String): String; - public constructor Create; virtual; destructor Destroy; override; - procedure RegisterType(const aInterface: TGUID; const aName : string = ''; const aRegType: TRegistrationType = rtTransient); + class var fInstance: IMVCServiceContainer; + public + class function Instance: IMVCServiceContainer; + class constructor Create; + class destructor Destroy; + function RegisterType(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(const aName: string = ''; const aParams: TArray = nil): TIntf; overload; function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''; const aParams: TArray = nil): IInterface; overload; + procedure Build(); type EMVCContainerError = class(Exception) end; EMVCUnknownService = class(EMVCContainerError) end; @@ -33,17 +63,50 @@ type EMVCUnknownConstructor = class(EMVCContainerError) end; end; -implementation - -uses - MVCFramework.Rtti.Utils; - { TMVCServiceContainer } +function TMVCServiceContainer.CreateServiceWithDependencies(const ServiceClass: TClassOfInterfacedObject; + const ConstructorMethod: TRttiMethod): TInterfacedObject; +var + lActionFormalParams: TArray; + lActualParams: TArray; + I: Integer; + lIntf, lOutIntf: IInterface; +begin + if ConstructorMethod <> nil then + begin + lActionFormalParams := ConstructorMethod.GetParameters; + SetLength(lActualParams, Length(lActionFormalParams)); + if Length(lActionFormalParams) > 0 then + begin + for I := 0 to Length(lActionFormalParams) - 1 do + begin + lIntf := 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', [ + lActionFormalParams[I].name, + lActionFormalParams[I].ParamType.ToString, + ServiceClass.ClassName + ]); + end; + TValue.Make(@lOutIntf, lActionFormalParams[I].ParamType.Handle, lActualParams[I]); + end; + end; + Result := TInterfacedObject(ConstructorMethod.Invoke(ServiceClass, lActualParams).AsObject); + end + else + begin + Result := TInterfacedObject(TRttiUtils.CreateObject(ServiceClass.QualifiedClassName)); + end; +end; + + constructor TMVCServiceContainer.Create; begin inherited; fRegistry := TObjectDictionary.Create([doOwnsValues]); + fBuilt := False; end; destructor TMVCServiceContainer.Destroy; @@ -52,21 +115,36 @@ begin inherited; end; +class destructor TMVCServiceContainer.Destroy; +begin + fInstance := nil; +end; + function TMVCServiceContainer.GetKey(const aGUID: TGUID; const aName: String): String; begin Result := aGUID.ToString + '_' + aName; end; -procedure TMVCServiceContainer.RegisterType(const aInterface: TGUID; const aName: string; const aRegType: TRegistrationType); +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 lType: TRttiType; lReg: TRegistration; begin - lType := TRttiUtils.GlContext.GetType(TImpl); - if Supports(TImpl, aInterface) then + if fBuilt then + begin + raise EMVCContainerError.Create('Cannot register new service if the container has been already built'); + end; + lType := TRttiUtils.GlContext.GetType(aImplementation); + if Supports(aImplementation, aInterface) then begin lReg := TRegistration.Create; - lReg.Clazz := TImpl; + lReg.Clazz := aImplementation; lReg.RegistrationType := aRegType; fRegistry.Add(GetKey(aInterface, aName), lReg); end @@ -74,6 +152,12 @@ begin begin raise EMVCUnknownService.Create(lType.Name + ' doesn''t supports requested interface'); end; + Result := Self; +end; + +function TMVCServiceContainer.RegisterType(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; @@ -84,6 +168,10 @@ var lType: TRttiType; lService: TObject; begin + if not fBuilt then + begin + raise EMVCContainerError.Create('Cannot resolve service if the container has not been built'); + end; lTypeInfo := aTypeInfo; if not fRegistry.TryGetValue(GetKey(lTypeInfo.TypeData.GUID, aName), lReg) then begin @@ -94,7 +182,8 @@ begin case lReg.RegistrationType of rtTransient: begin - lService := TRttiUtils.CreateObject(lType, AParams); + lService := CreateServiceWithDependencies(lReg.Clazz, TRttiUtils.GetConstructorWithAttribute(lType)); + //lService := TRttiUtils.CreateObject(lType, AParams); Supports(lService, lTypeInfo.TypeData.GUID, Result); end; @@ -118,8 +207,6 @@ begin else raise EMVCContainerError.Create('Unsupported RegistrationType'); end; - - end; function TMVCServiceContainer.Resolve(const aName: string; const aParams: TArray): TIntf; @@ -127,4 +214,19 @@ begin Result := Resolve(TypeInfo(TIntf), aName, aParams); end; +procedure TMVCServiceContainer.Build; +begin + fBuilt := True; +end; + +class constructor TMVCServiceContainer.Create; +begin + fInstance := TMVCServiceContainer.Create; +end; + +function DefaultServiceContainer: IMVCServiceContainer; +begin + Result := TMVCServiceContainer.fInstance; +end; + end. diff --git a/sources/MVCFramework.Router.pas b/sources/MVCFramework.Router.pas index 051eeef1..2686207f 100644 --- a/sources/MVCFramework.Router.pas +++ b/sources/MVCFramework.Router.pas @@ -114,7 +114,7 @@ implementation uses System.TypInfo, - System.NetEncoding; + System.NetEncoding, MVCFramework.Rtti.Utils, MVCFramework.Container; { TMVCRouter } @@ -277,15 +277,7 @@ begin // select the constructor with MVCInjectAttribute - Constructor must be called "Create" if not Assigned(FControllerCreateAction) then begin - lConstructors := LRttiType.GetMethods('Create'); - for lConstructor in lConstructors do - begin - if lConstructor.HasAttribute then - begin - FControllerInjectableConstructor := lConstructor; - break; { the first wins } - end; - end; + FControllerInjectableConstructor := TRttiUtils.GetConstructorWithAttribute(LRttiType); end; // end - select the constructor with MVCInjectAttribute diff --git a/sources/MVCFramework.Rtti.Utils.pas b/sources/MVCFramework.Rtti.Utils.pas index 2b36c5a2..79591778 100644 --- a/sources/MVCFramework.Rtti.Utils.pas +++ b/sources/MVCFramework.Rtti.Utils.pas @@ -90,6 +90,7 @@ type class function FindType(AQualifiedName: string): TRttiType; class function GetGUID: TGUID; class function GetArrayContainedRTTIType(const RTTIType: TRttiType): TRttiType; + class function GetConstructorWithAttribute(const RTTIType: TRttiType): TRttiMethod; end; {$IF not defined(BERLINORBETTER)} @@ -180,6 +181,23 @@ begin end; end; +class function TRttiUtils.GetConstructorWithAttribute(const RTTIType: TRttiType): TRttiMethod; +var + lConstructors: TArray; + lConstructor: TRttiMethod; +begin + Result := nil; + lConstructors := RttiType.GetMethods('Create'); + for lConstructor in lConstructors do + begin + if lConstructor.HasAttribute then + begin + Result := lConstructor; + break; { the first wins } + end; + end; +end; + class function TRttiUtils.GetField(AObject: TObject; const APropertyName: string): TValue; var Field: TRttiField; diff --git a/sources/MVCFramework.pas b/sources/MVCFramework.pas index fa757a69..3bc5195a 100644 --- a/sources/MVCFramework.pas +++ b/sources/MVCFramework.pas @@ -236,10 +236,6 @@ type property ResponseClass: TClass read FResponseClass; end; - MVCInjectAttribute = class(MVCBaseAttribute) - - end; - MVCResponseListAttribute = class(MVCBaseAttribute) private FStatusCode: Integer; @@ -537,7 +533,7 @@ type fWebSession: TMVCWebSession; fData: TMVCStringDictionary; fIntfObject: IInterface; - FServiceContainerRef: TMVCServiceContainer; + FServiceContainerRef: IMVCServiceContainer; function GetWebSession: TMVCWebSession; function GetLoggedUser: TUser; function GetParamsTable: TMVCRequestParamsTable; @@ -554,7 +550,7 @@ type const ASessionTimeout: Integer): TMVCWebSession; function GetData: TMVCStringDictionary; public - constructor Create(const AServiceContainer: TMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse; + constructor Create(const AServiceContainer: IMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse; const AConfig: TMVCConfig; const ASerializers: TDictionary); destructor Destroy; override; @@ -985,7 +981,7 @@ type fSavedOnBeforeDispatch: THTTPMethodEvent; fOnException: TMVCExceptionHandlerProc; fOnRouterLog: TMVCRouterLogHandlerProc; - fServiceContainer: TMVCServiceContainer; + fServiceContainer: IMVCServiceContainer; fWebContextCreateEvent: TWebContextCreateEvent; fWebContextDestroyEvent: TWebContextDestroyEvent; procedure FillActualParamsForAction(const ASelectedController: TMVCController; @@ -1070,7 +1066,7 @@ type property ApplicationSession: TWebApplicationSession read FApplicationSession write FApplicationSession; property OnRouterLog: TMVCRouterLogHandlerProc read fOnRouterLog write fOnRouterLog; - property ServiceContainer: TMVCServiceContainer read fServiceContainer; + property ServiceContainer: IMVCServiceContainer read fServiceContainer; end; [MVCNameCase(ncLowerCase)] @@ -2113,7 +2109,7 @@ begin raise EMVCException.Create('Session already bounded for this request'); end; -constructor TWebContext.Create(const AServiceContainer: TMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse; +constructor TWebContext.Create(const AServiceContainer: IMVCServiceContainer; const ARequest: TWebRequest; const AResponse: TWebResponse; const AConfig: TMVCConfig; const ASerializers: TDictionary); begin inherited Create; @@ -2498,7 +2494,7 @@ begin FSerializers := TDictionary.Create; FMiddlewares := TList.Create; FControllers := TObjectList.Create(True); - fServiceContainer := TMVCServiceContainer.Create; + fServiceContainer := DefaultServiceContainer; FApplicationSession := nil; FSavedOnBeforeDispatch := nil; WebRequestHandler.CacheConnections := True; @@ -2597,7 +2593,7 @@ begin fSerializers.Free; fMiddlewares.Free; fControllers.Free; - fServiceContainer.Free; + fServiceContainer := nil; inherited Destroy; end;