mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
ServiceContainer (wip)
This commit is contained in:
parent
8aa4ab5f7b
commit
b592963a78
@ -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')]
|
||||
|
40
samples/services_injection/Services.ConnectionU.pas
Normal file
40
samples/services_injection/Services.ConnectionU.pas
Normal 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.
|
@ -11,6 +11,11 @@ type
|
||||
function GetAll: TObjectList<TPerson>;
|
||||
end;
|
||||
|
||||
IConnectionService = interface
|
||||
['{146C21A5-07E8-456D-8E6D-A72820BD17AA}']
|
||||
function GetConnectionName: String;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user