DMVCFramework (for short) is a popular and powerful framework for WEB API in Delphi. Supports RESTful and JSON-RPC WEB APIs development.
THIS IS A PRE RELEASE!!
If no bugs/regressions will be found, this will become the stable version
Full Changelog: https://github.com/danieleteti/delphimvcframework/compare/v3.4.1-sodium...v3.4.2-magnesium-rc1
Full Changelog: https://github.com/danieleteti/delphimvcframework/compare/v3.4.0-neon...v3.4.1-sodium
Full Changelog: https://github.com/danieleteti/delphimvcframework/compare/v3.3.0-fluorine...v3.4.0-neon
ContextEvents
Profiler.LogsOnlyIfOverThreshold
which logs only if over the defined thresholdFull Changelog: https://github.com/danieleteti/delphimvcframework/compare/dmvcframework-3.2.3-radium...v3.3.0-fluorine
⚡ Default error responses contains the official "reason string" associated to the HTTP status code (this can be a breaking change for some generic client which doesn't correctly interpret the http status code)
⚡ Added static method HTTP_STATUS.ReasonStringFor(HTTPStatusCode)
wich returns the standard ReasonString
for a given HTTP status code.
⚡ Improved handling of TMVCErrorResponse
information
⚡ mid-air-collision handling now uses SHA1 instead of MD5
⚡ Added MVCFramework.Commons.MVC_HTTP_STATUS_CODES
const array containing all the HTTP status codes with its ReasonString
.
⚡ Support for TObject
descendants in JSONRPC APIs (not only for JSONObject and JSONArray).
⚡ New global configuration variable MVCSerializeNulls
.
⚡ Nullable types now have Equal
method support, the new method TryHasValue(out Value)
works like HasValue
but returns the contained value if present. Also there is a better "equality check" strategy.
⚡ Unit tests now are always executed for Win32 and Win64 bit (both client and server).
⚡ Added TMVCActiveRecord.Refresh
method
⚡ Unit test suites generates one NUnit XML output file for each platform
⚡ New built-in profiler (usable with Delphi 10.4+) - to profile a block of code, write the following
procedure TMyController.ProfilerSample1;
begin
NotProfiled(); //this line is not profiled
//the following begin..end block will be profiled
//timing will be saved in a "profiler" log
begin var lProf := Profiler.Start(Context.ActionQualifiedName);
DoSomething();
DoSomethingElse();
Render('Just executed ' + Context.ActionQualifiedName);
end; // profiler writes automatically to the log
NotProfiled(); //this line is not profiled
end;
procedure TMyController.DoSomething;
begin
begin var lProf := Profiler.Start('DoSomething');
Sleep(100);
end;
end;
procedure TMyController.DoSomethingElse;
begin
begin var lProf := Profiler.Start('DoSomethingElse');
Sleep(100);
DoSomething();
end;
end;
procedure TMyController.NotProfiled;
begin
Sleep(100);
end;
The log contains the following lines - check the caller/called relationship shown using >>
and <<
and the deep level
[>>][ 1][MainControllerU.TMyController.ProfilerSample1] [profiler]
[ >>][ 2][DoSomething] [profiler]
[ <<][ 2][DoSomething][ELAPSED: 00:00:00.1088214] [profiler]
[ >>][ 2][DoSomethingElse] [profiler]
[ >>][ 3][DoSomething] [profiler]
[ <<][ 3][DoSomething][ELAPSED: 00:00:00.1096617] [profiler]
[ <<][ 2][DoSomethingElse][ELAPSED: 00:00:00.2188468] [profiler]
[<<][ 1][MainControllerU.TMyController.ProfilerSample1][ELAPSED: 00:00:00.3277806] [profiler]
To get more info check the "profiling" example.
All profiler logs are generated with a log level info
. If measured time is greater than WarningThreshold
the log level is warning
.
WarningThreshold
is expressed in milliseconds and by default is equals to 1000.
⚡ New Context
property named ActionQualifiedName
which contains the currently executed action in the form UnitName.ClassName.ActionName
. It is available where the Context
property is available. Obviously is not available in the OnBeforeRouting
middleware events.
⚡ Added ObjectPool and IntfObjectPool (and related unit tests). Thanks to our sponsor Vivaticket S.p.A.
⚡ Method procedure Render(const AErrorCode: Integer; const AErrorMessage: string = '' ...
has been renamed to RenderStatusMessage
with a better parameter names.
⚡ IMVCJSONRPCExecutor
supports async call. Thanks to our sponsor Orion Law. Check the new Async sample in samples\jsonrpc_with_published_objects\
.
⚡ Removed foTransient
if TMVCActiveRecord
FieldOptions
. It became obsolete after introduction of foReadOnly
and foWriteOnly
.
⚡ Improved TMVCActiveRecordMiddleware
. Now it can handle multiple connections for the same request. Also, you can completely omit the 'default' connection and just specify wich connection you want to use before starting to create your TMVCActiveRecord
inherited entities.
More details about dmvcframework-3.2.3-radium fixes here
This version introduces new features in many different areas (swagger, server side view, MVCActiveRecord, renders etc.) so there isn't a single-big-feature to talk about. This version contains also a good number of bugfixes. It's not a critical updated, but this is the best version ever (at least, so far...) and is the suggested version for starting new projects. Enjoy!
⚡New! Support for Delphi 11.x Alexandria
⚡New TMVCRESTClient
implementation based on *Net components, the previous one was based on INDY Components (thanks to João Antônio Duarte).
⚡New! MVCJSONRPCAllowGET
attribute allows a remote JSON-RPC published object, or a specific method, to be called using GET HTTP Verb as well as POST HTTP Verb. POST is always available, GET is available only if explicitly allowed. IMVCJSONRPCExecutor
allows to specify which HTTP Verb to use when call the server JSON-RPC methods. The default verb can be injected in the constructor and each ExecuteRequest
/ExecuteNotification
allows to override od adhere to the instance default.
⚡New! eLua server side view support added! The View engine requires Lua's dlls so it is not included in the main package but in a sampl project. Check serversideviews_lua
sample.
✅ Improved! Under some heavy load circumnstances the logger queue can get full. Now TThreadSafeQueue
class uses a cubic function instead of a linear one to wait in case of very high concurrency. This allows a better resiliency in case of high load.
✅ Improved internal architecture of custom type serializers in case of dynamic linked packages.
✅ Improved Swagger/OpenAPI support for System Controllers and improved support for param models.
⚡New TMVCLRUCache
implementation. Very efficient implementation of LRU cache borrowed directly from DMSContainer
⚡New TMVCRedirectMiddleware
to handle HTTP redirections in a very simple and flexible way.
⚡New! TMVCActiveRecord
supports XML field type in PostgreSQL (in addition to JSON and JSONB).
⚡New OnContextCreate
and OnContextDetroyed
events for TMVCEngine
.
⚡New! Added parameter RootNode
in BodyFor
<T> and BodyForListOf<T>
methods, just like the BodyAs*
methods.
⚡New! Added NullableTGUID
in MVCFramework.Nullables.pas
.
⚡New property CustomIntfObject: IInterface
in TWebContext
. This property can be used to inject custom services factory.
procedure TMyWebModule.WebModuleCreate(Sender: TObject);
begin
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
//configuration code
end);
FMVC.AddController(TMyController);
FMVC.OnWebContextCreate(
procedure(const CTX: TWebContext)
begin
CTX.CustomIntfObject := TServicesFactory.Create; //implements an interface
end);
FMVC.OnWebContextDestroy(
procedure(const CTX: TWebContext)
begin
//do nothing here
end);
end;
✅ Added parameter to set local timeStamp as UTC.
✅ Improved OpenAPI (Swagger) support.
✅ Improved Support for OpenAPI (Swagger) API Versioning (check swagger_api_versioning_primer
sample)
✅ Improved! The unit tests fully test PostgreSQL, FirebirdSQL and SQLite while testing MVCActiveRecord framework. The other engines are tested using activerecord_showcase
sample project.
✅ Improved! MVCActiveRecord doeas a better job to handle TDate/TTime/TDateTime types for SQLite (it is automatic because SQLite doesn't support date/time types).
✅ Improved! PostgreSQL, FirebirdSQL, Interbase and SQLite now support tablename and fields with spaces.
✅ Improved Nullable Types. Now it's possible to assign nil
to a nullable type and to check its state using the new property IsNull
which is the negation of the already available property HasValue
.
✅ Improved! Now TMVCStaticFileMiddleware
is able to manage high-level criteria to show/hide/mask specific files in the documetn web root. Check Issue 548 and the updated sample samples\middleware_staticfiles\
for more info.
✅ Improved! In case of multiple MVCPath, Swagger consider only the first one (Thanks to V. Ferri and our sponsors)
⚡New! Mechanism to customize the JWT claims setup using the client request as suggested in issue495
⚡New! Added TMVCActiveRecord.Merge<T>(CurrentListOfT, ChangesOfT)
to allow merge between two lists of TMVCActiveRecord
descendants using UnitOfWork
design pattern. Check the button "Merge" in demo "activerecord_showcase".
⚡ New! Added default filtering for TMVCActiveRecord descendants
(more info ASAP)
⚡ New! Serialization and Deserialization for Pascal set
(Thanks to rshuck for his suggestions)
⚡ New! Added partitioning for TMVCActiveRecord descendants
(more info ASAP)
✅ Dramatically improved all "JSON-To-DataSet" operations (1 order of magnitude c.a.). Thanks to MPannier and David Moorhouse for their detailed analysis - More info here.
✅ Improved! After a big refactoring ("I love to delete code" -- cit. Daniele Teti), support a new SQLGenerator is just 2 (two) methods away! Just as example, this is the current version of TMVCSQLGeneratorPostgreSQL
type
TMVCSQLGeneratorPostgreSQL = class(TMVCSQLGenerator)
protected
function GetCompilerClass: TRQLCompilerClass; override;
public
function CreateInsertSQL(
const TableName: string;
const Map: TFieldsMap;
const PKFieldName: string;
const PKOptions: TMVCActiveRecordFieldOptions): string; override;
function GetSequenceValueSQL(const PKFieldName: string;
const SequenceName: string;
const Step: Integer = 1): string; override;
end;
⚡ New! Added new default parameter to TMVCActiveRecord.RemoveDefaultConnection
and TMVCActiveRecord.RemoveConnection
to avoid exceptions in case of not initialized connection.
⚡ New! Added the new MVCOwned
attribute which allows to auto-create nested objects in the deserialization phase. This will not change the current behavior, you ned to explocitly define a property (or a field) as MVCOwned
to allows the serialization to create or destroy object for you.
✅ Improved! Context.Data
property is now created on-demand using a lazy loading approach (expect an overall speed improvement).
✅ Added LogException
function in MVCFramework.Logger.pas
to easily log exception in standard way.
✅ Improved MVCAREntitiesGenerator
project - now it can better handle border cases, field names which collide with Delphi keywords and a big number of tables.
✅ Improved error handling for JSON-RPC APIs (Thanks to David Moorhouse). More info here.
✅ Improved parameter handling for enum and set in JSON-RPC APIs.
⚡ New! Added ActiveRecordConnectionRegistry.AddDefaultConnection(const aConnetionDefName: String)
. The connection definition must be known by FireDAC. This method simplifies the most common scenario shown below.
ActiveRecordConnectionRegistry.AddDefaultConnection('MyConDefName');
try
//Use active record classes
finally
ActiveRecordConnectionRegistry.RemoveDefaultConnection;
end;
⚡ New! Added ToJSONObject
and ToJSONArray
to the IMVCRESTResponse
. These methods automatically parse the response body and return a TJSONObject
or a TJSONArray
respectively. These methods work as a factory - the client code need to handle returned istances. Is the body is not compatible with the request (a.k.a. is not a JSONObject in case of ToJSONObject
, or is not a JSONArray in case of ToJSONArray
) an exception is raised.
⚡ New! Added support for primary GUID (UUID) primary keys, attributes and serialization (More info at issue 552). Thanks to Marcelo Jaloto for its important collaboration.
⚡ New! Added TMVCJWTBlackListMiddleware
to allow black-listing and (a sort of) logout for a JWT based authentication. This middleware must be registered after the TMVCJWTAuthenticationMiddleware
.
This middleware provides 2 events named:
OnAcceptToken
(invoked when a request contains a token - need to returns true/false if the token is still accepted by the server or not) andOnNewJWTToBlackList
(invoked when a client ask to blacklist its current token). There is a new sample available which shows the funtionalities:samples\middleware_jwtblacklist
.
⚡New! MVCFromBody
attribute, useful to automatically inject the request body as actual object in the action paramaters. For instance in the following action the body request is automatically deserialized as an object of class TPerson.
//interface
[MVCHTTPMethod([httpPOST])]
[MVCPath('/people')]
procedure CreatePerson(const [MVCFromBody] Person: TPerson);
//implementation
procedure TRenderSampleController.CreatePerson(const Person: TPerson);
begin
//here you can directly use Person without call Context.Request.BodyAs<TPerson>
//the Person object lifecycle is automatically handled by dmvcframework, so don't destroy
//If the request body doesn't exist (or cannot be deserialized) an exception is raised.
end;
MVCFromBody can be used also with "Collection Like" data structures. //interface
[MVCDoc('Creates new articles from a list and returns "201: Created"')]
[MVCPath('/bulk')]
[MVCHTTPMethod([httpPOST])]
procedure CreateArticles(const [MVCFromBody] ArticleList: TObjectList<TArticle>);
//implementation
procedure TArticlesController.CreateArticles(const ArticleList: TObjectList<TArticle>);
var
lArticle: TArticle;
begin
for lArticle in ArticleList do
begin
GetArticlesService.Add(lArticle);
end;
Render(201, 'Articles Created');
end;
⚡New! MVCFromQueryString
attribute, useful to automatically inject a query string paramater an action paramater. For instance in the following action the query string params fromDate
is automatically deserialized as a TDate
value and injected in the action.
//interface
[MVCHTTPMethod([httpGET])]
[MVCPath('/invoices')]
procedure GetInvoices(const [MVCFromQueryString('fromDate')] FromDate: TDate);
//implementation
procedure TRenderSampleController.GetInvoices(const FromDate: TDate);
begin
//here FromDate is a valid TDate value deserialized from the querystring paramater
//named fromDate.
//If the query string parameter doesn't exist (or cannot be deserialized) an exception is raised.
end;
⚡New! MVCFromHeader
attribute, useful to automatically inject a header value as an action parameter. For instance in the following action the header params XMyCoolHeader
is automatically deserialized as String
value and injected in the action.
//interface
[MVCHTTPMethod([httpGET])]
[MVCPath('/invoices')]
procedure GetInvoices(const [MVCFromQueryString('fromDate')] FromDate: TDate; const [MVCFromHeader('X-MY-COOL-HEADER')] XMyCoolHeader: String);
//implementation
procedure TRenderSampleController.GetInvoices(const FromDate: TDate; const XMyCoolHeader: String);
begin
//here XMyCoolHeader is a string read from the "X-MY-COOL-HEADER' request header.
//If the header doesn't exist (or cannot be deserialized) an exception is raised.
end;
⚡New! MVCFromCookie
attribute, useful to automatically inject a cookie value as an action parameter. For instance in the following action the cookie MyCoolCookie
is automatically deserialized as TDate
value and injected in the action.
//interface
[MVCHTTPMethod([httpGET])]
[MVCPath('/invoices')]
procedure GetInvoices(
const [MVCFromQueryString('fromDate')] FromDate: TDate;
const [MVCFromHeader('X-MY-COOL-HEADER')] XMyCoolHeader: String;
const [MVCFromCookie('MyCoolCookie')] MyCoolCookie: TDate;
);
//implementation
procedure TRenderSampleController.GetInvoices(const FromDate: TDate; const XMyCoolHeader: String; const MyCoolCookie: TDate);
begin
//here MyCoolCookie is a TDate read from "MyCoolCookie' cookie available in the request.
//If the cookie doesn't exist (or cannot be deserialized) an exception is raised.
end;
✅ Improved! While not strictly required nor defined, DMVCFramework supports sending body data for all HTTP VERBS - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
⚡ New! Automated support to avoid "mid-air collisions". New methods SetETag
and CheckIfMatch
allows a better security without adding complexity to the controller code - check avoid_mid_air_collisions_sample.dproj
sample and see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#avoiding_mid-air_collisions for more info about mid-air collisions.
✅ Improved! Ignored fields handling now is much better in renders method and in ObjDict
as well. See issue 528.
Fix https://github.com/danieleteti/delphimvcframework/issues/484 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/472 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/470 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/453 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/455 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/461
Fix https://github.com/danieleteti/delphimvcframework/issues/489 (thanks to drcrck for his initial analisys)
Fix https://github.com/danieleteti/delphimvcframework/issues/493 (thanks to DelphiMan68 for his initial analisys)
Fix https://github.com/danieleteti/delphimvcframework/issues/451
Fix https://github.com/danieleteti/delphimvcframework/issues/539
Fix https://github.com/danieleteti/delphimvcframework/issues/560 (thanks to David Moorhouse)
Fix https://github.com/danieleteti/delphimvcframework/issues/335 (thanks to João Antônio Duarte)
Fix https://github.com/danieleteti/delphimvcframework/issues/564
Fix https://github.com/danieleteti/delphimvcframework/issues/570 (Thanks Marcos Nielsen)
Fix https://github.com/danieleteti/delphimvcframework/issues/565
Merged PR#543 (Now the PathInfo
is trimmed so the router convert this "http://myserver.com/one " to this "http://myserver.com/one")
Fix for nil objects in lists during serialization
Fix a very subtle bug in MaxRecordCount
parameter for RQL based methods in TMVCActiveRecord
Uniformed behavior in Update
and Delete
method in TMVCActiveRecord
. Now these methods raise an exception if the record doesn't exists anymore in the table (update or delete statements return AffectedRows
= 0). The behavior can be altered using the new parameter in the call, which by default is true
.
WARNING! This change could raise some incompatibilities with the previous version, however this is the correct behavior. Consider the previous one a "incorrect behavior to fix".
Fix https://github.com/danieleteti/delphimvcframework/issues/489
Fix https://github.com/danieleteti/delphimvcframework/issues/518 (Thanks to Microcom-Bjarne)
Fix https://github.com/danieleteti/delphimvcframework/issues/526 (Thanks to David Moorhouse)
Fix https://github.com/danieleteti/delphimvcframework/issues/544 (Thanks to David Moorhouse)
Fix https://github.com/danieleteti/delphimvcframework/issues/542 (Thanks to Lamberto Lodi for the hints)
Fix https://github.com/danieleteti/delphimvcframework/issues/485
Fixed fileupload sample
Fixed an IFDEF
compatibility problem on mobile platforms (Thanks to Marco Cotroneo)
Samples are syntax compatible with Delphi 10.1 Berlin or better (Thanks to Mark Lobanov)
Fix https://github.com/danieleteti/delphimvcframework/issues/573 (Thanks to DeddyH the hints)
Fix https://github.com/danieleteti/delphimvcframework/issues/574 (Thanks to DeddyH the hints)
TMVCJWTAuthenticationMiddleware
- was deprecated since 2019. Just use the other one as suggested.IMVCMiddleware.OnAfterControllerAction
This version is the version referenced by the DelphiMVCFramework - The Official Guide book (available in english, portuguese and spanish).
This version introduces new features in many different areas (swagger, server side view, MVCActiveRecord, renders etc.) so there isn't a single-big-feature to talk about. This version contains also a good number of bugfixes. It's not a critical updated, but this is the best version ever (at least, so far...) and is the suggested version for starting new projects. Enjoy!
New Context: TWebContext
parameter in JSON-RPC Hooks
{ 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.
New React demo (Thanks to Angelo Sobreira da Silva)
Serialization support for TList<Integer>
, TList<String>
, TList<Boolean>
and for all TList<T>
of simple types.
Added method MetadataAsJSONObject(FieldNameCase: TMVCNameCase = ncLowerCase): TJSONObject;
in TDataSetHelper
. This method returns the dataset field definitions. While this is valid only for Delphi datasets, can be useful to describe a dataset to a Delphi client.
{
"fielddefs": [
{
"datatype": 3,
"displayname": "ID",
"fieldname": "id",
"precision": 0,
"size": 0
},
{
"datatype": 24,
"displayname": "CODE",
"fieldname": "code",
"precision": 0,
"size": 5
},
{
"datatype": 24,
"displayname": "DESCRIPTION",
"fieldname": "description",
"precision": 0,
"size": 200
},
{
"datatype": 37,
"displayname": "PRICE",
"fieldname": "price",
"precision": 18,
"size": 2
}
]
}
The static method class procedure TFireDACUtils.CreateDatasetFromMetadata( AFDMemTable: TFDMemTable; AMeta: TJSONObject);
gets the previous structure and initialize the fields of AFDMemTable
with it. When a TFDMemTable is initialized using this approach, the data can be directly loaded from a jsonarray of jsonobject with the same field names. WARNING: This mechanism works only for Delphi clients. Check sample articles_crud_vcl_client_meta.dproj
to understand the involved mechanisms.
Added foReadOnly
and foWriteOnly
as field options in MVCTableField
attribute (used by TMVCActiveRecord
). Currently available field options are:
Now it is possible to declare entities like the followings (or with any other combinations):
[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, or alist of objects, starting from an arbitrary node in the JSON (or other format) present in the request body. Works for BodyAs<T>
and for BodyAsListOf<T>
(Thanks to Raphaël Emourgeon for the BodyAsListOf<T>
implementation).
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.
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.
Improved TMVCStaticFilesMiddleware
. Now is able to correctly serve SPA applications from any subfolder.
Added property Context.HostingFrameworkType
. This property is of type TMVCHostingFrameworkType
and can assume one of the following values: hftIndy
(if the service is using the built-in Indy HTTP server) , hftApache
(if the project is compiled as Apache module) or hftISAPI
(if the project is compiled as ISAPI module).
TMVCNameCase
got the new ncSnakeCase
among the possibles casing (thanks to João Antônio Duarte for its work in this area).
Now the TMVCNameCase
declaration is the following:
TMVCNameCase = (ncAsIs, ncUpperCase, ncLowerCase, ncCamelCase, ncPascalCase, ncSnakeCase);
Here's how the new ncSnakeCase
works
Original | SnakeCase |
---|---|
OneTwo | one_two |
One__Two | one_two |
OneTwo03 | one_two_03 |
Support for Mustache partials (Thanks to David Moorhouse and his work about issue 221). Sample \samples\serversideviews_mustache has been updated to show how to use partials.
Added dynamic properties access to TMVCActiveRecord
descendants. Indexed property Attributes
is index using the property name and set/get a TValue
representing the property value.
procedure TMainForm.btnAttributesClick(Sender: TObject);
var
lCustomer: TCustomer;
lID: Integer;
begin
lCustomer := TCustomer.Create;
try
lCustomer.Attributes['CompanyName'] := 'Google Inc.';
lCustomer.Attributes['City'] := 'Montain View, CA';
lCustomer.Attributes['Note'] := 'Hello there!';
lCustomer.Attributes['Code'] := 'XX123';
lCustomer.Attributes['Rating'] := 3;
lCustomer.Insert;
lID := lCustomer.ID;
finally
lCustomer.Free;
end;
lCustomer := TMVCActiveRecord.GetByPK<TCustomer>(lID);
try
Assert('Google Inc.' =
lCustomer.Attributes['CompanyName']
.AsType<NullableString>().Value);
Assert('Montain View, CA' =
lCustomer.Attributes['City'].AsString);
Assert('XX123' =
lCustomer.Attributes['Code']
.AsType<NullableString>().Value);
Assert('Hello there!' =
lCustomer.Attributes['Note'].AsString);
lCustomer.Update;
finally
lCustomer.Free;
end;
Breaking Change! TMVCStaticFileMiddleware
cannot be registered to "/" anymore
Breaking Change! DocumentRoot
of TMVCStaticFileMiddleware
must be a valid folder. If DocumentRoot
doesn't exist an exception is raised.
New! Support for Delphi 10.4 Sydney!
New! Added Nullable support in MVCActiveRecord (nullables defined in MVCFramework.Nullables.pas
)! 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 (nullables defined in MVCFramework.Nullables.pas
)
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 and Geoffrey Smith)
New! Attribute MVCDoNotDeserialize
. If marked with this RTTI attribute, a property or a field is not deserialized and its value remain the same as was before the object deserialization.
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList, False);
end;
New approach with ObjectDict
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList);
end;
New approach with ObjectDict
procedure TRenderSampleController.GetLotOfPeople;
begin
Render(ObjectDict().Add('data', GetPeopleList));
end;
Example 3: Rendering a list of objects adding links for HATEOAS support
Classic
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
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.
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.
Fixed
(default: false). If Fixed
is true, then the name is not processed by the MVCNameCase
attribute assigned to the owner type.OnRouterLog
event to log custom information for each request (thanks to Andrea Ciotti for the first implementation and its PR)/describeserver.info
, /describeplatform.info
and /serverconfig.info
system actions) setting Config[TMVCConfigKey.LoadSystemControllers] := 'false';
in the configuration block.Accept:*/*
compatible for every MVCProduces
values.TRenderSampleController.GetPeople_AsObjectList_HATEOS
and all the others actions end with HATEOS
in renders.dproj
sample).//Now is really easy to add "links" property automatically for each collection element while rendering
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);
//Datasets have a similar anon method to do the same thing
Render(lDM.qryCustomers, False,
procedure(const DS: TDataset; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json');
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + '/orders')
.Add(HATEOAS.REL, 'orders')
.Add(HATEOAS._TYPE, 'application/json');
end);
//Single object rendering allows HATEOAS too!
Render(lPerson, False,
procedure(const AObject: TObject; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/people/' + TPerson(AObject).ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
Links.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
end);
packages
folder)TMVCActiveRecord.Count
method (e.g. TMVCActiveRecord.Count(TCustomer)
returns the number of records for the entity mapped by the class TCustomer
)TMVCACtiveRecord.GetByPK<T>
raises an exception by default if the record is not found - optionally can returns nil
using new parameter RaiseExceptionIfNotFound
contains
clause has been added in the RQL compiler for Firebird and InterbaseTMVCAnalyticsMiddleware
to do automatic analytics on the API (generates a CSV file). Based on an idea by Nirav Kaku (https://www.facebook.com/nirav.kaku). Check the sample in \samples\middleware_analytics\
TMVCActiveRecord.DeleteAll
deletes all the records from a tableTMVCActiveRecord.DeleteRQL
deletes records using an RQL
expression as where
clause.TMVCActiveRecord.Store
which automatically executes Insert or Update considering primary key value.TMVCActiveRecord
allows to use table name and field name with spaces (currently supported only by the PostgreSQL compiler).MVCActiveRecord
and RQL (thanks to one of the biggest Delphi based company in Italy which heavily uses DMVCFramework and DMSContainer).MVCActiveRecord
and RQL, so that MVCActiveRecord
can be used also for Delphi mobile projects!JsonDataObjects.TJSONObject
without using string
as carrier of JSON.ActiveRecordShowCase
sample is much better now.ActiveRecord
methods which retrieve records can now specify the data type of each parameter (using Delphi's TFieldType
enumeration).TMVCEngine
is compliant with the default response content-type (usually it did would reply using text/plain
).LogD(lMyObject)
to get logged lMyObject
as JSON (custom type serializers not supported in log).StrDict(array of string, array of string)
function allows to render a dictionary of strings in a really simple way. See the following action sample.procedure TMy.GetPeople(const Value: Integer);
begin
if Value mod 2 <> 0 then
begin
raise EMVCException.Create(HTTP_STATUS.NotAcceptable, 'We don''t like odd numbers');
end;
Render(
StrDict(
['id', 'message'],
['123', 'We like even numbers, thank you for your ' + Value.ToString]
));
end;
application/json
.TMVCListener
(Thanks to Sven Harazim).in
operator in RQL parser (Thank you to João Antônio Duarte for his initial work on this).TMVCActiveRecord.Count<T>(RQL)
to count record based on RQL criteria.TMVCActiveRecord
can handle non autogenerated primary key.X-HTTP-Method-Override
to work behind corporate firewalls.procedure TDataSetHelper.LoadJSONArrayFromJSONObjectProperty(const AJSONObjectString: string; const aPropertyName: String);
HTTP_STATUS
to better describe the http status response.CreateInsert
if not autogenerated.TArray<String>
, TArray<Integer>
and TArray<Double>
in default JSON serializer (Thank you Pedro Oliveira)OnBeforeDispatch
(Thanks to Spinettaro)StrToJSONObject
function to safely parse a string into a JSON object.TDataSet
descendants serialization in TMVCJsonDataObjectsSerializer
.procedure TMainForm.btnDataSetToJSONArrayClick(Sender: TObject);
var
lSer: TMVCJsonDataObjectsSerializer;
lJArray: TJSONArray;
begin
FDQuery1.Open();
lSer := TMVCJsonDataObjectsSerializer.Create;
try
lJArray := TJSONArray.Create;
try
lSer.DataSetToJsonArray(FDQuery1, lJArray, TMVCNameCase.ncLowerCase, [],
procedure(const aField: TField; const aJsonObject: TJSONObject; var Handled: Boolean)
begin
if SameText(aField.FieldName, 'created_at') then
begin
aJsonObject.S['year_and_month'] := FormatDateTime('yyyy-mm', TDateTimeField(aField).Value);
Handled := True;
end;
end);
//The json objects will not contains "created_at" anymore, but only "year_and_month".
Memo1.Lines.Text := lJArray.ToJSON(false);
finally
lJArray.Free;
end;
finally
lSer.Free;
end;
end;
New! Shortcut render' methods which simplify RESTful API development
procedure Render201Created(const Location: String = ''; const Reason: String = 'Created'); virtual;
procedure Render202Accepted(const HREF: String; const ID: String; const Reason: String = 'Accepted'); virtual;
procedure Render204NoContent(const Reason: String = 'No Content'); virtual;
Added de/serializing iterables (e.g. generic lists) support without MVCListOf
attribute (Thank you to João Antônio Duarte).
It is now possible to deserialize a generic class like this:
TGenericEntity<T: class> = class
private
FCode: Integer;
FItems: TObjectList<T>;
FDescription: string;
public
constructor Create;
destructor Destroy; override;
property Code: Integer read FCode write FCode;
property Description: string read FDescription write FDescription;
// MVCListOf(T) <- No need
property Items: TObjectList<T> read FItems write FItems;
end;
Before it was not possible because you should add the MVCListOf
attribute to the TObjectList
type property.
New! Added serialization support for (thanks to dockerandy for his initial work)
TArray<String>
TArray<Integer>
TArray<Int64>
TArray<Double>
New! The MVCAREntitiesGenerator can optionally register all the generated entities also in the ActiveRecordMappingRegistry
(Thanks to Fabrizio Bitti from bit Time Software)
New! Experimental (alpha stage) support for Android servers!
New! Children objects lifecycle management in TMVCActiveRecord
(methods AddChildren
and RemoveChildren
). Really useful to manage child objects such relations or derived properties and are safe in case of multiple addition of the same object as children.
//Having the following declaration
type
[MVCNameCase(ncCamelCase)]
[MVCTable('authors')]
TAuthor = class(TPersonEntityBase)
private
fBooks: TEnumerable<TBookRef>;
[MVCTableField('full_name')]
fFullName: string;
function GetBooks: TEnumerable<TBookRef>;
public
[MVCNameAs('full_name')]
property FullName: string read fFullName write fFullName;
property Books: TEnumerable<TBookRef> read GetBooks;
end;
//method GetBooks can be implemented as follows:
implementation
function TAuthor.GetBooks: TEnumerable<TBookRef>;
begin
if fBooks = nil then
begin
fBooks := TMVCActiveRecord.Where<TBookRef>('author_id = ?', [ID]);
AddChildren(fBooks); //fBooks will be freed when self will be freed
end;
Result := fBooks;
end;
JSON-RPC Improvements
New! Added TMVCJSONRPCExecutor.ConfigHTTPClient
to fully customize the inner THTTPClient
(e.g. ConnectionTimeout
, ResponseTimeout
and so on)
Improved! JSONRPC Automatic Object Publishing can not invoke inherited methods if not explicitly defined with MVCInheritable
attribute.
New! Calling <jsonrpcendpoint>/describe
returns the methods list available for that endpoint.
New! Full support for named parameters in JSON-RPC call (server and client)
Positional parameters example
procedure TMainForm.btnSubtractClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.Add(StrToInt(edtValue1.Text));
lReq.Params.Add(StrToInt(edtValue2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
edtResult.Text := lResp.Result.AsInteger.ToString;
end;
Named parameters example
procedure TMainForm.btnSubtractWithNamedParamsClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.AddByName('Value1', StrToInt(Edit1.Text));
lReq.Params.AddByName('Value2', StrToInt(Edit2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
Edit3.Text := lResp.Result.AsInteger.ToString;
end;
Check official JSONRPC 2.0 documentation for more examples.
New! JSONRPC Hooks for published objects
//Called before as soon as the HTTP arrives
procedure TMyPublishedObject.OnBeforeRouting(const JSON: TJDOJsonObject);
//Called before the invoked method
procedure TMyPublishedObject.OnBeforeCall(const JSONRequest: TJDOJsonObject);
//Called just before to send response to the client
procedure TMyPublishedObject.OnBeforeSendResponse(const JSONResponse: TJDOJsonObject);
Deprecated! TDataSetHolder
is deprecated! Use the shining new ObjectDict(boolean)
instead.
Added ability to serialize/deserialize types enumerated by an array of mapped values (Thanks to João Antônio Duarte).
type
TMonthEnum = (meJanuary, meFebruary, meMarch, meApril);
TEntityWithEnums = class
private
FMonthMappedNames: TMonthEnum;
FMonthEnumName: TMonthEnum;
FMonthOrder: TMonthEnum;
public
// List items separated by comma or semicolon
[MVCEnumSerializationType(estEnumMappedValues,
'January,February,March,April')]
property MonthMappedNames: TMonthEnum
read FMonthMappedNames write FMonthMappedNames;
[MVCEnumSerializationType(estEnumName)]
property MonthEnumName: TMonthEnum
read FMonthEnumName write FMonthEnumName;
[MVCEnumSerializationType(estEnumOrd)]
property MonthOrder: TMonthEnum read FMonthOrder write FMonthOrder;
end;
...
New Installation procedure!
dmvcframeworkDT
)C:\DEV\dmvcframework
is the dmvcframework
main folder)
C:\DEV\dmvcframework\sources
C:\DEV\dmvcframework\lib\loggerpro
C:\DEV\dmvcframework\lib\swagdoc\Source
C:\DEV\dmvcframework\lib\dmustache
Delphi Version | Project Group |
---|---|
Delphi 10.4 Sydney | packages\d104\dmvcframework_group.groupproj |
Delphi 10.3 Rio | packages\d103\dmvcframework_group.groupproj |
Delphi 10.2 Tokyo | packages\d102\dmvcframework_group.groupproj |
Delphi 10.1 Berlin | packages\d101\dmvcframework_group.groupproj |
Delphi 10.0 Seattle | packages\d100\dmvcframework_group.groupproj |
In MVCActiveRecord
attribute MVCPrimaryKey
has been removed and merged with MVCTableField
, so now TMVCActiveRecordFieldOption
is a set of foPrimaryKey
, foAutoGenerated
, foTransient
(check activerecord_showcase.dproj
sample).
Middleware OnAfterControllerAction
are now invoked in the same order of OnBeforeControllerAction
(previously were invoked in reversed order).
TMVCEngine
is no more responsible for static file serving. If you need static files used the new TMVCStaticFilesMiddleware
(check the sample). As consequence TMVCConfigKey.DocumentRoot
, TMVCConfigKey.IndexDocument
and TMVCConfigKey.FallbackResource
are no more available.
TMVCEngine.Config
property is now read-only. Can be changed only in the anonymous method injected in the constructor.
// This is valid
////////////////////////////////////////
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := '0';
//Other Configurations
end);
FMVC.AddController(TMyController);
// This is not valid (exception is raised)
/////////////////////////////////////////
FMVC := TMVCEngine.Create(Self);,
FMVC.Config[TMVCConfigKey.SessionTimeout] := '0'; {run-time error here}
FMVC.AddController(TMyController);
RC
phase)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! Support for Delphi 10.4 Sydney! This is the only new feature compared to 3.2.0-boron-RC7
New! Added Nullable support in MVCActiveRecord (nullables defined in MVCFramework.Nullables.pas
)! 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 (nullables defined in MVCFramework.Nullables.pas
)
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 and Geoffrey Smith)
New! Attribute MVCDoNotDeserialize
. If marked with this RTTI attribute, a property or a field is not deserialized and its value remain the same as was before the object deserialization.
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList, False);
end;
New approach with ObjectDict
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList);
end;
New approach with ObjectDict
procedure TRenderSampleController.GetLotOfPeople;
begin
Render(ObjectDict().Add('data', GetPeopleList));
end;
Example 3: Rendering a list of objects adding links for HATEOAS support
Classic
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
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.
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.
Fixed
(default: false). If Fixed
is true, then the name is not processed by the MVCNameCase
attribute assigned to the owner type.OnRouterLog
event to log custom information for each request (thanks to Andrea Ciotti for the first implementation and its PR)/describeserver.info
, /describeplatform.info
and /serverconfig.info
system actions) setting Config[TMVCConfigKey.LoadSystemControllers] := 'false';
in the configuration block.Accept:*/*
compatible for every MVCProduces
valuesTRenderSampleController.GetPeople_AsObjectList_HATEOS
and all the others actions end with HATEOS
in renders.dproj
sample)//Now is really easy to add "links" property automatically for each collection element while rendering
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);
//Datasets have a similar anon method to do the same thing
Render(lDM.qryCustomers, False,
procedure(const DS: TDataset; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json');
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + '/orders')
.Add(HATEOAS.REL, 'orders')
.Add(HATEOAS._TYPE, 'application/json');
end);
//Single object rendering allows HATEOAS too!
Render(lPerson, False,
procedure(const AObject: TObject; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/people/' + TPerson(AObject).ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
Links.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
end);
packages
folder)TMVCActiveRecord.Count
method (e.g. TMVCActiveRecord.Count(TCustomer)
returns the number of records for the entity mapped by the class TCustomer
)TMVCACtiveRecord.GetByPK<T>
raises an exception by default if the record is not found - optionally can returns nil
using new parameter RaiseExceptionIfNotFound
contains
clause has been added in the RQL compiler for Firebird and InterbaseTMVCAnalyticsMiddleware
to do automatic analytics on the API (generates a CSV file). Based on an idea by Nirav Kaku (https://www.facebook.com/nirav.kaku). Check the sample in \samples\middleware_analytics\
TMVCActiveRecord.DeleteAll
deletes all the records from a tableTMVCActiveRecord.DeleteRQL
deletes records using an RQL
expression as where
clause.TMVCActiveRecord.Store
which automatically executes Insert or Update considering primary key value.TMVCActiveRecord
allows to use table name and field name with spaces (currently supported only by the PostgreSQL compiler).MVCActiveRecord
and RQL (thanks to one of the biggest Delphi based company in Italy which heavily uses DMVCFramework and DMSContainer)MVCActiveRecord
and RQL, so that MVCActiveRecord
can be used also for Delphi mobile projects!JsonDataObjects.TJSONObject
without using string
as carrier of JSONActiveRecordShowCase
sample is much better now.ActiveRecord
methods which retrieve records can now specify the data type of each parameter (using Delphi's TFieldType
enumeration).TMVCEngine
is compliant with the default response content-type (usually it did would reply using text/plain
).LogD(lMyObject)
to get logged lMyObject
as JSON (custom type serializers not supported in log).StrDict(array of string, array of string)
function allows to render a dictionary of strings in a really simple way. See the following action sample.procedure TMy.GetPeople(const Value: Integer);
begin
if Value mod 2 <> 0 then
begin
raise EMVCException.Create(HTTP_STATUS.NotAcceptable, 'We don''t like odd numbers');
end;
Render(
StrDict(
['id', 'message'],
['123', 'We like even numbers, thank you for your ' + Value.ToString]
));
end;
application/json
.TMVCListener
(Thanks to Sven Harazim)in
operator in RQL parser (Thank you to João Antônio Duarte for his initial work on this)TMVCActiveRecord.Count<T>(RQL)
to count record based on RQL criteriaTMVCActiveRecord
can handle non autogenerated primary key.X-HTTP-Method-Override
to work behind corporate firewalls.procedure TDataSetHelper.LoadJSONArrayFromJSONObjectProperty(const AJSONObjectString: string; const aPropertyName: String);
HTTP_STATUS
to better describe the http status response.CreateInsert
if not autogenerated.TArray<String>
, TArray<Integer>
and TArray<Double>
in default JSON serializer (Thank you Pedro Oliveira)OnBeforeDispatch
(Thanks to Spinettaro)StrToJSONObject
function to safely parse a string into a JSON object.TDataSet
descendants serialization in TMVCJsonDataObjectsSerializer
.procedure TMainForm.btnDataSetToJSONArrayClick(Sender: TObject);
var
lSer: TMVCJsonDataObjectsSerializer;
lJArray: TJSONArray;
begin
FDQuery1.Open();
lSer := TMVCJsonDataObjectsSerializer.Create;
try
lJArray := TJSONArray.Create;
try
lSer.DataSetToJsonArray(FDQuery1, lJArray, TMVCNameCase.ncLowerCase, [],
procedure(const aField: TField; const aJsonObject: TJSONObject; var Handled: Boolean)
begin
if SameText(aField.FieldName, 'created_at') then
begin
aJsonObject.S['year_and_month'] := FormatDateTime('yyyy-mm', TDateTimeField(aField).Value);
Handled := True;
end;
end);
//The json objects will not contains "created_at" anymore, but only "year_and_month".
Memo1.Lines.Text := lJArray.ToJSON(false);
finally
lJArray.Free;
end;
finally
lSer.Free;
end;
end;
New! Shortcut render' methods which simplify RESTful API development
procedure Render201Created(const Location: String = ''; const Reason: String = 'Created'); virtual;
procedure Render202Accepted(const HREF: String; const ID: String; const Reason: String = 'Accepted'); virtual;
procedure Render204NoContent(const Reason: String = 'No Content'); virtual;
Added de/serializing iterables (e.g. generic lists) support without MVCListOf
attribute (Thank you to João Antônio Duarte).
It is now possible to deserialize a generic class like this:
TGenericEntity<T: class> = class
private
FCode: Integer;
FItems: TObjectList<T>;
FDescription: string;
public
constructor Create;
destructor Destroy; override;
property Code: Integer read FCode write FCode;
property Description: string read FDescription write FDescription;
// MVCListOf(T) <- No need
property Items: TObjectList<T> read FItems write FItems;
end;
Before it was not possible because you should add the MVCListOf
attribute to the TObjectList
type property.
New! Added serialization support for (thanks to dockerandy for his initial work)
TArray<String>
TArray<Integer>
TArray<Int64>
TArray<Double>
New! The MVCAREntitiesGenerator can optionally register all the generated entities also in the ActiveRecordMappingRegistry
(Thanks to Fabrizio Bitti from bit Time Software)
New! Children objects lifecycle management in TMVCActiveRecord
(methods AddChildren
and RemoveChildren
). Really useful to manage child objects such relations or derived properties and are safe in case of multiple addition of the same object as children.
//Having the following declaration
type
[MVCNameCase(ncCamelCase)]
[MVCTable('authors')]
TAuthor = class(TPersonEntityBase)
private
fBooks: TEnumerable<TBookRef>;
[MVCTableField('full_name')]
fFullName: string;
function GetBooks: TEnumerable<TBookRef>;
public
[MVCNameAs('full_name')]
property FullName: string read fFullName write fFullName;
property Books: TEnumerable<TBookRef> read GetBooks;
end;
//method GetBooks can be implemented as follows:
implementation
function TAuthor.GetBooks: TEnumerable<TBookRef>;
begin
if fBooks = nil then
begin
fBooks := TMVCActiveRecord.Where<TBookRef>('author_id = ?', [ID]);
AddChildren(fBooks); //fBooks will be freed when self will be freed
end;
Result := fBooks;
end;
JSON-RPC Improvements
New! Added TMVCJSONRPCExecutor.ConfigHTTPClient
to fully customize the inner THTTPClient
(e.g. ConnectionTimeout
, ResponseTimeout
and so on)
Improved! JSONRPC Automatic Object Publishing can not invoke inherited methods if not explicitly defined with MVCInheritable
attribute.
New! Calling <jsonrpcendpoint>/describe
returns the methods list available for that endpoint.
New! Full support for named parameters in JSON-RPC call (server and client)
Positional parameters example
procedure TMainForm.btnSubtractClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.Add(StrToInt(edtValue1.Text));
lReq.Params.Add(StrToInt(edtValue2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
edtResult.Text := lResp.Result.AsInteger.ToString;
end;
Named parameters example
procedure TMainForm.btnSubtractWithNamedParamsClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.AddByName('Value1', StrToInt(Edit1.Text));
lReq.Params.AddByName('Value2', StrToInt(Edit2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
Edit3.Text := lResp.Result.AsInteger.ToString;
end;
Check official JSONRPC 2.0 documentation for more examples.
New! JSONRPC Hooks for published objects
//Called before as soon as the HTTP arrives
procedure TMyPublishedObject.OnBeforeRouting(const JSON: TJDOJsonObject);
//Called before the invoked method
procedure TMyPublishedObject.OnBeforeCall(const JSONRequest: TJDOJsonObject);
//Called just before to send response to the client
procedure TMyPublishedObject.OnBeforeSendResponse(const JSONResponse: TJDOJsonObject);
Deprecated! TDataSetHolder
is deprecated! Use the shining new ObjectDict(boolean)
instead.
Added ability to serialize/deserialize types enumerated by an array of mapped values (Thanks to João Antônio Duarte)
type
TMonthEnum = (meJanuary, meFebruary, meMarch, meApril);
TEntityWithEnums = class
private
FMonthMappedNames: TMonthEnum;
FMonthEnumName: TMonthEnum;
FMonthOrder: TMonthEnum;
public
// List items separated by comma or semicolon
[MVCEnumSerializationType(estEnumMappedValues,
'January,February,March,April')]
property MonthMappedNames: TMonthEnum
read FMonthMappedNames write FMonthMappedNames;
[MVCEnumSerializationType(estEnumName)]
property MonthEnumName: TMonthEnum
read FMonthEnumName write FMonthEnumName;
[MVCEnumSerializationType(estEnumOrd)]
property MonthOrder: TMonthEnum read FMonthOrder write FMonthOrder;
end;
...
New Installation procedure!
dmvcframeworkDT
)C:\DEV\dmvcframework
is the dmvcframework
main folder)
C:\DEV\dmvcframework\sources
C:\DEV\dmvcframework\lib\loggerpro
C:\DEV\dmvcframework\lib\swagdoc\Source
C:\DEV\dmvcframework\lib\dmustache
Delphi Version | Project Group |
---|---|
Delphi 10.4 Sydney | packages\d104\dmvcframework_group.groupproj |
Delphi 10.3 Rio | packages\d103\dmvcframework_group.groupproj |
Delphi 10.2 Tokyo | packages\d102\dmvcframework_group.groupproj |
Delphi 10.1 Berlin | packages\d101\dmvcframework_group.groupproj |
Delphi 10.0 Seattle | packages\d100\dmvcframework_group.groupproj |
In MVCActiveRecord
attribute MVCPrimaryKey
has been removed and merged with MVCTableField
, so now TMVCActiveRecordFieldOption
is a set of foPrimaryKey
, foAutoGenerated
, foTransient
(check activerecord_showcase.dproj
sample).
Middleware OnAfterControllerAction
are now invoked in the same order of OnBeforeControllerAction
(previously were invoked in reversed order).
TMVCEngine
is no more responsible for static file serving. If you need static files used the new TMVCStaticFilesMiddleware
(check the sample). As consequence TMVCConfigKey.DocumentRoot
, TMVCConfigKey.IndexDocument
and TMVCConfigKey.FallbackResource
are no more available.
TMVCEngine.Config
property is now read-only. Can be changed only in the anonymous method injected in the constructor.
// This is valid
////////////////////////////////////////
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := '0';
//Other Configurations
end);
FMVC.AddController(TMyController);
// This is not valid (exception is raised)
/////////////////////////////////////////
FMVC := TMVCEngine.Create(Self);,
FMVC.Config[TMVCConfigKey.SessionTimeout] := '0'; {run-time error here}
FMVC.AddController(TMyController);
New! Added Nullable support in MVCActiveRecord (nullables defined in MVCFramework.Nullables.pas
)! 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 (nullables defined in MVCFramework.Nullables.pas
)
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 and Geoffrey Smith)
New! Attribute MVCDoNotDeserialize
. If marked with this RTTI attribute, a property or a field is not deserialized and its value remain the same as was before the object deserialization.
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList, False);
end;
New approach with ObjectDict
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
procedure TRenderSampleController.GetLotOfPeople;
begin
Render<TPerson>(GetPeopleList);
end;
New approach with ObjectDict
procedure TRenderSampleController.GetLotOfPeople;
begin
Render(ObjectDict().Add('data', GetPeopleList));
end;
Example 3: Rendering a list of objects adding links for HATEOAS support
Classic
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
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.
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.
Fixed
(default: false). If Fixed
is true, then the name is not processed by the MVCNameCase
attribute assigned to the owner type.OnRouterLog
event to log custom information for each request (thanks to Andrea Ciotti for the first implementation and its PR)/describeserver.info
, /describeplatform.info
and /serverconfig.info
system actions) setting Config[TMVCConfigKey.LoadSystemControllers] := 'false';
in the configuration block.Accept:*/*
compatible for every MVCProduces
valuesTRenderSampleController.GetPeople_AsObjectList_HATEOS
and all the others actions end with HATEOS
in renders.dproj
sample)//Now is really easy to add "links" property automatically for each collection element while rendering
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);
//Datasets have a similar anon method to do the same thing
Render(lDM.qryCustomers, False,
procedure(const DS: TDataset; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, 'application/json');
Links.AddRefLink
.Add(HATEOAS.HREF, '/customers/' + DS.FieldByName('cust_no').AsString + '/orders')
.Add(HATEOAS.REL, 'orders')
.Add(HATEOAS._TYPE, 'application/json');
end);
//Single object rendering allows HATEOAS too!
Render(lPerson, False,
procedure(const AObject: TObject; const Links: IMVCLinks)
begin
Links.AddRefLink
.Add(HATEOAS.HREF, '/people/' + TPerson(AObject).ID.ToString)
.Add(HATEOAS.REL, 'self')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
Links.AddRefLink
.Add(HATEOAS.HREF, '/people')
.Add(HATEOAS.REL, 'people')
.Add(HATEOAS._TYPE, TMVCMediaType.APPLICATION_JSON);
end);
packages
folder)TMVCActiveRecord.Count
method (e.g. TMVCActiveRecord.Count(TCustomer)
returns the number of records for the entity mapped by the class TCustomer
)TMVCACtiveRecord.GetByPK<T>
raises an exception by default if the record is not found - optionally can returns nil
using new parameter RaiseExceptionIfNotFound
contains
clause has been added in the RQL compiler for Firebird and InterbaseTMVCAnalyticsMiddleware
to do automatic analytics on the API (generates a CSV file). Based on an idea by Nirav Kaku (https://www.facebook.com/nirav.kaku). Check the sample in \samples\middleware_analytics\
TMVCActiveRecord.DeleteAll
deletes all the records from a tableTMVCActiveRecord.DeleteRQL
deletes records using an RQL
expression as where
clause.TMVCActiveRecord.Store
which automatically executes Insert or Update considering primary key value.TMVCActiveRecord
allows to use table name and field name with spaces (currently supported only by the PostgreSQL compiler).MVCActiveRecord
and RQL (thanks to one of the biggest Delphi based company in Italy which heavily uses DMVCFramework and DMSContainer)MVCActiveRecord
and RQL, so that MVCActiveRecord
can be used also for Delphi mobile projects!JsonDataObjects.TJSONObject
without using string
as carrier of JSONActiveRecordShowCase
sample is much better now.ActiveRecord
methods which retrieve records can now specify the data type of each parameter (using Delphi's TFieldType
enumeration).TMVCEngine
is compliant with the default response content-type (usually it did would reply using text/plain
).LogD(lMyObject)
to get logged lMyObject
as JSON (custom type serializers not supported in log).StrDict(array of string, array of string)
function allows to render a dictionary of strings in a really simple way. See the following action sample.procedure TMy.GetPeople(const Value: Integer);
begin
if Value mod 2 <> 0 then
begin
raise EMVCException.Create(HTTP_STATUS.NotAcceptable, 'We don''t like odd numbers');
end;
Render(
StrDict(
['id', 'message'],
['123', 'We like even numbers, thank you for your ' + Value.ToString]
));
end;
New! Custom Exception Handling (Based on work of David Moorhouse). Sample "custom_exception_handling" show how to use it.
Improved! Exceptions rendering while using MIME types different to application/json
.
SSL Server support for TMVCListener
(Thanks to Sven Harazim)
Improved! Datasets serialization speed improvement. In some case the performance improves of 2 order of magnitude. (Thanks to https://github.com/pedrooliveira01)
New! Added in
operator in RQL parser (Thank you to João Antônio Duarte for his initial work on this)
New! Added TMVCActiveRecord.Count<T>(RQL)
to count record based on RQL criteria
New! TMVCActiveRecord
can handle non autogenerated primary key.
New! Experimental (alpha stage) support for Android servers!
New! Added support for X-HTTP-Method-Override
to work behind corporate firewalls.
New Sample! Server in DLL
Added new method in the dataset helper to load data into a dataset from a specific JSONArray property of a JSONObject procedure TDataSetHelper.LoadJSONArrayFromJSONObjectProperty(const AJSONObjectString: string; const aPropertyName: String);
Improved! New constants defined in HTTP_STATUS
to better describe the http status response.
Improved! Now Firebird RQL' SQLGenerator can include primary key in CreateInsert
if not autogenerated.
New! Added support for TArray<String>
, TArray<Integer>
and TArray<Double>
in default JSON serializer (Thank you Pedro Oliveira)
Improved JWT Standard Compliance! Thanks to Vinicius Sanchez for his work on issue #241
Improved! DMVCFramework now has 130+ unit tests that checks its functionalities at each build!
New! StrToJSONObject
function to safely parse a string into a JSON object.
New! Serialization callback for custom TDataSet
descendants serialization in TMVCJsonDataObjectsSerializer
.
procedure TMainForm.btnDataSetToJSONArrayClick(Sender: TObject);
var
lSer: TMVCJsonDataObjectsSerializer;
lJArray: TJSONArray;
begin
FDQuery1.Open();
lSer := TMVCJsonDataObjectsSerializer.Create;
try
lJArray := TJSONArray.Create;
try
lSer.DataSetToJsonArray(FDQuery1, lJArray, TMVCNameCase.ncLowerCase, [],
procedure(const aField: TField; const aJsonObject: TJSONObject; var Handled: Boolean)
begin
if SameText(aField.FieldName, 'created_at') then
begin
aJsonObject.S['year_and_month'] := FormatDateTime('yyyy-mm', TDateTimeField(aField).Value);
Handled := True;
end;
end);
//The json objects will not contains "created_at" anymore, but only "year_and_month".
Memo1.Lines.Text := lJArray.ToJSON(false);
finally
lJArray.Free;
end;
finally
lSer.Free;
end;
end;
New! Shortcut render' methods which simplify RESTful API development
procedure Render201Created(const Location: String = ''; const Reason: String = 'Created'); virtual;
procedure Render202Accepted(const HREF: String; const ID: String; const Reason: String = 'Accepted'); virtual;
procedure Render204NoContent(const Reason: String = 'No Content'); virtual;
Added de/serializing iterables (e.g. generic lists) support without MVCListOf
attribute (Thank you to João Antônio Duarte).
It is now possible to deserialize a generic class like this:
TGenericEntity<T: class> = class
private
FCode: Integer;
FItems: TObjectList<T>;
FDescription: string;
public
constructor Create;
destructor Destroy; override;
property Code: Integer read FCode write FCode;
property Description: string read FDescription write FDescription;
// MVCListOf(T) <- No need
property Items: TObjectList<T> read FItems write FItems;
end;
Before it was not possible because you should add the MVCListOf
attribute to the TObjectList
type property.
New! Added serialization support for (thanks to dockerandy for his initial work)
TArray<String>
TArray<Integer>
TArray<Int64>
TArray<Double>
New! The MVCAREntitiesGenerator can optionally register all the generated entities also in the ActiveRecordMappingRegistry
(Thanks to Fabrizio Bitti from bit Time Software)
New! Children objects lifecycle management in TMVCActiveRecord
(methods AddChildren
and RemoveChildren
). Really useful to manage child objects such relations or derived properties and are safe in case of multiple addition of the same object as children.
//Having the following declaration
type
[MVCNameCase(ncCamelCase)]
[MVCTable('authors')]
TAuthor = class(TPersonEntityBase)
private
fBooks: TEnumerable<TBookRef>;
[MVCTableField('full_name')]
fFullName: string;
function GetBooks: TEnumerable<TBookRef>;
public
[MVCNameAs('full_name')]
property FullName: string read fFullName write fFullName;
property Books: TEnumerable<TBookRef> read GetBooks;
end;
//method GetBooks can be implemented as follows:
implementation
function TAuthor.GetBooks: TEnumerable<TBookRef>;
begin
if fBooks = nil then
begin
fBooks := TMVCActiveRecord.Where<TBookRef>('author_id = ?', [ID]);
AddChildren(fBooks); //fBooks will be freed when self will be freed
end;
Result := fBooks;
end;
JSON-RPC Improvements
New! Added TMVCJSONRPCExecutor.ConfigHTTPClient
to fully customize the inner THTTPClient
(e.g. ConnectionTimeout
, ResponseTimeout
and so on)
Improved! JSONRPC Automatic Object Publishing can not invoke inherited methods if not explicitly defined with MVCInheritable
attribute.
New! Calling <jsonrpcendpoint>/describe
returns the methods list available for that endpoint.
New! Full support for named parameters in JSON-RPC call (server and client)
Positional parameters example
procedure TMainForm.btnSubtractClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.Add(StrToInt(edtValue1.Text));
lReq.Params.Add(StrToInt(edtValue2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
edtResult.Text := lResp.Result.AsInteger.ToString;
end;
Named parameters example
procedure TMainForm.btnSubtractWithNamedParamsClick(Sender: TObject);
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
begin
lReq := TJSONRPCRequest.Create;
lReq.Method := 'subtract';
lReq.RequestID := Random(1000);
lReq.Params.AddByName('Value1', StrToInt(Edit1.Text));
lReq.Params.AddByName('Value2', StrToInt(Edit2.Text));
lResp := FExecutor.ExecuteRequest(lReq);
Edit3.Text := lResp.Result.AsInteger.ToString;
end;
Check official JSONRPC 2.0 documentation for more examples.
New! JSONRPC Hooks for published objects
//Called before as soon as the HTTP arrives
procedure TMyPublishedObject.OnBeforeRouting(const JSON: TJDOJsonObject);
//Called before the invoked method
procedure TMyPublishedObject.OnBeforeCall(const JSONRequest: TJDOJsonObject);
//Called just before to send response to the client
procedure TMyPublishedObject.OnBeforeSendResponse(const JSONResponse: TJDOJsonObject);
Breaking Change! In MVCActiveRecord
attribute MVCPrimaryKey
has been removed and merged with MVCTableField
, so now TMVCActiveRecordFieldOption
is a set of foPrimaryKey
, foAutoGenerated
, foTransient
(check activerecord_showcase.dproj
sample).
Breaking Change! Middleware OnAfterControllerAction
are now invoked in the same order of OnBeforeControllerAction
(previously were invoked in reversed order).
Breaking Change! TMVCEngine
is no more responsible for static file serving. If you need static files used the new TMVCStaticFilesMiddleware
(check the sample). As conseguence TMVCConfigKey.DocumentRoot
, TMVCConfigKey.IndexDocument
and TMVCConfigKey.FallbackResource
are no more availables.
Deprecated! TDataSetHolder
is deprecated! Use the shining new ObjectDict(boolean)
instead.
Added ability to serialize/deserialize types enumerated by an array of mapped values (Thanks to João Antônio Duarte)
type
TMonthEnum = (meJanuary, meFebruary, meMarch, meApril);
TEntityWithEnums = class
private
FMonthMappedNames: TMonthEnum;
FMonthEnumName: TMonthEnum;
FMonthOrder: TMonthEnum;
public
// List items separated by comma or semicolon
[MVCEnumSerializationType(estEnumMappedValues,
'January,February,March,April')]
property MonthMappedNames: TMonthEnum
read FMonthMappedNames write FMonthMappedNames;
[MVCEnumSerializationType(estEnumName)]
property MonthEnumName: TMonthEnum
read FMonthEnumName write FMonthEnumName;
[MVCEnumSerializationType(estEnumOrd)]
property MonthOrder: TMonthEnum read FMonthOrder write FMonthOrder;
end;
...
New Installation procedure!
dmvcframeworkDT
)C:\DEV\dmvcframework
is the dmvcframework
main folder)
C:\DEV\dmvcframework\sources
C:\DEV\dmvcframework\lib\loggerpro
C:\DEV\dmvcframework\lib\swagdoc\Source
C:\DEV\dmvcframework\lib\dmustache
Delphi Version | Project Group |
---|---|
Delphi 10.3 Rio | packages\d103\dmvcframework_group.groupproj |
Delphi 10.2 Tokyo | packages\d102\dmvcframework_group.groupproj |
Delphi 10.1 Berlin | packages\d101\dmvcframework_group.groupproj |
Delphi 10.0 Seattle | packages\d100\dmvcframework_group.groupproj |