mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-16 00:05:53 +01:00
Merge remote-tracking branch 'upstream/master' into new_restclient
This commit is contained in:
commit
fdb7ad30a3
114
README.md
114
README.md
@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
![](https://img.shields.io/badge/current%20dmvcframework%20version-3.2.0--boron-blue?style=for-the-badge)
|
![](https://img.shields.io/badge/current%20dmvcframework%20version-3.2.0--boron-blue?style=for-the-badge)
|
||||||
![DelphiMVCFramework Logo](docs/dmvcframework_logofacebook.png)
|
![DelphiMVCFramework Logo](docs/dmvcframework_logofacebook.png)
|
||||||
![](https://img.shields.io/badge/next%20dmvcframework%20version-3.2.1--carbon-red)![GitHub All Releases](https://img.shields.io/github/downloads/danieleteti/delphimvcframework/total?label=releases%20download)
|
![](https://img.shields.io/badge/We%20are%20working%20on%20dmvcframework%20new%20version-3.2.1--carbon-red)![GitHub All Releases](https://img.shields.io/github/downloads/danieleteti/delphimvcframework/total?label=releases%20download)
|
||||||
|
|
||||||
|
|
||||||
# DelphiMVCFramework 3.2.0-boron is [here](https://github.com/danieleteti/delphimvcframework/releases/tag/v3_2_0_boron)!
|
# DelphiMVCFramework 3.2.0-boron is [here](https://github.com/danieleteti/delphimvcframework/releases/tag/v3_2_0_boron)!
|
||||||
|
|
||||||
@ -385,7 +383,117 @@ Congratulations to Daniele Teti and all the staff for the excellent work!" -- Ma
|
|||||||
- Fixed! [issue388](https://github.com/danieleteti/delphimvcframework/issues/388)
|
- Fixed! [issue388](https://github.com/danieleteti/delphimvcframework/issues/388)
|
||||||
- Fixed! Has been patched a serious security bug affecting deployment configurations which uses internal WebServer to serve static files (do not affect all Apache, IIS or proxied deployments). Thanks to **Stephan Munz** to have discovered it. *Update to dmvcframework-3.2-RC5+ is required for all such kind of deployments.*
|
- Fixed! Has been patched a serious security bug affecting deployment configurations which uses internal WebServer to serve static files (do not affect all Apache, IIS or proxied deployments). Thanks to **Stephan Munz** to have discovered it. *Update to dmvcframework-3.2-RC5+ is required for all such kind of deployments.*
|
||||||
|
|
||||||
|
## Changes in upcoming version (3.2.1-carbon)
|
||||||
|
|
||||||
|
### Bug Fixes and Improvements
|
||||||
|
|
||||||
|
- [docExpansion parameter for Swagger](https://github.com/danieleteti/delphimvcframework/issues/408)
|
||||||
|
|
||||||
|
- New `Context: TWebContext` parameter in JSON-RPC Hooks
|
||||||
|
|
||||||
|
```delphi
|
||||||
|
{ Called before any actual routing }
|
||||||
|
procedure OnBeforeRoutingHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
{ Called after routing and before the actual remote method invocation }
|
||||||
|
procedure OnBeforeCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
{ Called after actual remote method invocation, even if the method raised an exception }
|
||||||
|
procedure OnAfterCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
```
|
||||||
|
|
||||||
|
- When a JSON-RPC Request returns a `System.Boolean` the `result` will be a JSON `true` or `false` and no `1` or `0` as it was in the `3.2.0-boron`.
|
||||||
|
|
||||||
|
- `IMVCJSONRPCExecutor.ExecuteNotification` returns a `IJSONRPCResponse`. In case of error response contains information about the error, in case of successful execution the response is a [Null Object](https://en.wikipedia.org/wiki/Null_object_pattern).
|
||||||
|
|
||||||
|
- Added `foReadOnly` and `foWriteOnly` as field options in `MVCTableField` attribute (used by `TMVCActiveRecord`). Currently available field options are:
|
||||||
|
|
||||||
|
- *foPrimaryKey* { it's the primary key of the mapped table }
|
||||||
|
- *foAutoGenerated* { not written, read - similar to foReadOnly but is reloaded after insert and update }
|
||||||
|
- *foTransient* { never stored nor read - managed only at run-time }
|
||||||
|
- *foReadOnly* { not written, read }
|
||||||
|
- *foWriteOnly* { written, not read }
|
||||||
|
|
||||||
|
Now it is possible to declare entities like the followings (or with any other combinations):
|
||||||
|
|
||||||
|
```delphi
|
||||||
|
[MVCNameCase(ncLowerCase)]
|
||||||
|
[MVCTable('articles')]
|
||||||
|
TArticleWithWriteOnlyFields = class(TCustomEntity)
|
||||||
|
private
|
||||||
|
[MVCTableField('ID', [foPrimaryKey, foAutoGenerated])]
|
||||||
|
fID: NullableInt32;
|
||||||
|
[MVCTableField('description', [foWriteOnly])]
|
||||||
|
fDescription: string;
|
||||||
|
[MVCTableField('price', [foWriteOnly])]
|
||||||
|
fPrice: Integer;
|
||||||
|
public
|
||||||
|
property ID: NullableInt32 read fID write fID;
|
||||||
|
property Description: string read fDescription write fDescription;
|
||||||
|
property Price: Integer read fPrice write fPrice;
|
||||||
|
end;
|
||||||
|
|
||||||
|
[MVCNameCase(ncLowerCase)]
|
||||||
|
[MVCTable('articles')]
|
||||||
|
TArticleWithReadOnlyFields = class(TCustomEntity)
|
||||||
|
private
|
||||||
|
[MVCTableField('ID', [foPrimaryKey, foReadOnly])]
|
||||||
|
fID: NullableInt32;
|
||||||
|
[MVCTableField('code', [foTransient])]
|
||||||
|
fCode: NullableString;
|
||||||
|
[MVCTableField('description', [foReadOnly])]
|
||||||
|
fDescrizione: string;
|
||||||
|
[MVCTableField('price', [foReadOnly])]
|
||||||
|
fPrice: Currency;
|
||||||
|
public
|
||||||
|
property ID: NullableInt32 read fID write fID;
|
||||||
|
property Code: NullableString read fCode write fCode;
|
||||||
|
property Description: string read fDescription write fDescription;
|
||||||
|
property Price: Currency read fPrice write fPrice;
|
||||||
|
end;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Added the ability to deserialize an object starting from an arbitrary node in the JSON (or other format) present in the request body.
|
||||||
|
|
||||||
|
```delphi
|
||||||
|
procedure TBooksController.CreateBook;
|
||||||
|
var
|
||||||
|
lBook: TBook;
|
||||||
|
begin
|
||||||
|
//this call deserialize a TBook instance
|
||||||
|
//starting from the 'book' node of
|
||||||
|
//the request body
|
||||||
|
lBook := Context.Request.BodyAs<TBook>('book');
|
||||||
|
try
|
||||||
|
lBook.Insert;
|
||||||
|
Render201Created('/api/books/' + lBook.ID.ToString);
|
||||||
|
finally
|
||||||
|
lBook.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Improved the primary key type handling for manual handling in MVCActiveRecord.
|
||||||
|
|
||||||
|
```delphi
|
||||||
|
procedure TMyBaseEntity.OnBeforeInsert;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
//regardless the name of the PK field
|
||||||
|
//the following code fills the PK with a GUID
|
||||||
|
//Inheriting the other entities from this, all
|
||||||
|
//will inherit this behavior.
|
||||||
|
SetPK(TValue.From<NullableString>(TGUID.NewGuid.ToString));
|
||||||
|
|
||||||
|
//if the PK was a simple string, the code
|
||||||
|
//should be like the following
|
||||||
|
//SetPK(TGUID.NewGuid.ToString);
|
||||||
|
end;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- Improved `activerecord_showcase` sample.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
DelphiMVCFramework roadmap is always updated as-soon-as the features planned are implemented. Check the roadmap [here](roadmap.md).
|
DelphiMVCFramework roadmap is always updated as-soon-as the features planned are implemented. Check the roadmap [here](roadmap.md).
|
||||||
|
|
||||||
## Trainings, consultancy or custom development service
|
## Trainings, consultancy or custom development service
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
Daniele Teti
|
|
||||||
Role : Founder, Lead Developer
|
|
||||||
email : d.teti@bittime.it
|
|
||||||
blog : www.danieleteti.it
|
|
||||||
company : www.bittimeprofessionals.it
|
|
||||||
country : ITALY
|
|
||||||
|
|
||||||
Daniele Spinetti
|
|
||||||
Role : Expert, Senior Developer
|
|
||||||
email : d.spinetti@bittime.it
|
|
||||||
blog : www.danielespinetti.it
|
|
||||||
company : www.bittimeprofessionals.it
|
|
||||||
country : ITALY
|
|
||||||
|
|
||||||
Nick Hodges
|
|
||||||
Role : Expert, Senior Developer
|
|
||||||
blog : www.nickhodges.com, www.codingindelphi.com/blog/
|
|
||||||
company : www.embarcadero.com
|
|
||||||
country : USA
|
|
||||||
|
|
||||||
Ezequiel Juliano Müller
|
|
||||||
Role : Expert, Senior Developer
|
|
||||||
email : ezequieljuliano@gmail.com
|
|
||||||
blog : www.ezequieljuliano.com.br
|
|
||||||
country : BRAZIL
|
|
@ -10,7 +10,7 @@ As we are in the detailed planning stages for these features, we will share addi
|
|||||||
- (DONE) Implement Strongly Typed Actions
|
- (DONE) Implement Strongly Typed Actions
|
||||||
- (DONE) Implement Custom Authentication and Authorization Middleware
|
- (DONE) Implement Custom Authentication and Authorization Middleware
|
||||||
- (DONE) Use a middleware to implement response compression for console type projects
|
- (DONE) Use a middleware to implement response compression for console type projects
|
||||||
- (INITIAL)Implement Swagger support (we need a good self contained YAML parser/generator...)
|
- (DONE)Implement Swagger support
|
||||||
- (DONE) Linux support
|
- (DONE) Linux support
|
||||||
- (DONE) Update Mapper framework to make it extensible and configurable
|
- (DONE) Update Mapper framework to make it extensible and configurable
|
||||||
- The default mapper interface will be the same as the current version so that no breaking changes happened
|
- The default mapper interface will be the same as the current version so that no breaking changes happened
|
||||||
@ -25,7 +25,7 @@ As we are in the detailed planning stages for these features, we will share addi
|
|||||||
- New samples with specific web related use cases:
|
- New samples with specific web related use cases:
|
||||||
- WebWorkers
|
- WebWorkers
|
||||||
- (DONE) Angular2+
|
- (DONE) Angular2+
|
||||||
- React
|
- (DONE) React
|
||||||
- (DONE) Create "Custom Authentication and Authorization" demo
|
- (DONE) Create "Custom Authentication and Authorization" demo
|
||||||
- (CANCELED) Complete the [DevGuide](https://danieleteti.gitbooks.io/delphimvcframework/content/) on gitbooks
|
- (CANCELED) Complete the [DevGuide](https://danieleteti.gitbooks.io/delphimvcframework/content/) on gitbooks
|
||||||
- Improve the session mechanism to allows more flexibility
|
- Improve the session mechanism to allows more flexibility
|
||||||
|
@ -46,9 +46,9 @@ type
|
|||||||
[MVCTable('articles')]
|
[MVCTable('articles')]
|
||||||
TArticle = class(TCustomEntity)
|
TArticle = class(TCustomEntity)
|
||||||
private
|
private
|
||||||
[MVCTableField('ID')]
|
[MVCTableField('ID', [foPrimaryKey, foAutoGenerated])]
|
||||||
fID: NullableInt32;
|
fID: NullableInt32;
|
||||||
[MVCTableField('code')]
|
[MVCTableField('code', [foTransient])]
|
||||||
fCodice: NullableString;
|
fCodice: NullableString;
|
||||||
[MVCTableField('description')]
|
[MVCTableField('description')]
|
||||||
fDescrizione: string;
|
fDescrizione: string;
|
||||||
@ -63,6 +63,41 @@ type
|
|||||||
property Price: Currency read fPrezzo write fPrezzo;
|
property Price: Currency read fPrezzo write fPrezzo;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
[MVCNameCase(ncLowerCase)]
|
||||||
|
[MVCTable('articles')]
|
||||||
|
TArticleWithWriteOnlyFields = class(TCustomEntity)
|
||||||
|
private
|
||||||
|
[MVCTableField('ID', [foPrimaryKey, foAutoGenerated, foReadOnly])]
|
||||||
|
fID: NullableInt32;
|
||||||
|
[MVCTableField('description', [foWriteOnly])]
|
||||||
|
fDescrizione: string;
|
||||||
|
[MVCTableField('price', [foWriteOnly])]
|
||||||
|
fPrice: Integer;
|
||||||
|
public
|
||||||
|
property ID: NullableInt32 read fID write fID;
|
||||||
|
property Description: string read fDescrizione write fDescrizione;
|
||||||
|
property Price: Integer read fPrice write fPrice;
|
||||||
|
end;
|
||||||
|
|
||||||
|
[MVCNameCase(ncLowerCase)]
|
||||||
|
[MVCTable('articles')]
|
||||||
|
TArticleWithReadOnlyFields = class(TCustomEntity)
|
||||||
|
private
|
||||||
|
[MVCTableField('ID', [foPrimaryKey, foReadOnly])]
|
||||||
|
fID: NullableInt32;
|
||||||
|
[MVCTableField('code', [foTransient])]
|
||||||
|
fCodice: NullableString;
|
||||||
|
[MVCTableField('description', [foReadOnly])]
|
||||||
|
fDescrizione: string;
|
||||||
|
[MVCTableField('price', [foReadOnly])]
|
||||||
|
fPrezzo: Currency;
|
||||||
|
public
|
||||||
|
property ID: NullableInt32 read fID write fID;
|
||||||
|
property Code: NullableString read fCodice write fCodice;
|
||||||
|
property Description: string read fDescrizione write fDescrizione;
|
||||||
|
property Price: Currency read fPrezzo write fPrezzo;
|
||||||
|
end;
|
||||||
|
|
||||||
TOrder = class;
|
TOrder = class;
|
||||||
|
|
||||||
[MVCNameCase(ncLowerCase)]
|
[MVCNameCase(ncLowerCase)]
|
||||||
@ -108,7 +143,6 @@ type
|
|||||||
fID: Integer;
|
fID: Integer;
|
||||||
[MVCTableField('code', [foTransient])]
|
[MVCTableField('code', [foTransient])]
|
||||||
fCode: string;
|
fCode: string;
|
||||||
[MVCTableField('', [foTransient])]
|
|
||||||
fFormattedCode: string;
|
fFormattedCode: string;
|
||||||
[MVCTableField('description')]
|
[MVCTableField('description')]
|
||||||
fCompanyName: string;
|
fCompanyName: string;
|
||||||
@ -238,6 +272,12 @@ type
|
|||||||
property Note: string read fNote write fNote;
|
property Note: string read fNote write fNote;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
[MVCTable('customers_with_code')]
|
||||||
|
TCustomerPlainWithClientPK = class(TCustomerWithCode)
|
||||||
|
protected
|
||||||
|
procedure OnBeforeInsert; override;
|
||||||
|
end;
|
||||||
|
|
||||||
[MVCNameCase(ncLowerCase)]
|
[MVCNameCase(ncLowerCase)]
|
||||||
[MVCTable('orders')]
|
[MVCTable('orders')]
|
||||||
TOrder = class(TCustomEntity)
|
TOrder = class(TCustomEntity)
|
||||||
@ -350,7 +390,7 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils, Data.DB, MVCFramework.Logger;
|
System.SysUtils, Data.DB, MVCFramework.Logger, System.Rtti;
|
||||||
|
|
||||||
constructor TArticle.Create;
|
constructor TArticle.Create;
|
||||||
begin
|
begin
|
||||||
@ -459,7 +499,7 @@ end;
|
|||||||
constructor TNullablesTest.Create;
|
constructor TNullablesTest.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
// ff_blob := TMemoryStream.Create;
|
ff_blob := TMemoryStream.Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TNullablesTest.Destroy;
|
destructor TNullablesTest.Destroy;
|
||||||
@ -476,4 +516,16 @@ begin
|
|||||||
Log.Info(ClassName + ' | ' + SQL, 'sql_trace');
|
Log.Info(ClassName + ' | ' + SQL, 'sql_trace');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TCustomerPlainWithClientPK }
|
||||||
|
|
||||||
|
procedure TCustomerPlainWithClientPK.OnBeforeInsert;
|
||||||
|
begin
|
||||||
|
inherited;
|
||||||
|
SetPK(TValue.From<NullableString>(TGUID.NewGuid.ToString
|
||||||
|
.Replace('{', '')
|
||||||
|
.Replace('-', '')
|
||||||
|
.Replace('}', '')
|
||||||
|
.Substring(0, 20)));
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
@ -2,7 +2,7 @@ object MainForm: TMainForm
|
|||||||
Left = 0
|
Left = 0
|
||||||
Top = 0
|
Top = 0
|
||||||
Caption = 'TMVCActiveRecord - ShowCase'
|
Caption = 'TMVCActiveRecord - ShowCase'
|
||||||
ClientHeight = 587
|
ClientHeight = 640
|
||||||
ClientWidth = 635
|
ClientWidth = 635
|
||||||
Color = clBtnFace
|
Color = clBtnFace
|
||||||
Font.Charset = DEFAULT_CHARSET
|
Font.Charset = DEFAULT_CHARSET
|
||||||
@ -15,7 +15,7 @@ object MainForm: TMainForm
|
|||||||
OnShow = FormShow
|
OnShow = FormShow
|
||||||
DesignSize = (
|
DesignSize = (
|
||||||
635
|
635
|
||||||
587)
|
640)
|
||||||
PixelsPerInch = 96
|
PixelsPerInch = 96
|
||||||
TextHeight = 13
|
TextHeight = 13
|
||||||
object btnCRUD: TButton
|
object btnCRUD: TButton
|
||||||
@ -40,7 +40,7 @@ object MainForm: TMainForm
|
|||||||
Left = 135
|
Left = 135
|
||||||
Top = 8
|
Top = 8
|
||||||
Width = 492
|
Width = 492
|
||||||
Height = 571
|
Height = 624
|
||||||
Anchors = [akLeft, akTop, akRight, akBottom]
|
Anchors = [akLeft, akTop, akRight, akBottom]
|
||||||
Ctl3D = True
|
Ctl3D = True
|
||||||
DoubleBuffered = True
|
DoubleBuffered = True
|
||||||
@ -57,6 +57,7 @@ object MainForm: TMainForm
|
|||||||
TabOrder = 2
|
TabOrder = 2
|
||||||
WantReturns = False
|
WantReturns = False
|
||||||
WordWrap = False
|
WordWrap = False
|
||||||
|
ExplicitHeight = 571
|
||||||
end
|
end
|
||||||
object btnRelations: TButton
|
object btnRelations: TButton
|
||||||
Left = 8
|
Left = 8
|
||||||
@ -158,6 +159,24 @@ object MainForm: TMainForm
|
|||||||
TabOrder = 13
|
TabOrder = 13
|
||||||
OnClick = btnCountWithRQLClick
|
OnClick = btnCountWithRQLClick
|
||||||
end
|
end
|
||||||
|
object btnReadAndWriteOnly: TButton
|
||||||
|
Left = 8
|
||||||
|
Top = 523
|
||||||
|
Width = 121
|
||||||
|
Height = 33
|
||||||
|
Caption = 'R/O, R/W'
|
||||||
|
TabOrder = 14
|
||||||
|
OnClick = btnReadAndWriteOnlyClick
|
||||||
|
end
|
||||||
|
object btnClientGeneratedPK: TButton
|
||||||
|
Left = 8
|
||||||
|
Top = 562
|
||||||
|
Width = 121
|
||||||
|
Height = 33
|
||||||
|
Caption = 'Client Generated PKs'
|
||||||
|
TabOrder = 15
|
||||||
|
OnClick = btnClientGeneratedPKClick
|
||||||
|
end
|
||||||
object FDConnection1: TFDConnection
|
object FDConnection1: TFDConnection
|
||||||
Left = 176
|
Left = 176
|
||||||
Top = 56
|
Top = 56
|
||||||
|
@ -42,6 +42,8 @@ type
|
|||||||
btnCRUDWithStringPKs: TButton;
|
btnCRUDWithStringPKs: TButton;
|
||||||
btnWithSpaces: TButton;
|
btnWithSpaces: TButton;
|
||||||
btnCountWithRQL: TButton;
|
btnCountWithRQL: TButton;
|
||||||
|
btnReadAndWriteOnly: TButton;
|
||||||
|
btnClientGeneratedPK: TButton;
|
||||||
procedure btnCRUDClick(Sender: TObject);
|
procedure btnCRUDClick(Sender: TObject);
|
||||||
procedure btnInheritanceClick(Sender: TObject);
|
procedure btnInheritanceClick(Sender: TObject);
|
||||||
procedure btnMultiThreadingClick(Sender: TObject);
|
procedure btnMultiThreadingClick(Sender: TObject);
|
||||||
@ -58,6 +60,8 @@ type
|
|||||||
procedure btnCRUDWithStringPKsClick(Sender: TObject);
|
procedure btnCRUDWithStringPKsClick(Sender: TObject);
|
||||||
procedure btnWithSpacesClick(Sender: TObject);
|
procedure btnWithSpacesClick(Sender: TObject);
|
||||||
procedure btnCountWithRQLClick(Sender: TObject);
|
procedure btnCountWithRQLClick(Sender: TObject);
|
||||||
|
procedure btnReadAndWriteOnlyClick(Sender: TObject);
|
||||||
|
procedure btnClientGeneratedPKClick(Sender: TObject);
|
||||||
private
|
private
|
||||||
procedure Log(const Value: string);
|
procedure Log(const Value: string);
|
||||||
procedure LoadCustomers;
|
procedure LoadCustomers;
|
||||||
@ -88,6 +92,19 @@ const
|
|||||||
CompanySuffix: array [0 .. 5] of string = ('Corp.', 'Inc.', 'Ltd.', 'Srl', 'SPA', 'doo');
|
CompanySuffix: array [0 .. 5] of string = ('Corp.', 'Inc.', 'Ltd.', 'Srl', 'SPA', 'doo');
|
||||||
Stuff: array [0 .. 4] of string = ('Burger', 'GAS', 'Motors', 'House', 'Boats');
|
Stuff: array [0 .. 4] of string = ('Burger', 'GAS', 'Motors', 'House', 'Boats');
|
||||||
|
|
||||||
|
procedure TMainForm.btnClientGeneratedPKClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
lCustomer: TCustomerPlainWithClientPK;
|
||||||
|
begin
|
||||||
|
Log('** OnBeforeInsert and SetPK');
|
||||||
|
lCustomer := TCustomerPlainWithClientPK.Create();
|
||||||
|
try
|
||||||
|
lCustomer.Store;
|
||||||
|
finally
|
||||||
|
lCustomer.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.btnCountWithRQLClick(Sender: TObject);
|
procedure TMainForm.btnCountWithRQLClick(Sender: TObject);
|
||||||
var
|
var
|
||||||
lRQL: string;
|
lRQL: string;
|
||||||
@ -137,7 +154,7 @@ begin
|
|||||||
Log('There are ' + TMVCActiveRecord.Count<TCustomer>().ToString + ' row/s for entity ' + TCustomer.ClassName);
|
Log('There are ' + TMVCActiveRecord.Count<TCustomer>().ToString + ' row/s for entity ' + TCustomer.ClassName);
|
||||||
lCustomer := TCustomer.Create;
|
lCustomer := TCustomer.Create;
|
||||||
try
|
try
|
||||||
Log('Entity ' + TCustomer.ClassName + ' is mapped to table ' + lCustomer.TableName);
|
Log('Entity ' + TCustomer.ClassName + ' is mapped to table ' + lCustomer.TableName);
|
||||||
lCustomer.CompanyName := 'Google Inc.';
|
lCustomer.CompanyName := 'Google Inc.';
|
||||||
lCustomer.City := 'Montain View, CA';
|
lCustomer.City := 'Montain View, CA';
|
||||||
lCustomer.Note := 'Hello there!';
|
lCustomer.Note := 'Hello there!';
|
||||||
@ -449,7 +466,12 @@ begin
|
|||||||
lTest.f_int2 := 2;
|
lTest.f_int2 := 2;
|
||||||
lTest.f_int4 := 4;
|
lTest.f_int4 := 4;
|
||||||
lTest.f_int8 := 8;
|
lTest.f_int8 := 8;
|
||||||
lTest.f_blob := TStringStream.Create('Hello World');
|
with TStreamWriter.Create(lTest.f_blob) do
|
||||||
|
try
|
||||||
|
write('Hello World');
|
||||||
|
finally
|
||||||
|
Free;
|
||||||
|
end;
|
||||||
lTest.Insert;
|
lTest.Insert;
|
||||||
Log('Inserting nulls');
|
Log('Inserting nulls');
|
||||||
finally
|
finally
|
||||||
@ -474,8 +496,7 @@ begin
|
|||||||
lTest.f_int2 := lTest.f_int2.Value + 2;
|
lTest.f_int2 := lTest.f_int2.Value + 2;
|
||||||
lTest.f_int4 := lTest.f_int4.Value + 4;
|
lTest.f_int4 := lTest.f_int4.Value + 4;
|
||||||
lTest.f_int8 := lTest.f_int8.Value + 8;
|
lTest.f_int8 := lTest.f_int8.Value + 8;
|
||||||
lTest.f_blob.Free;
|
lTest.f_blob.Size := 0;
|
||||||
lTest.f_blob := nil;
|
|
||||||
lTest.Update;
|
lTest.Update;
|
||||||
finally
|
finally
|
||||||
lTest.Free;
|
lTest.Free;
|
||||||
@ -494,7 +515,7 @@ begin
|
|||||||
Assert(not lTest.f_float4.HasValue);
|
Assert(not lTest.f_float4.HasValue);
|
||||||
Assert(not lTest.f_float8.HasValue);
|
Assert(not lTest.f_float8.HasValue);
|
||||||
Assert(not lTest.f_bool.HasValue);
|
Assert(not lTest.f_bool.HasValue);
|
||||||
Assert(not Assigned(lTest.f_blob), 'Blob contains a value when should not');
|
Assert(lTest.f_blob.Size = 0, 'Blob contains a value when should not');
|
||||||
TMVCActiveRecord.DeleteRQL(TNullablesTest, 'eq(f_int2,4)');
|
TMVCActiveRecord.DeleteRQL(TNullablesTest, 'eq(f_int2,4)');
|
||||||
finally
|
finally
|
||||||
lTest.Free;
|
lTest.Free;
|
||||||
@ -572,6 +593,65 @@ begin
|
|||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.btnReadAndWriteOnlyClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
lArtWO, lArtWO2: TArticleWithWriteOnlyFields;
|
||||||
|
lArtRO: TArticleWithReadOnlyFields;
|
||||||
|
lID: NullableInt32;
|
||||||
|
lArt: TArticle;
|
||||||
|
begin
|
||||||
|
lArtWO := TArticleWithWriteOnlyFields.Create();
|
||||||
|
try
|
||||||
|
lArtWO.Description := 'Description1';
|
||||||
|
lArtWO.Price := 12;
|
||||||
|
lArtWO.Insert;
|
||||||
|
Log('Stored TArticleWithWriteOnlyFields');
|
||||||
|
lID := lArtWO.ID;
|
||||||
|
|
||||||
|
lArt := TMVCActiveRecord.GetByPK<TArticle>(lID);
|
||||||
|
try
|
||||||
|
Assert(lArtWO.Description = lArt.Description);
|
||||||
|
Assert(lArtWO.Price = lArt.Price);
|
||||||
|
Log('Check Stored version of TArticleWithWriteOnlyFields');
|
||||||
|
|
||||||
|
Log('Reading data using TArticleWithReadOnlyFields');
|
||||||
|
lArtRO := TMVCActiveRecord.GetByPK<TArticleWithReadOnlyFields>(lID);
|
||||||
|
try
|
||||||
|
Assert(lArtRO.Description = lArt.Description);
|
||||||
|
Assert(lArtRO.Price = lArt.Price);
|
||||||
|
Log('Check Read data of TArticleWithWriteOnlyFields using TArticleWithReadOnlyFields');
|
||||||
|
finally
|
||||||
|
lArtRO.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Log('Reading data using TArticleWithWriteOnlyFields (???)');
|
||||||
|
lArtWO2 := TMVCActiveRecord.GetByPK<TArticleWithWriteOnlyFields>(lID);
|
||||||
|
try
|
||||||
|
Assert(lArtWO2.ID.ValueOrDefault = lID.ValueOrDefault);
|
||||||
|
Assert(lArtWO2.Description = '');
|
||||||
|
Assert(lArtWO2.Price = 0);
|
||||||
|
finally
|
||||||
|
lArtWO2.Free;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
lArt.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
lArtRO := TArticleWithReadOnlyFields.Create();
|
||||||
|
try
|
||||||
|
lArtRO.Description := 'Description1';
|
||||||
|
lArtRO.Price := 12;
|
||||||
|
ShowMessage('Now an exception will be raised...');
|
||||||
|
lArtRO.Insert; // exception here :-)
|
||||||
|
finally
|
||||||
|
lArtRO.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
finally
|
||||||
|
lArtWO.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.btnRelationsClick(Sender: TObject);
|
procedure TMainForm.btnRelationsClick(Sender: TObject);
|
||||||
var
|
var
|
||||||
lCustomer: TCustomerEx;
|
lCustomer: TCustomerEx;
|
||||||
@ -818,7 +898,7 @@ begin
|
|||||||
lCustomer := TCustomerWithTransient.Create;
|
lCustomer := TCustomerWithTransient.Create;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
'Code' and City will not be persisted because defined as 'transient'
|
'Code' will not be persisted because defined as 'transient'
|
||||||
}
|
}
|
||||||
lCustomer.Code := '1234';
|
lCustomer.Code := '1234';
|
||||||
lCustomer.CompanyName := 'Google Inc.';
|
lCustomer.CompanyName := 'Google Inc.';
|
||||||
|
@ -163,13 +163,13 @@
|
|||||||
</Excluded_Packages>
|
</Excluded_Packages>
|
||||||
</Delphi.Personality>
|
</Delphi.Personality>
|
||||||
<Deployment Version="3">
|
<Deployment Version="3">
|
||||||
<DeployFile LocalName="bin\activerecord_showcase.exe" Configuration="BUILD" Class="ProjectOutput">
|
<DeployFile LocalName="bin\activerecord_showcase.exe" Configuration="Debug" Class="ProjectOutput">
|
||||||
<Platform Name="Win32">
|
<Platform Name="Win32">
|
||||||
<RemoteName>activerecord_showcase.exe</RemoteName>
|
<RemoteName>activerecord_showcase.exe</RemoteName>
|
||||||
<Overwrite>true</Overwrite>
|
<Overwrite>true</Overwrite>
|
||||||
</Platform>
|
</Platform>
|
||||||
</DeployFile>
|
</DeployFile>
|
||||||
<DeployFile LocalName="bin\activerecord_showcase.exe" Configuration="Debug" Class="ProjectOutput">
|
<DeployFile LocalName="bin\activerecord_showcase.exe" Configuration="BUILD" Class="ProjectOutput">
|
||||||
<Platform Name="Win32">
|
<Platform Name="Win32">
|
||||||
<RemoteName>activerecord_showcase.exe</RemoteName>
|
<RemoteName>activerecord_showcase.exe</RemoteName>
|
||||||
<Overwrite>true</Overwrite>
|
<Overwrite>true</Overwrite>
|
||||||
|
@ -160,8 +160,11 @@
|
|||||||
<Source Name="MainSource">AuthenticateAuthorize.dpr</Source>
|
<Source Name="MainSource">AuthenticateAuthorize.dpr</Source>
|
||||||
</Source>
|
</Source>
|
||||||
<Excluded_Packages>
|
<Excluded_Packages>
|
||||||
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k170.bpl">Microsoft Office 2000 Beispiele für gekapselte Komponenten für Automatisierungsserver</Excluded_Packages>
|
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k260.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||||
<Excluded_Packages Name="$(BDSBIN)\dclofficexp170.bpl">Microsoft Office XP Beispiele für gekapselte Komponenten für Automation Server</Excluded_Packages>
|
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp260.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k260.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\dclofficexp260.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="C:\Program Files (x86)\FastReports\LibD26\dclfrxtee26.bpl">FastReport 6.0 Tee Components</Excluded_Packages>
|
||||||
</Excluded_Packages>
|
</Excluded_Packages>
|
||||||
</Delphi.Personality>
|
</Delphi.Personality>
|
||||||
<Deployment Version="3">
|
<Deployment Version="3">
|
||||||
|
@ -24,9 +24,10 @@ implementation
|
|||||||
|
|
||||||
{ TMVCAuthorization }
|
{ TMVCAuthorization }
|
||||||
|
|
||||||
procedure TAuthenticationSample.OnAuthentication(const AContext: TWebContext; const UserName: string; const Password: string;
|
procedure TAuthenticationSample.OnAuthentication(const AContext: TWebContext; const UserName: string;
|
||||||
UserRoles: System.Generics.Collections.TList<System.string>;
|
const Password: string;
|
||||||
var IsValid: Boolean; const SessionData: TSessionData);
|
UserRoles: System.Generics.Collections.TList<System.string>;
|
||||||
|
var IsValid: Boolean; const SessionData: TSessionData);
|
||||||
begin
|
begin
|
||||||
IsValid := UserName.Equals(Password); // hey!, this is just a demo!!!
|
IsValid := UserName.Equals(Password); // hey!, this is just a demo!!!
|
||||||
if IsValid then
|
if IsValid then
|
||||||
@ -53,9 +54,9 @@ end;
|
|||||||
|
|
||||||
procedure TAuthenticationSample.OnAuthorization
|
procedure TAuthenticationSample.OnAuthorization
|
||||||
(const AContext: TWebContext; UserRoles
|
(const AContext: TWebContext; UserRoles
|
||||||
: System.Generics.Collections.TList<System.string>;
|
: System.Generics.Collections.TList<System.string>;
|
||||||
const ControllerQualifiedClassName: string; const ActionName: string;
|
const ControllerQualifiedClassName: string; const ActionName: string;
|
||||||
var IsAuthorized: Boolean);
|
var IsAuthorized: Boolean);
|
||||||
begin
|
begin
|
||||||
IsAuthorized := False;
|
IsAuthorized := False;
|
||||||
if ActionName = 'Logout' then
|
if ActionName = 'Logout' then
|
||||||
@ -69,7 +70,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TAuthenticationSample.OnRequest(const AContext: TWebContext; const ControllerQualifiedClassName: string;
|
procedure TAuthenticationSample.OnRequest(const AContext: TWebContext; const ControllerQualifiedClassName: string;
|
||||||
const ActionName: string; var AuthenticationRequired: Boolean);
|
const ActionName: string; var AuthenticationRequired: Boolean);
|
||||||
begin
|
begin
|
||||||
AuthenticationRequired := ControllerQualifiedClassName =
|
AuthenticationRequired := ControllerQualifiedClassName =
|
||||||
'AppControllerU.TAdminController';
|
'AppControllerU.TAdminController';
|
||||||
|
@ -49,7 +49,9 @@ begin
|
|||||||
.AddMiddleware(TMVCBasicAuthenticationMiddleware.Create(TAuthenticationSample.Create))
|
.AddMiddleware(TMVCBasicAuthenticationMiddleware.Create(TAuthenticationSample.Create))
|
||||||
.AddMiddleware(TMVCStaticFilesMiddleware.Create(
|
.AddMiddleware(TMVCStaticFilesMiddleware.Create(
|
||||||
'/', { StaticFilesPath }
|
'/', { StaticFilesPath }
|
||||||
'..\..\www' { DocumentRoot }
|
'..\..\www', { DocumentRoot }
|
||||||
|
'index.html',
|
||||||
|
False { not serving a SPA }
|
||||||
));
|
));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Binary file not shown.
@ -2,7 +2,7 @@ object Form10: TForm10
|
|||||||
Left = 0
|
Left = 0
|
||||||
Top = 0
|
Top = 0
|
||||||
Caption = 'Form10'
|
Caption = 'Form10'
|
||||||
ClientHeight = 448
|
ClientHeight = 484
|
||||||
ClientWidth = 831
|
ClientWidth = 831
|
||||||
Color = clBtnFace
|
Color = clBtnFace
|
||||||
Font.Charset = DEFAULT_CHARSET
|
Font.Charset = DEFAULT_CHARSET
|
||||||
@ -14,9 +14,28 @@ object Form10: TForm10
|
|||||||
OnCreate = FormCreate
|
OnCreate = FormCreate
|
||||||
PixelsPerInch = 96
|
PixelsPerInch = 96
|
||||||
TextHeight = 13
|
TextHeight = 13
|
||||||
|
object Label2: TLabel
|
||||||
|
AlignWithMargins = True
|
||||||
|
Left = 3
|
||||||
|
Top = 3
|
||||||
|
Width = 825
|
||||||
|
Height = 39
|
||||||
|
Align = alTop
|
||||||
|
Alignment = taCenter
|
||||||
|
Caption =
|
||||||
|
'Please use the demo available in samples\jsonrpc_with_published_' +
|
||||||
|
'objects\'
|
||||||
|
Font.Charset = DEFAULT_CHARSET
|
||||||
|
Font.Color = clWindowText
|
||||||
|
Font.Height = -21
|
||||||
|
Font.Name = 'Tahoma'
|
||||||
|
Font.Style = []
|
||||||
|
ParentFont = False
|
||||||
|
Layout = tlCenter
|
||||||
|
end
|
||||||
object GroupBox1: TGroupBox
|
object GroupBox1: TGroupBox
|
||||||
Left = 8
|
Left = 8
|
||||||
Top = 16
|
Top = 48
|
||||||
Width = 815
|
Width = 815
|
||||||
Height = 124
|
Height = 124
|
||||||
Caption = 'Simple Types'
|
Caption = 'Simple Types'
|
||||||
@ -164,7 +183,7 @@ object Form10: TForm10
|
|||||||
end
|
end
|
||||||
object GroupBox2: TGroupBox
|
object GroupBox2: TGroupBox
|
||||||
Left = 8
|
Left = 8
|
||||||
Top = 146
|
Top = 178
|
||||||
Width = 489
|
Width = 489
|
||||||
Height = 159
|
Height = 159
|
||||||
Caption = 'Returning Objects'
|
Caption = 'Returning Objects'
|
||||||
@ -202,7 +221,7 @@ object Form10: TForm10
|
|||||||
end
|
end
|
||||||
object GroupBox3: TGroupBox
|
object GroupBox3: TGroupBox
|
||||||
Left = 509
|
Left = 509
|
||||||
Top = 146
|
Top = 178
|
||||||
Width = 314
|
Width = 314
|
||||||
Height = 294
|
Height = 294
|
||||||
Caption = 'Returning Datasets'
|
Caption = 'Returning Datasets'
|
||||||
@ -239,7 +258,7 @@ object Form10: TForm10
|
|||||||
end
|
end
|
||||||
object GroupBox4: TGroupBox
|
object GroupBox4: TGroupBox
|
||||||
Left = 8
|
Left = 8
|
||||||
Top = 311
|
Top = 343
|
||||||
Width = 489
|
Width = 489
|
||||||
Height = 129
|
Height = 129
|
||||||
Caption = 'Passing Objects as parameters'
|
Caption = 'Passing Objects as parameters'
|
||||||
@ -298,7 +317,7 @@ object Form10: TForm10
|
|||||||
object DataSource1: TDataSource
|
object DataSource1: TDataSource
|
||||||
DataSet = FDMemTable1
|
DataSet = FDMemTable1
|
||||||
Left = 767
|
Left = 767
|
||||||
Top = 184
|
Top = 216
|
||||||
end
|
end
|
||||||
object FDMemTable1: TFDMemTable
|
object FDMemTable1: TFDMemTable
|
||||||
FetchOptions.AssignedValues = [evMode]
|
FetchOptions.AssignedValues = [evMode]
|
||||||
@ -309,7 +328,7 @@ object Form10: TForm10
|
|||||||
UpdateOptions.CheckRequired = False
|
UpdateOptions.CheckRequired = False
|
||||||
UpdateOptions.AutoCommitUpdates = True
|
UpdateOptions.AutoCommitUpdates = True
|
||||||
Left = 767
|
Left = 767
|
||||||
Top = 248
|
Top = 280
|
||||||
object FDMemTable1Code: TIntegerField
|
object FDMemTable1Code: TIntegerField
|
||||||
FieldName = 'Code'
|
FieldName = 'Code'
|
||||||
end
|
end
|
||||||
|
@ -66,6 +66,7 @@ type
|
|||||||
btnInvalid2: TButton;
|
btnInvalid2: TButton;
|
||||||
btnNotification: TButton;
|
btnNotification: TButton;
|
||||||
btnInvalidMethod: TButton;
|
btnInvalidMethod: TButton;
|
||||||
|
Label2: TLabel;
|
||||||
procedure btnSubstractClick(Sender: TObject);
|
procedure btnSubstractClick(Sender: TObject);
|
||||||
procedure btnReverseStringClick(Sender: TObject);
|
procedure btnReverseStringClick(Sender: TObject);
|
||||||
procedure edtGetCustomersClick(Sender: TObject);
|
procedure edtGetCustomersClick(Sender: TObject);
|
||||||
@ -138,25 +139,25 @@ end;
|
|||||||
|
|
||||||
procedure TForm10.btnInvalid1Click(Sender: TObject);
|
procedure TForm10.btnInvalid1Click(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCNotification;
|
||||||
lResp: IJSONRPCResponse;
|
lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
lReq := TJSONRPCRequest.Create;
|
lReq := TJSONRPCNotification.Create;
|
||||||
lReq.Method := 'invalidmethod1';
|
lReq.Method := 'invalidmethod1';
|
||||||
lReq.Params.Add(1);
|
lReq.Params.Add(1);
|
||||||
lResp := FExecutor.ExecuteRequest(lReq);
|
lResp := FExecutor.ExecuteNotification(lReq);
|
||||||
ShowMessage(lResp.Error.ErrMessage);
|
ShowMessage(lResp.Error.ErrMessage);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TForm10.btnInvalid2Click(Sender: TObject);
|
procedure TForm10.btnInvalid2Click(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCNotification;
|
||||||
lResp: IJSONRPCResponse;
|
lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
lReq := TJSONRPCRequest.Create;
|
lReq := TJSONRPCNotification.Create;
|
||||||
lReq.Method := 'invalidmethod2';
|
lReq.Method := 'invalidmethod2';
|
||||||
lReq.Params.Add(1);
|
lReq.Params.Add(1);
|
||||||
lResp := FExecutor.ExecuteRequest(lReq);
|
lResp := FExecutor.ExecuteNotification(lReq);
|
||||||
ShowMessage(lResp.Error.ErrMessage);
|
ShowMessage(lResp.Error.ErrMessage);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -420,6 +420,37 @@ object MainForm: TMainForm
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
object TabSheet3: TTabSheet
|
||||||
|
Caption = 'Hooks Demo'
|
||||||
|
ImageIndex = 2
|
||||||
|
object btnDoNothing: TButton
|
||||||
|
Left = 24
|
||||||
|
Top = 24
|
||||||
|
Width = 145
|
||||||
|
Height = 33
|
||||||
|
Caption = 'Do Nothing'
|
||||||
|
TabOrder = 0
|
||||||
|
OnClick = btnDoNothingClick
|
||||||
|
end
|
||||||
|
object btnDoNothingError: TButton
|
||||||
|
Left = 24
|
||||||
|
Top = 63
|
||||||
|
Width = 145
|
||||||
|
Height = 33
|
||||||
|
Caption = 'Do Nothing With Errors'
|
||||||
|
TabOrder = 1
|
||||||
|
OnClick = btnDoNothingErrorClick
|
||||||
|
end
|
||||||
|
object btnNotExistent: TButton
|
||||||
|
Left = 24
|
||||||
|
Top = 102
|
||||||
|
Width = 145
|
||||||
|
Height = 33
|
||||||
|
Caption = 'Invalid Method'
|
||||||
|
TabOrder = 2
|
||||||
|
OnClick = btnNotExistentClick
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
object DataSource1: TDataSource
|
object DataSource1: TDataSource
|
||||||
DataSet = FDMemTable1
|
DataSet = FDMemTable1
|
||||||
|
@ -81,6 +81,10 @@ type
|
|||||||
Edit2: TEdit;
|
Edit2: TEdit;
|
||||||
btnSubtractWithNamedParams: TButton;
|
btnSubtractWithNamedParams: TButton;
|
||||||
Edit3: TEdit;
|
Edit3: TEdit;
|
||||||
|
TabSheet3: TTabSheet;
|
||||||
|
btnDoNothing: TButton;
|
||||||
|
btnDoNothingError: TButton;
|
||||||
|
btnNotExistent: TButton;
|
||||||
procedure btnSubstractClick(Sender: TObject);
|
procedure btnSubstractClick(Sender: TObject);
|
||||||
procedure btnReverseStringClick(Sender: TObject);
|
procedure btnReverseStringClick(Sender: TObject);
|
||||||
procedure edtGetCustomersClick(Sender: TObject);
|
procedure edtGetCustomersClick(Sender: TObject);
|
||||||
@ -97,9 +101,13 @@ type
|
|||||||
procedure btnFloatsTestsClick(Sender: TObject);
|
procedure btnFloatsTestsClick(Sender: TObject);
|
||||||
procedure btnWithJSONClick(Sender: TObject);
|
procedure btnWithJSONClick(Sender: TObject);
|
||||||
procedure btnSubtractWithNamedParamsClick(Sender: TObject);
|
procedure btnSubtractWithNamedParamsClick(Sender: TObject);
|
||||||
|
procedure btnDoNothingClick(Sender: TObject);
|
||||||
|
procedure btnNotExistentClick(Sender: TObject);
|
||||||
|
procedure btnDoNothingErrorClick(Sender: TObject);
|
||||||
private
|
private
|
||||||
FExecutor: IMVCJSONRPCExecutor;
|
FExecutor: IMVCJSONRPCExecutor;
|
||||||
FExecutor2: IMVCJSONRPCExecutor;
|
FExecutor2: IMVCJSONRPCExecutor;
|
||||||
|
FExecutor3: IMVCJSONRPCExecutor;
|
||||||
public
|
public
|
||||||
{ Public declarations }
|
{ Public declarations }
|
||||||
end;
|
end;
|
||||||
@ -121,6 +129,7 @@ uses
|
|||||||
|
|
||||||
{$R *.dfm}
|
{$R *.dfm}
|
||||||
|
|
||||||
|
|
||||||
procedure TMainForm.btnAddDayClick(Sender: TObject);
|
procedure TMainForm.btnAddDayClick(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCRequest;
|
||||||
@ -160,6 +169,22 @@ begin
|
|||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.btnDoNothingClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
lReq: IJSONRPCNotification;
|
||||||
|
begin
|
||||||
|
lReq := TJSONRPCNotification.Create('DoSomething');
|
||||||
|
FExecutor3.ExecuteNotification(lReq);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.btnDoNothingErrorClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
lReq: IJSONRPCNotification;
|
||||||
|
begin
|
||||||
|
lReq := TJSONRPCNotification.Create('DoSomethingWithError');
|
||||||
|
FExecutor3.ExecuteNotification(lReq);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.btnFloatsTestsClick(Sender: TObject);
|
procedure TMainForm.btnFloatsTestsClick(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCRequest;
|
||||||
@ -209,25 +234,25 @@ end;
|
|||||||
|
|
||||||
procedure TMainForm.btnInvalid1Click(Sender: TObject);
|
procedure TMainForm.btnInvalid1Click(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCNotification;
|
||||||
lResp: IJSONRPCResponse;
|
lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
lReq := TJSONRPCRequest.Create;
|
lReq := TJSONRPCNotification.Create;
|
||||||
lReq.Method := 'invalidmethod1';
|
lReq.Method := 'invalidmethod1';
|
||||||
lReq.Params.Add(1);
|
lReq.Params.Add(1);
|
||||||
lResp := FExecutor.ExecuteRequest(lReq);
|
lResp := FExecutor.ExecuteNotification(lReq);
|
||||||
ShowMessage(lResp.Error.ErrMessage);
|
ShowMessage(lResp.Error.ErrMessage);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.btnInvalid2Click(Sender: TObject);
|
procedure TMainForm.btnInvalid2Click(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCNotification;
|
||||||
lResp: IJSONRPCResponse;
|
lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
lReq := TJSONRPCRequest.Create;
|
lReq := TJSONRPCNotification.Create;
|
||||||
lReq.Method := 'invalidmethod2';
|
lReq.Method := 'invalidmethod2';
|
||||||
lReq.Params.Add(1);
|
lReq.Params.Add(1);
|
||||||
lResp := FExecutor.ExecuteRequest(lReq);
|
lResp := FExecutor.ExecuteNotification(lReq);
|
||||||
ShowMessage(lResp.Error.ErrMessage);
|
ShowMessage(lResp.Error.ErrMessage);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -361,6 +386,14 @@ begin
|
|||||||
ShowMessage(lPerson.ToJSON(False));
|
ShowMessage(lPerson.ToJSON(False));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.btnNotExistentClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
lReq: IJSONRPCNotification;
|
||||||
|
begin
|
||||||
|
lReq := TJSONRPCNotification.Create('blablabla');
|
||||||
|
FExecutor3.ExecuteNotification(lReq);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.edtGetCustomersClick(Sender: TObject);
|
procedure TMainForm.edtGetCustomersClick(Sender: TObject);
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCRequest;
|
lReq: IJSONRPCRequest;
|
||||||
@ -379,6 +412,8 @@ procedure TMainForm.FormCreate(Sender: TObject);
|
|||||||
begin
|
begin
|
||||||
FExecutor := TMVCJSONRPCExecutor.Create('http://localhost:8080/jsonrpc');
|
FExecutor := TMVCJSONRPCExecutor.Create('http://localhost:8080/jsonrpc');
|
||||||
FExecutor2 := TMVCJSONRPCExecutor.Create('http://localhost:8080/rpcdatamodule');
|
FExecutor2 := TMVCJSONRPCExecutor.Create('http://localhost:8080/rpcdatamodule');
|
||||||
|
FExecutor3 := TMVCJSONRPCExecutor.Create('http://localhost:8080/jsonrpchooks');
|
||||||
|
|
||||||
dtNextMonday.Date := Date;
|
dtNextMonday.Date := Date;
|
||||||
|
|
||||||
// these are the methods to handle http headers in JSONRPC
|
// these are the methods to handle http headers in JSONRPC
|
||||||
@ -389,6 +424,7 @@ begin
|
|||||||
FExecutor.ClearHTTPHeaders;
|
FExecutor.ClearHTTPHeaders;
|
||||||
Assert(FExecutor.HTTPHeadersCount = 0);
|
Assert(FExecutor.HTTPHeadersCount = 0);
|
||||||
FExecutor.AddHTTPHeader(TNetHeader.Create('x-token', TGUID.NewGuid.ToString));
|
FExecutor.AddHTTPHeader(TNetHeader.Create('x-token', TGUID.NewGuid.ToString));
|
||||||
|
PageControl1.ActivePageIndex := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
@ -59,6 +59,12 @@ begin
|
|||||||
Result := TdmMain.Create(nil);
|
Result := TdmMain.Create(nil);
|
||||||
end, '/rpcdatamodule');
|
end, '/rpcdatamodule');
|
||||||
|
|
||||||
|
FMVC.PublishObject(
|
||||||
|
function: TObject
|
||||||
|
begin
|
||||||
|
Result := TMyObjectWithHooks.Create;
|
||||||
|
end, '/jsonrpchooks');
|
||||||
|
|
||||||
FMVC.AddMiddleware(TCORSMiddleware.Create());
|
FMVC.AddMiddleware(TCORSMiddleware.Create());
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -52,10 +52,10 @@ type
|
|||||||
function GetCustomersDataset: TFDMemTable;
|
function GetCustomersDataset: TFDMemTable;
|
||||||
function GetPeopleDataset: TFDMemTable;
|
function GetPeopleDataset: TFDMemTable;
|
||||||
public
|
public
|
||||||
procedure OnBeforeCall(const JSONRequest: TJDOJsonObject);
|
procedure OnBeforeCall(const Context: TWebContext; const JSONRequest: TJDOJsonObject);
|
||||||
procedure OnBeforeRouting(const JSON: TJDOJsonObject);
|
procedure OnBeforeRouting(const Context: TWebContext; const JSON: TJDOJsonObject);
|
||||||
procedure OnBeforeSendResponse(
|
procedure OnAfterCallHook(
|
||||||
const JSONResponse: TJDOJsonObject);
|
const Context: TWebContext; const JSONResponse: TJDOJsonObject);
|
||||||
public
|
public
|
||||||
[MVCDoc('You know, returns aValue1 - aValue2')]
|
[MVCDoc('You know, returns aValue1 - aValue2')]
|
||||||
function Subtract(Value1, Value2: Integer): Integer;
|
function Subtract(Value1, Value2: Integer): Integer;
|
||||||
@ -79,6 +79,17 @@ type
|
|||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TMyObjectWithHooks = class
|
||||||
|
public
|
||||||
|
// hooks
|
||||||
|
procedure OnBeforeRoutingHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
procedure OnBeforeCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
procedure OnAfterCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
// dummy method
|
||||||
|
procedure DoSomething;
|
||||||
|
procedure DoSomethingWithError;
|
||||||
|
end;
|
||||||
|
|
||||||
TUtils = class sealed
|
TUtils = class sealed
|
||||||
class function JSONObjectAs<T: constructor, class>(const JSON: TJsonObject): T;
|
class function JSONObjectAs<T: constructor, class>(const JSON: TJsonObject): T;
|
||||||
end;
|
end;
|
||||||
@ -300,26 +311,53 @@ end;
|
|||||||
|
|
||||||
{ TMyObjectWithHooks }
|
{ TMyObjectWithHooks }
|
||||||
|
|
||||||
procedure TMyObject.OnBeforeCall(const JSONRequest: TJDOJsonObject);
|
procedure TMyObject.OnBeforeCall(const Context: TWebContext; const JSONRequest: TJDOJsonObject);
|
||||||
begin
|
begin
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeCall >> ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeCall >> ', 'jsonrpc');
|
||||||
Log.Info(JSONRequest.ToJSON(false), 'jsonrpc');
|
Log.Info(JSONRequest.ToJSON(false), 'jsonrpc');
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeCall << ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeCall << ', 'jsonrpc');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyObject.OnBeforeRouting(const JSON: TJDOJsonObject);
|
procedure TMyObject.OnBeforeRouting(const Context: TWebContext; const JSON: TJDOJsonObject);
|
||||||
begin
|
begin
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeRouting >> ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeRouting >> ', 'jsonrpc');
|
||||||
Log.Info(JSON.ToJSON(false), 'jsonrpc');
|
Log.Info(JSON.ToJSON(false), 'jsonrpc');
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeRouting << ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeRouting << ', 'jsonrpc');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyObject.OnBeforeSendResponse(
|
procedure TMyObject.OnAfterCallHook(
|
||||||
const JSONResponse: TJDOJsonObject);
|
const Context: TWebContext; const JSONResponse: TJDOJsonObject);
|
||||||
begin
|
begin
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeSendResponse >> ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeSendResponse >> ', 'jsonrpc');
|
||||||
Log.Info(JSONResponse.ToJSON(false), 'jsonrpc');
|
Log.Info(JSONResponse.ToJSON(false), 'jsonrpc');
|
||||||
Log.Info('TMyObjectWithHooks.OnBeforeSendResponse << ', 'jsonrpc');
|
Log.Info('TMyObjectWithHooks.OnBeforeSendResponse << ', 'jsonrpc');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TMyObjectWithHooks }
|
||||||
|
|
||||||
|
procedure TMyObjectWithHooks.DoSomething;
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyObjectWithHooks.DoSomethingWithError;
|
||||||
|
begin
|
||||||
|
raise Exception.Create('Boom');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyObjectWithHooks.OnAfterCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyObjectWithHooks.OnBeforeCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMyObjectWithHooks.OnBeforeRoutingHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
@ -6,6 +6,7 @@ uses
|
|||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
MVCFramework.Logger,
|
MVCFramework.Logger,
|
||||||
MVCFramework.Commons,
|
MVCFramework.Commons,
|
||||||
|
MVCFramework.Console,
|
||||||
MVCFramework.REPLCommandsHandlerU,
|
MVCFramework.REPLCommandsHandlerU,
|
||||||
Web.ReqMulti,
|
Web.ReqMulti,
|
||||||
Web.WebReq,
|
Web.WebReq,
|
||||||
@ -65,12 +66,17 @@ begin
|
|||||||
{ more info about ListenQueue
|
{ more info about ListenQueue
|
||||||
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_ListenQueue.html }
|
http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_ListenQueue.html }
|
||||||
LServer.ListenQueue := 200;
|
LServer.ListenQueue := 200;
|
||||||
|
SaveColors;
|
||||||
|
TextColor(Yellow);
|
||||||
WriteLn('Write "quit" or "exit" to shutdown the server');
|
WriteLn('Write "quit" or "exit" to shutdown the server');
|
||||||
|
RestoreSavedColors;
|
||||||
repeat
|
repeat
|
||||||
if lCmd.IsEmpty then
|
if lCmd.IsEmpty then
|
||||||
begin
|
begin
|
||||||
|
SaveColors;
|
||||||
|
TextColor(Green);
|
||||||
Write('-> ');
|
Write('-> ');
|
||||||
|
RestoreSavedColors;
|
||||||
ReadLn(lCmd)
|
ReadLn(lCmd)
|
||||||
end;
|
end;
|
||||||
try
|
try
|
||||||
@ -85,7 +91,10 @@ begin
|
|||||||
end;
|
end;
|
||||||
THandleCommandResult.Unknown:
|
THandleCommandResult.Unknown:
|
||||||
begin
|
begin
|
||||||
|
SaveColors;
|
||||||
|
TextColor(Red);
|
||||||
REPLEmit('Unknown command: ' + lCmd);
|
REPLEmit('Unknown command: ' + lCmd);
|
||||||
|
RestoreSavedColors;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
@ -101,6 +110,7 @@ end;
|
|||||||
begin
|
begin
|
||||||
ReportMemoryLeaksOnShutdown := True;
|
ReportMemoryLeaksOnShutdown := True;
|
||||||
IsMultiThread := True;
|
IsMultiThread := True;
|
||||||
|
TextColor(TConsoleColor.White);
|
||||||
try
|
try
|
||||||
if WebRequestHandler <> nil then
|
if WebRequestHandler <> nil then
|
||||||
WebRequestHandler.WebModuleClass := WebModuleClass;
|
WebRequestHandler.WebModuleClass := WebModuleClass;
|
||||||
|
@ -58,7 +58,12 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
TMVCActiveRecordClass = class of TMVCActiveRecord;
|
TMVCActiveRecordClass = class of TMVCActiveRecord;
|
||||||
TMVCActiveRecordFieldOption = (foPrimaryKey, foAutoGenerated, foTransient);
|
TMVCActiveRecordFieldOption = (
|
||||||
|
foPrimaryKey, { it's the primary key of the mapped table }
|
||||||
|
foAutoGenerated, { not written, read - similar to readonly }
|
||||||
|
foTransient, { not written, not read }
|
||||||
|
foReadOnly, { not written, read }
|
||||||
|
foWriteOnly); { written, not read }
|
||||||
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
|
TMVCActiveRecordFieldOptions = set of TMVCActiveRecordFieldOption;
|
||||||
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
|
TMVCEntityAction = (eaCreate, eaRetrieve, eaUpdate, eaDelete);
|
||||||
TMVCEntityActions = set of TMVCEntityAction;
|
TMVCEntityActions = set of TMVCEntityAction;
|
||||||
@ -81,7 +86,7 @@ type
|
|||||||
|
|
||||||
TFieldInfo = class
|
TFieldInfo = class
|
||||||
public
|
public
|
||||||
// TableName: string;
|
// TableName: string;
|
||||||
FieldName: string;
|
FieldName: string;
|
||||||
FieldOptions: TMVCActiveRecordFieldOptions;
|
FieldOptions: TMVCActiveRecordFieldOptions;
|
||||||
DataTypeName: string;
|
DataTypeName: string;
|
||||||
@ -91,11 +96,13 @@ type
|
|||||||
|
|
||||||
TFieldsMap = class(TObjectDictionary<TRTTIField, TFieldInfo>)
|
TFieldsMap = class(TObjectDictionary<TRTTIField, TFieldInfo>)
|
||||||
private
|
private
|
||||||
fNonTransientFieldsCount: Integer;
|
fWritableFieldsCount: Integer;
|
||||||
|
fReadableFieldsCount: Integer;
|
||||||
public
|
public
|
||||||
constructor Create;
|
constructor Create;
|
||||||
procedure EndUpdates;
|
procedure EndUpdates;
|
||||||
property NonTransientFieldsCount: Integer read fNonTransientFieldsCount;
|
property WritableFieldsCount: Integer read fWritableFieldsCount;
|
||||||
|
property ReadableFieldsCount: Integer read fWritableFieldsCount;
|
||||||
function GetInfoByFieldName(const FieldName: string): TFieldInfo;
|
function GetInfoByFieldName(const FieldName: string): TFieldInfo;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -155,7 +162,7 @@ type
|
|||||||
function GetPrimaryKeyIsAutogenerated: Boolean;
|
function GetPrimaryKeyIsAutogenerated: Boolean;
|
||||||
procedure SetPrimaryKeyIsAutogenerated(const Value: Boolean);
|
procedure SetPrimaryKeyIsAutogenerated(const Value: Boolean);
|
||||||
function GetPrimaryKeyFieldType: TFieldType;
|
function GetPrimaryKeyFieldType: TFieldType;
|
||||||
procedure SetTableName(const Value: String);
|
procedure SetTableName(const Value: string);
|
||||||
protected
|
protected
|
||||||
fRTTIType: TRttiInstanceType;
|
fRTTIType: TRttiInstanceType;
|
||||||
fProps: TArray<TRTTIField>;
|
fProps: TArray<TRTTIField>;
|
||||||
@ -282,7 +289,7 @@ type
|
|||||||
procedure AddChildren(const ChildObject: TObject);
|
procedure AddChildren(const ChildObject: TObject);
|
||||||
procedure RemoveChildren(const ChildObject: TObject);
|
procedure RemoveChildren(const ChildObject: TObject);
|
||||||
[MVCDoNotSerialize]
|
[MVCDoNotSerialize]
|
||||||
property TableName: String read fTableName write SetTableName;
|
property TableName: string read fTableName write SetTableName;
|
||||||
[MVCDoNotSerialize]
|
[MVCDoNotSerialize]
|
||||||
property PrimaryKeyIsAutogenerated: Boolean read GetPrimaryKeyIsAutogenerated write SetPrimaryKeyIsAutogenerated;
|
property PrimaryKeyIsAutogenerated: Boolean read GetPrimaryKeyIsAutogenerated write SetPrimaryKeyIsAutogenerated;
|
||||||
class function GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64;
|
class function GetByPK(const aClass: TMVCActiveRecordClass; const aValue: int64;
|
||||||
@ -452,7 +459,7 @@ type
|
|||||||
// end-capabilities
|
// end-capabilities
|
||||||
function CreateSQLWhereByRQL(const RQL: string; const Mapping: TMVCFieldsMapping;
|
function CreateSQLWhereByRQL(const RQL: string; const Mapping: TMVCFieldsMapping;
|
||||||
const UseArtificialLimit: Boolean = True;
|
const UseArtificialLimit: Boolean = True;
|
||||||
const UseFilterOnly: Boolean = False
|
const UseFilterOnly: Boolean = false
|
||||||
): string; virtual; abstract;
|
): string; virtual; abstract;
|
||||||
function CreateSelectSQL(const TableName: string; const Map: TFieldsMap;
|
function CreateSelectSQL(const TableName: string; const Map: TFieldsMap;
|
||||||
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
|
const PKFieldName: string; const PKOptions: TMVCActiveRecordFieldOptions): string; virtual; abstract;
|
||||||
@ -603,6 +610,13 @@ begin
|
|||||||
lName := aName.ToLower;
|
lName := aName.ToLower;
|
||||||
lConnKeyName := GetKeyName(lName);
|
lConnKeyName := GetKeyName(lName);
|
||||||
|
|
||||||
|
{ If the transaction is not started, initialize TxIsolation as ReadCommitted }
|
||||||
|
if aConnection.Transaction = nil then
|
||||||
|
begin
|
||||||
|
{ needed for Delphi 10.4 Sydney+ }
|
||||||
|
aConnection.TxOptions.Isolation := TFDTxIsolation.xiReadCommitted;
|
||||||
|
end;
|
||||||
|
|
||||||
fMREW.BeginWrite;
|
fMREW.BeginWrite;
|
||||||
try
|
try
|
||||||
lConnHolder := TConnHolder.Create;
|
lConnHolder := TConnHolder.Create;
|
||||||
@ -955,7 +969,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
fPrimaryKeyFieldType := ftLargeInt;
|
fPrimaryKeyFieldType := ftLargeInt;
|
||||||
end
|
end
|
||||||
else if lPrimaryFieldTypeAsStr.EndsWith('integer') then
|
else if lPrimaryFieldTypeAsStr.EndsWith('integer') or lPrimaryFieldTypeAsStr.EndsWith('int32') then
|
||||||
begin
|
begin
|
||||||
fPrimaryKeyFieldType := ftInteger;
|
fPrimaryKeyFieldType := ftInteger;
|
||||||
end
|
end
|
||||||
@ -975,9 +989,8 @@ begin
|
|||||||
continue;
|
continue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TODO -oDanieleT -cGeneral : Definire TFieldInfo per tute le info del field }
|
|
||||||
lFieldInfo := TFieldInfo.Create;
|
lFieldInfo := TFieldInfo.Create;
|
||||||
//lFieldInfo.TableName := fTableName;
|
// lFieldInfo.TableName := fTableName;
|
||||||
lFieldInfo.FieldName := MVCTableFieldAttribute(lAttribute).FieldName;
|
lFieldInfo.FieldName := MVCTableFieldAttribute(lAttribute).FieldName;
|
||||||
lFieldInfo.FieldOptions := MVCTableFieldAttribute(lAttribute).FieldOptions;
|
lFieldInfo.FieldOptions := MVCTableFieldAttribute(lAttribute).FieldOptions;
|
||||||
lFieldInfo.DataTypeName := MVCTableFieldAttribute(lAttribute).DataTypeName;
|
lFieldInfo.DataTypeName := MVCTableFieldAttribute(lAttribute).DataTypeName;
|
||||||
@ -1010,10 +1023,11 @@ begin
|
|||||||
OnValidation(TMVCEntityAction.eaCreate);
|
OnValidation(TMVCEntityAction.eaCreate);
|
||||||
OnBeforeInsert;
|
OnBeforeInsert;
|
||||||
OnBeforeInsertOrUpdate;
|
OnBeforeInsertOrUpdate;
|
||||||
if fMap.NonTransientFieldsCount = 0 then
|
if fMap.WritableFieldsCount = 0 then
|
||||||
begin
|
begin
|
||||||
raise EMVCActiveRecord.CreateFmt
|
raise EMVCActiveRecord.CreateFmt
|
||||||
('Cannot insert an entity if all fields are transient. Class [%s] mapped on table [%s]', [ClassName, fTableName]);
|
('Cannot insert an entity if all fields are not writable or transient. Class [%s] mapped on table [%s]',
|
||||||
|
[ClassName, fTableName]);
|
||||||
end;
|
end;
|
||||||
if (foAutoGenerated in fPrimaryKeyOptions) then
|
if (foAutoGenerated in fPrimaryKeyOptions) then
|
||||||
begin
|
begin
|
||||||
@ -1049,7 +1063,7 @@ begin
|
|||||||
lSQL := Self.SQLGenerator.CreateSelectCount(fTableName);
|
lSQL := Self.SQLGenerator.CreateSelectCount(fTableName);
|
||||||
if not RQL.IsEmpty then
|
if not RQL.IsEmpty then
|
||||||
begin
|
begin
|
||||||
lSQL := lSQL + fSQLGenerator.CreateSQLWhereByRQL(RQL, GetMapping, False, True);
|
lSQL := lSQL + fSQLGenerator.CreateSQLWhereByRQL(RQL, GetMapping, false, True);
|
||||||
end;
|
end;
|
||||||
Result := GetScalar(lSQL, []);
|
Result := GetScalar(lSQL, []);
|
||||||
end;
|
end;
|
||||||
@ -1135,19 +1149,24 @@ class function TMVCActiveRecordHelper.GetByPK<T>(const aValue: int64;
|
|||||||
const RaiseExceptionIfNotFound: Boolean = True): T;
|
const RaiseExceptionIfNotFound: Boolean = True): T;
|
||||||
var
|
var
|
||||||
lActiveRecord: TMVCActiveRecord;
|
lActiveRecord: TMVCActiveRecord;
|
||||||
|
lLoaded: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := T.Create;
|
Result := T.Create;
|
||||||
lActiveRecord := TMVCActiveRecord(Result);
|
lActiveRecord := TMVCActiveRecord(Result);
|
||||||
if not lActiveRecord.LoadByPK(aValue) then
|
|
||||||
|
try
|
||||||
|
lLoaded := lActiveRecord.LoadByPK(aValue);
|
||||||
|
except
|
||||||
|
FreeAndNil(Result);
|
||||||
|
raise;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if not lLoaded then
|
||||||
begin
|
begin
|
||||||
Result.Free;
|
FreeAndNil(Result);
|
||||||
if RaiseExceptionIfNotFound then
|
if RaiseExceptionIfNotFound then
|
||||||
begin
|
begin
|
||||||
raise EMVCActiveRecordNotFound.Create('Data not found');
|
raise EMVCActiveRecordNotFound.Create('Data not found');
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
Result := nil;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2176,23 +2195,14 @@ begin
|
|||||||
if fPrimaryKey.GetValue(Self).Kind = tkRecord then
|
if fPrimaryKey.GetValue(Self).Kind = tkRecord then
|
||||||
begin
|
begin
|
||||||
lPKValue := fPrimaryKey.GetValue(Self);
|
lPKValue := fPrimaryKey.GetValue(Self);
|
||||||
if lPKValue.IsType<NullableInt32> then
|
if lPKValue.IsType<NullableInt32> and aValue.IsType<NullableInt32>() then
|
||||||
begin
|
begin
|
||||||
if aValue.IsType<UInt32> then
|
if aValue.IsType<UInt32> then
|
||||||
begin
|
begin
|
||||||
lPKValue := TValue.From<NullableInt32>(IntToNullableInt(aValue.AsInteger));
|
lPKValue := TValue.From<NullableInt32>(IntToNullableInt(aValue.AsInteger));
|
||||||
end;
|
end;
|
||||||
//
|
|
||||||
// if aValue.AsType<NullableInt32>().HasValue then
|
|
||||||
// begin
|
|
||||||
// lPKValue := aValue;
|
|
||||||
// end
|
|
||||||
// else
|
|
||||||
// begin
|
|
||||||
// lPKValue.AsType<NullableInt32>().Clear;
|
|
||||||
// end;
|
|
||||||
end
|
end
|
||||||
else if lPKValue.IsType<NullableInt64> then
|
else if lPKValue.IsType<NullableInt64> and aValue.IsType<NullableInt64>() then
|
||||||
begin
|
begin
|
||||||
if aValue.AsType<NullableInt64>().HasValue then
|
if aValue.AsType<NullableInt64>().HasValue then
|
||||||
begin
|
begin
|
||||||
@ -2203,7 +2213,18 @@ begin
|
|||||||
lPKValue.AsType<NullableInt64>().Clear;
|
lPKValue.AsType<NullableInt64>().Clear;
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if lPKValue.IsType<NullableUInt32> then
|
else if lPKValue.IsType<NullableString> and aValue.IsType<NullableString>() then
|
||||||
|
begin
|
||||||
|
if aValue.AsType<NullableString>().HasValue then
|
||||||
|
begin
|
||||||
|
lPKValue := aValue;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
lPKValue.AsType<NullableString>().Clear;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else if lPKValue.IsType<NullableUInt32> and aValue.IsType<NullableUInt32>() then
|
||||||
begin
|
begin
|
||||||
if aValue.AsType<NullableUInt32>().HasValue then
|
if aValue.AsType<NullableUInt32>().HasValue then
|
||||||
begin
|
begin
|
||||||
@ -2214,7 +2235,7 @@ begin
|
|||||||
lPKValue.AsType<NullableUInt32>().Clear;
|
lPKValue.AsType<NullableUInt32>().Clear;
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if lPKValue.IsType<NullableUInt64> then
|
else if lPKValue.IsType<NullableUInt64> and aValue.IsType<NullableUInt64>() then
|
||||||
begin
|
begin
|
||||||
if aValue.AsType<NullableUInt64>().HasValue then
|
if aValue.AsType<NullableUInt64>().HasValue then
|
||||||
begin
|
begin
|
||||||
@ -2226,7 +2247,9 @@ begin
|
|||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise EMVCActiveRecord.Create('Invalid type for primary key');
|
begin
|
||||||
|
raise EMVCActiveRecord.Create('Invalid type for primary key [HINT] Double check if TypeInfo(PK) is equal to TypeInfo(Value)');
|
||||||
|
end;
|
||||||
fPrimaryKey.SetValue(Self, lPKValue);
|
fPrimaryKey.SetValue(Self, lPKValue);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -2247,7 +2270,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMVCActiveRecord.SetTableName(const Value: String);
|
procedure TMVCActiveRecord.SetTableName(const Value: string);
|
||||||
begin
|
begin
|
||||||
fTableName := Value;
|
fTableName := Value;
|
||||||
end;
|
end;
|
||||||
@ -2361,7 +2384,7 @@ begin
|
|||||||
OnValidation(TMVCEntityAction.eaUpdate);
|
OnValidation(TMVCEntityAction.eaUpdate);
|
||||||
OnBeforeUpdate;
|
OnBeforeUpdate;
|
||||||
OnBeforeInsertOrUpdate;
|
OnBeforeInsertOrUpdate;
|
||||||
if fMap.NonTransientFieldsCount = 0 then
|
if fMap.WritableFieldsCount = 0 then
|
||||||
begin
|
begin
|
||||||
raise EMVCActiveRecord.CreateFmt
|
raise EMVCActiveRecord.CreateFmt
|
||||||
('Cannot update an entity if all fields are transient. Class [%s] mapped on table [%s]', [ClassName, fTableName]);
|
('Cannot update an entity if all fields are transient. Class [%s] mapped on table [%s]', [ClassName, fTableName]);
|
||||||
@ -2378,7 +2401,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
fChildren := TObjectList<TObject>.Create(True);
|
fChildren := TObjectList<TObject>.Create(True);
|
||||||
end;
|
end;
|
||||||
if not(fChildren.Contains(ChildObject)) and (not (ChildObject = Self)) then
|
if not(fChildren.Contains(ChildObject)) and (not(ChildObject = Self)) then
|
||||||
begin
|
begin
|
||||||
fChildren.Add(ChildObject);
|
fChildren.Add(ChildObject);
|
||||||
end;
|
end;
|
||||||
@ -2659,7 +2682,14 @@ begin
|
|||||||
Result := Copy(Result, 1, Length(Result) - Length(Delimiter));
|
Result := Copy(Result, 1, Length(Result) - Length(Delimiter));
|
||||||
if not PKFieldName.IsEmpty then
|
if not PKFieldName.IsEmpty then
|
||||||
begin
|
begin
|
||||||
Result := GetFieldNameForSQL(PKFieldName) + ', ' + Result;
|
if not Result.IsEmpty then
|
||||||
|
begin
|
||||||
|
Result := GetFieldNameForSQL(PKFieldName) + ', ' + Result
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Result := GetFieldNameForSQL(PKFieldName)
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -2744,20 +2774,27 @@ end;
|
|||||||
constructor TFieldsMap.Create;
|
constructor TFieldsMap.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create([doOwnsValues]);
|
inherited Create([doOwnsValues]);
|
||||||
fNonTransientFieldsCount := 0;
|
fWritableFieldsCount := -1;
|
||||||
|
fReadableFieldsCount := -1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TFieldsMap.EndUpdates;
|
procedure TFieldsMap.EndUpdates;
|
||||||
var
|
var
|
||||||
lPair: TPair<TRTTIField, TFieldInfo>;
|
lPair: TPair<TRTTIField, TFieldInfo>;
|
||||||
begin
|
begin
|
||||||
fNonTransientFieldsCount := 0;
|
fWritableFieldsCount := 0;
|
||||||
|
fReadableFieldsCount := 0;
|
||||||
for lPair in Self do
|
for lPair in Self do
|
||||||
begin
|
begin
|
||||||
lPair.Value.EndUpdates;
|
lPair.Value.EndUpdates;
|
||||||
if not(foTransient in lPair.Value.FieldOptions) then
|
// if not(foTransient in lPair.Value.FieldOptions) then
|
||||||
|
if lPair.Value.Writeable then
|
||||||
begin
|
begin
|
||||||
Inc(fNonTransientFieldsCount);
|
Inc(fWritableFieldsCount);
|
||||||
|
end;
|
||||||
|
if lPair.Value.Readable then
|
||||||
|
begin
|
||||||
|
Inc(fReadableFieldsCount);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2781,8 +2818,18 @@ end;
|
|||||||
|
|
||||||
procedure TFieldInfo.EndUpdates;
|
procedure TFieldInfo.EndUpdates;
|
||||||
begin
|
begin
|
||||||
Writeable := (not FieldName.IsEmpty) and (not((foAutoGenerated in FieldOptions) or (foTransient in FieldOptions)));
|
if FieldName.IsEmpty then
|
||||||
Readable := not(foTransient in FieldOptions) and (not FieldName.IsEmpty);
|
begin
|
||||||
|
Writeable := false;
|
||||||
|
Readable := false;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Writeable := (not (foReadOnly in FieldOptions)) and (not((foAutoGenerated in FieldOptions) or (foTransient in FieldOptions)));
|
||||||
|
Writeable := ((FieldOptions * [foReadOnly, foTransient, foAutoGenerated]) = []);
|
||||||
|
// Readable := (not (foWriteOnly in FieldOptions)) and (not(foTransient in FieldOptions));
|
||||||
|
Readable := (FieldOptions * [foWriteOnly, foTransient]) = [];
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
|
@ -38,7 +38,8 @@ type
|
|||||||
IMVCJSONRPCExecutor = interface
|
IMVCJSONRPCExecutor = interface
|
||||||
['{55415094-9D28-4707-AEC5-5FCF925E82BC}']
|
['{55415094-9D28-4707-AEC5-5FCF925E82BC}']
|
||||||
function ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
function ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
||||||
procedure ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification);
|
function ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification): IJSONRPCResponse;
|
||||||
|
function HTTPResponse: IHTTPResponse;
|
||||||
// Http headers handling
|
// Http headers handling
|
||||||
procedure AddHTTPHeader(const aNetHeader: TNetHeader);
|
procedure AddHTTPHeader(const aNetHeader: TNetHeader);
|
||||||
procedure ClearHTTPHeaders;
|
procedure ClearHTTPHeaders;
|
||||||
@ -56,17 +57,19 @@ type
|
|||||||
|
|
||||||
TMVCJSONRPCExecutor = class(TInterfacedObject, IMVCJSONRPCExecutor)
|
TMVCJSONRPCExecutor = class(TInterfacedObject, IMVCJSONRPCExecutor)
|
||||||
private
|
private
|
||||||
FURL: string;
|
fURL: string;
|
||||||
FHTTP: THTTPClient;
|
fHTTP: THTTPClient;
|
||||||
FRaiseExceptionOnError: Boolean;
|
fRaiseExceptionOnError: Boolean;
|
||||||
FHTTPRequestHeaders: TList<TNetHeader>;
|
fHTTPRequestHeaders: TList<TNetHeader>;
|
||||||
|
fHTTPResponse: IHTTPResponse;
|
||||||
fOnReceiveResponse: TProc<IJSONRPCObject, IJSONRPCObject>;
|
fOnReceiveResponse: TProc<IJSONRPCObject, IJSONRPCObject>;
|
||||||
fOnSendCommand: TProc<IJSONRPCObject>;
|
fOnSendCommand: TProc<IJSONRPCObject>;
|
||||||
function GetHTTPRequestHeaders: TList<TNetHeader>;
|
function GetHTTPRequestHeaders: TList<TNetHeader>;
|
||||||
protected
|
protected
|
||||||
|
function HTTPResponse: IHTTPResponse;
|
||||||
function InternalExecute(const aJSONRPCObject: IJSONRPCObject): IJSONRPCResponse;
|
function InternalExecute(const aJSONRPCObject: IJSONRPCObject): IJSONRPCResponse;
|
||||||
function ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
function ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
||||||
procedure ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification);
|
function ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification): IJSONRPCResponse;
|
||||||
// Http headers handling
|
// Http headers handling
|
||||||
procedure AddHTTPHeader(const aNetHeader: TNetHeader);
|
procedure AddHTTPHeader(const aNetHeader: TNetHeader);
|
||||||
procedure ClearHTTPHeaders;
|
procedure ClearHTTPHeaders;
|
||||||
@ -128,26 +131,26 @@ end;
|
|||||||
|
|
||||||
procedure TMVCJSONRPCExecutor.ClearHTTPHeaders;
|
procedure TMVCJSONRPCExecutor.ClearHTTPHeaders;
|
||||||
begin
|
begin
|
||||||
if Assigned(FHTTPRequestHeaders) then
|
if Assigned(fHTTPRequestHeaders) then
|
||||||
begin
|
begin
|
||||||
FHTTPRequestHeaders.Clear;
|
fHTTPRequestHeaders.Clear;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMVCJSONRPCExecutor.ConfigureHTTPClient(
|
function TMVCJSONRPCExecutor.ConfigureHTTPClient(
|
||||||
const aConfigProc: TProc<THTTPClient>): IMVCJSONRPCExecutor;
|
const aConfigProc: TProc<THTTPClient>): IMVCJSONRPCExecutor;
|
||||||
begin
|
begin
|
||||||
aConfigProc(FHTTP);
|
aConfigProc(fHTTP);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TMVCJSONRPCExecutor.Create(const aURL: string; const aRaiseExceptionOnError: Boolean = True);
|
constructor TMVCJSONRPCExecutor.Create(const aURL: string; const aRaiseExceptionOnError: Boolean = True);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FRaiseExceptionOnError := aRaiseExceptionOnError;
|
fRaiseExceptionOnError := aRaiseExceptionOnError;
|
||||||
FURL := aURL;
|
fURL := aURL;
|
||||||
FHTTP := THTTPClient.Create;
|
fHTTP := THTTPClient.Create;
|
||||||
FHTTP.ResponseTimeout := MaxInt;
|
fHTTP.ResponseTimeout := MaxInt;
|
||||||
FHTTPRequestHeaders := nil;
|
fHTTPRequestHeaders := nil;
|
||||||
SetOnReceiveResponse(nil)
|
SetOnReceiveResponse(nil)
|
||||||
.SetOnReceiveData(nil)
|
.SetOnReceiveData(nil)
|
||||||
.SetOnNeedClientCertificate(nil)
|
.SetOnNeedClientCertificate(nil)
|
||||||
@ -156,15 +159,24 @@ end;
|
|||||||
|
|
||||||
destructor TMVCJSONRPCExecutor.Destroy;
|
destructor TMVCJSONRPCExecutor.Destroy;
|
||||||
begin
|
begin
|
||||||
FHTTP.Free;
|
fHTTP.Free;
|
||||||
FHTTPRequestHeaders.Free;
|
fHTTPRequestHeaders.Free;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMVCJSONRPCExecutor.ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification);
|
function TMVCJSONRPCExecutor.ExecuteNotification(const aJSONRPCNotification: IJSONRPCNotification): IJSONRPCResponse;
|
||||||
|
// var
|
||||||
|
// lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
if InternalExecute(aJSONRPCNotification as TJSONRPCObject) <> nil then
|
Result := InternalExecute(aJSONRPCNotification as TJSONRPCObject);
|
||||||
raise EMVCJSONRPCException.Create('A "notification" cannot returns a response. Use ExecuteRequest instead.');
|
// if Assigned(lResp) then
|
||||||
|
// begin
|
||||||
|
//
|
||||||
|
// end;
|
||||||
|
// if InternalExecute(aJSONRPCNotification as TJSONRPCObject) <> nil then
|
||||||
|
// begin
|
||||||
|
// raise EMVCJSONRPCException.Create('A "notification" cannot returns a response. Use ExecuteRequest instead.');
|
||||||
|
// end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMVCJSONRPCExecutor.ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
function TMVCJSONRPCExecutor.ExecuteRequest(const aJSONRPCRequest: IJSONRPCRequest): IJSONRPCResponse;
|
||||||
@ -174,18 +186,18 @@ end;
|
|||||||
|
|
||||||
function TMVCJSONRPCExecutor.GetHTTPRequestHeaders: TList<TNetHeader>;
|
function TMVCJSONRPCExecutor.GetHTTPRequestHeaders: TList<TNetHeader>;
|
||||||
begin
|
begin
|
||||||
if not Assigned(FHTTPRequestHeaders) then
|
if not Assigned(fHTTPRequestHeaders) then
|
||||||
begin
|
begin
|
||||||
FHTTPRequestHeaders := TList<TNetHeader>.Create;
|
fHTTPRequestHeaders := TList<TNetHeader>.Create;
|
||||||
end;
|
end;
|
||||||
Result := FHTTPRequestHeaders;
|
Result := fHTTPRequestHeaders;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMVCJSONRPCExecutor.HTTPHeadersCount: Integer;
|
function TMVCJSONRPCExecutor.HTTPHeadersCount: Integer;
|
||||||
begin
|
begin
|
||||||
if Assigned(FHTTPRequestHeaders) then
|
if Assigned(fHTTPRequestHeaders) then
|
||||||
begin
|
begin
|
||||||
Result := FHTTPRequestHeaders.Count;
|
Result := fHTTPRequestHeaders.Count;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
@ -201,9 +213,9 @@ var
|
|||||||
lCustomHeaders: TNetHeaders;
|
lCustomHeaders: TNetHeaders;
|
||||||
begin
|
begin
|
||||||
lCustomHeaders := [];
|
lCustomHeaders := [];
|
||||||
if Assigned(FHTTPRequestHeaders) then
|
if Assigned(fHTTPRequestHeaders) then
|
||||||
begin
|
begin
|
||||||
lCustomHeaders := FHTTPRequestHeaders.ToArray;
|
lCustomHeaders := fHTTPRequestHeaders.ToArray;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := nil;
|
Result := nil;
|
||||||
@ -214,37 +226,49 @@ begin
|
|||||||
begin
|
begin
|
||||||
fOnSendCommand(aJSONRPCObject);
|
fOnSendCommand(aJSONRPCObject);
|
||||||
end;
|
end;
|
||||||
lHttpResp := FHTTP.Post(FURL, lSS, nil, [TNetHeader.Create('content-type', 'application/json;charset=utf8'),
|
fHTTPResponse := nil;
|
||||||
|
lHttpResp := fHTTP.Post(fURL, lSS, nil, [TNetHeader.Create('content-type', 'application/json;charset=utf8'),
|
||||||
TNetHeader.Create('accept', 'application/json;charset=utf8')] + lCustomHeaders);
|
TNetHeader.Create('accept', 'application/json;charset=utf8')] + lCustomHeaders);
|
||||||
if (lHttpResp.StatusCode <> HTTP_STATUS.NoContent) then
|
fHTTPResponse := lHttpResp;
|
||||||
|
if lHttpResp.StatusCode = HTTP_STATUS.NoContent then
|
||||||
|
begin
|
||||||
|
lJSONRPCResponse := TJSONRPCNullResponse.Create;
|
||||||
|
end
|
||||||
|
else
|
||||||
begin
|
begin
|
||||||
lJSONRPCResponse := TJSONRPCResponse.Create;
|
lJSONRPCResponse := TJSONRPCResponse.Create;
|
||||||
lJSONRPCResponse.AsJSONString := lHttpResp.ContentAsString;
|
lJSONRPCResponse.AsJSONString := lHttpResp.ContentAsString;
|
||||||
if Assigned(fOnReceiveResponse) then
|
|
||||||
begin
|
|
||||||
fOnReceiveResponse(aJSONRPCObject, lJSONRPCResponse);
|
|
||||||
end;
|
|
||||||
if Assigned(lJSONRPCResponse.Error) and FRaiseExceptionOnError then
|
|
||||||
raise EMVCJSONRPCException.CreateFmt('[REMOTE EXCEPTION][%d]: %s',
|
|
||||||
[lJSONRPCResponse.Error.Code, lJSONRPCResponse.Error.ErrMessage]);
|
|
||||||
Result := lJSONRPCResponse;
|
|
||||||
end;
|
end;
|
||||||
|
if Assigned(fOnReceiveResponse) then
|
||||||
|
begin
|
||||||
|
fOnReceiveResponse(aJSONRPCObject, lJSONRPCResponse);
|
||||||
|
end;
|
||||||
|
fHTTPResponse := lHttpResp;
|
||||||
|
if Assigned(lJSONRPCResponse.Error) and fRaiseExceptionOnError then
|
||||||
|
raise EMVCJSONRPCException.CreateFmt('[REMOTE EXCEPTION][%d]: %s',
|
||||||
|
[lJSONRPCResponse.Error.Code, lJSONRPCResponse.Error.ErrMessage]);
|
||||||
|
Result := lJSONRPCResponse;
|
||||||
finally
|
finally
|
||||||
lSS.Free;
|
lSS.Free;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TMVCJSONRPCExecutor.HTTPResponse: IHTTPResponse;
|
||||||
|
begin
|
||||||
|
Result := fHTTPResponse;
|
||||||
|
end;
|
||||||
|
|
||||||
function TMVCJSONRPCExecutor.SetOnNeedClientCertificate(const aOnNeedClientCertificate: TNeedClientCertificateEvent)
|
function TMVCJSONRPCExecutor.SetOnNeedClientCertificate(const aOnNeedClientCertificate: TNeedClientCertificateEvent)
|
||||||
: IMVCJSONRPCExecutor;
|
: IMVCJSONRPCExecutor;
|
||||||
begin
|
begin
|
||||||
FHTTP.OnNeedClientCertificate := aOnNeedClientCertificate;
|
fHTTP.OnNeedClientCertificate := aOnNeedClientCertificate;
|
||||||
Result := Self;
|
Result := Self;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMVCJSONRPCExecutor.SetOnReceiveData(
|
function TMVCJSONRPCExecutor.SetOnReceiveData(
|
||||||
const aOnReceiveData: TReceiveDataEvent): IMVCJSONRPCExecutor;
|
const aOnReceiveData: TReceiveDataEvent): IMVCJSONRPCExecutor;
|
||||||
begin
|
begin
|
||||||
FHTTP.OnReceiveData := aOnReceiveData;
|
fHTTP.OnReceiveData := aOnReceiveData;
|
||||||
Result := Self;
|
Result := Self;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -265,7 +289,7 @@ end;
|
|||||||
function TMVCJSONRPCExecutor.SetOnValidateServerCertificate(const aOnValidateServerCertificate
|
function TMVCJSONRPCExecutor.SetOnValidateServerCertificate(const aOnValidateServerCertificate
|
||||||
: TValidateCertificateEvent): IMVCJSONRPCExecutor;
|
: TValidateCertificateEvent): IMVCJSONRPCExecutor;
|
||||||
begin
|
begin
|
||||||
FHTTP.OnValidateServerCertificate := aOnValidateServerCertificate;
|
fHTTP.OnValidateServerCertificate := aOnValidateServerCertificate;
|
||||||
Result := Self;
|
Result := Self;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -58,9 +58,9 @@ const
|
|||||||
const
|
const
|
||||||
JSONRPC_HOOKS_ON_BEFORE_ROUTING = 'OnBeforeRoutingHook';
|
JSONRPC_HOOKS_ON_BEFORE_ROUTING = 'OnBeforeRoutingHook';
|
||||||
JSONRPC_HOOKS_ON_BEFORE_CALL = 'OnBeforeCallHook';
|
JSONRPC_HOOKS_ON_BEFORE_CALL = 'OnBeforeCallHook';
|
||||||
JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE = 'OnBeforeSendResponseHook';
|
JSONRPC_HOOKS_ON_AFTER_CALL = 'OnAfterCallHook';
|
||||||
JSONRPC_HOOKS_METHOD_NAMES: array [0 .. 2] of string = (JSONRPC_HOOKS_ON_BEFORE_ROUTING,
|
JSONRPC_HOOKS_METHOD_NAMES: array [0 .. 2] of string = (JSONRPC_HOOKS_ON_BEFORE_ROUTING,
|
||||||
JSONRPC_HOOKS_ON_BEFORE_CALL, JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE);
|
JSONRPC_HOOKS_ON_BEFORE_CALL, JSONRPC_HOOKS_ON_AFTER_CALL);
|
||||||
|
|
||||||
{
|
{
|
||||||
http://www.jsonrpc.org/historical/json-rpc-over-http.html#response-codes
|
http://www.jsonrpc.org/historical/json-rpc-over-http.html#response-codes
|
||||||
@ -261,8 +261,8 @@ type
|
|||||||
FResult: TValue;
|
FResult: TValue;
|
||||||
FError: TJSONRPCResponseError;
|
FError: TJSONRPCResponseError;
|
||||||
FID: TValue;
|
FID: TValue;
|
||||||
function GetResult: TValue;
|
|
||||||
protected
|
protected
|
||||||
|
function GetResult: TValue;
|
||||||
function GetJSON: TJDOJsonObject; override;
|
function GetJSON: TJDOJsonObject; override;
|
||||||
procedure SetJSON(const JSON: TJDOJsonObject); override;
|
procedure SetJSON(const JSON: TJDOJsonObject); override;
|
||||||
procedure SetID(const Value: TValue);
|
procedure SetID(const Value: TValue);
|
||||||
@ -281,6 +281,29 @@ type
|
|||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TJSONRPCNullResponse = class(TJSONRPCObject, IJSONRPCResponse)
|
||||||
|
private
|
||||||
|
FError: TJSONRPCResponseError;
|
||||||
|
procedure RaiseErrorForNullObject;
|
||||||
|
protected
|
||||||
|
function GetJSONString: string; override;
|
||||||
|
procedure SetJsonString(const Value: string); override;
|
||||||
|
function GetJSON: TJDOJsonObject; override;
|
||||||
|
procedure SetJSON(const JSON: TJDOJsonObject); override;
|
||||||
|
procedure SetID(const Value: TValue);
|
||||||
|
procedure SetResult(const Value: TValue);
|
||||||
|
procedure SetError(const Value: TJSONRPCResponseError);
|
||||||
|
function GetError: TJSONRPCResponseError;
|
||||||
|
function GetID: TValue;
|
||||||
|
function GetResult: TValue;
|
||||||
|
function ResultAsJSONObject: TJDOJsonObject;
|
||||||
|
function ResultAsJSONArray: TJDOJsonArray;
|
||||||
|
function IsError: Boolean;
|
||||||
|
property Result: TValue read GetResult write SetResult;
|
||||||
|
property Error: TJSONRPCResponseError read GetError write SetError;
|
||||||
|
property RequestID: TValue read GetID write SetID;
|
||||||
|
end;
|
||||||
|
|
||||||
EMVCJSONRPCInvalidVersion = class(Exception)
|
EMVCJSONRPCInvalidVersion = class(Exception)
|
||||||
|
|
||||||
end;
|
end;
|
||||||
@ -357,7 +380,7 @@ type
|
|||||||
function CanBeRemotelyInvoked(const RTTIMethod: TRTTIMethod): Boolean;
|
function CanBeRemotelyInvoked(const RTTIMethod: TRTTIMethod): Boolean;
|
||||||
procedure ForEachInvokableMethod(const aProc: TProc<TRTTIMethod>);
|
procedure ForEachInvokableMethod(const aProc: TProc<TRTTIMethod>);
|
||||||
procedure TryToCallMethod(const aRTTIType: TRttiType; const MethodName: string;
|
procedure TryToCallMethod(const aRTTIType: TRttiType; const MethodName: string;
|
||||||
const Parameter: TJDOJsonObject; const ParameterName: string);
|
const Parameter: TJDOJsonObject);
|
||||||
public
|
public
|
||||||
[MVCPath]
|
[MVCPath]
|
||||||
[MVCHTTPMethods([httpPOST])]
|
[MVCHTTPMethods([httpPOST])]
|
||||||
@ -984,6 +1007,7 @@ var
|
|||||||
lClass: TJSONRPCProxyGeneratorClass;
|
lClass: TJSONRPCProxyGeneratorClass;
|
||||||
lGenerator: TJSONRPCProxyGenerator;
|
lGenerator: TJSONRPCProxyGenerator;
|
||||||
lRTTI: TRTTIContext;
|
lRTTI: TRTTIContext;
|
||||||
|
lContentType: string;
|
||||||
begin
|
begin
|
||||||
lLanguage := Context.Request.Params['language'].ToLower;
|
lLanguage := Context.Request.Params['language'].ToLower;
|
||||||
if lLanguage.IsEmpty then
|
if lLanguage.IsEmpty then
|
||||||
@ -991,6 +1015,15 @@ begin
|
|||||||
lLanguage := 'delphi';
|
lLanguage := 'delphi';
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if Context.Request.QueryStringParamExists('content-type') then
|
||||||
|
begin
|
||||||
|
lContentType := Context.Request.Params['content-type'];
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
lContentType := 'text/plain';
|
||||||
|
end;
|
||||||
|
|
||||||
if not Assigned(GProxyGeneratorsRegister) then
|
if not Assigned(GProxyGeneratorsRegister) then
|
||||||
begin
|
begin
|
||||||
raise EMVCJSONRPCException.Create
|
raise EMVCJSONRPCException.Create
|
||||||
@ -1013,7 +1046,7 @@ begin
|
|||||||
lGenerator.VisitMethod(aRTTIMethod);
|
lGenerator.VisitMethod(aRTTIMethod);
|
||||||
end);
|
end);
|
||||||
lGenerator.EndGeneration();
|
lGenerator.EndGeneration();
|
||||||
Context.Response.ContentType := 'text/plain';
|
Context.Response.ContentType := lContentType;
|
||||||
Render(lGenerator.GetCode);
|
Render(lGenerator.GetCode);
|
||||||
finally
|
finally
|
||||||
lRTTI.Free;
|
lRTTI.Free;
|
||||||
@ -1069,7 +1102,11 @@ var
|
|||||||
lReqID: TValue;
|
lReqID: TValue;
|
||||||
lJSON: TJDOJsonObject;
|
lJSON: TJDOJsonObject;
|
||||||
lJSONResp: TJDOJsonObject;
|
lJSONResp: TJDOJsonObject;
|
||||||
|
lBeforeCallHookHasBeenInvoked: Boolean;
|
||||||
|
lAfterCallHookHasBeenInvoked: Boolean;
|
||||||
begin
|
begin
|
||||||
|
lBeforeCallHookHasBeenInvoked := False;
|
||||||
|
lAfterCallHookHasBeenInvoked := False;
|
||||||
lRTTIType := nil;
|
lRTTIType := nil;
|
||||||
lReqID := TValue.Empty;
|
lReqID := TValue.Empty;
|
||||||
SetLength(lParamsToInject, 0);
|
SetLength(lParamsToInject, 0);
|
||||||
@ -1079,20 +1116,21 @@ begin
|
|||||||
lJSON := StrToJSONObject(Context.Request.Body);
|
lJSON := StrToJSONObject(Context.Request.Body);
|
||||||
try
|
try
|
||||||
if not Assigned(lJSON) then
|
if not Assigned(lJSON) then
|
||||||
|
begin
|
||||||
raise EMVCJSONRPCParseError.Create;
|
raise EMVCJSONRPCParseError.Create;
|
||||||
|
end;
|
||||||
lRTTIType := lRTTI.GetType(fRPCInstance.ClassType);
|
lRTTIType := lRTTI.GetType(fRPCInstance.ClassType);
|
||||||
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_ROUTING, lJSON, 'JSON');
|
|
||||||
lJSONRPCReq := CreateRequest(lJSON);
|
lJSONRPCReq := CreateRequest(lJSON);
|
||||||
lMethod := lJSONRPCReq.Method;
|
lMethod := lJSONRPCReq.Method;
|
||||||
|
|
||||||
if SameText(lMethod, JSONRPC_HOOKS_ON_BEFORE_ROUTING) or
|
if IsReservedMethodName(lMethod) then
|
||||||
SameText(lMethod, JSONRPC_HOOKS_ON_BEFORE_CALL) or
|
|
||||||
SameText(lMethod, JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE) then
|
|
||||||
begin
|
begin
|
||||||
raise EMVCJSONRPCInvalidRequest.Create
|
raise EMVCJSONRPCInvalidRequest.CreateFmt
|
||||||
('Requested method name is reserved and cannot be called remotely');
|
('Requested method name [%s] is reserved and cannot be called remotely', [lMethod]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_ROUTING, lJSON);
|
||||||
|
|
||||||
if lJSONRPCReq.RequestType = TJSONRPCRequestType.Request then
|
if lJSONRPCReq.RequestType = TJSONRPCRequestType.Request then
|
||||||
begin
|
begin
|
||||||
if lJSONRPCReq.RequestID.IsEmpty then
|
if lJSONRPCReq.RequestID.IsEmpty then
|
||||||
@ -1108,14 +1146,6 @@ begin
|
|||||||
|
|
||||||
if Assigned(lRTTIMethod) then
|
if Assigned(lRTTIMethod) then
|
||||||
begin
|
begin
|
||||||
if not CanBeRemotelyInvoked(lRTTIMethod) then
|
|
||||||
begin
|
|
||||||
LogW(Format
|
|
||||||
('Method "%s" cannot be called. Only public functions or procedures can be called. ',
|
|
||||||
[lMethod]));
|
|
||||||
raise EMVCJSONRPCMethodNotFound.Create(lMethod);
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (lJSONRPCReq.RequestType = TJSONRPCRequestType.Request) and
|
if (lJSONRPCReq.RequestType = TJSONRPCRequestType.Request) and
|
||||||
(lRTTIMethod.MethodKind <> mkFunction) then
|
(lRTTIMethod.MethodKind <> mkFunction) then
|
||||||
begin
|
begin
|
||||||
@ -1130,6 +1160,14 @@ begin
|
|||||||
('Cannot call a function using a JSON-RPC notification. [HINT] Use requests for functions and notifications for procedures');
|
('Cannot call a function using a JSON-RPC notification. [HINT] Use requests for functions and notifications for procedures');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if not CanBeRemotelyInvoked(lRTTIMethod) then
|
||||||
|
begin
|
||||||
|
LogW(Format
|
||||||
|
('Method [%s] cannot remotely invoked. Only public functions or procedures can be called.',
|
||||||
|
[lMethod]));
|
||||||
|
raise EMVCJSONRPCMethodNotFound.Create(lMethod);
|
||||||
|
end;
|
||||||
|
|
||||||
try
|
try
|
||||||
lJSONRPCReq.FillParameters(lJSON, lRTTIMethod);
|
lJSONRPCReq.FillParameters(lJSON, lRTTIMethod);
|
||||||
except
|
except
|
||||||
@ -1140,9 +1178,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
lJSONResp := nil;
|
||||||
|
// try
|
||||||
|
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_CALL, lJSON);
|
||||||
|
lBeforeCallHookHasBeenInvoked := True;
|
||||||
try
|
try
|
||||||
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_CALL, lJSON, 'JSONRequest');
|
LogD('[JSON-RPC][CALL][' + CALL_TYPE[lRTTIMethod.MethodKind] + '][' + fRPCInstance.ClassName + '.' +
|
||||||
LogD('[JSON-RPC][CALL][' + CALL_TYPE[lRTTIMethod.MethodKind] + '] ' + lRTTIMethod.Name);
|
lRTTIMethod.Name + ']');
|
||||||
lRes := lRTTIMethod.Invoke(fRPCInstance, lJSONRPCReq.Params.ToArray);
|
lRes := lRTTIMethod.Invoke(fRPCInstance, lJSONRPCReq.Params.ToArray);
|
||||||
except
|
except
|
||||||
on E: EInvalidCast do
|
on E: EInvalidCast do
|
||||||
@ -1165,21 +1207,30 @@ begin
|
|||||||
lJSONRPCResponse := CreateResponse(lJSONRPCReq.RequestID, lRes);
|
lJSONRPCResponse := CreateResponse(lJSONRPCReq.RequestID, lRes);
|
||||||
ResponseStatus(200);
|
ResponseStatus(200);
|
||||||
lJSONResp := lJSONRPCResponse.AsJSON;
|
lJSONResp := lJSONRPCResponse.AsJSON;
|
||||||
try
|
|
||||||
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE, lJSONResp,
|
|
||||||
'JSONResponse');
|
|
||||||
Render(lJSONResp);
|
|
||||||
except
|
|
||||||
try
|
|
||||||
lJSONResp.Free;
|
|
||||||
except
|
|
||||||
// do nothing
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
else
|
else
|
||||||
raise EMVCJSONRPCException.Create('Invalid RequestType');
|
raise EMVCJSONRPCException.Create('Invalid RequestType');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// finally
|
||||||
|
// if lBeforeCallHookHasBeenInvoked then
|
||||||
|
// begin
|
||||||
|
// TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_AFTER_CALL, lJSONResp);
|
||||||
|
// lAfterCallHookHasBeenInvoked := True;
|
||||||
|
// end;
|
||||||
|
// if lJSONResp <> nil then
|
||||||
|
// begin
|
||||||
|
// try
|
||||||
|
// Render(lJSONResp);
|
||||||
|
// except
|
||||||
|
// try
|
||||||
|
// lJSONResp.Free;
|
||||||
|
// except
|
||||||
|
// // do nothing
|
||||||
|
// end;
|
||||||
|
// end;
|
||||||
|
// end;
|
||||||
|
// end;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
@ -1187,9 +1238,8 @@ begin
|
|||||||
[lMethod, fRPCInstance.QualifiedClassName]));
|
[lMethod, fRPCInstance.QualifiedClassName]));
|
||||||
raise EMVCJSONRPCMethodNotFound.Create(lMethod);
|
raise EMVCJSONRPCMethodNotFound.Create(lMethod);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
finally
|
finally
|
||||||
lJSON.Free;
|
FreeAndNil(lJSON);
|
||||||
end;
|
end;
|
||||||
except
|
except
|
||||||
on E: EMVCJSONRPCErrorResponse do
|
on E: EMVCJSONRPCErrorResponse do
|
||||||
@ -1218,28 +1268,33 @@ begin
|
|||||||
JSONRPC_ERR_SERVER_ERROR_LOWERBOUND .. JSONRPC_ERR_SERVER_ERROR_UPPERBOUND:
|
JSONRPC_ERR_SERVER_ERROR_LOWERBOUND .. JSONRPC_ERR_SERVER_ERROR_UPPERBOUND:
|
||||||
ResponseStatus(500);
|
ResponseStatus(500);
|
||||||
end;
|
end;
|
||||||
lJSON := CreateError(lReqID, E.JSONRPCErrorCode, E.Message);
|
lJSONResp := CreateError(lReqID, E.JSONRPCErrorCode, E.Message);
|
||||||
try
|
|
||||||
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE, lJSON, 'JSONResponse');
|
|
||||||
Render(lJSON, False);
|
|
||||||
finally
|
|
||||||
lJSON.Free;
|
|
||||||
end;
|
|
||||||
LogE(Format('[JSON-RPC][CLS %s][ERR %d][MSG "%s"]', [E.ClassName, E.JSONRPCErrorCode,
|
LogE(Format('[JSON-RPC][CLS %s][ERR %d][MSG "%s"]', [E.ClassName, E.JSONRPCErrorCode,
|
||||||
E.Message]));
|
E.Message]));
|
||||||
end;
|
end;
|
||||||
on Ex: Exception do // use another name for exception variable, otherwise E is nil!!
|
on Ex: Exception do // use another name for exception variable, otherwise E is nil!!
|
||||||
begin
|
begin
|
||||||
lJSON := CreateError(lReqID, 0, Ex.Message);
|
lJSONResp := CreateError(lReqID, 0, Ex.Message);
|
||||||
try
|
|
||||||
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_BEFORE_SEND_RESPONSE, lJSON, 'JSONResponse');
|
|
||||||
Render(lJSON, False);
|
|
||||||
finally
|
|
||||||
lJSON.Free;
|
|
||||||
end;
|
|
||||||
LogE(Format('[JSON-RPC][CLS %s][MSG "%s"]', [Ex.ClassName, Ex.Message]));
|
LogE(Format('[JSON-RPC][CLS %s][MSG "%s"]', [Ex.ClassName, Ex.Message]));
|
||||||
end;
|
end;
|
||||||
|
end; // except
|
||||||
|
|
||||||
|
if lBeforeCallHookHasBeenInvoked and (not lAfterCallHookHasBeenInvoked) then
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
TryToCallMethod(lRTTIType, JSONRPC_HOOKS_ON_AFTER_CALL, lJSONResp);
|
||||||
|
except
|
||||||
|
on E: Exception do
|
||||||
|
begin
|
||||||
|
FreeAndNil(lJSONResp);
|
||||||
|
if E is EMVCJSONRPCErrorResponse then
|
||||||
|
lJSONResp := CreateError(lReqID, EMVCJSONRPCErrorResponse(E).JSONRPCErrorCode, E.Message)
|
||||||
|
else
|
||||||
|
lJSONResp := CreateError(lReqID, 0, E.Message);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
Render(lJSONResp, True);
|
||||||
finally
|
finally
|
||||||
lRTTI.Free;
|
lRTTI.Free;
|
||||||
end;
|
end;
|
||||||
@ -1257,12 +1312,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMVCJSONRPCController.TryToCallMethod(const aRTTIType: TRttiType;
|
procedure TMVCJSONRPCController.TryToCallMethod(const aRTTIType: TRttiType;
|
||||||
const MethodName: string; const Parameter: TJDOJsonObject; const ParameterName: string);
|
const MethodName: string; const Parameter: TJDOJsonObject);
|
||||||
var
|
var
|
||||||
lHookMethod: TRTTIMethod;
|
lHookMethod: TRTTIMethod;
|
||||||
lHookParam: TRttiParameter;
|
lHookSecondParam: TRttiParameter;
|
||||||
lHookParamParamType: string;
|
lHookSecondParamType: string;
|
||||||
lHookParamName: string;
|
lHookFirstParam: TRttiParameter;
|
||||||
|
lHookFirstParamType: string;
|
||||||
begin
|
begin
|
||||||
if not Assigned(aRTTIType) then
|
if not Assigned(aRTTIType) then
|
||||||
begin
|
begin
|
||||||
@ -1271,20 +1327,38 @@ begin
|
|||||||
lHookMethod := aRTTIType.GetMethod(MethodName);
|
lHookMethod := aRTTIType.GetMethod(MethodName);
|
||||||
if Assigned(lHookMethod) then
|
if Assigned(lHookMethod) then
|
||||||
begin
|
begin
|
||||||
if (Length(lHookMethod.GetParameters) <> 1) then
|
if (Length(lHookMethod.GetParameters) <> 2) then
|
||||||
|
begin
|
||||||
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: procedure '
|
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: procedure '
|
||||||
+ '%s.%s(const %s: TJDOJsonObject)', [MethodName, fRPCInstance.ClassName, MethodName,
|
+ '%s.%s(const Context: TWebContext; const Value: TJDOJsonObject)',
|
||||||
ParameterName]);
|
[MethodName, fRPCInstance.ClassName, MethodName]);
|
||||||
lHookParam := lHookMethod.GetParameters[0];
|
end;
|
||||||
lHookParamParamType := lHookParam.ParamType.ToString.ToLower;
|
|
||||||
lHookParamName := lHookParam.Name.ToLower;
|
lHookFirstParam := lHookMethod.GetParameters[0];
|
||||||
if ((lHookParamParamType <> 'tjdojsonobject') and (lHookParamParamType <> 'tjsonobject')) or
|
lHookSecondParam := lHookMethod.GetParameters[1];
|
||||||
(lHookParam.Flags * [pfConst, pfAddress] <> [pfConst, pfAddress]) or
|
|
||||||
(lHookParamName <> ParameterName.ToLower) then
|
lHookFirstParamType := lHookFirstParam.ParamType.ToString.ToLower;
|
||||||
|
lHookSecondParamType := lHookSecondParam.ParamType.ToString.ToLower;
|
||||||
|
|
||||||
|
if (lHookMethod.MethodKind <> mkProcedure) then
|
||||||
|
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: Hook methods MUST have the following signature "procedure '
|
||||||
|
+ '%s.%s(const Context: TWebContext; const Value: TJDOJsonObject)"',
|
||||||
|
[MethodName, fRPCInstance.ClassName, MethodName]);
|
||||||
|
|
||||||
|
if ((lHookSecondParamType <> 'tjdojsonobject') and (lHookSecondParamType <> 'tjsonobject')) or
|
||||||
|
(lHookSecondParam.Flags * [pfConst, pfAddress] <> [pfConst, pfAddress]) then
|
||||||
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: procedure '
|
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: procedure '
|
||||||
+ '%s.%s(const %s: TJDOJsonObject)', [MethodName, fRPCInstance.ClassName, MethodName,
|
+ '%s.%s(const Context: TWebContext; const Value: TJDOJsonObject)',
|
||||||
ParameterName]);
|
[MethodName, fRPCInstance.ClassName, MethodName]);
|
||||||
lHookMethod.Invoke(fRPCInstance, [Parameter])
|
|
||||||
|
if (lHookFirstParamType <> 'twebcontext') or
|
||||||
|
(lHookFirstParam.Flags * [pfConst, pfAddress] <> [pfConst, pfAddress]) then
|
||||||
|
raise EMVCJSONRPCException.CreateFmt('Invalid signature for [%s] Hook method [HINT: procedure '
|
||||||
|
+ '%s.%s(const Context: TWebContext; const Value: TJDOJsonObject)',
|
||||||
|
[MethodName, fRPCInstance.ClassName, MethodName]);
|
||||||
|
|
||||||
|
LogD('[JSON-RPC][HOOK][' + fRPCInstance.ClassName + '.' + MethodName + ']');
|
||||||
|
lHookMethod.Invoke(fRPCInstance, [Self.Context, Parameter])
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -1387,6 +1461,7 @@ end;
|
|||||||
constructor TJSONRPCRequest.Create;
|
constructor TJSONRPCRequest.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
|
Self.FID := TValue.Empty;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TJSONRPCRequest.Destroy;
|
destructor TJSONRPCRequest.Destroy;
|
||||||
@ -1798,24 +1873,32 @@ end;
|
|||||||
function TJSONRPCRequest.GetJSON: TJDOJsonObject;
|
function TJSONRPCRequest.GetJSON: TJDOJsonObject;
|
||||||
begin
|
begin
|
||||||
Result := inherited GetJSON;
|
Result := inherited GetJSON;
|
||||||
if not FID.IsEmpty then
|
try
|
||||||
begin
|
if not FID.IsEmpty then
|
||||||
if FID.IsType<string> then
|
|
||||||
begin
|
begin
|
||||||
Result.S[JSONRPC_ID] := FID.AsString;
|
if FID.IsType<string> then
|
||||||
end
|
begin
|
||||||
else if FID.IsType<Int32> then
|
Result.S[JSONRPC_ID] := FID.AsString;
|
||||||
begin
|
end
|
||||||
Result.I[JSONRPC_ID] := FID.AsInteger;
|
else if FID.IsType<Int32> then
|
||||||
end
|
begin
|
||||||
else if FID.IsType<Int64> then
|
Result.I[JSONRPC_ID] := FID.AsInteger;
|
||||||
begin
|
end
|
||||||
Result.I[JSONRPC_ID] := FID.AsInt64;
|
else if FID.IsType<Int64> then
|
||||||
|
begin
|
||||||
|
Result.I[JSONRPC_ID] := FID.AsInt64;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise EMVCJSONRPCException.Create('ID can be only Int32, Int64 or String');
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise EMVCJSONRPCException.Create('ID can be only Int32, Int64 or String');
|
begin
|
||||||
|
raise EMVCJSONRPCException.Create('ID cannot be empty in a JSON-RPC request');
|
||||||
|
end;
|
||||||
|
except
|
||||||
|
Result.Free;
|
||||||
|
raise;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TJSONRPCProxyGenerator }
|
{ TJSONRPCProxyGenerator }
|
||||||
@ -2032,6 +2115,81 @@ begin
|
|||||||
fJSONRPCErrorCode := ErrCode;
|
fJSONRPCErrorCode := ErrCode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TJSONRPCNullResponse }
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.GetError: TJSONRPCResponseError;
|
||||||
|
begin
|
||||||
|
Result := FError;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.GetID: TValue;
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.GetJSON: TJDOJsonObject;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.GetJSONString: string;
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.GetResult: TValue;
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.IsError: Boolean;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.RaiseErrorForNullObject;
|
||||||
|
begin
|
||||||
|
raise EMVCJSONRPCException.Create('Invalid Call for NULL object');
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.ResultAsJSONArray: TJDOJsonArray;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TJSONRPCNullResponse.ResultAsJSONObject: TJDOJsonObject;
|
||||||
|
begin
|
||||||
|
Result := nil;
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.SetError(const Value: TJSONRPCResponseError);
|
||||||
|
begin
|
||||||
|
FError := Value;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.SetID(const Value: TValue);
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.SetJSON(const JSON: TJDOJsonObject);
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.SetJsonString(const Value: string);
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCNullResponse.SetResult(const Value: TValue);
|
||||||
|
begin
|
||||||
|
RaiseErrorForNullObject;
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
|
|
||||||
finalization
|
finalization
|
||||||
|
@ -36,21 +36,83 @@ uses
|
|||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
{$SCOPEDENUMS ON}
|
{$SCOPEDENUMS ON}
|
||||||
|
|
||||||
TJWTCheckableClaim = (ExpirationTime, NotBefore, IssuedAt);
|
TJWTCheckableClaim = (ExpirationTime, NotBefore, IssuedAt);
|
||||||
TJWTCheckableClaims = set of TJWTCheckableClaim;
|
TJWTCheckableClaims = set of TJWTCheckableClaim;
|
||||||
|
|
||||||
TJWTRegisteredClaimNames = class sealed
|
TJWTRegisteredClaimNames = class sealed
|
||||||
public
|
public
|
||||||
const
|
const
|
||||||
|
/// <summary>
|
||||||
|
/// The "iss" (issuer) claim identifies the principal that issued the
|
||||||
|
/// JWT. The processing of this claim is generally application specific.
|
||||||
|
/// The "iss" value is a case-sensitive string containing a StringOrURI
|
||||||
|
/// value. Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
Issuer: string = 'iss';
|
Issuer: string = 'iss';
|
||||||
|
/// <summary>
|
||||||
|
/// The "sub" (subject) claim identifies the principal that is the
|
||||||
|
/// subject of the JWT. The claims in a JWT are normally statements
|
||||||
|
/// about the subject. The subject value MUST either be scoped to be
|
||||||
|
/// locally unique in the context of the issuer or be globally unique.
|
||||||
|
/// The processing of this claim is generally application specific. The
|
||||||
|
/// "sub" value is a case-sensitive string containing a StringOrURI
|
||||||
|
/// value. Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
Subject: string = 'sub';
|
Subject: string = 'sub';
|
||||||
|
/// <summary>
|
||||||
|
/// The "aud" (audience) claim identifies the recipients that the JWT is
|
||||||
|
/// intended for. Each principal intended to process the JWT MUST
|
||||||
|
/// identify itself with a value in the audience claim. If the principal
|
||||||
|
/// processing the claim does not identify itself with a value in the
|
||||||
|
/// "aud" claim when this claim is present, then the JWT MUST be
|
||||||
|
/// rejected. In the general case, the "aud" value is an array of case-
|
||||||
|
/// sensitive strings, each containing a StringOrURI value. In the
|
||||||
|
/// special case when the JWT has one audience, the "aud" value MAY be a
|
||||||
|
/// single case-sensitive string containing a StringOrURI value. The
|
||||||
|
/// interpretation of audience values is generally application specific.
|
||||||
|
/// Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
Audience: string = 'aud';
|
Audience: string = 'aud';
|
||||||
|
/// <summary>
|
||||||
|
/// The "exp" (expiration time) claim identifies the expiration time on
|
||||||
|
/// or after which the JWT MUST NOT be accepted for processing. The
|
||||||
|
/// processing of the "exp" claim requires that the current date/time
|
||||||
|
/// MUST be before the expiration date/time listed in the "exp" claim.
|
||||||
|
/// Implementers MAY provide for some small leeway, usually no more than
|
||||||
|
/// a few minutes, to account for clock skew. Its value MUST be a number
|
||||||
|
/// containing a NumericDate value. Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
ExpirationTime: string = 'exp';
|
ExpirationTime: string = 'exp';
|
||||||
|
/// <summary>
|
||||||
|
/// The "nbf" (not before) claim identifies the time before which the JWT
|
||||||
|
/// MUST NOT be accepted for processing. The processing of the "nbf"
|
||||||
|
/// claim requires that the current date/time MUST be after or equal to
|
||||||
|
/// the not-before date/time listed in the "nbf" claim. Implementers MAY
|
||||||
|
/// provide for some small leeway, usually no more than a few minutes, to
|
||||||
|
/// account for clock skew. Its value MUST be a number containing a
|
||||||
|
/// NumericDate value. Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
NotBefore: string = 'nbf';
|
NotBefore: string = 'nbf';
|
||||||
|
/// <summary>
|
||||||
|
/// The "iat" (issued at) claim identifies the time at which the JWT was
|
||||||
|
/// issued. This claim can be used to determine the age of the JWT. Its
|
||||||
|
/// value MUST be a number containing a NumericDate value. Use of this
|
||||||
|
/// claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
IssuedAt: string = 'iat';
|
IssuedAt: string = 'iat';
|
||||||
|
/// <summary>
|
||||||
|
/// The "jti" (JWT ID) claim provides a unique identifier for the JWT.
|
||||||
|
/// The identifier value MUST be assigned in a manner that ensures that
|
||||||
|
/// there is a negligible probability that the same value will be
|
||||||
|
/// accidentally assigned to a different data object; if the application
|
||||||
|
/// uses multiple issuers, collisions MUST be prevented among values
|
||||||
|
/// produced by different issuers as well. The "jti" claim can be used
|
||||||
|
/// to prevent the JWT from being replayed. The "jti" value is a case-
|
||||||
|
/// sensitive string. Use of this claim is OPTIONAL.
|
||||||
|
/// </summary>
|
||||||
JWT_ID: string = 'jti';
|
JWT_ID: string = 'jti';
|
||||||
|
|
||||||
Names: array [0 .. 6] of string = (
|
Names: array [0 .. 6] of string = (
|
||||||
'iss',
|
'iss',
|
||||||
'sub',
|
'sub',
|
||||||
@ -213,7 +275,8 @@ type
|
|||||||
/// ExpirationTime will be incremented by LiveValidityWindowInSeconds seconds automatically
|
/// ExpirationTime will be incremented by LiveValidityWindowInSeconds seconds automatically
|
||||||
/// if the remaining seconds are less than the LiveValidityWindowInSeconds.
|
/// if the remaining seconds are less than the LiveValidityWindowInSeconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
property LiveValidityWindowInSeconds: Cardinal read GetLiveValidityWindowInSeconds write SetLiveValidityWindowInSeconds;
|
property LiveValidityWindowInSeconds: Cardinal read GetLiveValidityWindowInSeconds
|
||||||
|
write SetLiveValidityWindowInSeconds;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@ -478,7 +541,7 @@ begin
|
|||||||
try
|
try
|
||||||
lPayload := TJDOJSONObject.Create;
|
lPayload := TJDOJSONObject.Create;
|
||||||
try
|
try
|
||||||
lHeader.S['alg'] := HMACAlgorithm;
|
lHeader.S['alg'] := HMACAlgorithm;
|
||||||
lHeader.S['typ'] := 'JWT';
|
lHeader.S['typ'] := 'JWT';
|
||||||
for lRegClaimName in TJWTRegisteredClaimNames.Names do
|
for lRegClaimName in TJWTRegisteredClaimNames.Names do
|
||||||
begin
|
begin
|
||||||
@ -495,7 +558,7 @@ begin
|
|||||||
|
|
||||||
for lCustomClaimName in FCustomClaims.Keys do
|
for lCustomClaimName in FCustomClaims.Keys do
|
||||||
begin
|
begin
|
||||||
lPayload.S[lCustomClaimName] := FCustomClaims[lCustomClaimName];
|
lPayload.S[lCustomClaimName] := FCustomClaims[lCustomClaimName];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
lHeaderEncoded := URLSafeB64encode(lHeader.ToString, False, IndyTextEncoding_UTF8);
|
lHeaderEncoded := URLSafeB64encode(lHeader.ToString, False, IndyTextEncoding_UTF8);
|
||||||
@ -623,8 +686,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
lIsRegistered := False;
|
lIsRegistered := False;
|
||||||
|
|
||||||
lName := lJPayload.Names[I];
|
lName := lJPayload.Names[i];
|
||||||
lValue := lJPayload.Items[I].Value;
|
lValue := lJPayload.Items[i].Value;
|
||||||
|
|
||||||
// if is a registered claim, load it in the proper dictionary...
|
// if is a registered claim, load it in the proper dictionary...
|
||||||
for j := 0 to high(TJWTRegisteredClaimNames.Names) do
|
for j := 0 to high(TJWTRegisteredClaimNames.Names) do
|
||||||
|
@ -94,7 +94,7 @@ type
|
|||||||
const AActionName: string; var AHandled: Boolean);
|
const AActionName: string; var AHandled: Boolean);
|
||||||
protected
|
protected
|
||||||
procedure OnBeforeControllerAction(AContext: TWebContext; const AControllerQualifiedClassName: string;
|
procedure OnBeforeControllerAction(AContext: TWebContext; const AControllerQualifiedClassName: string;
|
||||||
const AActionName: string; var AHandled: Boolean);
|
const AActionName: string; var AHandled: Boolean); override;
|
||||||
public
|
public
|
||||||
constructor Create(const AAuthenticationHandler: IMVCAuthenticationHandler;
|
constructor Create(const AAuthenticationHandler: IMVCAuthenticationHandler;
|
||||||
const ALoginUrl: string = '/system/users/logged'); override;
|
const ALoginUrl: string = '/system/users/logged'); override;
|
||||||
|
@ -61,12 +61,12 @@ type
|
|||||||
const AActionName: string;
|
const AActionName: string;
|
||||||
const AHandled: Boolean
|
const AHandled: Boolean
|
||||||
);
|
);
|
||||||
|
procedure OnAfterRouting(AContext: TWebContext; const AHandled: Boolean);
|
||||||
public
|
public
|
||||||
constructor Create(
|
constructor Create(
|
||||||
const AAuthenticationHandler: IMVCAuthenticationHandler;
|
const AAuthenticationHandler: IMVCAuthenticationHandler;
|
||||||
const ARealm: string = 'DelphiMVCFramework REALM'
|
const ARealm: string = 'DelphiMVCFramework REALM'
|
||||||
); virtual;
|
); virtual;
|
||||||
procedure OnAfterRouting(AContext: TWebContext; const AHandled: Boolean);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMVCCustomAuthenticationMiddleware = class(TInterfacedObject, IMVCMiddleware)
|
TMVCCustomAuthenticationMiddleware = class(TInterfacedObject, IMVCMiddleware)
|
||||||
@ -84,7 +84,7 @@ type
|
|||||||
const AControllerQualifiedClassName: string;
|
const AControllerQualifiedClassName: string;
|
||||||
const AActionName: string;
|
const AActionName: string;
|
||||||
var AHandled: Boolean
|
var AHandled: Boolean
|
||||||
);
|
); virtual;
|
||||||
|
|
||||||
procedure OnAfterControllerAction(
|
procedure OnAfterControllerAction(
|
||||||
AContext: TWebContext;
|
AContext: TWebContext;
|
||||||
@ -96,7 +96,6 @@ type
|
|||||||
const AHandled: Boolean
|
const AHandled: Boolean
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
procedure SendResponse(AContext: TWebContext; var AHandled: Boolean; AHttpStatus: Word = HTTP_STATUS.Unauthorized);
|
procedure SendResponse(AContext: TWebContext; var AHandled: Boolean; AHttpStatus: Word = HTTP_STATUS.Unauthorized);
|
||||||
procedure DoLogin(AContext: TWebContext; var AHandled: Boolean);
|
procedure DoLogin(AContext: TWebContext; var AHandled: Boolean);
|
||||||
procedure DoLogout(AContext: TWebContext; var AHandled: Boolean);
|
procedure DoLogout(AContext: TWebContext; var AHandled: Boolean);
|
||||||
@ -149,15 +148,18 @@ procedure TMVCBasicAuthenticationMiddleware.OnBeforeControllerAction(
|
|||||||
if AContext.Request.ClientPreferHTML then
|
if AContext.Request.ClientPreferHTML then
|
||||||
begin
|
begin
|
||||||
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
||||||
AContext.Response.RawWebResponse.Content := Format(CONTENT_HTML_FORMAT, [CONTENT_401_NOT_AUTHORIZED, AContext.Config[TMVCConfigKey.ServerName]]);
|
AContext.Response.RawWebResponse.Content :=
|
||||||
|
Format(CONTENT_HTML_FORMAT, [CONTENT_401_NOT_AUTHORIZED, AContext.Config[TMVCConfigKey.ServerName]]);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
AContext.Response.ContentType := TMVCMediaType.TEXT_PLAIN;
|
AContext.Response.ContentType := TMVCMediaType.TEXT_PLAIN;
|
||||||
AContext.Response.RawWebResponse.Content := CONTENT_401_NOT_AUTHORIZED + sLineBreak + AContext.Config[TMVCConfigKey.ServerName];
|
AContext.Response.RawWebResponse.Content := CONTENT_401_NOT_AUTHORIZED + sLineBreak + AContext.Config
|
||||||
|
[TMVCConfigKey.ServerName];
|
||||||
end;
|
end;
|
||||||
AContext.Response.StatusCode := HTTP_STATUS.Unauthorized;
|
AContext.Response.StatusCode := HTTP_STATUS.Unauthorized;
|
||||||
AContext.Response.SetCustomHeader('WWW-Authenticate', 'Basic realm=' + QuotedStr(FRealm));
|
AContext.Response.SetCustomHeader('WWW-Authenticate', 'Basic realm=' + QuotedStr(FRealm));
|
||||||
|
AContext.SessionStop(False);
|
||||||
AHandled := True;
|
AHandled := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -167,21 +169,30 @@ procedure TMVCBasicAuthenticationMiddleware.OnBeforeControllerAction(
|
|||||||
if AContext.Request.ClientPreferHTML then
|
if AContext.Request.ClientPreferHTML then
|
||||||
begin
|
begin
|
||||||
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
||||||
AContext.Response.RawWebResponse.Content := Format(CONTENT_HTML_FORMAT, [CONTENT_403_FORBIDDEN, AContext.Config[TMVCConfigKey.ServerName]]);
|
AContext.Response.RawWebResponse.Content :=
|
||||||
|
Format(CONTENT_HTML_FORMAT, [CONTENT_403_FORBIDDEN, AContext.Config[TMVCConfigKey.ServerName]]);
|
||||||
|
end
|
||||||
|
else if AContext.Request.ContentMediaType.StartsWith(TMVCMediaType.APPLICATION_JSON) then
|
||||||
|
begin
|
||||||
|
AContext.Response.ContentType := TMVCMediaType.APPLICATION_JSON;
|
||||||
|
AContext.Response.RawWebResponse.Content :=
|
||||||
|
'{"status":"error", "message":"' + CONTENT_403_FORBIDDEN.Replace('"', '\"') + '"}';
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
AContext.Response.ContentType := TMVCMediaType.TEXT_PLAIN;
|
AContext.Response.ContentType := TMVCMediaType.TEXT_PLAIN;
|
||||||
AContext.Response.RawWebResponse.Content := CONTENT_403_FORBIDDEN + sLineBreak + AContext.Config[TMVCConfigKey.ServerName];
|
AContext.Response.RawWebResponse.Content := CONTENT_403_FORBIDDEN + sLineBreak + AContext.Config
|
||||||
|
[TMVCConfigKey.ServerName];
|
||||||
end;
|
end;
|
||||||
AContext.Response.StatusCode := HTTP_STATUS.Forbidden;
|
AContext.Response.StatusCode := HTTP_STATUS.Forbidden;
|
||||||
|
AContext.Response.ReasonString := AContext.Config[TMVCConfigKey.ServerName];
|
||||||
AHandled := True;
|
AHandled := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
AuthRequired: Boolean;
|
AuthRequired: Boolean;
|
||||||
IsValid, IsAuthorized: Boolean;
|
IsValid, IsAuthorized: Boolean;
|
||||||
AuthHeader: string;
|
AuthHeader, Token: string;
|
||||||
AuthPieces: TArray<string>;
|
AuthPieces: TArray<string>;
|
||||||
RolesList: TList<string>;
|
RolesList: TList<string>;
|
||||||
SessionData: TSessionData;
|
SessionData: TSessionData;
|
||||||
@ -199,9 +210,15 @@ begin
|
|||||||
if not IsValid then
|
if not IsValid then
|
||||||
begin
|
begin
|
||||||
AuthHeader := AContext.Request.Headers['Authorization'];
|
AuthHeader := AContext.Request.Headers['Authorization'];
|
||||||
AuthHeader := TMVCSerializerHelper.DecodeString(AuthHeader.Remove(0, 'Basic'.Length).Trim);
|
if AuthHeader.IsEmpty or (not AuthHeader.StartsWith('Basic ', True)) then
|
||||||
|
begin
|
||||||
|
SendWWWAuthenticate;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
Token := AuthHeader.Remove(0, 'Basic '.Length).Trim;
|
||||||
|
AuthHeader := TMVCSerializerHelper.DecodeString(Token);
|
||||||
AuthPieces := AuthHeader.Split([':']);
|
AuthPieces := AuthHeader.Split([':']);
|
||||||
if AuthHeader.IsEmpty or (Length(AuthPieces) <> 2) then
|
if Length(AuthPieces) <> 2 then
|
||||||
begin
|
begin
|
||||||
SendWWWAuthenticate;
|
SendWWWAuthenticate;
|
||||||
Exit;
|
Exit;
|
||||||
@ -211,7 +228,8 @@ begin
|
|||||||
try
|
try
|
||||||
SessionData := TSessionData.Create;
|
SessionData := TSessionData.Create;
|
||||||
try
|
try
|
||||||
FAuthenticationHandler.OnAuthentication(AContext, AuthPieces[0], AuthPieces[1], RolesList, IsValid, SessionData);
|
FAuthenticationHandler.OnAuthentication(AContext, AuthPieces[0], AuthPieces[1], RolesList, IsValid,
|
||||||
|
SessionData);
|
||||||
if IsValid then
|
if IsValid then
|
||||||
begin
|
begin
|
||||||
AContext.LoggedUser.Roles.AddRange(RolesList);
|
AContext.LoggedUser.Roles.AddRange(RolesList);
|
||||||
@ -232,7 +250,8 @@ begin
|
|||||||
|
|
||||||
IsAuthorized := False;
|
IsAuthorized := False;
|
||||||
if IsValid then
|
if IsValid then
|
||||||
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName, AActionName, IsAuthorized);
|
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName,
|
||||||
|
AActionName, IsAuthorized);
|
||||||
|
|
||||||
if IsAuthorized then
|
if IsAuthorized then
|
||||||
AHandled := False
|
AHandled := False
|
||||||
@ -241,7 +260,9 @@ begin
|
|||||||
if IsValid then
|
if IsValid then
|
||||||
Send403Forbidden
|
Send403Forbidden
|
||||||
else
|
else
|
||||||
|
begin
|
||||||
SendWWWAuthenticate;
|
SendWWWAuthenticate;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -249,7 +270,7 @@ procedure TMVCBasicAuthenticationMiddleware.OnBeforeRouting(
|
|||||||
AContext: TWebContext;
|
AContext: TWebContext;
|
||||||
var AHandled: Boolean);
|
var AHandled: Boolean);
|
||||||
begin
|
begin
|
||||||
// Implement as needed
|
AHandled := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMVCCustomAuthenticationMiddleware }
|
{ TMVCCustomAuthenticationMiddleware }
|
||||||
@ -281,7 +302,8 @@ begin
|
|||||||
AHandled := True;
|
AHandled := True;
|
||||||
AContext.Response.StatusCode := HTTP_STATUS.BadRequest;
|
AContext.Response.StatusCode := HTTP_STATUS.BadRequest;
|
||||||
AContext.Response.ContentType := TMVCMediaType.APPLICATION_JSON;
|
AContext.Response.ContentType := TMVCMediaType.APPLICATION_JSON;
|
||||||
AContext.Response.RawWebResponse.Content := '{"status":"KO", "message":"username and password are mandatory in the body request as json object"}';
|
AContext.Response.RawWebResponse.Content :=
|
||||||
|
'{"status":"error", "message":"username and password are mandatory in the body request as json object"}';
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -393,7 +415,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
IsAuthorized := False;
|
IsAuthorized := False;
|
||||||
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName, AActionName, IsAuthorized);
|
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName,
|
||||||
|
AActionName, IsAuthorized);
|
||||||
if IsAuthorized then
|
if IsAuthorized then
|
||||||
AHandled := False
|
AHandled := False
|
||||||
else
|
else
|
||||||
@ -412,7 +435,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
AHandled := False;
|
AHandled := False;
|
||||||
|
|
||||||
if (AContext.Request.HTTPMethod = httpPOST) and (AContext.Request.ContentType.StartsWith(TMVCMediaType.APPLICATION_JSON)) then
|
if (AContext.Request.HTTPMethod = httpPOST) and
|
||||||
|
(AContext.Request.ContentType.StartsWith(TMVCMediaType.APPLICATION_JSON)) then
|
||||||
DoLogin(AContext, AHandled);
|
DoLogin(AContext, AHandled);
|
||||||
|
|
||||||
if (AContext.Request.HTTPMethod = httpDELETE) then
|
if (AContext.Request.HTTPMethod = httpDELETE) then
|
||||||
@ -433,7 +457,8 @@ begin
|
|||||||
if AContext.Request.ClientPreferHTML then
|
if AContext.Request.ClientPreferHTML then
|
||||||
begin
|
begin
|
||||||
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
AContext.Response.ContentType := TMVCMediaType.TEXT_HTML;
|
||||||
AContext.Response.RawWebResponse.Content := Format(CONTENT_HTML_FORMAT, [IntToStr(AHttpStatus), AContext.Config[TMVCConfigKey.ServerName]]);
|
AContext.Response.RawWebResponse.Content :=
|
||||||
|
Format(CONTENT_HTML_FORMAT, [IntToStr(AHttpStatus), AContext.Config[TMVCConfigKey.ServerName]]);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
|
@ -167,7 +167,14 @@ end;
|
|||||||
|
|
||||||
function TRQLPostgreSQLCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string;
|
function TRQLPostgreSQLCompiler.RQLLimitToSQL(const aRQLLimit: TRQLLimit): string;
|
||||||
begin
|
begin
|
||||||
Result := Format(' /*limit*/ LIMIT %d OFFSET %d', [aRQLLimit.Count, aRQLLimit.Start]);
|
if aRQLLimit.Start = 0 then
|
||||||
|
begin
|
||||||
|
Result := Format(' /*limit*/ LIMIT %d', [aRQLLimit.Count]);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Result := Format(' /*limit*/ LIMIT %d OFFSET %d', [aRQLLimit.Count, aRQLLimit.Start]);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRQLPostgreSQLCompiler.RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string;
|
function TRQLPostgreSQLCompiler.RQLLogicOperatorToSQL(const aRQLFIlter: TRQLLogicOperator): string;
|
||||||
|
@ -221,10 +221,15 @@ begin
|
|||||||
LMethods := LRttiType.GetMethods; {do not use GetDeclaredMethods because JSON-RPC rely on this!!}
|
LMethods := LRttiType.GetMethods; {do not use GetDeclaredMethods because JSON-RPC rely on this!!}
|
||||||
for LMethod in LMethods do
|
for LMethod in LMethods do
|
||||||
begin
|
begin
|
||||||
if (LMethod.MethodKind <> mkProcedure) or LMethod.IsClassMethod then
|
if LMethod.Visibility <> mvPublic then //2020-08-08
|
||||||
|
Continue;
|
||||||
|
if (LMethod.MethodKind <> mkProcedure) {or LMethod.IsClassMethod} then
|
||||||
Continue;
|
Continue;
|
||||||
|
|
||||||
LAttributes := LMethod.GetAttributes;
|
LAttributes := LMethod.GetAttributes;
|
||||||
|
if Length(LAttributes) = 0 then
|
||||||
|
Continue;
|
||||||
|
|
||||||
for LAtt in LAttributes do
|
for LAtt in LAttributes do
|
||||||
begin
|
begin
|
||||||
if LAtt is MVCPathAttribute then
|
if LAtt is MVCPathAttribute then
|
||||||
@ -240,7 +245,7 @@ begin
|
|||||||
FControllerClazz := LControllerDelegate.Clazz;
|
FControllerClazz := LControllerDelegate.Clazz;
|
||||||
FControllerCreateAction := LControllerDelegate.CreateAction;
|
FControllerCreateAction := LControllerDelegate.CreateAction;
|
||||||
LProduceAttribute := GetAttribute<MVCProducesAttribute>(LAttributes);
|
LProduceAttribute := GetAttribute<MVCProducesAttribute>(LAttributes);
|
||||||
if Assigned(LProduceAttribute) then
|
if LProduceAttribute <> nil then
|
||||||
begin
|
begin
|
||||||
AResponseContentMediaType := LProduceAttribute.Value;
|
AResponseContentMediaType := LProduceAttribute.Value;
|
||||||
AResponseContentCharset := LProduceAttribute.Charset;
|
AResponseContentCharset := LProduceAttribute.Charset;
|
||||||
@ -396,7 +401,7 @@ var
|
|||||||
FoundOneAttProduces: Boolean;
|
FoundOneAttProduces: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := False;
|
Result := False;
|
||||||
if AAccept = '*/*' then
|
if AAccept.Contains('*/*') then //2020-08-08
|
||||||
begin
|
begin
|
||||||
Exit(True);
|
Exit(True);
|
||||||
end;
|
end;
|
||||||
|
@ -91,6 +91,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + TableName + '(');
|
lSB.Append('INSERT INTO ' + TableName + '(');
|
||||||
|
@ -63,6 +63,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty); // and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty); // and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + TableName + '(');
|
lSB.Append('INSERT INTO ' + TableName + '(');
|
||||||
|
@ -88,6 +88,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + TableName + '(');
|
lSB.Append('INSERT INTO ' + TableName + '(');
|
||||||
|
@ -88,6 +88,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + TableName + '(');
|
lSB.Append('INSERT INTO ' + TableName + '(');
|
||||||
|
@ -99,6 +99,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + ' (');
|
lSB.Append('INSERT INTO ' + GetTableNameForSQL(TableName) + ' (');
|
||||||
@ -146,6 +147,11 @@ function TMVCSQLGeneratorPostgreSQL.CreateSelectByPKSQL(
|
|||||||
const Map: TFieldsMap; const PKFieldName: string;
|
const Map: TFieldsMap; const PKFieldName: string;
|
||||||
const PKOptions: TMVCActiveRecordFieldOptions): string;
|
const PKOptions: TMVCActiveRecordFieldOptions): string;
|
||||||
begin
|
begin
|
||||||
|
if PKFieldName.IsEmpty then
|
||||||
|
begin
|
||||||
|
raise EMVCActiveRecord.Create('No primary key provided. [HINT] Define a primary key field adding foPrimaryKey in field options.');
|
||||||
|
end;
|
||||||
|
|
||||||
Result := CreateSelectSQL(TableName, Map, PKFieldName, PKOptions) + ' WHERE ' +
|
Result := CreateSelectSQL(TableName, Map, PKFieldName, PKOptions) + ' WHERE ' +
|
||||||
GetFieldNameForSQL(PKFieldName) + '= :' + GetParamNameForSQL(PKFieldName); // IntToStr(PrimaryKeyValue);
|
GetFieldNameForSQL(PKFieldName) + '= :' + GetParamNameForSQL(PKFieldName); // IntToStr(PrimaryKeyValue);
|
||||||
end;
|
end;
|
||||||
|
@ -87,6 +87,7 @@ var
|
|||||||
lPKInInsert: Boolean;
|
lPKInInsert: Boolean;
|
||||||
begin
|
begin
|
||||||
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
lPKInInsert := (not PKFieldName.IsEmpty) and (not(TMVCActiveRecordFieldOption.foAutoGenerated in PKOptions));
|
||||||
|
lPKInInsert := lPKInInsert and (not(TMVCActiveRecordFieldOption.foReadOnly in PKOptions));
|
||||||
lSB := TStringBuilder.Create;
|
lSB := TStringBuilder.Create;
|
||||||
try
|
try
|
||||||
lSB.Append('INSERT INTO ' + TableName + ' (');
|
lSB.Append('INSERT INTO ' + TableName + ' (');
|
||||||
|
@ -736,7 +736,7 @@ var
|
|||||||
Attrs: TArray<TCustomAttribute>;
|
Attrs: TArray<TCustomAttribute>;
|
||||||
Attr: TCustomAttribute;
|
Attr: TCustomAttribute;
|
||||||
begin
|
begin
|
||||||
{ TODO -oDanieleT -cGeneral : in un rendering di una lista, quante volte viene chiamata questa funzione?}
|
{ TODO -oDanieleT -cGeneral : in un rendering di una lista, quante volte viene chiamata questa funzione? }
|
||||||
{ Tante volte, ma eliminando tutta la logica si guadagnerebbe al massiom il 6% nel caso tipico, forse non vale la pena di aggiungere una cache apposita }
|
{ Tante volte, ma eliminando tutta la logica si guadagnerebbe al massiom il 6% nel caso tipico, forse non vale la pena di aggiungere una cache apposita }
|
||||||
Result := AProperty.Name;
|
Result := AProperty.Name;
|
||||||
|
|
||||||
@ -1006,6 +1006,7 @@ var
|
|||||||
lInternalStream: TStream;
|
lInternalStream: TStream;
|
||||||
lSStream: TStringStream;
|
lSStream: TStringStream;
|
||||||
lValue: TValue;
|
lValue: TValue;
|
||||||
|
lStrValue: string;
|
||||||
{$IF not Defined(TokyoOrBetter)}
|
{$IF not Defined(TokyoOrBetter)}
|
||||||
lFieldValue: string;
|
lFieldValue: string;
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
@ -1023,7 +1024,35 @@ begin
|
|||||||
case AField.DataType of
|
case AField.DataType of
|
||||||
ftString, ftWideString:
|
ftString, ftWideString:
|
||||||
begin
|
begin
|
||||||
aRTTIField.SetValue(AObject, AField.AsString);
|
// mysql tinytext is identified as string, but raises an Invalid Class Cast
|
||||||
|
// so we need to do some more checks...
|
||||||
|
case aRTTIField.FieldType.TypeKind of
|
||||||
|
tkString, tkUString:
|
||||||
|
begin
|
||||||
|
aRTTIField.SetValue(AObject, AField.AsString);
|
||||||
|
end;
|
||||||
|
tkClass: { mysql - maps a tiny field, identified as string, into a TStream }
|
||||||
|
begin
|
||||||
|
lInternalStream := aRTTIField.GetValue(AObject).AsObject as TStream;
|
||||||
|
if lInternalStream = nil then
|
||||||
|
begin
|
||||||
|
raise EMVCException.CreateFmt('Property target for %s field is nil. [HINT] Initialize the stream before load data', [AField.FieldName]);
|
||||||
|
end;
|
||||||
|
lInternalStream.Size := 0;
|
||||||
|
lStrValue := AField.AsString;
|
||||||
|
if not lStrValue.IsEmpty then
|
||||||
|
begin
|
||||||
|
lInternalStream.Write(lStrValue, Length(lStrValue));
|
||||||
|
lInternalStream.Position := 0;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
raise EMVCException.CreateFmt('Unsupported FieldType (%d) for field %s',
|
||||||
|
[Ord(AField.DataType), AField.FieldName]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
// aRTTIField.SetValue(AObject, AField.AsString);
|
||||||
end;
|
end;
|
||||||
ftLargeint, ftAutoInc:
|
ftLargeint, ftAutoInc:
|
||||||
begin
|
begin
|
||||||
|
@ -118,7 +118,8 @@ type
|
|||||||
const ASerializedObject: string;
|
const ASerializedObject: string;
|
||||||
const AObject: TObject;
|
const AObject: TObject;
|
||||||
const AType: TMVCSerializationType = stDefault;
|
const AType: TMVCSerializationType = stDefault;
|
||||||
const AIgnoredAttributes: TMVCIgnoredList = nil
|
const AIgnoredAttributes: TMVCIgnoredList = nil;
|
||||||
|
const ARootNode: String = ''
|
||||||
); overload;
|
); overload;
|
||||||
|
|
||||||
procedure DeserializeObject(
|
procedure DeserializeObject(
|
||||||
|
@ -135,7 +135,8 @@ type
|
|||||||
const SerializationAction: TMVCDatasetSerializationAction = nil): string;
|
const SerializationAction: TMVCDatasetSerializationAction = nil): string;
|
||||||
|
|
||||||
procedure DeserializeObject(const ASerializedObject: string; const AObject: TObject;
|
procedure DeserializeObject(const ASerializedObject: string; const AObject: TObject;
|
||||||
const AType: TMVCSerializationType = stDefault; const AIgnoredAttributes: TMVCIgnoredList = []); overload;
|
const AType: TMVCSerializationType = stDefault; const AIgnoredAttributes: TMVCIgnoredList = [];
|
||||||
|
const ARootNode: string = ''); overload;
|
||||||
|
|
||||||
procedure DeserializeObject(const ASerializedObject: string; const AObject: IInterface;
|
procedure DeserializeObject(const ASerializedObject: string; const AObject: IInterface;
|
||||||
const AType: TMVCSerializationType = stDefault; const AIgnoredAttributes: TMVCIgnoredList = []); overload;
|
const AType: TMVCSerializationType = stDefault; const AIgnoredAttributes: TMVCIgnoredList = []); overload;
|
||||||
@ -194,6 +195,18 @@ uses
|
|||||||
MVCFramework.DataSet.Utils,
|
MVCFramework.DataSet.Utils,
|
||||||
MVCFramework.Nullables;
|
MVCFramework.Nullables;
|
||||||
|
|
||||||
|
function SelectRootNodeOrWholeObject(const RootNode: string; const JSONObject: TJsonObject): TJsonObject; inline;
|
||||||
|
begin
|
||||||
|
if RootNode.IsEmpty then
|
||||||
|
begin
|
||||||
|
Result := JSONObject
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Result := JSONObject.O[RootNode];
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TMVCJsonDataObjectsSerializer }
|
{ TMVCJsonDataObjectsSerializer }
|
||||||
|
|
||||||
procedure TMVCJsonDataObjectsSerializer.AfterConstruction;
|
procedure TMVCJsonDataObjectsSerializer.AfterConstruction;
|
||||||
@ -2013,7 +2026,7 @@ end;
|
|||||||
|
|
||||||
procedure TMVCJsonDataObjectsSerializer.DeserializeObject(const ASerializedObject: string;
|
procedure TMVCJsonDataObjectsSerializer.DeserializeObject(const ASerializedObject: string;
|
||||||
const AObject: TObject;
|
const AObject: TObject;
|
||||||
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList);
|
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList; const ARootNode: string);
|
||||||
var
|
var
|
||||||
JSONObject: TJDOJsonObject;
|
JSONObject: TJDOJsonObject;
|
||||||
JsonBase: TJsonBaseObject;
|
JsonBase: TJsonBaseObject;
|
||||||
@ -2041,11 +2054,12 @@ begin
|
|||||||
try
|
try
|
||||||
if GetTypeSerializers.ContainsKey(AObject.ClassInfo) then
|
if GetTypeSerializers.ContainsKey(AObject.ClassInfo) then
|
||||||
begin
|
begin
|
||||||
GetTypeSerializers.Items[AObject.ClassInfo].DeserializeRoot(JSONObject, AObject, []);
|
GetTypeSerializers.Items[AObject.ClassInfo].DeserializeRoot(SelectRootNodeOrWholeObject(ARootNode, JSONObject),
|
||||||
|
AObject, [])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
JsonObjectToObject(JSONObject, AObject, GetSerializationType(AObject, AType), AIgnoredAttributes);
|
JsonObjectToObject(SelectRootNodeOrWholeObject(ARootNode, JSONObject), AObject, GetSerializationType(AObject, AType), AIgnoredAttributes);
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
JSONObject.Free;
|
JSONObject.Free;
|
||||||
@ -2097,9 +2111,9 @@ begin
|
|||||||
{$IFDEF NEXTGEN}
|
{$IFDEF NEXTGEN}
|
||||||
lTypeName := PChar(Pointer(Value.TypeInfo.Name))
|
lTypeName := PChar(Pointer(Value.TypeInfo.Name))
|
||||||
{$ELSE}
|
{$ELSE}
|
||||||
lTypeName := String(Value.TypeInfo.Name);
|
lTypeName := string(Value.TypeInfo.Name);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
if (lTypeName = 'TDate') or (lTypeName = 'TDateTime') or (lTypeName = 'TTime') then
|
if (lTypeName = 'TDate') or (lTypeName = 'TDateTime') or (lTypeName = 'TTime') then
|
||||||
begin
|
begin
|
||||||
JSON.D[KeyName] := Value.AsExtended;
|
JSON.D[KeyName] := Value.AsExtended;
|
||||||
end
|
end
|
||||||
@ -2118,8 +2132,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
tkEnumeration:
|
tkEnumeration:
|
||||||
begin
|
begin
|
||||||
Value.TryAsOrdinal(lOrdinalValue);
|
if (Value.TypeInfo = System.TypeInfo(Boolean)) then
|
||||||
JSON.I[KeyName] := lOrdinalValue;
|
begin
|
||||||
|
JSON.B[KeyName] := Value.AsBoolean;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Value.TryAsOrdinal(lOrdinalValue);
|
||||||
|
JSON.I[KeyName] := lOrdinalValue;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
tkClass, tkInterface:
|
tkClass, tkInterface:
|
||||||
begin
|
begin
|
||||||
|
@ -88,7 +88,7 @@ uses
|
|||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
TSessionData = TDictionary<String, String>;
|
TSessionData = TDictionary<string, string>;
|
||||||
TMVCCustomData = TSessionData;
|
TMVCCustomData = TSessionData;
|
||||||
TMVCBaseViewEngine = class;
|
TMVCBaseViewEngine = class;
|
||||||
TMVCViewEngineClass = class of TMVCBaseViewEngine;
|
TMVCViewEngineClass = class of TMVCBaseViewEngine;
|
||||||
@ -348,11 +348,11 @@ type
|
|||||||
function ContentParam(const AName: string): string;
|
function ContentParam(const AName: string): string;
|
||||||
function Cookie(const AName: string): string;
|
function Cookie(const AName: string): string;
|
||||||
function Body: string;
|
function Body: string;
|
||||||
function BodyAs<T: class, constructor>: T;
|
function BodyAs<T: class, constructor>(const RootNode: string = ''): T;
|
||||||
function BodyAsListOf<T: class, constructor>: TObjectList<T>;
|
function BodyAsListOf<T: class, constructor>: TObjectList<T>;
|
||||||
procedure BodyFor<T: class, constructor>(const AObject: T);
|
procedure BodyFor<T: class, constructor>(const AObject: T);
|
||||||
procedure BodyForListOf<T: class, constructor>(const AObjectList: TObjectList<T>);
|
procedure BodyForListOf<T: class, constructor>(const AObjectList: TObjectList<T>);
|
||||||
// function HeaderNames: TArray<String>;
|
// function HeaderNames: TArray<String>;
|
||||||
property RawWebRequest: TWebRequest read FWebRequest;
|
property RawWebRequest: TWebRequest read FWebRequest;
|
||||||
property ContentMediaType: string read FContentMediaType;
|
property ContentMediaType: string read FContentMediaType;
|
||||||
property ContentType: string read FContentType;
|
property ContentType: string read FContentType;
|
||||||
@ -606,7 +606,7 @@ type
|
|||||||
FContentCharset: string;
|
FContentCharset: string;
|
||||||
FResponseStream: TStringBuilder;
|
FResponseStream: TStringBuilder;
|
||||||
function ToMVCList(const AObject: TObject; AOwnsObject: Boolean = False): IMVCList;
|
function ToMVCList(const AObject: TObject; AOwnsObject: Boolean = False): IMVCList;
|
||||||
public
|
public { this must be public because of entity processors }
|
||||||
function GetContentType: string;
|
function GetContentType: string;
|
||||||
function GetStatusCode: Integer;
|
function GetStatusCode: Integer;
|
||||||
procedure SetContentType(const AValue: string);
|
procedure SetContentType(const AValue: string);
|
||||||
@ -756,16 +756,17 @@ type
|
|||||||
property StatusCode: Integer read GetStatusCode write SetStatusCode;
|
property StatusCode: Integer read GetStatusCode write SetStatusCode;
|
||||||
property ViewModelList: TMVCViewDataObject read GetViewModel;
|
property ViewModelList: TMVCViewDataObject read GetViewModel;
|
||||||
property ViewDataSetList: TMVCViewDataSet read GetViewDataSets;
|
property ViewDataSetList: TMVCViewDataSet read GetViewDataSets;
|
||||||
public
|
|
||||||
constructor Create; virtual;
|
|
||||||
destructor Destroy; override;
|
|
||||||
|
|
||||||
// procedure PushToView(const AModelName: string; const AModel: string);
|
|
||||||
procedure PushObjectToView(const aModelName: string; const AModel: TObject); deprecated 'Use "ViewData"';
|
procedure PushObjectToView(const aModelName: string; const AModel: TObject); deprecated 'Use "ViewData"';
|
||||||
procedure PushDataSetToView(const aModelName: string; const ADataSet: TDataSet); deprecated 'Use "ViewDataSet"';
|
procedure PushDataSetToView(const aModelName: string; const ADataSet: TDataSet); deprecated 'Use "ViewDataSet"';
|
||||||
|
|
||||||
property ViewData[const aModelName: string]: TObject read GetViewData write SetViewData;
|
property ViewData[const aModelName: string]: TObject read GetViewData write SetViewData;
|
||||||
property ViewDataset[const aDataSetName: string]: TDataSet read GetViewDataset write SetViewDataset;
|
property ViewDataset[const aDataSetName: string]: TDataSet read GetViewDataset write SetViewDataset;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create; virtual;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TMVCControllerClazz = class of TMVCController;
|
TMVCControllerClazz = class of TMVCController;
|
||||||
@ -854,7 +855,7 @@ type
|
|||||||
FConfig: TMVCConfig;
|
FConfig: TMVCConfig;
|
||||||
FConfigCache_MaxRequestSize: Int64;
|
FConfigCache_MaxRequestSize: Int64;
|
||||||
FConfigCache_ExposeServerSignature: Boolean;
|
FConfigCache_ExposeServerSignature: Boolean;
|
||||||
FConfigCache_ServerSignature: String;
|
FConfigCache_ServerSignature: string;
|
||||||
FConfigCache_ExposeXPoweredBy: Boolean;
|
FConfigCache_ExposeXPoweredBy: Boolean;
|
||||||
FSerializers: TDictionary<string, IMVCSerializer>;
|
FSerializers: TDictionary<string, IMVCSerializer>;
|
||||||
FMiddlewares: TList<IMVCMiddleware>;
|
FMiddlewares: TList<IMVCMiddleware>;
|
||||||
@ -1144,7 +1145,7 @@ begin
|
|||||||
Result := FBody;
|
Result := FBody;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMVCWebRequest.BodyAs<T>: T;
|
function TMVCWebRequest.BodyAs<T>(const RootNode: string): T;
|
||||||
var
|
var
|
||||||
Obj: TObject;
|
Obj: TObject;
|
||||||
lSerializer: IMVCSerializer;
|
lSerializer: IMVCSerializer;
|
||||||
@ -1154,7 +1155,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
Obj := TMVCSerializerHelper.CreateObject(TClass(T).QualifiedClassName);
|
Obj := TMVCSerializerHelper.CreateObject(TClass(T).QualifiedClassName);
|
||||||
try
|
try
|
||||||
lSerializer.DeserializeObject(Body, Obj);
|
lSerializer.DeserializeObject(Body, Obj, TMVCSerializationType.stDefault, nil, RootNode);
|
||||||
Result := Obj as T;
|
Result := Obj as T;
|
||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
@ -1209,19 +1210,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
//function TMVCWebRequest.HeaderNames: TArray<String>;
|
// function TMVCWebRequest.HeaderNames: TArray<String>;
|
||||||
//var
|
// var
|
||||||
// lHeaderList: TIdHeaderList;
|
// lHeaderList: TIdHeaderList;
|
||||||
// I: Integer;
|
// I: Integer;
|
||||||
//begin
|
// begin
|
||||||
// EnsureINDY;
|
// EnsureINDY;
|
||||||
// lHeaderList := THackIdHTTPAppRequest(TMVCIndyWebRequest(Self).RawWebRequest).FRequestInfo.RawHeaders;
|
// lHeaderList := THackIdHTTPAppRequest(TMVCIndyWebRequest(Self).RawWebRequest).FRequestInfo.RawHeaders;
|
||||||
// SetLength(Result, lHeaderList.Count);
|
// SetLength(Result, lHeaderList.Count);
|
||||||
// for I := 0 to Pred(lHeaderList.Count) do
|
// for I := 0 to Pred(lHeaderList.Count) do
|
||||||
// begin
|
// begin
|
||||||
// Result[I] := lHeaderList.Names[I];
|
// Result[I] := lHeaderList.Names[I];
|
||||||
// end;
|
// end;
|
||||||
//end;
|
// end;
|
||||||
|
|
||||||
procedure TMVCWebRequest.BodyForListOf<T>(const AObjectList: TObjectList<T>);
|
procedure TMVCWebRequest.BodyForListOf<T>(const AObjectList: TObjectList<T>);
|
||||||
var
|
var
|
||||||
@ -1335,7 +1336,7 @@ end;
|
|||||||
|
|
||||||
procedure TMVCWebRequest.EnsureINDY;
|
procedure TMVCWebRequest.EnsureINDY;
|
||||||
begin
|
begin
|
||||||
if not (Self is TMVCIndyWebRequest) then
|
if not(Self is TMVCIndyWebRequest) then
|
||||||
begin
|
begin
|
||||||
raise EMVCException.Create(http_status.InternalServerError, 'Method available only in INDY implementation');
|
raise EMVCException.Create(http_status.InternalServerError, 'Method available only in INDY implementation');
|
||||||
end;
|
end;
|
||||||
@ -2111,7 +2112,8 @@ begin
|
|||||||
|
|
||||||
if ARequest.ContentLength > FConfigCache_MaxRequestSize then
|
if ARequest.ContentLength > FConfigCache_MaxRequestSize then
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.RequestEntityTooLarge, 'Request size exceeded the max allowed size [%d KiB] (1)',
|
raise EMVCException.CreateFmt(http_status.RequestEntityTooLarge,
|
||||||
|
'Request size exceeded the max allowed size [%d KiB] (1)',
|
||||||
[(FConfigCache_MaxRequestSize div 1024)]);
|
[(FConfigCache_MaxRequestSize div 1024)]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -2121,7 +2123,8 @@ begin
|
|||||||
// Double check for malicious content-length header
|
// Double check for malicious content-length header
|
||||||
if ARequest.ContentLength > FConfigCache_MaxRequestSize then
|
if ARequest.ContentLength > FConfigCache_MaxRequestSize then
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.RequestEntityTooLarge, 'Request size exceeded the max allowed size [%d KiB] (2)',
|
raise EMVCException.CreateFmt(http_status.RequestEntityTooLarge,
|
||||||
|
'Request size exceeded the max allowed size [%d KiB] (2)',
|
||||||
[(FConfigCache_MaxRequestSize div 1024)]);
|
[(FConfigCache_MaxRequestSize div 1024)]);
|
||||||
end;
|
end;
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
@ -2155,7 +2158,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
Log.ErrorFmt('[%s] %s (Custom message: "%s")',
|
Log.ErrorFmt('[%s] %s (Custom message: "%s")',
|
||||||
[Ex.Classname, Ex.Message, 'Cannot create controller'], LOGGERPRO_TAG);
|
[Ex.Classname, Ex.Message, 'Cannot create controller'], LOGGERPRO_TAG);
|
||||||
raise EMVCException.Create(HTTP_STATUS.InternalServerError, 'Cannot create controller');
|
raise EMVCException.Create(http_status.InternalServerError, 'Cannot create controller');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
lSelectedController.Engine := Self;
|
lSelectedController.Engine := Self;
|
||||||
@ -2208,14 +2211,14 @@ begin
|
|||||||
begin
|
begin
|
||||||
if Config[TMVCConfigKey.AllowUnhandledAction] = 'false' then
|
if Config[TMVCConfigKey.AllowUnhandledAction] = 'false' then
|
||||||
begin
|
begin
|
||||||
lContext.Response.StatusCode := HTTP_STATUS.NotFound;
|
lContext.Response.StatusCode := http_status.NotFound;
|
||||||
lContext.Response.ReasonString := 'Not Found';
|
lContext.Response.ReasonString := 'Not Found';
|
||||||
fOnRouterLog(lRouter, rlsRouteNotFound, lContext);
|
fOnRouterLog(lRouter, rlsRouteNotFound, lContext);
|
||||||
raise EMVCException.Create(
|
raise EMVCException.Create(
|
||||||
lContext.Response.ReasonString,
|
lContext.Response.ReasonString,
|
||||||
lContext.Request.HTTPMethodAsString + ' ' + lContext.Request.PathInfo,
|
lContext.Request.HTTPMethodAsString + ' ' + lContext.Request.PathInfo,
|
||||||
0,
|
0,
|
||||||
HTTP_STATUS.NotFound
|
http_status.NotFound
|
||||||
);
|
);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -2262,12 +2265,12 @@ begin
|
|||||||
LOGGERPRO_TAG);
|
LOGGERPRO_TAG);
|
||||||
if Assigned(lSelectedController) then
|
if Assigned(lSelectedController) then
|
||||||
begin
|
begin
|
||||||
lSelectedController.ResponseStatus(HTTP_STATUS.InternalServerError);
|
lSelectedController.ResponseStatus(http_status.InternalServerError);
|
||||||
lSelectedController.Render(EIO);
|
lSelectedController.Render(EIO);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SendRawHTTPStatus(lContext, HTTP_STATUS.InternalServerError,
|
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||||
Format('[%s] %s', [EIO.Classname, EIO.Message]), EIO.Classname);
|
Format('[%s] %s', [EIO.Classname, EIO.Message]), EIO.Classname);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2280,12 +2283,12 @@ begin
|
|||||||
[Ex.Classname, Ex.Message, 'Global Action Exception Handler'], LOGGERPRO_TAG);
|
[Ex.Classname, Ex.Message, 'Global Action Exception Handler'], LOGGERPRO_TAG);
|
||||||
if Assigned(lSelectedController) then
|
if Assigned(lSelectedController) then
|
||||||
begin
|
begin
|
||||||
lSelectedController.ResponseStatus(HTTP_STATUS.InternalServerError);
|
lSelectedController.ResponseStatus(http_status.InternalServerError);
|
||||||
lSelectedController.Render(Ex);
|
lSelectedController.Render(Ex);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SendRawHTTPStatus(lContext, HTTP_STATUS.InternalServerError,
|
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||||
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2303,12 +2306,12 @@ begin
|
|||||||
if Assigned(lSelectedController) then
|
if Assigned(lSelectedController) then
|
||||||
begin
|
begin
|
||||||
{ middlewares *must* not raise unhandled exceptions }
|
{ middlewares *must* not raise unhandled exceptions }
|
||||||
lSelectedController.ResponseStatus(HTTP_STATUS.InternalServerError);
|
lSelectedController.ResponseStatus(http_status.InternalServerError);
|
||||||
lSelectedController.Render(Ex);
|
lSelectedController.Render(Ex);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SendRawHTTPStatus(lContext, HTTP_STATUS.InternalServerError,
|
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||||
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2402,7 +2405,7 @@ var
|
|||||||
lQualifiedName: string;
|
lQualifiedName: string;
|
||||||
begin
|
begin
|
||||||
if AContext.Request.SegmentParamsCount <> Length(AActionFormalParams) then
|
if AContext.Request.SegmentParamsCount <> Length(AActionFormalParams) then
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Parameters count mismatch (expected %d actual %d) for action "%s"',
|
'Parameters count mismatch (expected %d actual %d) for action "%s"',
|
||||||
[Length(AActionFormalParams), AContext.Request.SegmentParamsCount, AActionName]);
|
[Length(AActionFormalParams), AContext.Request.SegmentParamsCount, AActionName]);
|
||||||
|
|
||||||
@ -2413,7 +2416,7 @@ begin
|
|||||||
|
|
||||||
if not AContext.Request.SegmentParam(lParamName, lStrValue) then
|
if not AContext.Request.SegmentParam(lParamName, lStrValue) then
|
||||||
raise EMVCException.CreateFmt
|
raise EMVCException.CreateFmt
|
||||||
(HTTP_STATUS.BadRequest, 'Invalid parameter %s for action %s (Hint: Here parameters names are case-sensitive)',
|
(http_status.BadRequest, 'Invalid parameter %s for action %s (Hint: Here parameters names are case-sensitive)',
|
||||||
[lParamName, AActionName]);
|
[lParamName, AActionName]);
|
||||||
|
|
||||||
case AActionFormalParams[I].ParamType.TypeKind of
|
case AActionFormalParams[I].ParamType.TypeKind of
|
||||||
@ -2423,7 +2426,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid Integer value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid Integer value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2434,7 +2437,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid Int64 value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid Int64 value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2455,7 +2458,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid TDate value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid TDate value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2470,7 +2473,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid TDateTime value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid TDateTime value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2485,7 +2488,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid TTime value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid TTime value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2498,7 +2501,7 @@ begin
|
|||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest,
|
raise EMVCException.CreateFmt(http_status.BadRequest,
|
||||||
'Invalid Float value for param [%s] - [CLASS: %s][MSG: %s]',
|
'Invalid Float value for param [%s] - [CLASS: %s][MSG: %s]',
|
||||||
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
[AActionFormalParams[I].name, E.Classname, E.Message]);
|
||||||
end;
|
end;
|
||||||
@ -2516,14 +2519,14 @@ begin
|
|||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt
|
raise EMVCException.CreateFmt
|
||||||
(HTTP_STATUS.BadRequest,
|
(http_status.BadRequest,
|
||||||
'Invalid boolean value for parameter %s. Boolean parameters accepts only "true"/"false" or "1"/"0".',
|
'Invalid boolean value for parameter %s. Boolean parameters accepts only "true"/"false" or "1"/"0".',
|
||||||
[lParamName]);
|
[lParamName]);
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest, 'Invalid type for parameter %s. Allowed types are ' +
|
raise EMVCException.CreateFmt(http_status.BadRequest, 'Invalid type for parameter %s. Allowed types are ' +
|
||||||
ALLOWED_TYPED_ACTION_PARAMETERS_TYPES, [lParamName]);
|
ALLOWED_TYPED_ACTION_PARAMETERS_TYPES, [lParamName]);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2543,7 +2546,7 @@ begin
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
raise EMVCException.CreateFmt(HTTP_STATUS.BadRequest, 'Invalid type for parameter %s. Allowed types are ' +
|
raise EMVCException.CreateFmt(http_status.BadRequest, 'Invalid type for parameter %s. Allowed types are ' +
|
||||||
ALLOWED_TYPED_ACTION_PARAMETERS_TYPES, [lParamName]);
|
ALLOWED_TYPED_ACTION_PARAMETERS_TYPES, [lParamName]);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -2610,7 +2613,7 @@ end;
|
|||||||
|
|
||||||
procedure TMVCEngine.HTTP404(const AContext: TWebContext);
|
procedure TMVCEngine.HTTP404(const AContext: TWebContext);
|
||||||
begin
|
begin
|
||||||
AContext.Response.SetStatusCode(HTTP_STATUS.NotFound);
|
AContext.Response.SetStatusCode(http_status.NotFound);
|
||||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
||||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||||
AContext.Response.SetReasonString('Not Found');
|
AContext.Response.SetReasonString('Not Found');
|
||||||
@ -2619,7 +2622,7 @@ end;
|
|||||||
|
|
||||||
procedure TMVCEngine.HTTP500(const AContext: TWebContext; const AReasonString: string);
|
procedure TMVCEngine.HTTP500(const AContext: TWebContext; const AReasonString: string);
|
||||||
begin
|
begin
|
||||||
AContext.Response.SetStatusCode(HTTP_STATUS.InternalServerError);
|
AContext.Response.SetStatusCode(http_status.InternalServerError);
|
||||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
||||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||||
AContext.Response.SetReasonString('Internal server error');
|
AContext.Response.SetReasonString('Internal server error');
|
||||||
@ -2680,7 +2683,7 @@ begin
|
|||||||
|
|
||||||
if IsShuttingDown then
|
if IsShuttingDown then
|
||||||
begin
|
begin
|
||||||
AResponse.StatusCode := HTTP_STATUS.ServiceUnavailable;
|
AResponse.StatusCode := http_status.ServiceUnavailable;
|
||||||
AResponse.ContentType := TMVCMediaType.TEXT_PLAIN;
|
AResponse.ContentType := TMVCMediaType.TEXT_PLAIN;
|
||||||
AResponse.Content := 'Server is shutting down';
|
AResponse.Content := 'Server is shutting down';
|
||||||
AHandled := True;
|
AHandled := True;
|
||||||
@ -2695,10 +2698,10 @@ begin
|
|||||||
begin
|
begin
|
||||||
Log.ErrorFmt('[%s] %s', [E.Classname, E.Message], LOGGERPRO_TAG);
|
Log.ErrorFmt('[%s] %s', [E.Classname, E.Message], LOGGERPRO_TAG);
|
||||||
|
|
||||||
AResponse.StatusCode:= HTTP_STATUS.InternalServerError; // default is Internal Server Error
|
AResponse.StatusCode := http_status.InternalServerError; // default is Internal Server Error
|
||||||
if E is EMVCException then
|
if E is EMVCException then
|
||||||
begin
|
begin
|
||||||
AResponse.StatusCode:= (E as EMVCException).HttpErrorCode;
|
AResponse.StatusCode := (E as EMVCException).HTTPErrorCode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
AResponse.Content := E.Message;
|
AResponse.Content := E.Message;
|
||||||
@ -2741,7 +2744,7 @@ procedure TMVCEngine.ResponseErrorPage(const AException: Exception; const AReque
|
|||||||
const AResponse: TWebResponse);
|
const AResponse: TWebResponse);
|
||||||
begin
|
begin
|
||||||
AResponse.SetCustomHeader('x-mvc-error', AException.Classname + ': ' + AException.Message);
|
AResponse.SetCustomHeader('x-mvc-error', AException.Classname + ': ' + AException.Message);
|
||||||
AResponse.StatusCode := HTTP_STATUS.OK;
|
AResponse.StatusCode := http_status.OK;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
AResponse.ContentType := TMVCMediaType.TEXT_PLAIN;
|
AResponse.ContentType := TMVCMediaType.TEXT_PLAIN;
|
||||||
@ -2755,7 +2758,8 @@ class function TMVCEngine.SendSessionCookie(const AContext: TWebContext): string
|
|||||||
var
|
var
|
||||||
SId: string;
|
SId: string;
|
||||||
begin
|
begin
|
||||||
SId := StringReplace(StringReplace(StringReplace(GUIDToString(TGUID.NewGuid), '}', '', []), '{', '', []), '-', '',
|
SId := StringReplace(StringReplace(StringReplace('DT' + GUIDToString(TGUID.NewGuid), '}', '',
|
||||||
|
[]), '{', '', []), '-', '',
|
||||||
[rfReplaceAll]);
|
[rfReplaceAll]);
|
||||||
Result := SendSessionCookie(AContext, SId);
|
Result := SendSessionCookie(AContext, SId);
|
||||||
end;
|
end;
|
||||||
@ -3132,7 +3136,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
raise EMVCException.Create('Cannot send 202 without provide an HREF');
|
raise EMVCException.Create('Cannot send 202 without provide an HREF');
|
||||||
end;
|
end;
|
||||||
ResponseStatus(HTTP_STATUS.Accepted, Reason);
|
ResponseStatus(http_status.Accepted, Reason);
|
||||||
Render(TMVCAcceptedResponse.Create(HREF, ID));
|
Render(TMVCAcceptedResponse.Create(HREF, ID));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -3142,10 +3146,10 @@ begin
|
|||||||
begin
|
begin
|
||||||
FContext.Response.CustomHeaders.AddPair('location', Location);
|
FContext.Response.CustomHeaders.AddPair('location', Location);
|
||||||
end;
|
end;
|
||||||
ResponseStatus(HTTP_STATUS.Created, Reason);
|
ResponseStatus(http_status.Created, Reason);
|
||||||
{$IF CompilerVersion >= 34}
|
{$IF CompilerVersion >= 34}
|
||||||
Render(''); //in 10.4 INDY requires something on the content
|
Render(''); // in 10.4 INDY requires something on the content
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMVCRenderer.Render204NoContent(const Location, Reason: string);
|
procedure TMVCRenderer.Render204NoContent(const Location, Reason: string);
|
||||||
@ -3154,7 +3158,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
FContext.Response.CustomHeaders.AddPair('location', Location);
|
FContext.Response.CustomHeaders.AddPair('location', Location);
|
||||||
end;
|
end;
|
||||||
ResponseStatus(HTTP_STATUS.NoContent, Reason);
|
ResponseStatus(http_status.NoContent, Reason);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMVCRenderer.ResponseStatus(const AStatusCode: Integer; const AReasonString: string);
|
procedure TMVCRenderer.ResponseStatus(const AStatusCode: Integer; const AReasonString: string);
|
||||||
@ -3442,7 +3446,7 @@ begin
|
|||||||
// setting up the correct SSE headers
|
// setting up the correct SSE headers
|
||||||
SetContentType('text/event-stream');
|
SetContentType('text/event-stream');
|
||||||
GetContext.Response.SetCustomHeader('Cache-Control', 'no-cache');
|
GetContext.Response.SetCustomHeader('Cache-Control', 'no-cache');
|
||||||
GetContext.Response.StatusCode := HTTP_STATUS.OK;
|
GetContext.Response.StatusCode := http_status.OK;
|
||||||
|
|
||||||
// render the response using SSE compliant data format
|
// render the response using SSE compliant data format
|
||||||
|
|
||||||
@ -3509,8 +3513,8 @@ begin
|
|||||||
if AException is EMVCException then
|
if AException is EMVCException then
|
||||||
ResponseStatus(EMVCException(AException).HTTPErrorCode, AException.Message + ' [' + AException.Classname + ']');
|
ResponseStatus(EMVCException(AException).HTTPErrorCode, AException.Message + ' [' + AException.Classname + ']');
|
||||||
|
|
||||||
if (GetContext.Response.StatusCode = HTTP_STATUS.OK) then
|
if (GetContext.Response.StatusCode = http_status.OK) then
|
||||||
ResponseStatus(HTTP_STATUS.InternalServerError, AException.Message + ' [' + AException.Classname + ']');
|
ResponseStatus(http_status.InternalServerError, AException.Message + ' [' + AException.Classname + ']');
|
||||||
|
|
||||||
if (not GetContext.Request.IsAjax) and (GetContext.Request.ClientPrefer(TMVCMediaType.TEXT_HTML)) then
|
if (not GetContext.Request.IsAjax) and (GetContext.Request.ClientPrefer(TMVCMediaType.TEXT_HTML)) then
|
||||||
begin
|
begin
|
||||||
|
@ -5,25 +5,29 @@ program DMVCFrameworkTests;
|
|||||||
{$APPTYPE CONSOLE}
|
{$APPTYPE CONSOLE}
|
||||||
{$ENDIF}{$ENDIF}{$STRONGLINKTYPES ON}
|
{$ENDIF}{$ENDIF}{$STRONGLINKTYPES ON}
|
||||||
|
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
System.SysUtils,
|
||||||
{$IFDEF GUI_TESTRUNNER}
|
{$IFDEF GUI_TESTRUNNER}
|
||||||
Vcl.Forms,
|
Vcl.Forms,
|
||||||
DUnitX.Loggers.GUI.Vcl,
|
DUnitX.Loggers.GUI.Vcl,
|
||||||
{$ENDIF }
|
// Fmx.Forms,
|
||||||
{$IFDEF CONSOLE_TESTRUNNER}
|
// DUNitX.Loggers.GUIX,
|
||||||
|
{$ENDIF }
|
||||||
|
{$IFDEF CONSOLE_TESTRUNNER}
|
||||||
DUnitX.Loggers.Console,
|
DUnitX.Loggers.Console,
|
||||||
{$ENDIF }
|
{$ENDIF }
|
||||||
DUnitX.Loggers.Xml.NUnit,
|
// DUnitX.Loggers.Xml.NUnit,
|
||||||
DUnitX.TestFramework,
|
DUnitX.TestFramework,
|
||||||
FrameworkTestsU in 'FrameworkTestsU.pas',
|
FrameworkTestsU in 'FrameworkTestsU.pas',
|
||||||
LiveServerTestU in 'LiveServerTestU.pas',
|
LiveServerTestU in 'LiveServerTestU.pas',
|
||||||
BOs in 'BOs.pas',
|
BOs in 'BOs.pas',
|
||||||
TestServerControllerU in '..\TestServer\TestServerControllerU.pas',
|
TestServerControllerU in '..\TestServer\TestServerControllerU.pas',
|
||||||
RESTAdapterTestsU in 'RESTAdapterTestsU.pas',
|
RESTAdapterTestsU in 'RESTAdapterTestsU.pas',
|
||||||
MVCFramework.Tests.WebModule2 in '..\StandaloneServer\MVCFramework.Tests.WebModule2.pas' {TestWebModule2: TWebModule},
|
MVCFramework.Tests.WebModule2
|
||||||
|
in '..\StandaloneServer\MVCFramework.Tests.WebModule2.pas' {TestWebModule2: TWebModule} ,
|
||||||
MVCFramework.Tests.StandaloneServer in '..\StandaloneServer\MVCFramework.Tests.StandaloneServer.pas',
|
MVCFramework.Tests.StandaloneServer in '..\StandaloneServer\MVCFramework.Tests.StandaloneServer.pas',
|
||||||
MVCFramework.Tests.WebModule1 in '..\RESTClient\MVCFramework.Tests.WebModule1.pas' {TestWebModule1: TWebModule},
|
MVCFramework.Tests.WebModule1 in '..\RESTClient\MVCFramework.Tests.WebModule1.pas' {TestWebModule1: TWebModule} ,
|
||||||
MVCFramework.Tests.RESTClient in '..\RESTClient\MVCFramework.Tests.RESTClient.pas',
|
MVCFramework.Tests.RESTClient in '..\RESTClient\MVCFramework.Tests.RESTClient.pas',
|
||||||
MVCFramework.Tests.AppController in '..\RESTClient\MVCFramework.Tests.AppController.pas',
|
MVCFramework.Tests.AppController in '..\RESTClient\MVCFramework.Tests.AppController.pas',
|
||||||
BusinessObjectsU in '..\..\..\samples\commons\BusinessObjectsU.pas',
|
BusinessObjectsU in '..\..\..\samples\commons\BusinessObjectsU.pas',
|
||||||
@ -37,21 +41,24 @@ uses
|
|||||||
JsonDataObjects in '..\..\..\sources\JsonDataObjects.pas',
|
JsonDataObjects in '..\..\..\sources\JsonDataObjects.pas',
|
||||||
Serializers.JsonDataObjectsTestU in 'Serializers.JsonDataObjectsTestU.pas',
|
Serializers.JsonDataObjectsTestU in 'Serializers.JsonDataObjectsTestU.pas',
|
||||||
MVCFramework.Tests.Serializer.Entities in '..\..\common\MVCFramework.Tests.Serializer.Entities.pas',
|
MVCFramework.Tests.Serializer.Entities in '..\..\common\MVCFramework.Tests.Serializer.Entities.pas',
|
||||||
MVCFramework.Tests.Serializer.EntitiesModule in '..\..\common\MVCFramework.Tests.Serializer.EntitiesModule.pas' {EntitiesModule: TDataModule},
|
MVCFramework.Tests.Serializer.EntitiesModule
|
||||||
|
in '..\..\common\MVCFramework.Tests.Serializer.EntitiesModule.pas' {EntitiesModule: TDataModule} ,
|
||||||
MVCFramework.Tests.Serializer.Intf in '..\..\common\MVCFramework.Tests.Serializer.Intf.pas',
|
MVCFramework.Tests.Serializer.Intf in '..\..\common\MVCFramework.Tests.Serializer.Intf.pas',
|
||||||
MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes in '..\..\..\sources\MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes.pas',
|
MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes
|
||||||
|
in '..\..\..\sources\MVCFramework.Serializer.JsonDataObjects.OptionalCustomTypes.pas',
|
||||||
ActiveRecordTestsU in 'ActiveRecordTestsU.pas',
|
ActiveRecordTestsU in 'ActiveRecordTestsU.pas',
|
||||||
TestConstsU in 'TestConstsU.pas';
|
TestConstsU in 'TestConstsU.pas';
|
||||||
|
|
||||||
{$R *.RES}
|
{$R *.RES}
|
||||||
{$IFDEF CONSOLE_TESTRUNNER}
|
{$IFDEF CONSOLE_TESTRUNNER}
|
||||||
|
|
||||||
|
|
||||||
procedure MainConsole();
|
procedure MainConsole();
|
||||||
var
|
var
|
||||||
runner: ITestRunner;
|
runner: ITestRunner;
|
||||||
results: IRunResults;
|
results: IRunResults;
|
||||||
logger: ITestLogger;
|
logger: ITestLogger;
|
||||||
// nunitLogger: ITestLogger;
|
// nunitLogger: ITestLogger;
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
// Check command line options, will exit if invalid
|
// Check command line options, will exit if invalid
|
||||||
@ -90,14 +97,17 @@ end;
|
|||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
{$IFDEF GUI_TESTRUNNER}
|
{$IFDEF GUI_TESTRUNNER}
|
||||||
|
|
||||||
|
|
||||||
procedure MainGUI;
|
procedure MainGUI;
|
||||||
begin
|
begin
|
||||||
Application.Initialize;
|
Application.Initialize;
|
||||||
Application.CreateForm(TGUIVCLTestRunner, GUIVCLTestRunner);
|
Application.CreateForm(TGUIVCLTestRunner, GUIVCLTestRunner);
|
||||||
|
// Application.CreateForm(TGUIXTestRunner, GUIXTestRunner);
|
||||||
Application.Run;
|
Application.Run;
|
||||||
end;
|
end;
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
ReportMemoryLeaksOnShutdown := True;
|
ReportMemoryLeaksOnShutdown := True;
|
||||||
{$IFDEF CONSOLE_TESTRUNNER}
|
{$IFDEF CONSOLE_TESTRUNNER}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<ProjectVersion>19.0</ProjectVersion>
|
<ProjectVersion>19.0</ProjectVersion>
|
||||||
<FrameworkType>VCL</FrameworkType>
|
<FrameworkType>VCL</FrameworkType>
|
||||||
<Base>True</Base>
|
<Base>True</Base>
|
||||||
<Config Condition="'$(Config)'==''">GUI</Config>
|
<Config Condition="'$(Config)'==''">CONSOLE</Config>
|
||||||
<Platform Condition="'$(Platform)'==''">Win32</Platform>
|
<Platform Condition="'$(Platform)'==''">Win32</Platform>
|
||||||
<TargetedPlatforms>1</TargetedPlatforms>
|
<TargetedPlatforms>1</TargetedPlatforms>
|
||||||
<AppType>Console</AppType>
|
<AppType>Console</AppType>
|
||||||
|
@ -33,7 +33,6 @@ uses
|
|||||||
System.DateUtils,
|
System.DateUtils,
|
||||||
System.Hash;
|
System.Hash;
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
TBaseServerTest = class(TObject)
|
TBaseServerTest = class(TObject)
|
||||||
@ -224,12 +223,17 @@ type
|
|||||||
protected
|
protected
|
||||||
FExecutor: IMVCJSONRPCExecutor;
|
FExecutor: IMVCJSONRPCExecutor;
|
||||||
FExecutor2: IMVCJSONRPCExecutor;
|
FExecutor2: IMVCJSONRPCExecutor;
|
||||||
|
FExecutor3: IMVCJSONRPCExecutor;
|
||||||
public
|
public
|
||||||
[Setup]
|
[Setup]
|
||||||
procedure Setup;
|
procedure Setup;
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestRequestWithoutParams;
|
procedure TestRequestWithoutParams;
|
||||||
[Test]
|
[Test]
|
||||||
|
procedure TestNotificationWithoutParams;
|
||||||
|
[Test]
|
||||||
|
procedure TestNotificationWhichRaisesError;
|
||||||
|
[Test]
|
||||||
procedure TestRequestToNotFoundMethod;
|
procedure TestRequestToNotFoundMethod;
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestRequestWithParams_I_I_ret_I;
|
procedure TestRequestWithParams_I_I_ret_I;
|
||||||
@ -249,6 +253,26 @@ type
|
|||||||
procedure TestRequestWithParams_I_I_ret_A;
|
procedure TestRequestWithParams_I_I_ret_A;
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestRequestWithParams_DT_T_ret_DT;
|
procedure TestRequestWithParams_DT_T_ret_DT;
|
||||||
|
// hooks tests
|
||||||
|
[Test]
|
||||||
|
procedure TestHooks;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksWhenMethodRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksWhenOnAfterCallHookRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksNotif;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksNotifWhenOnBeforeRoutingHookRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksNotifWhenOnBeforeCallHookRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksNotifWhenOnAfterCallHookRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksWhenOnBeforeCallHookRaisesError;
|
||||||
|
[Test]
|
||||||
|
procedure TestHooksWhenOnBeforeRoutingHookRaisesError;
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@ -278,7 +302,7 @@ uses
|
|||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
, TestConstsU;
|
, TestConstsU;
|
||||||
|
|
||||||
function GetServer: String;
|
function GetServer: string;
|
||||||
begin
|
begin
|
||||||
Result := 'http://' + TEST_SERVER_ADDRESS + ':9999';
|
Result := 'http://' + TEST_SERVER_ADDRESS + ':9999';
|
||||||
end;
|
end;
|
||||||
@ -1876,6 +1900,123 @@ procedure TJSONRPCServerTest.Setup;
|
|||||||
begin
|
begin
|
||||||
FExecutor := TMVCJSONRPCExecutor.Create('http://' + TEST_SERVER_ADDRESS + ':9999/jsonrpc', false);
|
FExecutor := TMVCJSONRPCExecutor.Create('http://' + TEST_SERVER_ADDRESS + ':9999/jsonrpc', false);
|
||||||
FExecutor2 := TMVCJSONRPCExecutor.Create('http://' + TEST_SERVER_ADDRESS + ':9999/jsonrpcclass', false);
|
FExecutor2 := TMVCJSONRPCExecutor.Create('http://' + TEST_SERVER_ADDRESS + ':9999/jsonrpcclass', false);
|
||||||
|
FExecutor3 := TMVCJSONRPCExecutor.Create('http://' + TEST_SERVER_ADDRESS + ':9999/jsonrpcclass1', false);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooks;
|
||||||
|
begin
|
||||||
|
var lRequest1: IJSONRPCRequest := TJSONRPCRequest.Create(1234, 'request1');
|
||||||
|
var lResp := FExecutor3.ExecuteRequest(lRequest1);
|
||||||
|
Assert.areEqual('OnBeforeRoutingHook|OnBeforeCallHook|OnAfterCallHook',
|
||||||
|
FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksNotif;
|
||||||
|
begin
|
||||||
|
var lNotif: IJSONRPCNotification := TJSONRPCNotification.Create('Notif1');
|
||||||
|
var lResp := FExecutor3.ExecuteNotification(lNotif);
|
||||||
|
Assert.areEqual('OnBeforeRoutingHook|OnBeforeCallHook|OnAfterCallHook', FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
Assert.IsFalse(lResp.IsError);
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
lResp.AsJSONString;
|
||||||
|
end, EMVCJSONRPCException);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksNotifWhenOnAfterCallHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lNotif: IJSONRPCNotification := TJSONRPCNotification.Create('error_OnAfterCallHook');
|
||||||
|
var lResp: IJSONRPCResponse := FExecutor3.ExecuteNotification(lNotif);
|
||||||
|
Assert.areEqual('', FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
Assert.isTrue(lResp.IsError);
|
||||||
|
Assert.WillNotRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
lResp.AsJSONString;
|
||||||
|
end, EMVCJSONRPCException);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksNotifWhenOnBeforeCallHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lNotif: IJSONRPCNotification := TJSONRPCNotification.Create('error_OnBeforeCallHook');
|
||||||
|
var lResp: IJSONRPCResponse := FExecutor3.ExecuteNotification(lNotif);
|
||||||
|
Assert.areEqual('', FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
Assert.isTrue(lResp.IsError);
|
||||||
|
Assert.WillNotRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
lResp.AsJSONString;
|
||||||
|
end, EMVCJSONRPCException);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksNotifWhenOnBeforeRoutingHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lNotif: IJSONRPCNotification := TJSONRPCNotification.Create('error_OnBeforeRoutingHook');
|
||||||
|
var lResp: IJSONRPCResponse := FExecutor3.ExecuteNotification(lNotif);
|
||||||
|
Assert.areEqual('', FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
Assert.isTrue(lResp.IsError);
|
||||||
|
Assert.WillNotRaise(
|
||||||
|
procedure
|
||||||
|
begin
|
||||||
|
lResp.AsJSONString;
|
||||||
|
end, EMVCJSONRPCException);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksWhenMethodRaisesError;
|
||||||
|
begin
|
||||||
|
var lRequest1: IJSONRPCRequest := TJSONRPCRequest.Create(1234, 'RequestWithError');
|
||||||
|
var lResp := FExecutor3.ExecuteRequest(lRequest1);
|
||||||
|
Assert.areEqual('OnBeforeRoutingHook|OnBeforeCallHook|OnAfterCallHook|error',
|
||||||
|
FExecutor3.HTTPResponse.HeaderValue['x-history']);
|
||||||
|
Assert.isTrue(lResp.IsError, 'Method raised error but response is not an error');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksWhenOnAfterCallHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lRequest1: IJSONRPCRequest := TJSONRPCRequest.Create(1234, 'error_OnAfterCallHook');
|
||||||
|
var lResp := FExecutor3.ExecuteRequest(lRequest1);
|
||||||
|
Assert.isTrue(lResp.IsError, lResp.ToString(true));
|
||||||
|
Assert.areEqual(lResp.Error.ErrMessage, 'error_OnAfterCallHook');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksWhenOnBeforeCallHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lRequest1: IJSONRPCRequest := TJSONRPCRequest.Create(1234, 'error_OnBeforeCallHook');
|
||||||
|
var lResp := FExecutor3.ExecuteRequest(lRequest1);
|
||||||
|
Assert.isTrue(lResp.IsError, lResp.ToString(true));
|
||||||
|
Assert.areEqual(lResp.Error.ErrMessage, 'error_OnBeforeCallHook');
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestHooksWhenOnBeforeRoutingHookRaisesError;
|
||||||
|
begin
|
||||||
|
var lRequest1: IJSONRPCRequest := TJSONRPCRequest.Create(1234, 'error_OnBeforeRoutingHook');
|
||||||
|
var lResp := FExecutor3.ExecuteRequest(lRequest1);
|
||||||
|
Assert.isTrue(lResp.IsError, lResp.ToString(true));
|
||||||
|
Assert.areEqual(lResp.Error.ErrMessage, 'error_OnBeforeRoutingHook');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestNotificationWhichRaisesError;
|
||||||
|
var
|
||||||
|
lReq: IJSONRPCNotification;
|
||||||
|
begin
|
||||||
|
lReq := TJSONRPCNotification.Create;
|
||||||
|
lReq.Method := 'NotifWithError';
|
||||||
|
var lResp := FExecutor3.ExecuteNotification(lReq);
|
||||||
|
Assert.IsTrue(lResp.IsError);
|
||||||
|
Assert.Contains(lResp.Error.ErrMessage, 'BOOM NOTIF');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TJSONRPCServerTest.TestNotificationWithoutParams;
|
||||||
|
var
|
||||||
|
lReq: IJSONRPCNotification;
|
||||||
|
begin
|
||||||
|
lReq := TJSONRPCNotification.Create;
|
||||||
|
lReq.Method := 'mynotify';
|
||||||
|
FExecutor.ExecuteNotification(lReq);
|
||||||
|
FExecutor2.ExecuteNotification(lReq);
|
||||||
|
Assert.Pass();
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TJSONRPCServerTest.TestRequestToNotFoundMethod;
|
procedure TJSONRPCServerTest.TestRequestToNotFoundMethod;
|
||||||
@ -1978,13 +2119,15 @@ end;
|
|||||||
|
|
||||||
procedure TJSONRPCServerTest.TestRequestWithoutParams;
|
procedure TJSONRPCServerTest.TestRequestWithoutParams;
|
||||||
var
|
var
|
||||||
lReq: IJSONRPCNotification;
|
lReq: IJSONRPCRequest;
|
||||||
|
lResp: IJSONRPCResponse;
|
||||||
begin
|
begin
|
||||||
lReq := TJSONRPCNotification.Create;
|
lReq := TJSONRPCRequest.Create;
|
||||||
lReq.Method := 'mynotify';
|
lReq.Method := 'MyRequest';
|
||||||
FExecutor.ExecuteNotification(lReq);
|
lReq.RequestID := 1234;
|
||||||
FExecutor2.ExecuteNotification(lReq);
|
lResp := FExecutor.ExecuteRequest(lReq);
|
||||||
Assert.Pass();
|
Assert.isFalse(lResp.IsError);
|
||||||
|
Assert.isTrue(lResp.Result.AsBoolean);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TJSONRPCServerTest.TestRequestWithParams_I_I_ret_I;
|
procedure TJSONRPCServerTest.TestRequestWithParams_I_I_ret_I;
|
||||||
|
@ -10,6 +10,7 @@ type
|
|||||||
public
|
public
|
||||||
function Subtract(Value1, Value2: Int64): Integer;
|
function Subtract(Value1, Value2: Int64): Integer;
|
||||||
procedure MyNotify;
|
procedure MyNotify;
|
||||||
|
function MyRequest: Boolean;
|
||||||
function Add(Value1, Value2, Value3: Int64): TJsonObject;
|
function Add(Value1, Value2, Value3: Int64): TJsonObject;
|
||||||
function GetListFromTo(aFrom, aTo: Int64): TJsonArray;
|
function GetListFromTo(aFrom, aTo: Int64): TJsonArray;
|
||||||
function MultiplyString(aString: string; Multiplier: Int64): string;
|
function MultiplyString(aString: string; Multiplier: Int64): string;
|
||||||
@ -25,6 +26,25 @@ type
|
|||||||
function AddTimeToDateTime(aDateTime: TDateTime; aTime: TTime): TDateTime;
|
function AddTimeToDateTime(aDateTime: TDateTime; aTime: TTime): TDateTime;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TTestJSONRPCHookClass = class(TObject)
|
||||||
|
private
|
||||||
|
fJSONReq: TJsonObject;
|
||||||
|
fHistory: string;
|
||||||
|
fJSONRPCKind: TJSONRPCRequestType;
|
||||||
|
public
|
||||||
|
procedure OnBeforeRoutingHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
procedure OnBeforeCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
procedure OnAfterCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
function error_OnBeforeRoutingHook: Boolean;
|
||||||
|
function error_OnBeforeCallHook: Boolean;
|
||||||
|
function error_OnAfterCallHook: Boolean;
|
||||||
|
|
||||||
|
procedure Notif1;
|
||||||
|
procedure NotifWithError;
|
||||||
|
function Request1: string;
|
||||||
|
function RequestWithError: string;
|
||||||
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
@ -65,6 +85,11 @@ begin
|
|||||||
Self.ClassName;
|
Self.ClassName;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TTestJSONRPCController.MyRequest: Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
function TTestJSONRPCController.Subtract(Value1, Value2: Int64): Integer;
|
function TTestJSONRPCController.Subtract(Value1, Value2: Int64): Integer;
|
||||||
begin
|
begin
|
||||||
Result := Value1 - Value2;
|
Result := Value1 - Value2;
|
||||||
@ -116,4 +141,100 @@ begin
|
|||||||
Result := Value1 - Value2;
|
Result := Value1 - Value2;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TTestJSONRPCHookClass }
|
||||||
|
|
||||||
|
function TTestJSONRPCHookClass.error_OnAfterCallHook: Boolean;
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTestJSONRPCHookClass.error_OnBeforeCallHook: Boolean;
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTestJSONRPCHookClass.error_OnBeforeRoutingHook: Boolean;
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestJSONRPCHookClass.Notif1;
|
||||||
|
begin
|
||||||
|
// do nothing
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestJSONRPCHookClass.NotifWithError;
|
||||||
|
begin
|
||||||
|
raise Exception.Create('BOOM NOTIF');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestJSONRPCHookClass.OnAfterCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
if SameText(fJSONReq.S['method'], 'error_OnAfterCallHook') then
|
||||||
|
raise Exception.Create('error_OnAfterCallHook');
|
||||||
|
|
||||||
|
fHistory := fHistory + '|OnAfterCallHook';
|
||||||
|
|
||||||
|
// do nothing
|
||||||
|
if fJSONRPCKind = TJSONRPCRequestType.Request then
|
||||||
|
begin
|
||||||
|
Assert(Assigned(JSON));
|
||||||
|
LogD('TTestJSONRPCHookClass.OnAfterCallHook: ' + JSON.ToJSON());
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
if Assigned(JSON) then
|
||||||
|
Assert(JSON.Contains('error'), 'ERROR! Notification has a response but is not an error');
|
||||||
|
LogD('TTestJSONRPCHookClass.OnAfterCallHook: Param is nil');
|
||||||
|
end;
|
||||||
|
if Assigned(JSON) then
|
||||||
|
if JSON.Contains('error') then
|
||||||
|
fHistory := fHistory + '|error';
|
||||||
|
Context.Response.CustomHeaders.Values['x-history'] := fHistory;
|
||||||
|
finally
|
||||||
|
FreeAndNil(fJSONReq);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestJSONRPCHookClass.OnBeforeCallHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
if SameText(JSON.S['method'], 'error_OnBeforeCallHook') then
|
||||||
|
raise Exception.Create('error_OnBeforeCallHook');
|
||||||
|
|
||||||
|
fHistory := fHistory + '|OnBeforeCallHook';
|
||||||
|
Assert(Assigned(JSON), 'JSON not assigned in OnBeforeCallHook');
|
||||||
|
LogD('TTestJSONRPCHookClass.OnBeforeCallHook: ' + JSON.ToJSON());
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestJSONRPCHookClass.OnBeforeRoutingHook(const Context: TWebContext; const JSON: TJsonObject);
|
||||||
|
begin
|
||||||
|
fJSONReq := JSON.Clone;
|
||||||
|
|
||||||
|
if SameText(JSON.S['method'], 'error_OnBeforeRoutingHook') then
|
||||||
|
raise Exception.Create('error_OnBeforeRoutingHook');
|
||||||
|
|
||||||
|
fHistory := 'OnBeforeRoutingHook';
|
||||||
|
// do nothing
|
||||||
|
Assert(Assigned(JSON), 'JSON not assigned in OnBeforeRoutingHook');
|
||||||
|
LogD('TTestJSONRPCHookClass.OnBeforeRoutingHook: ' + JSON.ToJSON());
|
||||||
|
if JSON.Contains('id') then
|
||||||
|
fJSONRPCKind := TJSONRPCRequestType.Request
|
||||||
|
else
|
||||||
|
fJSONRPCKind := TJSONRPCRequestType.Notification;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTestJSONRPCHookClass.Request1: string;
|
||||||
|
begin
|
||||||
|
Result := 'empty';
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTestJSONRPCHookClass.RequestWithError: string;
|
||||||
|
begin
|
||||||
|
raise Exception.Create('BOOM REQUEST');
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
@ -92,6 +92,11 @@ begin
|
|||||||
begin
|
begin
|
||||||
Result := TTestJSONRPCClass.Create
|
Result := TTestJSONRPCClass.Create
|
||||||
end, '/jsonrpcclass')
|
end, '/jsonrpcclass')
|
||||||
|
.PublishObject(
|
||||||
|
function: TObject
|
||||||
|
begin
|
||||||
|
Result := TTestJSONRPCHookClass.Create
|
||||||
|
end, '/jsonrpcclass1')
|
||||||
.AddController(TTestFaultController) // this will raise an exception
|
.AddController(TTestFaultController) // this will raise an exception
|
||||||
.AddController(TTestFault2Controller,
|
.AddController(TTestFault2Controller,
|
||||||
function: TMVCController
|
function: TMVCController
|
||||||
|
Loading…
Reference in New Issue
Block a user