[+] added two new samples (simple_api_using_datasets, simple_api_using_mvcactiverecord). Thiese sample are used in official trainings.

This commit is contained in:
Daniele Teti 2021-04-06 19:23:30 +02:00
parent a850d2c31d
commit ea17335bcd
13 changed files with 3035 additions and 0 deletions

View File

@ -0,0 +1,104 @@
unit CustomersControllerU;
interface
uses
MVCFramework,
MVCFramework.ActiveRecord,
MVCFramework.Commons;
type
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
procedure GetCustomers;
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpGET])]
procedure GetCustomerByID(const ID: Integer);
[MVCPath]
[MVCHTTPMethods([httpPOST])]
procedure CreateCustomers;
end;
implementation
uses
System.SysUtils,
FireDAC.Comp.Client,
FireDAC.Stan.Param,
MVCFramework.Logger,
MVCFramework.Serializer.Commons,
JsonDataObjects,
MainDM;
{ TCustomersController }
procedure TCustomersController.CreateCustomers;
var
lJSON: TJsonObject;
lCustDM: TCustomersDM;
lSQL: string;
lNewCustomerId: Integer;
begin
lJSON := StrToJSONObject(Context.Request.Body);
if lJSON = nil then
raise EMVCDeserializationException.Create('Invalid JSON Body');
try
lSQL := 'INSERT INTO customers (code, description, city, rating, note) ' +
'VALUES (:code, :description, :city, :rating, :note) returning id';
lCustDM := TCustomersDM.Create(nil);
try
lCustDM.MyQuery.SQL.Text := lSQL;
lCustDM.MyQuery.ParamByName('code').AsString := lJSON.S['code'];
lCustDM.MyQuery.ParamByName('description').AsString :=
lJSON.S['description'];
lCustDM.MyQuery.ParamByName('city').AsString := lJSON.S['city'];
lCustDM.MyQuery.ParamByName('rating').AsInteger := lJSON.I['rating'];
lCustDM.MyQuery.ParamByName('note').AsString := lJSON.S['note'];
lCustDM.MyQuery.OpenOrExecute;
lNewCustomerId := lCustDM.MyQuery.FieldByName('id').AsInteger;
Render201Created('/api/customers/' + lNewCustomerId.ToString);
finally
lCustDM.Free;
end;
finally
lJSON.Free;
end;
end;
procedure TCustomersController.GetCustomerByID(const ID: Integer);
var
lCustDM: TCustomersDM;
begin
lCustDM := TCustomersDM.Create(nil);
try
lCustDM.MyQuery.Open('select * from customers where id = ?', [ID]);
if lCustDM.MyQuery.Eof then
raise EMVCException.Create(HTTP_STATUS.NotFound, 'Customer not found');
Render(
ObjectDict(False)
.Add('data', lCustDM.MyQuery, nil, dstSingleRecord));
finally
lCustDM.Free;
end;
end;
procedure TCustomersController.GetCustomers;
var
lCustDM: TCustomersDM;
begin
lCustDM := TCustomersDM.Create(nil);
try
lCustDM.MyQuery.Open('select * from customers order by code');
Render(ObjectDict(False).Add('data', lCustDM.MyQuery));
finally
lCustDM.Free;
end;
end;
end.

View File

@ -0,0 +1,21 @@
object CustomersDM: TCustomersDM
OldCreateOrder = False
Height = 266
Width = 405
object FDConnection1: TFDConnection
Params.Strings = (
'Database=activerecorddb'
'User_Name=postgres'
'Password=postgres'
'Server=127.0.0.1'
'DriverID=PG')
ConnectedStoredUsage = []
Left = 88
Top = 64
end
object MyQuery: TFDQuery
Connection = FDConnection1
Left = 208
Top = 64
end
end

View File

@ -0,0 +1,41 @@
unit MainDM;
interface
uses
System.SysUtils,
System.Classes,
FireDAC.Stan.Intf,
FireDAC.Stan.Option,
FireDAC.Stan.Error,
FireDAC.UI.Intf,
FireDAC.Phys.Intf,
FireDAC.Stan.Def,
FireDAC.Stan.Pool,
FireDAC.Stan.Async,
FireDAC.Phys,
FireDAC.Phys.PG,
FireDAC.Phys.PGDef,
FireDAC.ConsoleUI.Wait,
FireDAC.Stan.Param,
FireDAC.DatS,
FireDAC.DApt.Intf,
FireDAC.DApt,
Data.DB,
FireDAC.Comp.DataSet,
FireDAC.Comp.Client;
type
TCustomersDM = class(TDataModule)
FDConnection1: TFDConnection;
MyQuery: TFDQuery;
private
{ Private declarations }
end;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
end.

View File

@ -0,0 +1,8 @@
object TMunicipalLibraryWebModule: TTMunicipalLibraryWebModule
OldCreateOrder = False
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <>
Height = 230
Width = 415
end

View File

@ -0,0 +1,69 @@
unit MainWM;
interface
uses
System.SysUtils,
System.Classes,
Web.HTTPApp,
MVCFramework;
type
TTMunicipalLibraryWebModule = class(TWebModule)
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
FMVC: TMVCEngine;
public
{ Public declarations }
end;
var
WebModuleClass: TComponentClass = TTMunicipalLibraryWebModule;
implementation
{$R *.dfm}
uses
CustomersControllerU,
System.IOUtils,
MVCFramework.Commons,
MVCFramework.Middleware.Compression;
procedure TTMunicipalLibraryWebModule.WebModuleCreate(Sender: TObject);
begin
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := '0';
// default content-type
Config[TMVCConfigKey.DefaultContentType] := TMVCConstants.DEFAULT_CONTENT_TYPE;
// default content charset
Config[TMVCConfigKey.DefaultContentCharset] := TMVCConstants.DEFAULT_CONTENT_CHARSET;
// unhandled actions are permitted?
Config[TMVCConfigKey.AllowUnhandledAction] := 'false';
// default view file extension
Config[TMVCConfigKey.DefaultViewFileExtension] := 'html';
// view path
Config[TMVCConfigKey.ViewPath] := 'templates';
// Max Record Count for automatic Entities CRUD
Config[TMVCConfigKey.MaxEntitiesRecordCount] := '20';
// Enable Server Signature in response
Config[TMVCConfigKey.ExposeServerSignature] := 'true';
// Max request size in bytes
Config[TMVCConfigKey.MaxRequestSize] := IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE);
end);
FMVC.AddController(TCustomersController);
// To enable compression (deflate, gzip) just add this middleware as the last one
FMVC.AddMiddleware(TMVCCompressionMiddleware.Create);
end;
procedure TTMunicipalLibraryWebModule.WebModuleDestroy(Sender: TObject);
begin
FMVC.Free;
end;
end.

View File

@ -0,0 +1,112 @@
program SimpleRESTAPIUsingDatasets;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework.Logger,
MVCFramework.Commons,
MVCFramework.REPLCommandsHandlerU,
Web.ReqMulti,
Web.WebReq,
Web.WebBroker,
IdContext,
IdHTTPWebBrokerBridge,
CustomersControllerU in 'CustomersControllerU.pas',
MainWM in 'MainWM.pas' {TMunicipalLibraryWebModule: TWebModule},
MainDM in 'MainDM.pas' {CustomersDM: TDataModule};
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
LCustomHandler: TMVCCustomREPLCommandsHandler;
LCmd: string;
begin
Writeln('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION);
LCmd := 'start';
if ParamCount >= 1 then
LCmd := ParamStr(1);
LCustomHandler := function(const Value: String; const Server: TIdHTTPWebBrokerBridge; out Handled: Boolean): THandleCommandResult
begin
Handled := False;
Result := THandleCommandResult.Unknown;
// Write here your custom command for the REPL using the following form...
// ***
// Handled := False;
// if (Value = 'apiversion') then
// begin
// REPLEmit('Print my API version number');
// Result := THandleCommandResult.Continue;
// Handled := True;
// end
// else if (Value = 'datetime') then
// begin
// REPLEmit(DateTimeToStr(Now));
// Result := THandleCommandResult.Continue;
// Handled := True;
// end;
end;
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.OnParseAuthentication := TMVCParseAuthentication.OnParseAuthentication;
LServer.DefaultPort := APort;
{ more info about MaxConnections
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_MaxConnections.html }
LServer.MaxConnections := 0;
{ more info about ListenQueue
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_ListenQueue.html }
LServer.ListenQueue := 200;
WriteLn('Write "quit" or "exit" to shutdown the server');
repeat
if LCmd.IsEmpty then
begin
Write('-> ');
ReadLn(LCmd)
end;
try
case HandleCommand(LCmd.ToLower, LServer, LCustomHandler) of
THandleCommandResult.Continue:
begin
Continue;
end;
THandleCommandResult.Break:
begin
Break;
end;
THandleCommandResult.Unknown:
begin
REPLEmit('Unknown command: ' + LCmd);
end;
end;
finally
LCmd := '';
end;
until False;
finally
LServer.Free;
end;
end;
begin
ReportMemoryLeaksOnShutdown := True;
IsMultiThread := True;
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
WebRequestHandlerProc.MaxConnections := 1024;
RunServer(8080);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
unit CustomersControllerU;
interface
uses
MVCFramework,
MVCFramework.ActiveRecord,
MVCFramework.Commons;
type
[MVCPath('/api/customers')]
TCustomersController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethods([httpGET])]
procedure GetCustomers;
[MVCPath('/($ID)')]
[MVCHTTPMethods([httpGET])]
procedure GetCustomerByID(const ID: Integer);
[MVCPath]
[MVCHTTPMethods([httpPOST])]
procedure CreateCustomers;
end;
implementation
uses
System.SysUtils,
FireDAC.Comp.Client,
FireDAC.Stan.Param,
MVCFramework.Logger,
MVCFramework.Serializer.Commons,
JsonDataObjects, EntitiesU;
{ TCustomersController }
procedure TCustomersController.CreateCustomers;
var
lCustomer: TCustomer;
begin
lCustomer := Context.Request.BodyAs<TCustomer>;
try
lCustomer.Insert;
Render201Created('/api/customers/' + lCustomer.ID.Value.ToString);
finally
lCustomer.Free;
end;
end;
procedure TCustomersController.GetCustomerByID(const ID: Integer);
begin
Render(ObjectDict().Add('data', TMVCActiveRecord.GetByPK<TCustomer>(ID)));
end;
procedure TCustomersController.GetCustomers;
begin
Render(ObjectDict().Add('data', TMVCActiveRecord.All<TCustomer>));
end;
end.

View File

@ -0,0 +1,68 @@
// *************************************************************************** }
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2021 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit EntitiesU;
{$RTTI EXPLICIT METHODS([vcPublic, vcPublished]) FIELDS([vcPrivate, vcProtected, vcPublic, vcPublished]) PROPERTIES([vcPublic, vcPublished])}
interface
uses
MVCFramework.Serializer.Commons,
MVCFramework.ActiveRecord,
MVCFramework.SQLGenerators.PostgreSQL,
System.Generics.Collections,
System.Classes,
FireDAC.Stan.Param,
MVCFramework.Nullables;
type
[MVCNameCase(ncLowerCase)]
[MVCTable('customers')]
TCustomer = class(TMVCActiveRecord)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: NullableInt64;
[MVCTableField('code')]
fCode: NullableString;
[MVCTableField('description')]
fCompanyName: NullableString;
[MVCTableField('city')]
fCity: string;
[MVCTableField('rating')]
fRating: NullableInt32;
[MVCTableField('note')]
fNote: string;
public
property ID: NullableInt64 read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: NullableString read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
property Rating: NullableInt32 read fRating write fRating;
property Note: string read fNote write fNote;
end;
implementation
end.

View File

@ -0,0 +1,8 @@
object TMunicipalLibraryWebModule: TTMunicipalLibraryWebModule
OldCreateOrder = False
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <>
Height = 230
Width = 415
end

View File

@ -0,0 +1,71 @@
unit MainWM;
interface
uses
System.SysUtils,
System.Classes,
Web.HTTPApp,
MVCFramework;
type
TTMunicipalLibraryWebModule = class(TWebModule)
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
FMVC: TMVCEngine;
public
{ Public declarations }
end;
var
WebModuleClass: TComponentClass = TTMunicipalLibraryWebModule;
implementation
{$R *.dfm}
uses
CustomersControllerU,
MVCFramework.Middleware.ActiveRecord,
System.IOUtils,
MVCFramework.Commons,
MVCFramework.Middleware.Compression;
procedure TTMunicipalLibraryWebModule.WebModuleCreate(Sender: TObject);
begin
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := '0';
// default content-type
Config[TMVCConfigKey.DefaultContentType] := TMVCConstants.DEFAULT_CONTENT_TYPE;
// default content charset
Config[TMVCConfigKey.DefaultContentCharset] := TMVCConstants.DEFAULT_CONTENT_CHARSET;
// unhandled actions are permitted?
Config[TMVCConfigKey.AllowUnhandledAction] := 'false';
// default view file extension
Config[TMVCConfigKey.DefaultViewFileExtension] := 'html';
// view path
Config[TMVCConfigKey.ViewPath] := 'templates';
// Max Record Count for automatic Entities CRUD
Config[TMVCConfigKey.MaxEntitiesRecordCount] := '20';
// Enable Server Signature in response
Config[TMVCConfigKey.ExposeServerSignature] := 'true';
// Max request size in bytes
Config[TMVCConfigKey.MaxRequestSize] := IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE);
end);
FMVC.AddController(TCustomersController);
// To enable compression (deflate, gzip) just add this middleware as the last one
FMVC.AddMiddleware(TMVCActiveRecordMiddleware.Create('activerecorddb'));
FMVC.AddMiddleware(TMVCCompressionMiddleware.Create);
end;
procedure TTMunicipalLibraryWebModule.WebModuleDestroy(Sender: TObject);
begin
FMVC.Free;
end;
end.

View File

@ -0,0 +1,112 @@
program SimpleRESTAPIUsingActiveRecord;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework.Logger,
MVCFramework.Commons,
MVCFramework.REPLCommandsHandlerU,
Web.ReqMulti,
Web.WebReq,
Web.WebBroker,
IdContext,
IdHTTPWebBrokerBridge,
CustomersControllerU in 'CustomersControllerU.pas',
MainWM in 'MainWM.pas' {TMunicipalLibraryWebModule: TWebModule},
EntitiesU in 'EntitiesU.pas';
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
LCustomHandler: TMVCCustomREPLCommandsHandler;
LCmd: string;
begin
Writeln('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION);
LCmd := 'start';
if ParamCount >= 1 then
LCmd := ParamStr(1);
LCustomHandler := function(const Value: String; const Server: TIdHTTPWebBrokerBridge; out Handled: Boolean): THandleCommandResult
begin
Handled := False;
Result := THandleCommandResult.Unknown;
// Write here your custom command for the REPL using the following form...
// ***
// Handled := False;
// if (Value = 'apiversion') then
// begin
// REPLEmit('Print my API version number');
// Result := THandleCommandResult.Continue;
// Handled := True;
// end
// else if (Value = 'datetime') then
// begin
// REPLEmit(DateTimeToStr(Now));
// Result := THandleCommandResult.Continue;
// Handled := True;
// end;
end;
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.OnParseAuthentication := TMVCParseAuthentication.OnParseAuthentication;
LServer.DefaultPort := APort;
{ more info about MaxConnections
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_MaxConnections.html }
LServer.MaxConnections := 0;
{ more info about ListenQueue
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_ListenQueue.html }
LServer.ListenQueue := 200;
WriteLn('Write "quit" or "exit" to shutdown the server');
repeat
if LCmd.IsEmpty then
begin
Write('-> ');
ReadLn(LCmd)
end;
try
case HandleCommand(LCmd.ToLower, LServer, LCustomHandler) of
THandleCommandResult.Continue:
begin
Continue;
end;
THandleCommandResult.Break:
begin
Break;
end;
THandleCommandResult.Unknown:
begin
REPLEmit('Unknown command: ' + LCmd);
end;
end;
finally
LCmd := '';
end;
until False;
finally
LServer.Free;
end;
end;
begin
ReportMemoryLeaksOnShutdown := True;
IsMultiThread := True;
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
WebRequestHandlerProc.MaxConnections := 1024;
RunServer(8080);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

File diff suppressed because it is too large Load Diff