diff --git a/samples/activerecord_showcase/EntitiesU.pas b/samples/activerecord_showcase/EntitiesU.pas index bacee9b1..e448e9f7 100644 --- a/samples/activerecord_showcase/EntitiesU.pas +++ b/samples/activerecord_showcase/EntitiesU.pas @@ -67,7 +67,12 @@ type [MVCTable('articles')] TArticleWithWriteOnlyFields = class(TCustomEntity) private - [MVCTableField('ID', [foPrimaryKey, foAutoGenerated, foReadOnly])] +{$IFNDEF USE_SEQUENCES} + [MVCTableField('id', [foPrimaryKey, foAutoGenerated, foReadOnly])] +{$ELSE} + [MVCTableField('id', [foPrimaryKey, foAutoGenerated], + 'SEQ_ARTICLES_ID' { required for interbase } )] +{$ENDIF} fID: NullableInt32; [MVCTableField('description', [foWriteOnly])] fDescrizione: string; @@ -137,7 +142,12 @@ type [MVCTable('customers')] TPartitionedCustomer = class(TCustomEntity) private +{$IFNDEF USE_SEQUENCES} [MVCTableField('id', [foPrimaryKey, foAutoGenerated])] +{$ELSE} + [MVCTableField('id', [foPrimaryKey, foAutoGenerated], + 'SEQ_CUSTOMERS_ID' { required for interbase } )] +{$ENDIF} fID: NullableInt64; [MVCTableField('code')] fCode: NullableString; diff --git a/samples/articles_crud_server_with_injectable_parameters/articles_crud_with_injectable_parameters.dproj b/samples/articles_crud_server_with_injectable_parameters/articles_crud_with_injectable_parameters.dproj index 9c4561a1..5df79736 100644 --- a/samples/articles_crud_server_with_injectable_parameters/articles_crud_with_injectable_parameters.dproj +++ b/samples/articles_crud_server_with_injectable_parameters/articles_crud_with_injectable_parameters.dproj @@ -1,7 +1,7 @@  {1576AA4D-0623-40AC-97D3-AA4BB4381A0A} - 19.2 + 19.3 VCL articles_crud_with_injectable_parameters.dpr True @@ -106,61 +106,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - Base @@ -168,6 +113,10 @@ Cfg_1 Base + + Cfg_2 + Base + Delphi.Personality.12 @@ -185,318 +134,12 @@ - - - .\ - true - - - - - .\ - true - - articles_crud_with_injectable_parameters.exe true - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - - - - .\ - true - - 1 @@ -505,6 +148,16 @@ 0 + + + classes + 64 + + + classes + 64 + + classes @@ -804,6 +457,10 @@ 1 .framework + + 1 + .framework + 0 @@ -817,6 +474,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -843,6 +504,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -870,6 +535,9 @@ 0 + + 0 + 0 @@ -1423,6 +1091,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -1451,6 +1123,9 @@ 1 + + 1 + 0 @@ -1489,16 +1164,17 @@ 1 - - - - - - - + + + + + + + + True diff --git a/samples/data/ACTIVERECORDDB.FDB b/samples/data/ACTIVERECORDDB.FDB index 266ae99c..c331fde6 100644 Binary files a/samples/data/ACTIVERECORDDB.FDB and b/samples/data/ACTIVERECORDDB.FDB differ diff --git a/samples/data/ACTIVERECORDDB.IB b/samples/data/ACTIVERECORDDB.IB index 9821563a..2b927c3f 100644 Binary files a/samples/data/ACTIVERECORDDB.IB and b/samples/data/ACTIVERECORDDB.IB differ diff --git a/samples/middleware/MiddlewareSample1.pas b/samples/middleware/MiddlewareSample1.pas index 826f2340..aadd30fb 100644 --- a/samples/middleware/MiddlewareSample1.pas +++ b/samples/middleware/MiddlewareSample1.pas @@ -65,7 +65,7 @@ begin if Context.Request.Headers['User-Agent'].Contains('Android') then begin Context.Response.Location := 'http://play.google.com'; - Context.Response.StatusCode := 307; // temporary redirect + Context.Response.StatusCode := HTTP_STATUS.TemporaryRedirect; // 307 - temporary redirect Handled := True; end; end; diff --git a/samples/renders/RenderSampleControllerU.pas b/samples/renders/RenderSampleControllerU.pas index cd1d2faf..86970bb6 100644 --- a/samples/renders/RenderSampleControllerU.pas +++ b/samples/renders/RenderSampleControllerU.pas @@ -586,20 +586,21 @@ begin lDM.qryCustomers.Open; lDict := ObjectDict(False { data are not freed after ObjectDict if freed } ) .Add('customers', lDM.qryCustomers, - procedure(const DS: TDataset; const Links: IMVCLinks) - begin - Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString) - .Add(HATEOAS.REL, 'self').Add(HATEOAS._TYPE, 'application/json'); - Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + - '/orders').Add(HATEOAS.REL, 'orders').Add(HATEOAS._TYPE, 'application/json'); - end).Add('singleCustomer', lDM.qryCustomers, - procedure(const DS: TDataset; const Links: IMVCLinks) - begin - Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString) - .Add(HATEOAS.REL, 'self').Add(HATEOAS._TYPE, 'application/json'); - Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + - '/orders').Add(HATEOAS.REL, 'orders').Add(HATEOAS._TYPE, 'application/json'); - end, dstSingleRecord, ncPascalCase); + procedure(const DS: TDataset; const Links: IMVCLinks) + begin + Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString) + .Add(HATEOAS.REL, 'self').Add(HATEOAS._TYPE, 'application/json'); + Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + + '/orders').Add(HATEOAS.REL, 'orders').Add(HATEOAS._TYPE, 'application/json'); + end) + .Add('singleCustomer', lDM.qryCustomers, + procedure(const DS: TDataset; const Links: IMVCLinks) + begin + Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString) + .Add(HATEOAS.REL, 'self').Add(HATEOAS._TYPE, 'application/json'); + Links.AddRefLink.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + + '/orders').Add(HATEOAS.REL, 'orders').Add(HATEOAS._TYPE, 'application/json'); + end, dstSingleRecord, ncPascalCase); Render(lDict); finally lDM.Free; @@ -748,8 +749,11 @@ end; procedure TRenderSampleController.GetPerson_AsText(const ID: Integer); begin - ResponseStream.AppendLine('ID : ' + ID.ToString).AppendLine('FirstName : Daniele') - .AppendLine('LastName : Teti').AppendLine('DOB : ' + DateToStr(EncodeDate(1979, 5, 2))) + ResponseStream + .AppendLine('ID : ' + ID.ToString) + .AppendLine('FirstName : Daniele') + .AppendLine('LastName : Teti') + .AppendLine('DOB : ' + DateToStr(EncodeDate(1979, 5, 2))) .AppendLine('Married : yes'); RenderResponseStream; end; @@ -875,7 +879,7 @@ begin {$ENDREGION} { classic approach } - // Render(HTTP_STATUS.OK, People, True); + //Render(HTTP_STATUS.OK, People, True); { new approach with ObjectDict } Render(HTTP_STATUS.OK, ObjectDict().Add('data', People)); end; diff --git a/samples/renders/renders.dpr b/samples/renders/renders.dpr index 29704c89..d7cd2b7d 100644 --- a/samples/renders/renders.dpr +++ b/samples/renders/renders.dpr @@ -2,7 +2,7 @@ // // Delphi MVC Framework // -// Copyright (c) 2010-2021 Daniele Teti and the DMVCFramework Team +// Copyright (c) 2010-2022 Daniele Teti and the DMVCFramework Team // // https://github.com/danieleteti/delphimvcframework // diff --git a/samples/routing/RoutingSampleControllerU.pas b/samples/routing/RoutingSampleControllerU.pas index ad9c30f0..108eb4a0 100644 --- a/samples/routing/RoutingSampleControllerU.pas +++ b/samples/routing/RoutingSampleControllerU.pas @@ -8,6 +8,7 @@ uses type [MVCPath('/')] + [MVCPath('/api')] TRoutingSampleController = class(TMVCController) public [MVCPath] @@ -133,8 +134,13 @@ begin if Context.Request.QueryStringParamExists('order') then orderby := Context.Request.QueryStringParam('order'); S := Format('SEARCHTEXT: "%s" - PAGE: %d - ORDER BY FIELD: "%s"', [search, Page, orderby]); - ResponseStream.AppendLine(S).AppendLine(StringOfChar('*', 30)).AppendLine('1. Daniele Teti') - .AppendLine('2. John Doe').AppendLine('3. Mark Rossi').AppendLine('4. Jack Verdi') + ResponseStream + .AppendLine(S) + .AppendLine(StringOfChar('*', 30)) + .AppendLine('1. Daniele Teti') + .AppendLine('2. John Doe') + .AppendLine('3. Mark Rossi') + .AppendLine('4. Jack Verdi') .AppendLine(StringOfChar('*', 30)); RenderResponseStream; end; diff --git a/samples/routing/routingsample.dproj b/samples/routing/routingsample.dproj index e82c3a88..15aee538 100644 --- a/samples/routing/routingsample.dproj +++ b/samples/routing/routingsample.dproj @@ -1,7 +1,7 @@  {E3725756-A58C-46BB-9708-ADE38AF9523C} - 19.2 + 19.3 None routingsample.dpr True @@ -23,16 +23,6 @@ Base true - - true - Base - true - - - true - Base - true - true Base @@ -116,19 +106,6 @@ package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png - - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers - iPhoneAndiPad - true - Debug - $(MSBuildProjectName) - - - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers - iPhoneAndiPad - true - 1033 FireDACSqliteDriver;DBXSqliteDriver;FireDACPgDriver;fmx;IndySystem;TeeDB;frx19;inetdbbde;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;MetropolisUILiveTile;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;IndyIPCommon;CloudService;DBXMSSQLDriver;FmxTeeUI;FireDACIBDriver;CodeSiteExpressPkg;FireDACDBXDriver;inetdbxpress;webdsnap;frxe19;FireDACDb2Driver;adortl;frxDB19;FireDACASADriver;bindcompfmx;ADVLikeComponents;vcldbx;FireDACODBCDriver;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;Tee;DBXOdbcDriver;vclFireDAC;xmlrtl;svnui;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;vclactnband;bindengine;soaprtl;bindcompdbx;FMXTee;TeeUI;bindcompvcl;vclie;FireDACADSDriver;vcltouch;VclSmp;FireDACMSSQLDriver;FireDAC;VCLRESTComponents;Intraweb;DBXInformixDriver;DataSnapConnectors;FireDACDataSnapDriver;dsnapcon;DBXFirebirdDriver;inet;fmxobj;FireDACMySQLDriver;vclx;svn;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;bdertl;FireDACMSAccDriver;DataSnapIndy10ServerTransport;dbexpress;IndyIPClient;$(DCC_UsePackage) @@ -169,10 +146,6 @@ - - Cfg_2 - Base - Base @@ -180,6 +153,10 @@ Cfg_1 Base + + Cfg_2 + Base + Delphi.Personality.12 @@ -245,11 +222,6 @@ - - - true - - routingsample @@ -261,21 +233,26 @@ true - - - true - - true + + + true + + true + + + true + + true @@ -289,6 +266,16 @@ 0 + + + classes + 64 + + + classes + 64 + + classes @@ -587,6 +574,10 @@ 1 .framework + + 1 + .framework + 0 @@ -600,6 +591,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -626,6 +621,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -653,6 +652,9 @@ 0 + + 0 + 0 @@ -1206,6 +1208,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -1234,6 +1240,9 @@ 1 + + 1 + 0 @@ -1272,22 +1281,21 @@ 1 - - - - - - - + + + + + + + + False False - False - False True True False diff --git a/sources/MVCFramework.ActiveRecord.pas b/sources/MVCFramework.ActiveRecord.pas index 2accbd88..aa7b002a 100644 --- a/sources/MVCFramework.ActiveRecord.pas +++ b/sources/MVCFramework.ActiveRecord.pas @@ -1245,6 +1245,10 @@ begin GetBackEnd + ' requires it'); end; + if foReadOnly in fPrimaryKeyOptions then + begin + raise EMVCActiveRecord.Create('Cannot define a read-only primary key when a sequence is used for the class ' + ClassName); + end; FillPrimaryKey(fPrimaryKeySequenceName); end; end; diff --git a/sources/MVCFramework.FireDAC.Utils.pas b/sources/MVCFramework.FireDAC.Utils.pas index af1fe3d8..7318d84b 100644 --- a/sources/MVCFramework.FireDAC.Utils.pas +++ b/sources/MVCFramework.FireDAC.Utils.pas @@ -45,7 +45,12 @@ type class procedure ExecuteQuery(AQuery: TFDQuery; AObject: TObject); class procedure ObjectToParameters(AFDParams: TFDParams; AObject: TObject; AParamPrefix: string = ''; ASetParamTypes: boolean = True); - class procedure CreateDatasetFromMetadata(AFDMemTable: TFDMemTable; AMeta: TJSONObject); + class procedure CreateDatasetFromMetadata(AFDMemTable: TFDCustomMemTable; AMeta: TJSONObject); + end; + + TFDCustomMemTableHelper = class helper for TFDCustomMemTable + public + procedure InitFromMetadata(const AJSONMetadata: TJSONObject); end; implementation @@ -65,7 +70,7 @@ begin end; class procedure TFireDACUtils.CreateDatasetFromMetadata( - AFDMemTable: TFDMemTable; AMeta: TJSONObject); + AFDMemTable: TFDCustomMemTable; AMeta: TJSONObject); var lJArr: TJSONArray; I: Integer; @@ -217,4 +222,9 @@ begin end; end; +procedure TFDCustomMemTableHelper.InitFromMetadata(const AJSONMetadata: TJSONObject); +begin + TFireDACUtils.CreateDatasetFromMetadata(Self, AJSONMetadata); +end; + end. diff --git a/sources/MVCFramework.Router.pas b/sources/MVCFramework.Router.pas index 57b6edf5..6ce4083b 100644 --- a/sources/MVCFramework.Router.pas +++ b/sources/MVCFramework.Router.pas @@ -37,7 +37,7 @@ uses System.RegularExpressions, MVCFramework, MVCFramework.Commons, - IdURI; + IdURI, System.Classes; type TMVCActionParamCacheItem = class @@ -82,9 +82,10 @@ type var aParams: TMVCRequestParamsTable): Boolean; function GetParametersNames(const V: string): TList; protected - function GetControllerMappedPath( + procedure FillControllerMappedPaths( const aControllerName: string; - const aControllerAttributes: TArray): string; + const aControllerAttributes: TArray; + const aControllerMappedPaths: TStringList); public class function StringMethodToHTTPMetod(const aValue: string): TMVCHTTPMethodType; static; constructor Create(const aConfig: TMVCConfig; @@ -148,6 +149,7 @@ var LRequestAccept: string; LRequestContentType: string; LControllerMappedPath: string; + LControllerMappedPaths: TStringList; LControllerDelegate: TMVCControllerDelegate; LAttributes: TArray; LAtt: TCustomAttribute; @@ -157,6 +159,7 @@ var LMethodPath: string; LProduceAttribute: MVCProducesAttribute; lURLSegment: string; + LItem: String; // JUST FOR DEBUG // lMethodCompatible: Boolean; // lContentTypeCompatible: Boolean; @@ -185,93 +188,108 @@ begin TMonitor.Enter(gLock); try - LControllerMappedPath := EmptyStr; - for LControllerDelegate in AControllers do - begin - SetLength(LAttributes, 0); - LRttiType := FRttiContext.GetType(LControllerDelegate.Clazz.ClassInfo); - - lURLSegment := LControllerDelegate.URLSegment; - if lURLSegment.IsEmpty then + //LControllerMappedPaths := TArray.Create(); + LControllerMappedPaths := TStringList.Create; + try + for LControllerDelegate in AControllers do begin - LAttributes := LRttiType.GetAttributes; - if (LAttributes = nil) then - Continue; - LControllerMappedPath := GetControllerMappedPath(LRttiType.Name, LAttributes); - end - else - begin - LControllerMappedPath := lURLSegment; - end; + LControllerMappedPaths.Clear; + SetLength(LAttributes, 0); + LRttiType := FRttiContext.GetType(LControllerDelegate.Clazz.ClassInfo); - if (LControllerMappedPath = '/') then - begin - LControllerMappedPath := ''; - end; - -{$IF defined(TOKYOORBETTER)} - if not LRequestPathInfo.StartsWith(APathPrefix + LControllerMappedPath, True) then -{$ELSE} - if not TMVCStringHelper.StartsWith(APathPrefix + LControllerMappedPath, LRequestPathInfo, True) then -{$ENDIF} - begin - Continue; - end; - - LMethods := LRttiType.GetMethods; { do not use GetDeclaredMethods because JSON-RPC rely on this!! } - for LMethod in LMethods do - begin - if LMethod.Visibility <> mvPublic then // 2020-08-08 - Continue; - if (LMethod.MethodKind <> mkProcedure) { or LMethod.IsClassMethod } then - Continue; - - LAttributes := LMethod.GetAttributes; - if Length(LAttributes) = 0 then - Continue; - - for LAtt in LAttributes do + lURLSegment := LControllerDelegate.URLSegment; + if lURLSegment.IsEmpty then begin - if LAtt is MVCPathAttribute then - begin - // THIS BLOCK IS HERE JUST FOR DEBUG - // if LMethod.Name.Contains('GetProject') then - // begin - // lMethodCompatible := True; //debug here - // end; - // lMethodCompatible := IsHTTPMethodCompatible(ARequestMethodType, LAttributes); - // lContentTypeCompatible := IsHTTPContentTypeCompatible(ARequestMethodType, LRequestContentType, LAttributes); - // lAcceptCompatible := IsHTTPAcceptCompatible(ARequestMethodType, LRequestAccept, LAttributes); + LAttributes := LRttiType.GetAttributes; + if (LAttributes = nil) then + Continue; + //LControllerMappedPaths := GetControllerMappedPath(LRttiType.Name, LAttributes); + FillControllerMappedPaths(LRttiType.Name, LAttributes, LControllerMappedPaths); + end + else + begin + LControllerMappedPaths.Add(lURLSegment); + end; - if IsHTTPMethodCompatible(ARequestMethodType, LAttributes) and - IsHTTPContentTypeCompatible(ARequestMethodType, LRequestContentType, LAttributes) and - IsHTTPAcceptCompatible(ARequestMethodType, LRequestAccept, LAttributes) then + for LItem in LControllerMappedPaths do + begin + LControllerMappedPath := LItem; + if (LControllerMappedPath = '/') then + begin + LControllerMappedPath := ''; + end; + + {$IF defined(TOKYOORBETTER)} + if not LRequestPathInfo.StartsWith(APathPrefix + LControllerMappedPath, True) then + {$ELSE} + if not TMVCStringHelper.StartsWith(APathPrefix + LControllerMappedPath, LRequestPathInfo, True) then + {$ENDIF} + begin + Continue; + end; +// end; + +// if (not LControllerMappedPathFound) then +// continue; + + LMethods := LRttiType.GetMethods; { do not use GetDeclaredMethods because JSON-RPC rely on this!! } + for LMethod in LMethods do + begin + if LMethod.Visibility <> mvPublic then // 2020-08-08 + Continue; + if (LMethod.MethodKind <> mkProcedure) { or LMethod.IsClassMethod } then + Continue; + + LAttributes := LMethod.GetAttributes; + if Length(LAttributes) = 0 then + Continue; + + for LAtt in LAttributes do begin - LMethodPath := MVCPathAttribute(LAtt).Path; - if IsCompatiblePath(APathPrefix + LControllerMappedPath + LMethodPath, - LRequestPathInfo, ARequestParams) then + if LAtt is MVCPathAttribute then begin - FMethodToCall := LMethod; - FControllerClazz := LControllerDelegate.Clazz; - FControllerCreateAction := LControllerDelegate.CreateAction; - LProduceAttribute := GetAttribute(LAttributes); - if LProduceAttribute <> nil then + // THIS BLOCK IS HERE JUST FOR DEBUG + // if LMethod.Name.Contains('GetProject') then + // begin + // lMethodCompatible := True; //debug here + // end; + // lMethodCompatible := IsHTTPMethodCompatible(ARequestMethodType, LAttributes); + // lContentTypeCompatible := IsHTTPContentTypeCompatible(ARequestMethodType, LRequestContentType, LAttributes); + // lAcceptCompatible := IsHTTPAcceptCompatible(ARequestMethodType, LRequestAccept, LAttributes); + + if IsHTTPMethodCompatible(ARequestMethodType, LAttributes) and + IsHTTPContentTypeCompatible(ARequestMethodType, LRequestContentType, LAttributes) and + IsHTTPAcceptCompatible(ARequestMethodType, LRequestAccept, LAttributes) then begin - AResponseContentMediaType := LProduceAttribute.Value; - AResponseContentCharset := LProduceAttribute.Charset; - end - else - begin - AResponseContentMediaType := ADefaultContentType; - AResponseContentCharset := ADefaultContentCharset; + LMethodPath := MVCPathAttribute(LAtt).Path; + if IsCompatiblePath(APathPrefix + LControllerMappedPath + LMethodPath, + LRequestPathInfo, ARequestParams) then + begin + FMethodToCall := LMethod; + FControllerClazz := LControllerDelegate.Clazz; + FControllerCreateAction := LControllerDelegate.CreateAction; + LProduceAttribute := GetAttribute(LAttributes); + if LProduceAttribute <> nil then + begin + AResponseContentMediaType := LProduceAttribute.Value; + AResponseContentCharset := LProduceAttribute.Charset; + end + else + begin + AResponseContentMediaType := ADefaultContentType; + AResponseContentCharset := ADefaultContentCharset; + end; + Exit(True); + end; end; - Exit(True); - end; - end; - end; // if MVCPathAttribute - end; // for in Attributes - end; // for in Methods - end; // for in Controllers + end; // if MVCPathAttribute + end; // for in Attributes + end; // for in Methods + end; + end; // for in Controllers + finally + LControllerMappedPaths.Free; + end; finally TMonitor.Exit(gLock); end; @@ -287,9 +305,10 @@ begin Exit(T(Att)); end; -function TMVCRouter.GetControllerMappedPath( - const aControllerName: string; - const aControllerAttributes: TArray): string; +procedure TMVCRouter.FillControllerMappedPaths( + const aControllerName: string; + const aControllerAttributes: TArray; + const aControllerMappedPaths: TStringList); var LFound: Boolean; LAtt: TCustomAttribute; @@ -300,13 +319,13 @@ begin if LAtt is MVCPathAttribute then begin LFound := True; - Result := MVCPathAttribute(LAtt).Path; - Break; + aControllerMappedPaths.Add(MVCPathAttribute(LAtt).Path); end; end; - if not LFound then + begin raise EMVCException.CreateFmt('Controller %s does not have MVCPath attribute', [aControllerName]); + end; end; function TMVCRouter.GetFirstMediaType(const AContentType: string): string; @@ -349,7 +368,7 @@ begin FActionParamsCache.Add(AMVCPath, lCacheItem); end; - if (APath = AMVCPath) then + if (APath = AMVCPath) or ((APath = '/') and (AMVCPath = '')) then Exit(True) else begin diff --git a/sources/MVCFramework.SQLGenerators.Firebird.pas b/sources/MVCFramework.SQLGenerators.Firebird.pas index 7361e69a..d3d394dd 100644 --- a/sources/MVCFramework.SQLGenerators.Firebird.pas +++ b/sources/MVCFramework.SQLGenerators.Firebird.pas @@ -109,7 +109,7 @@ begin begin if lKeyValue.Value.Writeable then begin - lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ','); + lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ','); end; end; @@ -140,11 +140,9 @@ end; initialization TMVCSQLGeneratorRegistry.Instance.RegisterSQLGenerator('firebird', TMVCSQLGeneratorFirebird); -TMVCSQLGeneratorRegistry.Instance.RegisterSQLGenerator('interbase', TMVCSQLGeneratorFirebird); finalization TMVCSQLGeneratorRegistry.Instance.UnRegisterSQLGenerator('firebird'); -TMVCSQLGeneratorRegistry.Instance.UnRegisterSQLGenerator('interbase'); end. diff --git a/sources/MVCFramework.SQLGenerators.Interbase.pas b/sources/MVCFramework.SQLGenerators.Interbase.pas index 75725470..f5ecd409 100644 --- a/sources/MVCFramework.SQLGenerators.Interbase.pas +++ b/sources/MVCFramework.SQLGenerators.Interbase.pas @@ -61,21 +61,30 @@ var lKeyValue: TPair; lSB: TStringBuilder; lPKInInsert: Boolean; + lFieldName: String; begin lPKInInsert := (not PKFieldName.IsEmpty); // and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions)); lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions)); lSB := TStringBuilder.Create; try - lSB.Append('INSERT INTO ' + TableName + '('); + lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + '('); if lPKInInsert then begin - lSB.Append(PKFieldName + ','); + lSB.Append(GetFieldNameForSQL(PKFieldName) + ','); end; + + {partition} + for lFieldName in fPartitionInfo.FieldNames do + begin + lSB.Append(GetFieldNameForSQL(lFieldName) + ','); + end; + {end-partition} + for lKeyValue in Map do begin if lKeyValue.Value.Writeable then begin - lSB.Append(lKeyValue.Value.FieldName + ','); + lSB.Append(GetFieldNameForSQL(lKeyValue.Value.FieldName) + ','); end; end; @@ -83,13 +92,21 @@ begin lSB.Append(') values ('); if lPKInInsert then begin - lSB.Append(':' + PKFieldName + ','); + lSB.Append(':' + GetParamNameForSQL(PKFieldName) + ','); end; + + {partition} + for lFieldName in fPartitionInfo.FieldNames do + begin + lSB.Append(':' + GetParamNameForSQL(lFieldName) + ','); + end; + {end-partition} + for lKeyValue in Map do begin if lKeyValue.Value.Writeable then begin - lSB.Append(':' + lKeyValue.Value.FieldName + ','); + lSB.Append(':' + GetParamNameForSQL(lKeyValue.Value.FieldName) + ','); end; end; diff --git a/unittests/general/Several/LiveServerTestU.pas b/unittests/general/Several/LiveServerTestU.pas index 45c9fae5..1c71e4cb 100644 --- a/unittests/general/Several/LiveServerTestU.pas +++ b/unittests/general/Several/LiveServerTestU.pas @@ -57,6 +57,18 @@ type [TestCase('request url /fault2', '/exception/fault2')] procedure TestControllerWithExceptionInCreate(const URLSegment: string); + [Test] + [TestCase('url "/"', '/')] + [TestCase('url "/action1"', '/action1')] + [TestCase('url "/action2"', '/action2')] + [TestCase('url "/api/v1"', '/api/v1')] + [TestCase('url "/api/v1/action1"', '/api/v1/action1')] + [TestCase('url "/api/v1/action2"', '/api/v1/action2')] + [TestCase('url "/api/v2"', '/api/v2')] + [TestCase('url "/api/v2/action1"', '/api/v2/action1')] + [TestCase('url "/api/v2/action2"', '/api/v2/action2')] + procedure TestMultiMVCPathOnControllerAndAction(const URLSegment: string); + [Test] procedure TestReqWithParams; @@ -1149,7 +1161,7 @@ procedure TServerTest.TestFileWithFolderName; var lRes: IMVCRESTResponse; begin - lRes := RESTClient.Accept(TMVCMediaType.TEXT_HTML).Get(''); + lRes := RESTClient.Accept(TMVCMediaType.TEXT_HTML).Get('doesn''t exist'); Assert.areEqual(404, lRes.StatusCode, ''); lRes := RESTClient.Accept(TMVCMediaType.TEXT_HTML).Get('/static/index.html'); @@ -1302,6 +1314,15 @@ begin Assert.AreNotEqual('', r.HeaderValue('request_gen_time')); end; +procedure TServerTest.TestMultiMVCPathOnControllerAndAction( + const URLSegment: string); +var + lRes: IMVCRESTResponse; +begin + lRes := RESTClient.Get(URLSegment); + Assert.areEqual(HTTP_STATUS.OK, lRes.StatusCode); +end; + procedure TServerTest.TestObjectDict; var lRes: IMVCRESTResponse; diff --git a/unittests/general/TestServer/TestServer.dpr b/unittests/general/TestServer/TestServer.dpr index 10e42f0c..8dfa02c3 100644 --- a/unittests/general/TestServer/TestServer.dpr +++ b/unittests/general/TestServer/TestServer.dpr @@ -24,7 +24,8 @@ uses MVCFramework.JSONRPC in '..\..\..\sources\MVCFramework.JSONRPC.pas', RandomUtilsU in '..\..\..\samples\commons\RandomUtilsU.pas', MVCFramework.Serializer.HTML in '..\..\..\sources\MVCFramework.Serializer.HTML.pas', - MVCFramework.Tests.Serializer.Entities in '..\..\common\MVCFramework.Tests.Serializer.Entities.pas'; + MVCFramework.Tests.Serializer.Entities in '..\..\common\MVCFramework.Tests.Serializer.Entities.pas', + MVCFramework.Router in '..\..\..\sources\MVCFramework.Router.pas'; {$R *.res} diff --git a/unittests/general/TestServer/TestServer.dproj b/unittests/general/TestServer/TestServer.dproj index 21358932..3159ed51 100644 --- a/unittests/general/TestServer/TestServer.dproj +++ b/unittests/general/TestServer/TestServer.dproj @@ -120,6 +120,7 @@ + Base @@ -169,6 +170,11 @@ true + + + true + + true @@ -180,11 +186,6 @@ true - - - true - - TestServer diff --git a/unittests/general/TestServer/TestServerControllerU.pas b/unittests/general/TestServer/TestServerControllerU.pas index a4ce7d6e..aadfa146 100644 --- a/unittests/general/TestServer/TestServerControllerU.pas +++ b/unittests/general/TestServer/TestServerControllerU.pas @@ -38,7 +38,8 @@ uses type - [MVCPath('/')] + [MVCPath] + [MVCPath('/donotusethis')] TTestServerController = class(TMVCController) private FFormatSettings: TFormatSettings; @@ -359,6 +360,17 @@ type constructor Create; override; end; + [MVCPath] + [MVCPath('/api/v1')] + [MVCPath('/api/v2')] + TTestMultiPathController = class(TMVCController) + public + [MVCPath] + [MVCPath('/action1')] + [MVCPath('/action2')] + procedure Action1or2; + end; + implementation uses @@ -1049,4 +1061,11 @@ begin // do nothing end; +{ TTestMultiPathController } + +procedure TTestMultiPathController.Action1or2; +begin + Render(HTTP_STATUS.OK); +end; + end. diff --git a/unittests/general/TestServer/WebModuleUnit.dfm b/unittests/general/TestServer/WebModuleUnit.dfm index a395d906..4b1928fe 100644 --- a/unittests/general/TestServer/WebModuleUnit.dfm +++ b/unittests/general/TestServer/WebModuleUnit.dfm @@ -1,9 +1,9 @@ object MainWebModule: TMainWebModule - OldCreateOrder = False OnCreate = WebModuleCreate Actions = <> Height = 230 Width = 415 + PixelsPerInch = 96 object FDStanStorageJSONLink1: TFDStanStorageJSONLink Left = 192 Top = 96 diff --git a/unittests/general/TestServer/WebModuleUnit.pas b/unittests/general/TestServer/WebModuleUnit.pas index ba2180a8..f10566d3 100644 --- a/unittests/general/TestServer/WebModuleUnit.pas +++ b/unittests/general/TestServer/WebModuleUnit.pas @@ -80,12 +80,14 @@ begin Config[TMVCConfigKey.ViewPath] := '..\templates'; Config[TMVCConfigKey.DefaultViewFileExtension] := 'html'; end, nil); - MVCEngine.AddController(TTestServerController) + MVCEngine + .AddController(TTestServerController) .AddController(TTestPrivateServerController) .AddController(TTestServerControllerExceptionAfterCreate) .AddController(TTestServerControllerExceptionBeforeDestroy) .AddController(TTestServerControllerActionFilters) .AddController(TTestPrivateServerControllerCustomAuth) + .AddController(TTestMultiPathController) .AddController(TTestJSONRPCController, '/jsonrpc') .AddController(TTestJSONRPCControllerWithGet, '/jsonrpcwithget') .PublishObject(