Merge remote-tracking branch 'upstream/master' into middleware_etag

This commit is contained in:
João Antônio Duarte 2020-04-24 15:07:29 -03:00
commit 2aeb322f55
7 changed files with 331 additions and 115 deletions

198
README.md
View File

@ -79,10 +79,206 @@ Congratulations to Daniele Teti and all the staff for the excellent work!" -- Ma
> WARNING! Considering the huge amount of features added in 3.1.1-beryllium during its RC phase, the dmvcframework-3.1.1-beryllium has been renamed to dmvcframework-3.2.0-boron
- New! Added Nullable support in MVCActiveRecord! Check *activerecord_showcase* sample.
- New! Added non autogenerated primary keys in MVCActiveRecord! Check *activerecord_showcase* sample.
- New! Complete support for nullable types in the default serializer.
- New! Added `ncCamelCase` and `ncPascalCase` to the available attribute formatters.
| MVCNameCase | Property/Field Name | Rendered Name |
| ------------ | --------------------- | --------------- |
| ncUpperCase | Cod_Article | COD_ARTICLE |
| ncLowerCase | Cod_Article | cod_article |
| ncPascalCase | Cod_Article | CodArticle |
| ncPascalCase | CodArticle | CodArticle |
| ncPascalCase | `_WITH__UNDERSCORES_` | WithUnderscores |
| ncCamelCase | Cod_Article | codArticle |
| ncCamelCase | CodArticle | codArticle |
| ncCamelCase | `_WITH__UNDERSCORES_` | WithUnderscores |
| | | |
- New! Added Swagger support (thanks to [João Antônio Duarte](https://github.com/joaoduarte19) and [Geoffrey Smith](https://github.com/geoffsmith82))
- New! **ObjectDict** function is the suggested way to render all the most common data types. It returns a `IMVCObjectDictionary` which is automatically rendered by the renders. Check the `renders.dproj` sample. Here's some example of the shining new `ObjectDict()`
**Example 1: Rendering a list of objects not freeing them after rendering**
*Classic*
```delphi
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList, False);
end;
```
*New approach with ObjectDict*
```delphi
procedure TRenderSampleController.GetLotOfPeople;
begin
Render(ObjectDict(False).Add('data', GetPeopleList));
end;
```
**Example 2: Rendering a list of objects and automatically free them after rendering**
*Classic*
```delphi
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList);
end;
```
*New approach with ObjectDict*
```delphi
procedure TRenderSampleController.GetLotOfPeople;
begin
Render(ObjectDict().Add('data', GetPeopleList));
end;
```
**Example 3: Rendering a list of objects adding links for HATEOAS support**
*Classic*
```delphi
procedure TRenderSampleController.GetPeople_AsObjectList_HATEOAS;
var
p: TPerson;
People: TObjectList<TPerson>;
begin
People := TObjectList<TPerson>.Create(True);
{$REGION 'Fake data'}
p := TPerson.Create;
p.FirstName := 'Daniele';
p.LastName := 'Teti';
p.DOB := EncodeDate(1979, 8, 4);
p.Married := True;
People.Add(p);
p := TPerson.Create;
p.FirstName := 'John';
p.LastName := 'Doe';
p.DOB := EncodeDate(1879, 10, 2);
p.Married := False;
People.Add(p);
p := TPerson.Create;
p.FirstName := 'Jane';
p.LastName := 'Doe';
p.DOB := EncodeDate(1883, 1, 5);
p.Married := True;
People.Add(p);
{$ENDREGION}
Render<TPerson>(People, True,
procedure(const APerson: TPerson; const Links: IMVCLinks)
begin
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people/' + APerson.ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json')
.Add('title', 'Details for ' + APerson.FullName);
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, 'application/json');
end);
end;
```
*New approach with ObjectDict*
```delphi
procedure TRenderSampleController.GetPeople_AsObjectList_HATEOAS;
var
p: TPerson;
People: TObjectList<TPerson>;
begin
People := TObjectList<TPerson>.Create(True);
{$REGION 'Fake data'}
p := TPerson.Create;
p.FirstName := 'Daniele';
p.LastName := 'Teti';
p.DOB := EncodeDate(1979, 8, 4);
p.Married := True;
People.Add(p);
p := TPerson.Create;
p.FirstName := 'John';
p.LastName := 'Doe';
p.DOB := EncodeDate(1879, 10, 2);
p.Married := False;
People.Add(p);
p := TPerson.Create;
p.FirstName := 'Jane';
p.LastName := 'Doe';
p.DOB := EncodeDate(1883, 1, 5);
p.Married := True;
People.Add(p);
{$ENDREGION}
Render(ObjectDict().Add('data', People,
procedure(const APerson: TObject; const Links: IMVCLinks)
begin
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people/' + TPerson(APerson).ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json')
.Add('title', 'Details for ' + TPerson(APerson).FullName);
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, 'application/json');
end));
end;
```
`ObjectDict` is able to render multiple data sources (datasets, objectlists, objects or StrDict) at the same time using different casing, HATEOAS callbacks and modes.
```delphi
procedure TTestServerController.TestObjectDict;
var
lDict: IMVCObjectDictionary;
begin
lDict := ObjectDict(false)
.Add('ncUpperCase_List', GetDataSet, nil, dstAllRecords, ncUpperCase)
.Add('ncLowerCase_List', GetDataSet, nil, dstAllRecords, ncLowerCase)
.Add('ncCamelCase_List', GetDataSet, nil, dstAllRecords, ncCamelCase)
.Add('ncPascalCase_List', GetDataSet, nil, dstAllRecords, ncPascalCase)
.Add('ncUpperCase_Single', GetDataSet, nil, dstSingleRecord, ncUpperCase)
.Add('ncLowerCase_Single', GetDataSet, nil, dstSingleRecord, ncLowerCase)
.Add('ncCamelCase_Single', GetDataSet, nil, dstSingleRecord, ncCamelCase)
.Add('ncPascalCase_Single', GetDataSet, nil, dstSingleRecord, ncPascalCase)
.Add('meta', StrDict(['page'], ['1']));
Render(lDict);
end;
```
>ObjectDict is the suggested way to renders data. However, the other ones are still there and works as usual.
- New! Added SQLGenerator and RQL compiler for PostgreSQL, SQLite and MSSQLServer (in addition to MySQL, MariaDB, Firebird and Interbase)
- New! *MVCNameAs* attribute got the param `Fixed` (default: false). If Fixed is true, then the name is not processed by the `MVCNameCase` attribute assigned to the owner type.
- New! Added support for interfaces serialization - now it is possible to serialize Spring4D collections (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- New! Added support for Spring4D Nullable Types - check (thanks to [João Antônio Duarte](https://github.com/joaoduarte19))
- New! Added `OnRouterLog` event to log custom information for each request (thanks to [Andrea Ciotti](https://github.com/andreaciotti) for the first implementation and its PR)
@ -334,7 +530,7 @@ end;
- **Breaking Change!** Middleware `OnAfterControllerAction` are now invoked in the same order of `OnBeforeControllerAction` (previously were invoked in reversed order).
- **Breaking Change!** `TDataSetHolder` doesn't renders dataset in a property called `items` but in a property named `data` (to be more standard).
- **Deprecated!** `TDataSetHolder` is deprecated! Use the shining new `ObjectDict(boolean)` instead.
- 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.*

View File

@ -496,7 +496,7 @@ begin
lDM := TMyDataModule.Create(nil);
try
lDM.qryCustomers.Open;
lDict := ObjectDict(False)
lDict := ObjectDict(False { data are not freed after ObjectDict if freed } )
.Add('customers', lDM.qryCustomers,
procedure(const DS: TDataset; const Links: IMVCLinks)
begin
@ -576,7 +576,10 @@ end;
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList, False);
{ classic approach }
// Render<TPerson>(GetPeopleList, False);
{ new approach with ObjectDict }
Render(ObjectDict(False).Add('data', GetPeopleList));
end;
procedure TRenderSampleController.GetManyNullableObjects;
@ -763,7 +766,10 @@ begin
People.Add(p);
{$ENDREGION}
Render<TPerson>(HTTP_STATUS.OK, People, True);
{ classic approach }
// Render<TPerson>(HTTP_STATUS.OK, People, True);
{ new approach with ObjectDict }
Render(HTTP_STATUS.OK, ObjectDict().Add('data', People));
end;
procedure TRenderSampleController.GetPeople_AsObjectList_HATEOAS;
@ -796,21 +802,40 @@ begin
People.Add(p);
{$ENDREGION}
Render<TPerson>(People, True,
{ classic approach }
{
Render<TPerson>(People, True,
procedure(const APerson: TPerson; const Links: IMVCLinks)
begin
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people/' + APerson.ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json')
.Add('title', 'Details for ' + APerson.FullName);
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, 'application/json');
end);
}
{ new approach with ObjectDict }
Render(ObjectDict().Add('data', People,
procedure(const APerson: TObject; const Links: IMVCLinks)
begin
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people/' + APerson.ID.ToString)
.Add(HATEOAS.HREF, '/people/' + TPerson(APerson).ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json')
.Add('title', 'Details for ' + APerson.FullName);
.Add('title', 'Details for ' + TPerson(APerson).FullName);
Links
.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, 'application/json');
end);
end));
end;
procedure TRenderSampleController.GetPersonById(const ID: Integer);

View File

@ -446,22 +446,22 @@ type
function LinksData: TMVCStringDictionaryList;
end;
// IMVCStringDictionary = interface
// ['{164117AD-8DDD-47F7-877C-453979707D10}']
// function GetItems(const Key: string): string;
// procedure SetItems(const Key, Value: string);
// procedure Clear;
// function Add(const Name, Value: string): IMVCStringDictionary;
// function TryGetValue(const Name: string; out Value: string): Boolean; overload;
// function TryGetValue(const Name: string; out Value: Integer): Boolean; overload;
// function Count: Integer;
// function GetEnumerator: TDictionary<string, string>.TPairEnumerator;
// function ContainsKey(const Key: string): Boolean;
// function Keys: TArray<string>;
// property Items[const Key: string]: string read GetItems write SetItems; default;
// end;
// IMVCStringDictionary = interface
// ['{164117AD-8DDD-47F7-877C-453979707D10}']
// function GetItems(const Key: string): string;
// procedure SetItems(const Key, Value: string);
// procedure Clear;
// function Add(const Name, Value: string): IMVCStringDictionary;
// function TryGetValue(const Name: string; out Value: string): Boolean; overload;
// function TryGetValue(const Name: string; out Value: Integer): Boolean; overload;
// function Count: Integer;
// function GetEnumerator: TDictionary<string, string>.TPairEnumerator;
// function ContainsKey(const Key: string): Boolean;
// function Keys: TArray<string>;
// property Items[const Key: string]: string read GetItems write SetItems; default;
// end;
TMVCStringDictionary = class //(TInterfacedObject, IMVCStringDictionary)
TMVCStringDictionary = class // (TInterfacedObject, IMVCStringDictionary)
strict private
function GetItems(const Key: string): string;
procedure SetItems(const Key, Value: string);
@ -1318,15 +1318,25 @@ var
I: Integer;
lNextUpCase: Boolean;
lSB: TStringBuilder;
C: Char;
lIsLowerCase: Boolean;
lIsUpperCase, lPreviousWasUpperCase: Boolean;
lIsAlpha: Boolean;
begin
lNextUpCase := MakeFirstUpperToo;
lPreviousWasUpperCase := True;
lSB := TStringBuilder.Create;
try
for I := 0 to Length(Value) - 1 do
begin
if not CharInSet(Value.Chars[I], ['A' .. 'Z', 'a' .. 'z']) then
C := Value.Chars[I];
lIsLowerCase := CharInSet(C, ['a' .. 'z']);
lIsUpperCase := CharInSet(C, ['A' .. 'Z']);
lIsAlpha := lIsLowerCase or lIsUpperCase;
if not lIsAlpha then
begin
lNextUpCase := True;
lPreviousWasUpperCase := False;
Continue;
end
else
@ -1334,13 +1344,21 @@ begin
if lNextUpCase then
begin
lNextUpCase := False;
lSB.Append(UpCase(Value.Chars[I]));
lSB.Append(UpCase(C));
end
else
begin
lSB.Append(LowerCase(Value.Chars[I]));
if lPreviousWasUpperCase then
begin
lSB.Append(LowerCase(C));
end
else
begin
lSB.Append(C);
end;
end;
end;
lPreviousWasUpperCase := lIsUpperCase;
end;
Result := lSB.ToString;
finally

View File

@ -27,6 +27,7 @@
unit MVCFramework.Serializer.Commons;
{$I dmvcframework.inc}
{$WARN SYMBOL_DEPRECATED OFF}
interface
@ -117,13 +118,14 @@ type
MVCNameAsAttribute = class(TCustomAttribute)
private
FName: string;
function GetName: string;
fName: string;
fFixed: Boolean;
protected
{ protected declarations }
public
constructor Create(const AName: string);
property name: string read GetName;
constructor Create(const AName: string; const Fixed: Boolean = False);
property name: string read fName;
property Fixed: Boolean read fFixed;
end;
MapperJSONSer = MVCNameAsAttribute deprecated 'Use MVCNameAsAttribute';
@ -167,7 +169,7 @@ type
procedure SetFieldName(const Value: string);
procedure SetIsPK(const Value: Boolean);
public
constructor Create(AFieldName: string; AIsPK: Boolean = false);
constructor Create(AFieldName: string; AIsPK: Boolean = False);
property FieldName: string read FFieldName write SetFieldName;
property IsPK: Boolean read FIsPK write SetIsPK;
end;
@ -285,20 +287,20 @@ type
function GetData: TObject;
override;
public
constructor Create(const AObject: TObject; const AOwns: Boolean = false;
constructor Create(const AObject: TObject; const AOwns: Boolean = False;
const ADataSetSerializationType: TMVCDatasetSerializationType = TMVCDatasetSerializationType.
dstAllRecords);
virtual;
destructor Destroy;
override;
function SerializationType: TMVCDatasetSerializationType;
[MVCNameAs('data')]
[MVCNameAs('items')]
property Items: TObject read GetData;
[MVCNameAs('meta')]
property MetaData: TMVCStringDictionary read GetMetadata;
end;
end deprecated 'Use "ObjectDict"';
TDataObjectHolder = TMVCResponseData deprecated 'Use one of the specialized versions';
TDataObjectHolder = TMVCResponseData deprecated 'Use "ObjectDict"';
THTTPStatusCode = 100 .. 599;
@ -410,11 +412,6 @@ const
JSONNameLowerCase = ncLowerCase deprecated 'Use MVCNameCaseAttribute(ncLowerCase)';
JSONNameUpperCase = ncUpperCase deprecated 'Use MVCNameCaseAttribute(ncUpperCase)';
function NewObjectHolder(const AObject: TObject; const AMetaFiller: TProc<TMVCStringDictionary> = nil;
const AOwns: Boolean = false): TMVCObjectResponse;
function NewCollectionHolder(const AList: TObject; const AMetaFiller: TProc<TMVCStringDictionary> = nil;
const AOwns: Boolean = false): TMVCObjectListResponse;
function StrDict: TMVCStringDictionary; overload;
function StrDict(const aKeys: array of string; const aValues: array of string)
: TMVCStringDictionary; overload;
@ -480,26 +477,6 @@ begin
end;
end;
function NewObjectHolder(const AObject: TObject; const AMetaFiller: TProc<TMVCStringDictionary> = nil;
const AOwns: Boolean = false): TMVCObjectResponse;
begin
Result := TMVCObjectResponse.Create(AObject, AOwns);
if Assigned(AMetaFiller) then
begin
AMetaFiller(Result.fMetaData);
end;
end;
function NewCollectionHolder(const AList: TObject; const AMetaFiller: TProc<TMVCStringDictionary> = nil;
const AOwns: Boolean = false): TMVCObjectListResponse;
begin
Result := TMVCObjectListResponse.Create(AList, AOwns);
if Assigned(AMetaFiller) then
begin
AMetaFiller(Result.fMetaData);
end;
end;
function DateTimeToISOTimeStamp(const ADateTime: TDateTime): string;
begin
// fs.TimeSeparator := ':';
@ -586,37 +563,30 @@ var
Attrs: TArray<TCustomAttribute>;
Attr: TCustomAttribute;
begin
{
Dear future me...
Yes, this method is called a lot of times, but after some tests
seems that the performance loss is very low, so if you don't have any
new evidence don't try to improve it...
}
Result := AField.Name;
Attrs := AField.GetAttributes;
for Attr in Attrs do
begin
if Attr is MVCNameAsAttribute then
begin
Exit(MVCNameAsAttribute(Attr).Name);
end;
end;
Attrs := AType.GetAttributes;
for Attr in Attrs do
begin
if Attr is MVCNameCaseAttribute then
begin
Exit(TMVCSerializerHelper.ApplyNameCase(MVCNameCaseAttribute(Attr).KeyCase, AField.Name));
// case MVCNameCaseAttribute(Attr).KeyCase of
// ncUpperCase:
// begin
// Exit(UpperCase(AField.Name));
// end;
// ncLowerCase:
// begin
// Exit(LowerCase(AField.Name));
// end;
// ncCamelCase:
// begin
// Exit(CamelCase(AField.Name));
// end;
// ncPascalCase:
// begin
// Exit(CamelCase(AField.Name, True));
// end;
// end;
end;
end;
end;
class function TMVCSerializerHelper.AttributeExists<T>(const AAttributes: TArray<TCustomAttribute>;
@ -667,7 +637,7 @@ class function TMVCSerializerHelper.AttributeExists<T>(const AAttributes: TArray
var
Att: TCustomAttribute;
begin
Result := false;
Result := False;
for Att in AAttributes do
if Att is T then
Exit(True);
@ -769,37 +739,36 @@ var
Attrs: TArray<TCustomAttribute>;
Attr: TCustomAttribute;
begin
{ in un rendering di una lista, quante volte viene chiamata questa funzione? }
Result := AProperty.Name;
Attrs := AProperty.GetAttributes;
for Attr in Attrs do
begin
{ TODO -oDaniele -cGeneral : Time this! }
if Attr is MVCNameAsAttribute then
Exit(MVCNameAsAttribute(Attr).Name);
begin
// Exit(MVCNameAsAttribute(Attr).Name);
Result := MVCNameAsAttribute(Attr).Name;
if MVCNameAsAttribute(Attr).Fixed then { if FIXED the attribute NameAs remains untouched }
begin
Exit
end
else
begin
Break;
end;
end;
end;
Attrs := AType.GetAttributes;
for Attr in Attrs do
begin
if Attr is MVCNameCaseAttribute then
begin
Exit(TMVCSerializerHelper.ApplyNameCase(MVCNameCaseAttribute(Attr).KeyCase, AProperty.Name));
// case MVCNameCaseAttribute(Attr).KeyCase of
// ncUpperCase:
// begin
// Exit(UpperCase(AProperty.Name));
// end;
// ncLowerCase:
// begin
// Exit(LowerCase(AProperty.Name));
// end;
// ncCamelCase:
// begin
// Exit(CamelCase(AProperty.Name));
// end;
// ncPascalCase:
// begin
// Exit(CamelCase(AProperty.Name, True));
// end;
// end;
Exit(TMVCSerializerHelper.ApplyNameCase(MVCNameCaseAttribute(Attr).KeyCase, Result));
end;
end;
end;
class function TMVCSerializerHelper.GetTypeKindAsString(const ATypeKind: TTypeKind): string;
@ -813,10 +782,10 @@ var
Attrs: TArray<TCustomAttribute>;
Attr: TCustomAttribute;
begin
Result := false;
Result := False;
Attrs := AMember.GetAttributes;
if Length(Attrs) = 0 then
Exit(false);
Exit(False);
for Attr in Attrs do
if Attr is T then
Exit(True);
@ -828,7 +797,7 @@ var
Attr: TCustomAttribute;
begin
AAttribute := nil;
Result := false;
Result := False;
Attrs := AMember.GetAttributes;
for Attr in Attrs do
if Attr is T then
@ -876,15 +845,11 @@ end;
{ MVCNameAsAttribute }
constructor MVCNameAsAttribute.Create(const AName: string);
constructor MVCNameAsAttribute.Create(const AName: string; const Fixed: Boolean = False);
begin
inherited Create;
FName := AName;
end;
function MVCNameAsAttribute.GetName: string;
begin
Result := FName;
fName := AName;
fFixed := Fixed;
end;
{ MVCListOfAttribute }
@ -1169,7 +1134,7 @@ function MapDataSetFieldToNullableRTTIField(const AValue: TValue; const AField:
const AObject: TObject): Boolean;
begin
Assert(AValue.Kind = tkRecord);
Result := false;
Result := False;
if AValue.IsType(TypeInfo(NullableString)) then
begin
if AField.IsNull then
@ -1357,7 +1322,7 @@ const aRTTIProp: TRttiProperty;
const AObject: TObject): Boolean;
begin
Assert(AValue.Kind = tkRecord);
Result := false;
Result := False;
if AValue.IsType(TypeInfo(NullableString)) then
begin
if AField.IsNull then

View File

@ -655,6 +655,7 @@ type
procedure Render(const AStatusCode: Integer; AObject: TObject; const AOwns: Boolean;
const ASerializationAction: TMVCSerializationAction = nil); overload;
procedure Render(const AObject: IInterface; const ASerializationAction: TMVCSerializationAction = nil); overload;
procedure Render(const AStatusCode: Integer; const AObject: IInterface; const ASerializationAction: TMVCSerializationAction = nil); overload;
// PODOs Collection render
procedure Render<T: class>(const ACollection: TObjectList<T>;
const ASerializationAction: TMVCSerializationAction<T> = nil); overload;
@ -3285,6 +3286,14 @@ begin
raise EMVCException.Create('Can not render an empty dataset.');
end;
procedure TMVCRenderer.Render(const AStatusCode: Integer;
const AObject: IInterface;
const ASerializationAction: TMVCSerializationAction);
begin
SetStatusCode(AStatusCode);
Render(AObject, ASerializationAction);
end;
procedure TMVCRenderer.Render(const AObject: IInterface; const ASerializationAction: TMVCSerializationAction);
begin
Render(TObject(AObject), False, ASerializationAction);

View File

@ -226,7 +226,7 @@ uses System.DateUtils, System.Math,
{$ENDIF}
TestServerControllerU, System.Classes,
MVCFramework.DuckTyping, System.IOUtils, MVCFramework.SystemJSONUtils,
IdGlobal;
IdGlobal, System.TypInfo;
var
JWT_SECRET_KEY_TEST: string = 'myk3y';
@ -1847,6 +1847,7 @@ var
lNameCaseIdx: TMVCNameCase;
lOrig: string;
lOutData: string;
lActualOutData: string;
begin
for lNameCaseIdx := ncAsIs to ncPascalCase do
begin
@ -1854,7 +1855,9 @@ begin
begin
lOrig := fOrigDATA[I];
lOutData := fOutDATA[I][lNameCaseIdx];
Assert.areEqual(lOutData, TMVCSerializerHelper.ApplyNameCase(lNameCaseIdx, lOrig), lOrig);
lActualOutData := TMVCSerializerHelper.ApplyNameCase(lNameCaseIdx, lOrig);
Assert.areEqual(lOutData, lActualOutData, False, lOrig + ' for ' + GetEnumName(TypeInfo(TMVCNameCase),
Ord(lNameCaseIdx)));
end;
end;
end;

View File

@ -499,7 +499,7 @@ begin
Assert.isTrue(Dm.EntityAsIsName.AsString = 'Ezequiel Juliano Müller');
Dm.EntityAsIs.EmptyDataSet;
Dm.EntityAsIs.LoadJSONArrayFromJSONObjectProperty(JSON_ITEMS, 'items', ncAsIs);
Dm.EntityAsIs.LoadJSONArrayFromJSONObjectProperty('items', JSON_ITEMS, ncAsIs);
Dm.EntityAsIs.First;
Assert.isTrue(Dm.EntityAsIsId.AsLargeInt = 1);
Assert.isTrue(Dm.EntityAsIsName.AsString = 'Pedro Henrique de Oliveira');