ServiceContainer (wip)

This commit is contained in:
Daniele Teti 2024-03-25 00:15:50 +01:00
parent 8aa4ab5f7b
commit b592963a78
12 changed files with 217 additions and 55 deletions

View File

@ -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')]

View File

@ -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.

View File

@ -11,6 +11,11 @@ type
function GetAll: TObjectList<TPerson>;
end;
IConnectionService = interface
['{146C21A5-07E8-456D-8E6D-A72820BD17AA}']
function GetConnectionName: String;
end;
implementation
end.

View File

@ -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<TPerson>;
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;

View File

@ -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<TPeopleService>(IPeopleService);
// Analytics middleware generates a csv log, useful to do traffic analysis
//FMVC.AddMiddleware(TMVCAnalyticsMiddleware.Create(GetAnalyticsDefaultLogger));

File diff suppressed because one or more lines are too long

View File

@ -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

View File

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

View File

@ -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<TValue> = 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<string, TRegistration>;
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<TImpl: class>(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<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 = ''; const aParams: TArray<TValue> = nil): TIntf; overload;
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''; const aParams: TArray<TValue> = 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<TRttiParameter>;
lActualParams: TArray<TValue>;
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<String, TRegistration>.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<TImpl>(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<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;
@ -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<MVCInjectAttribute>(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<TIntf>(const aName: string; const aParams: TArray<TValue>): 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.

View File

@ -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<MVCInjectAttribute> then
begin
FControllerInjectableConstructor := lConstructor;
break; { the first wins }
end;
end;
FControllerInjectableConstructor := TRttiUtils.GetConstructorWithAttribute<MVCInjectAttribute>(LRttiType);
end;
// end - select the constructor with MVCInjectAttribute

View File

@ -90,6 +90,7 @@ type
class function FindType(AQualifiedName: string): TRttiType;
class function GetGUID<T>: TGUID;
class function GetArrayContainedRTTIType(const RTTIType: TRttiType): TRttiType;
class function GetConstructorWithAttribute<T:TCustomAttribute>(const RTTIType: TRttiType): TRttiMethod;
end;
{$IF not defined(BERLINORBETTER)}
@ -180,6 +181,23 @@ begin
end;
end;
class function TRttiUtils.GetConstructorWithAttribute<T>(const RTTIType: TRttiType): TRttiMethod;
var
lConstructors: TArray<TRttiMethod>;
lConstructor: TRttiMethod;
begin
Result := nil;
lConstructors := RttiType.GetMethods('Create');
for lConstructor in lConstructors do
begin
if lConstructor.HasAttribute<T> then
begin
Result := lConstructor;
break; { the first wins }
end;
end;
end;
class function TRttiUtils.GetField(AObject: TObject; const APropertyName: string): TValue;
var
Field: TRttiField;

View File

@ -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<string, IMVCSerializer>);
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<string, IMVCSerializer>);
begin
inherited Create;
@ -2498,7 +2494,7 @@ begin
FSerializers := TDictionary<string, IMVCSerializer>.Create;
FMiddlewares := TList<IMVCMiddleware>.Create;
FControllers := TObjectList<TMVCControllerDelegate>.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;