delphimvcframework/sources/MVCFramework.Container.pas

453 lines
15 KiB
ObjectPascal
Raw Normal View History

unit MVCFramework.Container;
2024-03-21 18:57:41 +01:00
2024-04-17 09:57:22 +02:00
{$I dmvcframework.inc}
2024-03-21 18:57:41 +01:00
interface
uses
System.Generics.Collections, System.Rtti, System.SysUtils, System.TypInfo;
2024-03-21 18:57:41 +01:00
2024-03-25 00:15:50 +01:00
2024-03-21 18:57:41 +01:00
type
{$SCOPEDENUMS ON}
TRegistrationType = (Transient, Singleton, SingletonPerRequest);
2024-03-25 00:15:50 +01:00
TClassOfInterfacedObject = class of TInterfacedObject;
TInterfacedObjectFactory = reference to function: TInterfacedObject;
IMVCServiceContainerResolver = interface
2024-03-28 16:31:44 +01:00
['{2C920EC2-001F-40BE-9911-43A65077CADD}']
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
2024-03-28 16:31:44 +01:00
end;
IMVCServiceContainer = interface
['{1BB3F4A8-DDA1-4526-981C-A0BF877CFFD5}']
2024-04-17 09:57:22 +02:00
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aRegType: TRegistrationType = TRegistrationType.Transient; const aName : string = ''): IMVCServiceContainer; overload;
function RegisterType(const aDelegate: TInterfacedObjectFactory; const aInterface: TGUID; const aRegType: TRegistrationType = TRegistrationType.Transient; const aName : string = ''): IMVCServiceContainer; overload;
procedure Build();
end;
2024-03-28 16:31:44 +01:00
2024-03-27 00:10:48 +01:00
EMVCContainerError = class(Exception) end;
EMVCContainerErrorUnknownService = class(EMVCContainerError) end;
EMVCContainerErrorInterfaceNotSupported = class(EMVCContainerError) end;
EMVCContainerErrorUnknownConstructor = class(EMVCContainerError) end;
2024-03-25 00:15:50 +01:00
2024-03-28 16:31:44 +01:00
function DefaultMVCServiceContainer: IMVCServiceContainer;
function NewMVCServiceContainer: IMVCServiceContainer;
function NewServiceContainerResolver: IMVCServiceContainerResolver; overload;
function NewServiceContainerResolver(Container: IMVCServiceContainer): IMVCServiceContainerResolver; overload;
2024-03-25 00:15:50 +01:00
implementation
uses
MVCFramework.Rtti.Utils, MVCFramework;
2024-03-25 00:15:50 +01:00
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;
2024-03-25 00:15:50 +01:00
TRegistration = class
2024-03-31 18:09:13 +02:00
public
2024-03-25 00:15:50 +01:00
Intf: TGUID;
Clazz: TClassOfInterfacedObject;
RttiType: TRttiType;
2024-03-25 00:15:50 +01:00
Instance: IInterface;
Delegate: TInterfacedObjectFactory;
2024-03-25 00:15:50 +01:00
RegistrationType: TRegistrationType;
end;
TMVCServiceContainer = class(TInterfacedObject, IMVCServiceContainer, IMVCServiceInternalResolver)
2024-03-21 18:57:41 +01:00
private
2024-03-25 00:15:50 +01:00
fBuilt: Boolean;
2024-03-21 18:57:41 +01:00
fRegistry: TObjectDictionary<string, TRegistration>;
function CreateServiceWithDependencies(
const ServiceContainerResolver: IMVCServiceContainerResolver;
const ServiceClass: TClassOfInterfacedObject;
2024-03-25 00:15:50 +01:00
const ConstructorMethod: TRttiMethod): TInterfacedObject;
2024-03-21 18:57:41 +01:00
protected
2024-03-28 16:31:44 +01:00
class function GetKey(const aGUID: TGUID; const aName: String): String;
2024-03-21 18:57:41 +01:00
constructor Create; virtual;
destructor Destroy; override;
procedure CheckBuilt;
2024-03-25 00:15:50 +01:00
public
2024-04-17 09:57:22 +02:00
function RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID; const aRegType: TRegistrationType = TRegistrationType.Transient; const aName : string = ''): IMVCServiceContainer; overload;
function RegisterType(const aDelegate: TInterfacedObjectFactory; const aInterface: TGUID; const aRegType: TRegistrationType = TRegistrationType.Transient; const aName : string = ''): 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;
2024-03-25 00:15:50 +01:00
procedure Build();
2024-03-21 18:57:41 +01:00
end;
TMVCServiceContainerAdapter = class(TInterfacedObject, IMVCServiceContainerResolver)
private
fCachedServices: TDictionary<String, IInterface>;
fContainer: IMVCServiceInternalResolver;
2024-03-28 16:31:44 +01:00
protected
function Resolve(const aTypeInfo: PTypeInfo; const aName: string = ''): IInterface; overload;
public
constructor Create(Container: IMVCServiceContainer);
destructor Destroy; override;
2024-03-28 16:31:44 +01:00
end;
var
gDefaultMVCServiceContainer: IMVCServiceContainer = nil;
gLock: TObject = nil;
2024-03-25 00:15:50 +01:00
{ TMVCServiceContainer }
2024-03-21 18:57:41 +01:00
function TMVCServiceContainer.CreateServiceWithDependencies(
const ServiceContainerResolver: IMVCServiceContainerResolver;
const ServiceClass: TClassOfInterfacedObject;
2024-03-25 00:15:50 +01:00
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
if ServiceContainerResolver = nil then
lIntf := Resolve(nil, lActionFormalParams[I].ParamType.Handle)
else
lIntf := ServiceContainerResolver.Resolve(lActionFormalParams[I].ParamType.Handle);
2024-03-25 00:15:50 +01:00
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;
2024-03-21 18:57:41 +01:00
procedure TMVCServiceContainer.CheckBuilt;
begin
if fBuilt then
begin
raise EMVCContainerError.Create('Cannot register new service if the container has been already built');
end;
end;
2024-03-21 18:57:41 +01:00
constructor TMVCServiceContainer.Create;
begin
inherited;
fRegistry := TObjectDictionary<String, TRegistration>.Create([doOwnsValues]);
2024-03-25 00:15:50 +01:00
fBuilt := False;
2024-03-21 18:57:41 +01:00
end;
destructor TMVCServiceContainer.Destroy;
begin
fRegistry.Free;
inherited;
end;
2024-03-28 16:31:44 +01:00
class function TMVCServiceContainer.GetKey(const aGUID: TGUID; const aName: String): String;
2024-03-21 18:57:41 +01:00
begin
Result := aGUID.ToString + '_' + aName;
end;
2024-03-25 00:15:50 +01:00
function TMVCServiceContainer.RegisterType(const aImplementation: TClassOfInterfacedObject; const aInterface: TGUID;
2024-04-17 09:57:22 +02:00
const aRegType: TRegistrationType; const aName: string): IMVCServiceContainer;
2024-03-21 18:57:41 +01:00
var
lReg: TRegistration;
2024-04-17 09:57:22 +02:00
lKey: string;
2024-03-21 18:57:41 +01:00
begin
CheckBuilt;
2024-03-25 00:15:50 +01:00
if Supports(aImplementation, aInterface) then
2024-03-21 18:57:41 +01:00
begin
lReg := TRegistration.Create;
2024-03-25 00:15:50 +01:00
lReg.Clazz := aImplementation;
lReg.Delegate := nil;
lReg.RttiType := TRttiUtils.GlContext.GetType(lReg.Clazz);
2024-03-21 18:57:41 +01:00
lReg.RegistrationType := aRegType;
2024-04-17 09:57:22 +02:00
lKey := GetKey(aInterface, aName);
{$IF Defined(RIOORBETTER)}
if not fRegistry.TryAdd(lKey, lReg) then
begin
2024-04-17 09:57:22 +02:00
raise EMVCContainerError.CreateFmt('Cannot register duplicated service "%s"',[lKey]);
end;
2024-04-17 09:57:22 +02:00
{$ELSE}
if not fRegistry.ContainsKey(lKey) then
begin
fRegistry.Add(lKey, lReg)
end
else
begin
raise EMVCContainerError.CreateFmt('Cannot register duplicated service "%s"',[lKey]);
end;
{$ENDIF}
2024-03-21 18:57:41 +01:00
end
else
begin
raise EMVCContainerErrorUnknownService.CreateFmt('"%s" doesn''t supports requested interface', [aImplementation.QualifiedClassName]);
2024-03-21 18:57:41 +01:00
end;
2024-03-25 00:15:50 +01:00
Result := Self;
end;
function TMVCServiceContainer.RegisterType(
const aDelegate: TInterfacedObjectFactory; const aInterface: TGUID;
const aRegType: TRegistrationType; const aName: string): IMVCServiceContainer;
var
lReg: TRegistration;
lKey: string;
begin
CheckBuilt;
lReg := TRegistration.Create;
lReg.Clazz := nil;
lReg.Delegate := aDelegate;
lReg.RttiType := nil; //TRttiUtils.GlContext.GetType(lReg.Clazz);
lReg.RegistrationType := aRegType;
lKey := GetKey(aInterface, aName);
{$IF Defined(RIOORBETTER)}
if not fRegistry.TryAdd(lKey, lReg) then
begin
raise EMVCContainerError.CreateFmt('Cannot register duplicated service "%s"',[lKey]);
end;
{$ELSE}
if not fRegistry.ContainsKey(lKey) then
begin
fRegistry.Add(lKey, lReg)
end
else
begin
raise EMVCContainerError.CreateFmt('Cannot register duplicated service "%s"',[lKey]);
end;
{$ENDIF}
Result := Self;
end;
function TMVCServiceContainer.Resolve(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string): IInterface;
2024-03-21 18:57:41 +01:00
var
lReg: TRegistration;
lTypeInfo: PTypeInfo;
lType: TRttiType;
lService: TObject;
begin
2024-03-25 00:15:50 +01:00
if not fBuilt then
begin
raise EMVCContainerError.Create('Container has not been built');
2024-03-25 00:15:50 +01:00
end;
lTypeInfo := aTypeInfo;
2024-03-21 18:57:41 +01:00
if not fRegistry.TryGetValue(GetKey(lTypeInfo.TypeData.GUID, aName), lReg) then
begin
2024-03-27 00:10:48 +01:00
raise EMVCContainerErrorUnknownService.CreateFmt('Unknown service "%s" with name "%s"', [lTypeInfo.Name, aName])
2024-03-21 18:57:41 +01:00
end;
lType := lReg.RttiType;
2024-03-21 18:57:41 +01:00
case lReg.RegistrationType of
TRegistrationType.Transient, TRegistrationType.SingletonPerRequest:
2024-03-21 18:57:41 +01:00
begin
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
2024-03-21 18:57:41 +01:00
Supports(lService, lTypeInfo.TypeData.GUID, Result);
end;
TRegistrationType.Singleton:
2024-03-21 18:57:41 +01:00
begin
if lReg.Instance = nil then
begin
TMonitor.Enter(Self);
try
if lReg.Instance = nil then
begin
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
2024-03-21 18:57:41 +01:00
Supports(lService, lTypeInfo.TypeData.GUID, lReg.Instance)
end;
finally
TMonitor.Exit(Self)
end;
end;
Supports(lReg.Instance, lTypeInfo.TypeData.GUID, Result);
end;
else
raise EMVCContainerError.Create('Unsupported RegistrationType');
end;
end;
function TMVCServiceContainer.ResolveEx(const ServiceContainerResolver: IMVCServiceContainerResolver; const aTypeInfo: PTypeInfo; const aName: string;
2024-03-28 16:31:44 +01:00
out ServiceKey: String; out RegType: TRegistrationType): IInterface;
var
lReg: TRegistration;
lTypeInfo: PTypeInfo;
lType: TRttiType;
lService: TObject;
lServiceKey: string;
begin
if not fBuilt then
begin
raise EMVCContainerError.Create('Container has not been built');
end;
lTypeInfo := aTypeInfo;
lServiceKey := GetKey(lTypeInfo.TypeData.GUID, aName);
if not fRegistry.TryGetValue(lServiceKey, lReg) then
begin
raise EMVCContainerErrorUnknownService.CreateFmt('Unknown service "%s" with name "%s"', [lTypeInfo.Name, aName])
end;
lType := lReg.RttiType;
2024-03-28 16:31:44 +01:00
RegType := lReg.RegistrationType;
ServiceKey := lServiceKey;
case lReg.RegistrationType of
TRegistrationType.Transient, TRegistrationType.SingletonPerRequest:
2024-03-28 16:31:44 +01:00
begin
if lReg.Delegate = nil then
begin
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
end
else
begin
lService := lReg.Delegate();
end;
if not Supports(lService, lTypeInfo.TypeData.GUID, Result) then
begin
raise EMVCContainerErrorUnknownService.
CreateFmt('"%s" doesn''t supports requested interface', [TInterfacedObject(lReg.Instance).QualifiedClassName]);
end;
2024-03-28 16:31:44 +01:00
{rtSingletonPerRequest is destroyed by the adapter owned by Context}
end;
TRegistrationType.Singleton:
2024-03-28 16:31:44 +01:00
begin
if lReg.Instance = nil then
begin
TMonitor.Enter(Self);
try
if lReg.Instance = nil then
begin
if lReg.Delegate = nil then
begin
lService := CreateServiceWithDependencies(ServiceContainerResolver, lReg.Clazz, TRttiUtils.GetFirstDeclaredConstructor(lType));
end
else
begin
lService := lReg.Delegate();
end;
if not Supports(lService, lTypeInfo.TypeData.GUID, lReg.Instance) then
begin
raise EMVCContainerErrorUnknownService.
CreateFmt('"%s" doesn''t supports requested interface', [TInterfacedObject(lReg.Instance).QualifiedClassName]);
end;
2024-03-28 16:31:44 +01:00
end;
finally
TMonitor.Exit(Self)
end;
end;
Supports(lReg.Instance, lTypeInfo.TypeData.GUID, Result);
end;
else
raise EMVCContainerError.Create('Unsupported RegistrationType');
end;
end;
2024-03-25 00:15:50 +01:00
procedure TMVCServiceContainer.Build;
begin
fBuilt := True;
end;
function DefaultMVCServiceResolver: IMVCServiceContainerResolver;
begin
Result := DefaultMVCServiceContainer as IMVCServiceContainerResolver;
end;
function DefaultMVCServiceContainer: IMVCServiceContainer;
2024-03-25 00:15:50 +01:00
begin
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;
2024-03-25 00:15:50 +01:00
end;
function NewMVCServiceContainer: IMVCServiceContainer;
2024-03-25 00:15:50 +01:00
begin
Result := TMVCServiceContainer.Create;
2024-03-25 00:15:50 +01:00
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;
2024-03-21 18:57:41 +01:00
end.