diff --git a/samples/articles_crud_server/BusinessObjects.pas b/samples/articles_crud_server/BusinessObjects.pas index ea592246..cf429219 100644 --- a/samples/articles_crud_server/BusinessObjects.pas +++ b/samples/articles_crud_server/BusinessObjects.pas @@ -3,46 +3,51 @@ interface uses - MVCFramework.Serializer.Commons, MVCFramework.Nullables; + MVCFramework.Serializer.Commons, MVCFramework.Nullables, + MVCFramework.ActiveRecord; type - TBaseBO = class + TBaseBO = class(TMVCActiveRecord) private + [MVCTableField('ID', [foPrimaryKey])] FID: Integer; procedure SetID(const Value: Integer); public - procedure CheckInsert; virtual; - procedure CheckUpdate; virtual; - procedure CheckDelete; virtual; property ID: Integer read FID write SetID; end; [MVCNameCase(ncLowerCase)] + [MVCTable('ARTICOLI')] + [MVCNamedSQLQuery( + 'search_by_text', + 'SELECT * FROM ARTICOLI WHERE DESCRIZIONE CONTAINING ? ORDER BY ID') + ] TArticle = class(TBaseBO) private - FPrice: Currency; + [MVCTableField('CODICE')] FCode: string; + [MVCTableField('DESCRIZIONE')] FDescription: String; + [MVCTableField('PREZZO')] + FPrice: Currency; + [MVCTableField('UPDATED_AT')] FUpdatedAt: TDateTime; + [MVCTableField('CREATED_AT')] FCreatedAt: TDateTime; procedure SetCode(const Value: string); procedure SetDescription(const Value: String); procedure SetPrice(const Value: Currency); procedure SetCreatedAt(const Value: TDateTime); procedure SetUpdatedAt(const Value: TDateTime); + protected + procedure OnBeforeInsertOrUpdate; override; + procedure OnBeforeUpdate; override; + procedure OnBeforeDelete; override; public - procedure CheckInsert; override; - procedure CheckUpdate; override; - procedure CheckDelete; override; - [MVCColumn('CODICE')] property Code: string read FCode write SetCode; - [MVCColumn('DESCRIZIONE')] property Description: String read FDescription write SetDescription; - [MVCColumn('PREZZO')] property Price: Currency read FPrice write SetPrice; - [MVCColumn('CREATED_AT')] property CreatedAt: TDateTime read FCreatedAt write SetCreatedAt; - [MVCColumn('UPDATED_AT')] property UpdatedAt: TDateTime read FUpdatedAt write SetUpdatedAt; end; @@ -53,46 +58,28 @@ implementation { TBaseBO } -procedure TBaseBO.CheckDelete; -begin - -end; - -procedure TBaseBO.CheckInsert; -begin - -end; - -procedure TBaseBO.CheckUpdate; -begin - -end; - procedure TBaseBO.SetID(const Value: Integer); begin FID := Value; end; -{ TArticolo } - -procedure TArticle.CheckDelete; +procedure TArticle.OnBeforeDelete; begin inherited; if Price <= 5 then raise Exception.Create('Cannot delete an article with a price below 5 euros (yes, it is a silly check)'); end; -procedure TArticle.CheckInsert; +procedure TArticle.OnBeforeInsertOrUpdate; begin inherited; if not TRegEx.IsMatch(Code, '^C[0-9]{2,4}') then raise Exception.Create('Article code must be in the format "CXX or CXXX or CXXXX"'); end; -procedure TArticle.CheckUpdate; +procedure TArticle.OnBeforeUpdate; begin inherited; - CheckInsert; if Price <= 2 then raise Exception.Create('We cannot sell so low cost pizzas!'); end; diff --git a/samples/articles_crud_server/Commons.pas b/samples/articles_crud_server/Commons.pas index 42999135..50dba7c1 100644 --- a/samples/articles_crud_server/Commons.pas +++ b/samples/articles_crud_server/Commons.pas @@ -10,6 +10,9 @@ EServiceException = class(Exception) end; +const + CON_DEF_NAME = 'MyConnX'; + implementation end. diff --git a/samples/articles_crud_server/Controllers.Articles.pas b/samples/articles_crud_server/Controllers.Articles.pas index a20431be..8284afd3 100644 --- a/samples/articles_crud_server/Controllers.Articles.pas +++ b/samples/articles_crud_server/Controllers.Articles.pas @@ -7,14 +7,19 @@ interface mvcframework.Commons, mvcframework.Serializer.Commons, System.Generics.Collections, - Controllers.Base, BusinessObjects; + Controllers.Base, BusinessObjects, Services; type [MVCDoc('Resource that manages articles CRUD')] [MVCPath('/articles')] TArticlesController = class(TBaseController) + private + fArticlesService: IArticlesService; public + [MVCInject] + constructor Create(ArticlesService: IArticlesService); reintroduce; + [MVCDoc('Returns the list of articles')] [MVCPath] [MVCHTTPMethod([httpGET])] @@ -23,17 +28,17 @@ TArticlesController = class(TBaseController) [MVCDoc('Returns the list of articles')] [MVCPath('/searches')] [MVCHTTPMethod([httpGET])] - procedure GetArticlesByDescription(const [MVCFromQueryString('q', '')] Search: String); + function GetArticlesByDescription(const [MVCFromQueryString('q', '')] Search: String): IMVCObjectDictionary; [MVCDoc('Returns the article with the specified id')] [MVCPath('/meta')] [MVCHTTPMethod([httpGET])] - procedure GetArticleMeta; + function GetArticleMeta: IMVCObjectDictionary; [MVCDoc('Returns the article with the specified id')] [MVCPath('/($id)')] [MVCHTTPMethod([httpGET])] - procedure GetArticleByID(id: Integer); + function GetArticleByID(id: Integer): IMVCObjectDictionary; [MVCDoc('Deletes the article with the specified id')] [MVCPath('/($id)')] @@ -61,14 +66,19 @@ implementation { TArticlesController } uses - Services, Commons, mvcframework.Serializer.Intf, System.SysUtils; +constructor TArticlesController.Create(ArticlesService: IArticlesService); +begin + inherited Create; + fArticlesService := ArticlesService; +end; + procedure TArticlesController.CreateArticle(const Article: TArticle); begin - GetArticlesService.Add(Article); + fArticlesService.Add(Article); Render201Created('/articles/' + Article.id.ToString, 'Article Created'); end; @@ -76,101 +86,60 @@ procedure TArticlesController.CreateArticles(const ArticleList: TObjectList; + function GetArticles(const aTextSearch: string): TObjectList; + function GetByID(const AID: Integer): TArticle; + procedure Delete(AArticolo: TArticle); + procedure DeleteAllArticles; + procedure Add(AArticolo: TArticle); + procedure Update(AArticolo: TArticle); + function GetMeta: TJSONObject; end; - TArticlesService = class(TServiceBase) + TArticlesService = class(TInterfacedObject, IArticlesService) public function GetAll: TObjectList; function GetArticles(const aTextSearch: string): TObjectList; @@ -36,112 +36,59 @@ TArticlesService = class(TServiceBase) implementation uses - FireDAC.Stan.Option, - FireDAC.Comp.Client, FireDAC.Stan.Param, + MVCFramework.ActiveRecord, MVCFramework.FireDAC.Utils, MVCFramework.DataSet.Utils, - MVCFramework.Serializer.Commons; + MVCFramework.Serializer.Commons, Data.DB; + { TArticoliService } procedure TArticlesService.Add(AArticolo: TArticle); -var - Cmd: TFDCustomCommand; begin - AArticolo.CheckInsert; - Cmd := FDM.updArticles.Commands[arInsert]; - TFireDACUtils.ObjectToParameters(Cmd.Params, AArticolo, 'NEW_'); - Cmd.Execute; - AArticolo.ID := Cmd.ParamByName('ID').AsInteger; + AArticolo.Insert; end; procedure TArticlesService.Delete(AArticolo: TArticle); -var - Cmd: TFDCustomCommand; begin - AArticolo.CheckDelete; - Cmd := FDM.updArticles.Commands[arDelete]; - TFireDACUtils.ObjectToParameters(Cmd.Params, AArticolo, 'OLD_'); - Cmd.Execute; + AArticolo.Delete(); end; procedure TArticlesService.DeleteAllArticles; begin - FDM.Connection.ExecSQL('delete from articoli'); + TMVCActiveRecord.DeleteAll(TArticle); end; function TArticlesService.GetAll: TObjectList; begin - FDM.dsArticles.Open('SELECT * FROM ARTICOLI ORDER BY ID', []); - Result := FDM.dsArticles.AsObjectList; - FDM.dsArticles.Close; + Result := TMVCActiveRecord.SelectRQL('sort(+id)', 1000); end; function TArticlesService.GetArticles( const aTextSearch: string): TObjectList; begin - FDM.dsArticles.Open('SELECT * FROM ARTICOLI WHERE DESCRIZIONE CONTAINING ? ORDER BY ID', [aTextSearch]); - try - Result := FDM.dsArticles.AsObjectList() - finally - FDM.dsArticles.Close; - end; + Result := TMVCActiveRecord.SelectByNamedQuery('search_by_text',[aTextSearch],[ftString]); end; function TArticlesService.GetByID(const AID: Integer): TArticle; begin - FDM.dsArticles.Open('SELECT * FROM ARTICOLI WHERE ID = :ID', [AID]); - try - if not FDM.dsArticles.Eof then - Result := FDM.dsArticles.AsObject - else - raise EServiceException.Create('Article not found'); - finally - FDM.dsArticles.Close; - end; + Result := TMVCActiveRecord.GetByPK(AID); end; function TArticlesService.GetMeta: TJSONObject; begin - FDM.dsArticles.Open('SELECT ID, CODICE as CODE, DESCRIZIONE as DESCRIPTION, PREZZO as PRICE, CREATED_AT as CREATEDAT, UPDATED_AT as UPDATEDAT FROM ARTICOLI WHERE TRUE = FALSE'); - Result := FDM.dsArticles.MetadataAsJSONObject(); + var lDS := TMVCActiveRecord.SelectDataSet('SELECT ID, CODICE as CODE, DESCRIZIONE as DESCRIPTION, PREZZO as PRICE, CREATED_AT as CREATEDAT, UPDATED_AT as UPDATEDAT FROM ARTICOLI WHERE TRUE = FALSE',[]); + try + Result := lDS.MetadataAsJSONObject() + finally + lDS.Free; + end; end; procedure TArticlesService.Update(AArticolo: TArticle); -var - Cmd: TFDCustomCommand; -begin - AArticolo.CheckUpdate; - Cmd := FDM.updArticles.Commands[arUpdate]; - TFireDACUtils.ObjectToParameters(Cmd.Params, AArticolo, 'NEW_'); - Cmd.ParamByName('OLD_ID').AsInteger := AArticolo.ID; - Cmd.Execute; - if Cmd.RowsAffected <> 1 then - raise Exception.Create('Article not found'); -end; - -{ TServiceBase } - -procedure TServiceBase.Commit; -begin - FDM.Connection.Commit; -end; - -constructor TServiceBase.Create(AdmMain: TdmMain); -begin - inherited Create; - FDM := AdmMain; -end; - -procedure TServiceBase.Rollback; -begin - FDM.Connection.Rollback; -end; - -procedure TServiceBase.StartTransaction; begin - FDM.Connection.StartTransaction; + AArticolo.Update(); end; end. diff --git a/samples/articles_crud_server/WebModuleUnit1.pas b/samples/articles_crud_server/WebModuleUnit1.pas index a5c0c9f5..85d7c1ed 100644 --- a/samples/articles_crud_server/WebModuleUnit1.pas +++ b/samples/articles_crud_server/WebModuleUnit1.pas @@ -2,7 +2,8 @@ interface -uses System.SysUtils, System.Classes, Web.HTTPApp, mvcframework; +uses System.SysUtils, System.Classes, Web.HTTPApp, mvcframework, FireDAC.Phys.FBDef, FireDAC.Stan.Intf, FireDAC.Phys, + FireDAC.Phys.IBBase, FireDAC.Phys.FB; type TWebModule1 = class(TWebModule) @@ -23,10 +24,13 @@ implementation uses Controllers.Articles, MVCFramework.Middleware.CORS, + MVCFramework.Middleware.ActiveRecord, MVCFramework.Middleware.Compression, MVCFramework.Middleware.Trace, + MVCFramework.SQLGenerators.Firebird, MVCFramework.Commons, - Controllers.Base; + Controllers.Base, + Commons; {$R *.dfm} @@ -45,6 +49,8 @@ procedure TWebModule1.WebModuleCreate(Sender: TObject); {$ENDIF} FEngine.AddMiddleware(TCORSMiddleware.Create); FEngine.AddMiddleware(TMVCCompressionMiddleware.Create(256)); + FEngine.AddMiddleware(TMVCActiveRecordMiddleware.Create(CON_DEF_NAME)); + // FEngine.AddMiddleware(TMVCTraceMiddleware.Create); end; diff --git a/samples/articles_crud_server/articles_crud_server.dpr b/samples/articles_crud_server/articles_crud_server.dpr index 24d540d3..b4e77131 100644 --- a/samples/articles_crud_server/articles_crud_server.dpr +++ b/samples/articles_crud_server/articles_crud_server.dpr @@ -10,6 +10,7 @@ uses MVCFramework.Commons, MVCFramework.Signal, MVCFramework.Logger, + MVCFramework.Container, MVCFramework.dotEnv, Web.WebReq, Web.WebBroker, @@ -18,10 +19,10 @@ uses Controllers.Articles in 'Controllers.Articles.pas', Services in 'Services.pas', BusinessObjects in 'BusinessObjects.pas', - MainDM in 'MainDM.pas' {dmMain: TDataModule}, Commons in 'Commons.pas', MVCFramework.ActiveRecord in '..\..\sources\MVCFramework.ActiveRecord.pas', - MVCFramework.Serializer.JsonDataObjects in '..\..\sources\MVCFramework.Serializer.JsonDataObjects.pas'; + MVCFramework.Serializer.JsonDataObjects in '..\..\sources\MVCFramework.Serializer.JsonDataObjects.pas', + FDConnectionConfigU in 'FDConnectionConfigU.pas'; {$R *.res} @@ -68,6 +69,12 @@ begin .Build(); //uses the executable folder to look for .env* files end); + CreateFirebirdPrivateConnDef(True); + + DefaultMVCServiceContainer + .RegisterType(TArticlesService, IArticlesService, '', TRegistrationType.SingletonPerRequest) + .Build; + WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024); RunServer(dotEnv.Env('dmvc.server.port', 8080)); except diff --git a/samples/articles_crud_server/articles_crud_server.dproj b/samples/articles_crud_server/articles_crud_server.dproj index 9b90c6bd..d5ed346a 100644 --- a/samples/articles_crud_server/articles_crud_server.dproj +++ b/samples/articles_crud_server/articles_crud_server.dproj @@ -97,13 +97,10 @@ - -
dmMain
- TDataModule -
+ Base diff --git a/sources/MVCFramework.Container.pas b/sources/MVCFramework.Container.pas index 757fc10f..15e3e67b 100644 --- a/sources/MVCFramework.Container.pas +++ b/sources/MVCFramework.Container.pas @@ -23,14 +23,6 @@ TClassOfInterfacedObject = class of TInterfacedObject; procedure Build(); end; - MVCInjectAttribute = class(TCustomAttribute) - private - fServiceName: String; - public - constructor Create(ServiceName: String = ''); - property ServiceName: String read fServiceName; - end; - EMVCContainerError = class(Exception) end; EMVCContainerErrorUnknownService = class(EMVCContainerError) end; EMVCContainerErrorInterfaceNotSupported = class(EMVCContainerError) end; @@ -45,7 +37,7 @@ EMVCContainerErrorUnknownConstructor = class(EMVCContainerError) end; implementation uses - MVCFramework.Rtti.Utils; + MVCFramework.Rtti.Utils, MVCFramework; type IMVCServiceInternalResolver = interface @@ -314,15 +306,6 @@ function NewMVCServiceContainer: IMVCServiceContainer; Result := TMVCServiceContainer.Create; end; - -{ MVCInjectAttribute } - -constructor MVCInjectAttribute.Create(ServiceName: String); -begin - inherited Create; - fServiceName := ServiceName; -end; - { TMVCServiceContainerAdapter } constructor TMVCServiceContainerAdapter.Create(Container: IMVCServiceContainer); diff --git a/sources/MVCFramework.RQL.Parser.pas b/sources/MVCFramework.RQL.Parser.pas index e69bd1ba..7e248139 100644 --- a/sources/MVCFramework.RQL.Parser.pas +++ b/sources/MVCFramework.RQL.Parser.pas @@ -315,7 +315,7 @@ procedure TRQL2SQL.Error(const Message: string); end; if lMsg.Trim.IsEmpty then lMsg := ''; - raise ERQLException.CreateFmt('[Error] %s (column %d - found %s)', [message, fCurIdx, lMsg]); + raise ERQLException.CreateFmt('[Error] %s (column %d - found %s)', [message, fCurIdx, lMsg]) at AddressOfReturnAddress; end; procedure TRQL2SQL.Execute( diff --git a/sources/MVCFramework.pas b/sources/MVCFramework.pas index ca915885..e055ce4f 100644 --- a/sources/MVCFramework.pas +++ b/sources/MVCFramework.pas @@ -338,6 +338,14 @@ MVCFromContentFieldAttribute = class(MVCInjectableParamAttribute) end; + MVCInjectAttribute = class(TCustomAttribute) + private + fServiceName: String; + public + constructor Create(ServiceName: String = ''); + property ServiceName: String read fServiceName; + end; + // test // TMVCHackHTTPAppRequest = class(TIdHTTPAppRequest) // private @@ -5051,6 +5059,16 @@ constructor TMVCBaseResponse.Create; inherited; end; +{ MVCInjectAttribute } + +{ MVCInjectAttribute } + +constructor MVCInjectAttribute.Create(ServiceName: String); +begin + inherited Create; + fServiceName := ServiceName; +end; + initialization // https://quality.embarcadero.com/browse/RSP-38281