DelphiMVCFramework 3.4.2-magnesium
▶️ 3° blog update 2025/02/13 ▶️ 2° blog update 2024/06/19 ▶️ 1° blog update 2024/05/31
What’s new in DelphiMVCFramework-3.4.2-magnesium
👉 This features list is related to the stable versione release on Feb 13, 2025!
DelphiMVCFramework continues its evolution and mantains its position as the most popular Delphi web oriented framework on github. Thanks to its 47 contributors the project is getting better than ever.
The latest DelphiMVCFramework stable version is always available at this link. In this article I’ll mention all the major new features and the most important changes.
DelphiMVCFramework Book
While all these new features can change and simplify the way you’ll write your next APIs and Web Application, the contents in the Official Guide is still largely relevant and all the concepts explained there are still valid. To avoid to reinvent-the-weel, and to follow industry best practices, I suggest to all the serious DMVCFramework users to read DelphiMVCFramework: the official guide book that will cover from the basic utilization to the advanced scenarios with a lot real-world samples and how to sections. More info about the book and its translations here.
Join the DMVCFramework community on PATREON!
By becoming a PATREON supporter, you will gain access not only to this invaluable video series but also to an extensive repository of exclusive content. This includes advanced programming tutorials, expert insights, and practical tips, all designed to enhance your programming expertise.
DMVCFramework PATREON community is getting bigger and bigger at each month. Joining the DMVCFramework PATREON community provides more than just educational content; it is an opportunity to connect with a network of passionate developers. Here, we embrace the principle of ‘Scientia potentia est’ (Knowledge is power), striving for continuous learning and improvement in our craft.
Dedicated DMVCFramework Discord server
All “Official PATRON” (or higher levels) supporters have access to the dedicated DMVCFramework Discord server, where we offer priority support. To join the server, please use the following Discord invitation link: https://discord.gg/B5W7fmZa (more info here)
Server Side Views and HTMX development
In this release there are a number of new features and changes related to SSV and WebApp development in general. HEre’s the most important.
Support for TemplatePro
TemplatePro is a modern and versatile templating engine designed to simplify the dynamic generation of HTML, email content, and text files. With a syntax inspired by popular templating systems such as Jinja and Smarty, TemplatePro offers powerful features, including conditional blocks, loops, template inheritance and support for JSON data. In this article, we’ll explore the core features of TemplatePro, from basic syntax to advanced features like custom filters and JSON manipulation.
Support for TemplatePro Filters
Filters are the technology which allows to apply processing to values just before render them in the output stream (eg. an HTML page). TemplatePro filters are automatically generated and configured by the Wizard.
function MyHelper1(const Value: TValue; const Parameters: TArray<TFilterParameter>): TValue;
begin
Result := Value.ToString + ' (I''m The MyHelper1)';
end;
function MyHelper2(const Value: TValue; const Parameters: TArray<TFilterParameter>): TValue;
begin
Result := Value.ToString + ' (I''m The MyHelper2)';
end;
procedure TemplateProContextConfigure;
begin
TTProConfiguration.OnContextConfiguration :=
procedure(const CompiledTemplate: ITProCompiledTemplate)
begin
// These filters will be available to the TemplatePro views as if they were the standard ones
CompiledTemplate.AddFilter('MyHelper1', MyHelper1);
CompiledTemplate.AddFilter('MyHelper2', MyHelper2);
end;
end;
Support WebStencils and WebStencils filters
WebStencils engine is available since Delphi 12.2. WebStencils helpers (similar to the already mentioned filters) are the technology which allows to apply processing to values just before render them in the output stream (eg. an HTML page). WebStencils helpers are automatically generated and configured by the Wizard.
function MyHelper1(const Parameters: TArray<IValue>): TValue;
begin
Result := Parameters[0].GetValue.ToString + ' (I''m The MyHelper1)';
end;
function MyHelper2(const Parameters: TArray<IValue>): TValue;
begin
Result := Parameters[0].GetValue.ToString + ' (I''m The MyHelper2)';
end;
procedure WebStencilsProcessorConfigure;
begin
TBindingMethodsFactory.RegisterMethod(
TMethodDescription.Create(
MakeInvokable(function(Args: TArray<IValue>): IValue
begin
Result := TValueWrapper.Create(MyHelper1(Args));
end),
'MyHelper1', 'MyHelper1', '', True, 'MyHelper1 is just a sample', nil));
TBindingMethodsFactory.RegisterMethod(
TMethodDescription.Create(
MakeInvokable(function(Args: TArray<IValue>): IValue
begin
Result := TValueWrapper.Create(MyHelper2(Args));
end),
'MyHelper2', 'MyHelper2', '', True, 'MyHelper2 is just a sample', nil));
end;
Rewritten Server Side Session support
While not directly erelated to SSV, server side session is particularly useful in such kind of application (instead of a json api which should relais on stateless and JWT). The new session architecture is especially useful in cluster environments and load-balancing scenarios, or when you want your session survive to a server restart. There is already some material to understand and leverage sessions in your web application:
- 📹 VIDEO: Session Management in DMVCFramework (memory, file, database)
- Session goes to “Middleware”
- Server-Side Session Management in DMVCFramework: A Deep Dive into Database Sessions and Community Contributions
Why this new Database Session Management is a Game-Changer Database session management is one of the most awaited features introduced in DMVCFramework. Unlike in-memory or file-based sessions, this mode stores session data directly in a database, making it accessible from multiple servers. This is particularly useful in complex scenarios, such as:
- Cluster Environments: In a cluster setup, multiple instances of an application run on different servers. Database session management ensures that all servers can access the same session data, avoiding inconsistencies or data loss.
- Load Balancing: When an application is distributed across multiple servers with load balancing, user requests may be handled by different servers. Database session management ensures that session data remains consistent, regardless of which server handles the request.
- Data Persistence: Session data stored in a database is persistent, meaning it survives server restarts. This is critical for applications requiring high reliability.
Error page handling for server side applications, included the HTMX ones
Now server side application can leverage the power of DMVCFramework also for the automatic handling of error pages. Check the sample. It will be very useful to the ever increasing number of Delphi developers using HTMX (of which DMVCFramework is the official supporting framework).
New support for Sempare Delphi Template Engine
Thanks to Conrad Vermeulen for its great template engine and for developing the DMVCFramework view engine based on Sempare.
Architecture
New Dependency Injector Container for Controllers and Services
This is the most awaited feature released in this version. The automatic injector uses the default Dependency Injection Container to.. well… inject, dependencies into controllers and/or services. Supported Services Life Cycles are:
Transient
(service is always created - always you get a new instance)Singleton
(created just one time and istance is reused)SingletonPerRequest
(reuse the same instance within the request)
Here’s a sample of a controllers which uses a couple of services.
type
[MVCPath('/api')]
TMyController = class(TMVCController)
private
fPeopleService: IPeopleService;
public
[MVCInject]
constructor Create(const PeopleService: IPeopleService); reintroduce;
[MVCPath('/people2')]
[MVCHTTPMethod([httpGET])]
function GetPeople([MVCInject] OtherPeopleService: IPeopleService): TObjectList<TPerson>;
[MVCPath('/people')]
[MVCHTTPMethod([httpPOST])]
function CreatePerson(
[MVCInject] PeopleService: IPeopleService;
[MVCFromBody] Person: TPerson): IMVCResponse;
end;
// implementation
constructor TMyController.Create(const PeopleService: IPeopleService);
begin
inherited Create;
fPeopleService := PeopleService;
end;
function TMyController.CreatePerson(
PeopleService: IPeopleService;
Person: TPerson): IMVCResponse;
begin
PeopleService.CreatePerson(Person);
Result := CreatedResponse('', 'Person created (' + Person.ToString + ')' );
end;
function TMyController.GetPeople(OtherPeopleService: IPeopleService): TObjectList<TPerson>;
begin
Result := OtherPeopleService.GetAll;
end;
Services are declared as follow:
type
IPeopleService = interface
['{347532A0-1B28-40C3-A2E9-51DF62365FE7}']
function GetAll: TObjectList<TPerson>;
end;
IConnectionService = interface
['{146C21A5-07E8-456D-8E6D-A72820BD17AA}']
function GetConnectionName: String;
end;
TPeopleService = class(TInterfacedObject, IPeopleService)
private
fConnService: IConnectionService;
public
[MVCInject] // << Constructor dependencies are recursively resolved on services too
constructor Create(ConnectionService: IConnectionService); virtual;
destructor Destroy; override;
function GetAll: TObjectList<TPerson>;
end;
TConnectionService = class(TInterfacedObject, IConnectionService)
protected
function GetConnectionName: string;
public
constructor Create; virtual;
destructor Destroy; override;
end;
In the next weeks will be published a video tutorial on PATREON about dependency injection in DMVCFramework. Don’t Miss IT if you want to create decoupled, robust and manageable controllers and service quickly.
New “Response Methods” methods returning data and standard status codes
This is going to change the way we write actions’ controller.
This is the “classic” way to create a “Get” action - it’s available since DMVCFramework 2.x and still works today.
procedure TArticlesController.GetArticles;
begin
Render<TArticle>(GetArticlesService.GetAll);
end;
However, starting from 3.4.2-magnesium we can get more with less.
function TArticlesController.GetArticles(ArticlesService: IArticlesService): IMVCResponse;
begin
Result := OKResponse(fArticlesService.GetAll);
end;
More complex tha action is, more significative is the improvement and evidence are the benefits.
The “Classic” way.
procedure TArticlesController.GetArticlesByDescription;
var
lSearch: string;
lDict: IMVCObjectDictionary;
begin
try
lSearch := Context.Request.Params['q'];
if lSearch = '' then
begin
lDict := ObjectDict().Add('data', GetArticlesService.GetAll);
end
else
begin
lDict := ObjectDict().Add('data', GetArticlesService.GetArticles(lSearch));
end;
Render(lDict);
except
on E: EServiceException do
begin
raise EMVCException.Create(E.Message, '', 0, 404);
end
else
raise;
end;
end;
The Today version is way better and supports services injection and “Response Methods”
function TArticlesController.GetArticlesByDescription(const Search: String): IMVCResponse;
begin
Result := OKResponse(fArticlesService.GetArticles(Search));
end;
There a lot of “Response Methods”. Just to name a few:
function StatusResponse(const StatusCode: Word; const Body: TObject): IMVCResponse;
function StatusResponse(const StatusCode: Word; const Message: String): IMVCResponse;
function StatusResponse(const StatusCode: Word): IMVCResponse;
function OKResponse(const Body: TObject): IMVCResponse;
function OKResponse(const Body: IMVCObjectDictionary): IMVCResponse;
function OKResponse(const Message: String): IMVCResponse;
function OKResponse: IMVCResponse;
function NotFoundResponse(const Body: TObject): IMVCResponse;
function NotFoundResponse(const Message: String): IMVCResponse;
function NotFoundResponse: IMVCResponse;
function NotModifiedResponse: IMVCResponse;
function NoContentResponse: IMVCResponse;
function UnauthorizedResponse: IMVCResponse;
function UnauthorizedResponse(const Message: String): IMVCResponse;
function ForbiddenResponse: IMVCResponse;
function ForbiddenResponse(const Message: String): IMVCResponse;
function BadRequestResponse(const Error: TObject): IMVCResponse;
function BadRequestResponse: IMVCResponse;
function BadRequestResponse(const Message: String): IMVCResponse;
function UnprocessableContentResponse(const Error: TObject): IMVCResponse;
function UnprocessableContentResponse: IMVCResponse;
function UnprocessableContentResponse(const Message: String): IMVCResponse;
function CreatedResponse(const Location: string = '';
const Body: TObject = nil): IMVCResponse;
function CreatedResponse(const Location: string;
const Message: String): IMVCResponse;
function AcceptedResponse(const Location: string = '';
const Body: TObject = nil; const Message: String = ''): IMVCResponse;
function ConflictResponse: IMVCResponse;
function RedirectResponse(Location: String; Permanent: Boolean = False;
PreserveMethod: Boolean = False): IMVCResponse;
function InternalServerErrorResponse: IMVCResponse;
function InternalServerErrorResponse(const Error: TObject): IMVCResponse;
function InternalServerErrorResponse(const Message: String): IMVCResponse;
These methods can be used to prepare standard responses directly usable from a functional action which returns a IMVCResponse
.
👉🏼 A detailed article (more than 20 pages) about functional actions and response methods is available for PATREON supporters.
Logging
New Console Logger used by the project generated by the wizard
By default logs are written to file and to the console, as shown below.
This approach greatly improve the development phase giving more visibility to what’s going on under the hood.
If you don’t want console logger, just set the global config variable UseConsoleLogger := False
as shown below in the main begin end
block.
begin
{ Enable ReportMemoryLeaksOnShutdown during debug }
// ReportMemoryLeaksOnShutdown := True;
// DMVCFramework Specific Configurations
// When MVCSerializeNulls = True empty nullables and nil are serialized as json null.
// When MVCSerializeNulls = False empty nullables and nil are not serialized at all.
MVCSerializeNulls := True;
// MVCNameCaseDefault defines the name case of property names generated by the serializers.
// Possibile values are: ncAsIs, ncUpperCase, ncLowerCase (default), ncCamelCase, ncPascalCase, ncSnakeCase
MVCNameCaseDefault := TMVCNameCase.ncLowerCase;
// UseConsoleLogger defines if logs must be emitted to also the console (if available).
UseConsoleLogger := True;
// UseLoggerVerbosityLevel defines the lowest level of logs that will be produced.
UseLoggerVerbosityLevel := TLogLevel.levNormal;
LogI('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION);
Using the UseLoggerVerbosityLevel
variable you can also set the default logger verbosity level.
New Logger TLoggerProFileByFolderAppender to log in folders by day
Thanks to Mark Lobanov
This new logger allows to create log files by day creating a different folder when the day changes. This is very useful if you don’t want to have a lot of log files to mantain a long log history. Each day will have their own logs.
Check this example.
New database logger (for both FireDAC and ADO)
dmvcframework-3.4.2-magnesium come with the new LoggerPro 2.0. One important addition to LoggerPro (Thanks to David Moorhouse) is the database logger which supports FireDAC and ADO.
Let’s say we have a PostgreSQL database and we want to create a LoggerPro appender which logs there.
Execute this DDL:
drop procedure if exists sp_loggerpro_writer;
drop table if exists loggerpro_logs;
create table loggerpro_logs (
id int generated by default as IDENTITY PRIMARY key,
log_type int,
log_tag varchar,
log_message varchar,
log_timestamp timestamp,
log_thread_id integer
);
create or replace procedure sp_loggerpro_writer(
p_log_type int,
p_log_tag varchar,
p_log_message varchar,
p_log_timestamp timestamp,
p_log_thread_id integer
)
language plpgsql
as $$
begin
INSERT INTO
public.loggerpro_logs(log_type, log_tag, log_message, log_timestamp, log_thread_id)
values (p_log_type, p_log_tag, p_log_message, p_log_timestamp, p_log_thread_id);
end;
$$;
Then you can configure a custom logger as shown in the unit below:
unit CustomLoggerConfigU;
interface
uses
LoggerPro; // loggerpro core
function GetLogger: ILogWriter;
implementation
uses
System.IOUtils
, LoggerPro.FileAppender // loggerpro file appender (logs to file)
, LoggerPro.SimpleConsoleAppender // loggerpro simple console appender (logs to console with colors)
, LoggerPro.DBAppender.FireDAC // loggerpro DB Appender (logs to database)
, Commons, System.SysUtils;
const
FailedDBWriteTag = 'FailedDBWrite';
var _FallbackLog: ILogWriter;
function GetFallBackLogger: ILogWriter;
begin
if _FallbackLog = nil then
begin
_FallbackLog := BuildLogWriter([
TLoggerProSimpleFileAppender.Create(10, 2048, 'logs',
TLoggerProFileAppender.DEFAULT_FILENAME_FORMAT)
]);
end;
Result := _FallbackLog;
end;
function GetLogger: ILogWriter;
begin
Result := BuildLogWriter([
TLoggerProFileAppender.Create(10, 1000, TPath.Combine('MyFolder', 'MyLogs')),
TLoggerProDBAppenderFireDAC.Create(
CON_DEF_NAME,
'sp_loggerpro_writer',
// error handler, just write to disk on the server for later analysis
procedure(const Sender: TObject; const LogItem: TLogItem;
const DBError: Exception; var RetryCount: Integer)
var
lIntf: ILogItemRenderer;
begin
lIntf := TLogItemRenderer.GetDefaultLogItemRenderer();
GetFallBackLogger.Error('DBAppender Is Failing (%d): %s %s',
[RetryCount, DBError.ClassName, DBError.Message], FailedDBWriteTag);
GetFallBackLogger.Error(lIntf.RenderLogItem(LogItem), FailedDBWriteTag);
end),
TLoggerProSimpleConsoleAppender.Create
],
nil,
[
TLogType.Debug,
TLogType.Warning, {writes on DB only for WARNING+}
TLogType.Debug
]);
end;
end.
Now you can use the DB logger in the usual way:
MVCFramework.Logger.SetDefaultLogger(CustomLoggerConfigU.GetLogger);
Just as another example, in case you are using FirebirdSQL the script is the following:
SET term ^;
EXECUTE BLOCK AS
BEGIN
IF (exists(select 1 from RDB$PROCEDURES
where upper(rdb$procedure_name) = 'SP_LOGGERPRO_WRITER')) THEN
BEGIN
execute statement 'drop procedure SP_LOGGERPRO_WRITER;';
END
END^
RECREATE table loggerpro_logs (
id int generated by default as identity,
log_type integer,
log_tag varchar(20),
log_message varchar(1024),
log_timestamp timestamp,
log_thread_id integer
)^
CREATE OR ALTER procedure sp_loggerpro_writer(
p_log_type int,
p_log_tag varchar(20),
p_log_message varchar(1024),
p_log_timestamp timestamp,
p_log_thread_id integer
) AS
BEGIN
INSERT INTO loggerpro_logs(log_type, log_tag, log_message, log_timestamp, log_thread_id)
VALUES (:p_log_type, :p_log_tag, :p_log_message, :p_log_timestamp, :p_log_thread_id);
END^
SET term ;^
That’s it. All your warnings
are automatically logged to database in async way.
🛠️ Note: All the other database engine supported by FireDAC and ADO are automatically supported. Just change the script mantaining the same stored procedure parameter names.
Data Access
New “TransactionContexts” to automatically handle database transactions
TransactionContext
are inspired by Python context managers and greatly improve the code readability leveraging Custom Managed record and their automatic scope handling (just like the Profiler
class does since its inception).
Check by yourself; the following code snippets are equivalent.
{ Without TransactionContext}
procedure TArticlesService.CreateArticles(const ArticleList: TObjectList<TArticle>);
begin
TMVCActiveRecord.CurrentConnection.StartTransaction;
try
for var lArticle in ArticleList do
begin
Add(lArticle);
end;
TMVCActiveRecord.CurrentConnection.Commit;
except
TMVCActiveRecord.CurrentConnection.RollBack;
raise;
end;
end;
{ With TransactionContext}
procedure TArticlesService.CreateArticles(const ArticleList: TObjectList<TArticle>);
begin
var lCtx := TMVCActiveRecord.UseTransactionContext;
for var lArticle in ArticleList do
begin
Add(lArticle);
end;
end;
TransactionContext supports multiple transaction in the same method using different block scope.
{ With TransactionContext and multiple blocks}
procedure TArticlesService.CreateArticles(const ArticleList: TObjectList<TArticle>; const ProductList: TObjectList<TProduct>);
begin
begin var lCtx1 := TMVCActiveRecord.UseTransactionContext;
for var lArticle in ArticleList do
begin
Add(lArticle);
end;
end;
begin var lCtx2 := TMVCActiveRecord.UseTransactionContext;
for var lProduct in ProductList do
begin
Add(lProduct);
end;
end
end;
TransactionContexts boundaries are protected because cannot be copied nor passed as parameter to other methods.
begin var Ctx := TMVCActiveRecord.UseTransactionContext;
var S := Ctx; // will raise EMVCActiveRecordTransactionContext, Cannot be copied!
end;
//////
begin var Ctx := TMVCActiveRecord.UseTransactionContext;
DoSomething(Ctx); // will raise EMVCActiveRecordTransactionContext, Cannot be passed!
end;
Huge improvement to MVCTableField
’s FieldOptions
Fields handling for TMVCActiveRecord
descendants is simpler and more flexible than ever with the new field options.
foDoNotInsert
: field is not included inINSERT
statementsfoDoNotUpdate
: field is not included inUPDATE
statementsfoDoNotSelect
: field is not included inSELECT
statements (can be used where previuoslyfoWriteOnly
was used)foReadOnly
: alias forfoDoNotInsert
+foDoNotUpdate
Such options can be mixed, as shown in the next snippet.
[MVCTable('customers')]
TCustomerWithOptions = class(TCustomEntity)
private
[MVCTableField('id', [foPrimaryKey, foAutoGenerated])]
fID: Integer;
[MVCTableField('code', [foDoNotInsert, foDoNotUpdate])]
fCode: NullableString;
[MVCTableField('description', [foDoNotInsert])]
fCompanyName: string;
[MVCTableField('city', [foDoNotUpdate])]
fCity: string;
public
property ID: Integer read fID write fID;
property Code: NullableString read fCode write fCode;
property CompanyName: string read fCompanyName write fCompanyName;
property City: string read fCity write fCity;
end;
Improved Automatic support for Offline Optimistic Locking design pattern
The Offline Optimistic Locking design pattern is very important for any disconnected system which access to shared resources. Considering that a web application and a Web API falls in this category, it is really important to understand the pattern. It is even better to have an implementation ready to use. Did you ever faced the issue that 2 or more users can update a database record and the last wins? If you can rely on database transaction this is quite simple, but in a disconnected environment (like web is) it can be challeging. In the next week there will be a video about this feature for PATREON supporters.
While waiting for the video, check the sample activerecord_showcase.
Improved TMVCActiveRecordController
Now TMVCActiveRecordController
uses the TMVCActiveRecordMiddleware
configured connection without having to specify a different connection manually.
procedure TMyWebModule.WebModuleCreate(Sender: TObject);
begin
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
// other configurations
end);
FMVC.AddController(TMVCActiveRecordController, '/api/entities');
{Since 3.4.2-magnesium, TMVCActiveRecordMiddleware is required by TMVCActiveRecordController!}
FMVC.AddMiddleware(TMVCActiveRecordMiddleware.Create(CON_DEF_NAME));
end;
Other Improvements
Improved and restructured IDE Wizard.
The Wizard code generation has been completely rewritten. This refactor makes possibile to expand the generated code easily.
The new IDE wizard now can:
-
Generate a sample configuration for default dependency injector container
//------------------------------------------------- //in the main program file RegisterServices(DefaultMVCServiceContainer); DefaultMVCServiceContainer.Build; //------------------------------------------------- //in the services unit type IPeopleService = interface ['{FE6BD980-74BE-44EC-9BAF-026F71966E82}'] function GetAll: TObjectList<TPerson>; end; TPeopleService = class(TInterfacedObject, IPeopleService) protected function GetAll: TObjectList<TPerson>; end; procedure RegisterServices(Container: IMVCServiceContainer); implementation uses System.SysUtils; procedure RegisterServices(Container: IMVCServiceContainer); begin Container.RegisterType( TPeopleService, IPeopleService, TRegistrationType.SingletonPerRequest); // Register other services here end; function TPeopleService.GetAll: TObjectList<TPerson>; begin Result := TObjectList<TPerson>.Create; Result.AddRange([ TPerson.Create(1, 'Henry', 'Ford', EncodeDate(1863, 7, 30)), TPerson.Create(2, 'Guglielmo', 'Marconi', EncodeDate(1874, 4, 25)), TPerson.Create(3, 'Antonio', 'Meucci', EncodeDate(1808, 4, 13)), TPerson.Create(4, 'Michael', 'Faraday', EncodeDate(1867, 9, 22)) ]); end;
-
Generate an entity in a separate unit
-
Generate profiling code for actions using
Profiler.Start
to fine tune your actions in terms of speed.
function TMyController.GetPeople(PeopleService: IPeopleService): IMVCResponse;
begin
{$IF CompilerVersion >= 34} //SYDNEY+
var lProf := Profiler.Start(Context.ActionQualifiedName);
{$ENDIF}
Result := OkResponse(PeopleService.GetAll);
end;
- Automatically configure support for server side views with TemplatePro, WebStencils or Mustache.
// Controllers
FMVC.AddController(TMyController);
// Controllers - END
// Server Side View
FMVC.SetViewEngine(TMVCTemplateProViewEngine);
// Server Side View - END
- Automaticaly configure sqids support using a piece of code similar to the following in the project dpr.
TMVCSqids.SQIDS_ALPHABET := dotEnv.Env('dmvc.sqids.alphabet',
'0Kcl8tS9IjUnJN1feuDi2vdWqEPxpaXRbrkszwTghmMOHB7G5FYLCA43yQ6VoZ');
TMVCSqids.SQIDS_MIN_LENGTH := dotEnv.Env('dmvc.sqids.min_length', 6);
More info about sqids later
- Wizard can also defines the default value of
MVCNameCaseDefault
variable used to specify the name casing of the serialized property names (eg. foobar, FooBar, foo_bar, fooBar etc)
New Http Only Cookie support for JWT
Thanks to Marcelo Varela
TMVCJWTAuthenticationMiddleware
got an options to set the cookie as HTTP ONLY
so that javascript scripts cannot read the cookie value.
Improved OpenAPI support (a.k.a. Swagger)
Thanks to Marcelo Varela
Border cases have been improved. Moreover, APIs generated by TMVCActiveRecordController
now are correctly generated in the swagger descriptor file.
Changed the default serialization case for objects and datasets
So far, if not specified using a MVCNameCase
attribute, property names were serialized with the same case as declared.
//declaration
type
TPerson = class
FullName: String
end;
//serialization - the property name is serialized with the same case
// of the original declaration
{
"FullName":"Daniele"
}
To change this default you had to define the MVCNameCase attribute on each class declaration selecting the required name case serialization.
Since the version 3.4.2-magnesium you can just change the default serialization name casing setting the variable MVCNameCaseDefault
in the main begin/end block as shown below.
begin
// MVCNameCaseDefault defines the name case of property names generated by the serializers.
// Possibile values are:
// ncAsIs, ncUpperCase, ncLowerCase (default), ncCamelCase, ncPascalCase, ncSnakeCase
MVCNameCaseDefault := TMVCNameCase.ncLowerCase;
//...
The casing of the generated JSON objects will be generated accordingly.
If you want to define a specific serialization, just define the MVCNameCase
attribute as you do up to now.
//declaration
[MVCNameCase(ncCamelCase)]
type
TPerson = class
FullName: String
end;
//serialization - the property name is serialized with the case
// defined on the class declaration.
{
"fullName":"Daniele"
}
Sqids support
As the Sqids web site says: “Sqids (pronounced “squids”) is an open-source library that lets you generate short unique identifiers from numbers. These IDs are URL-safe, can encode several numbers, and do not contain common profanity words.”
DMVCFramework supports Sqids for all the supported Delphi version.
- To play a little bit with Sqids, check the showcase example.
- To have an idea about using Sqids in DMVCFramework, check the Wizard generated code when the “Use Sqids” is checked.
Remember to join PATREON community to get valuable informations, tutorials, insight, get priority support and more.
Enjoy!
– Daniele Teti
Comments
comments powered by Disqus