Become a member!

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!

PATREON Community

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:

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 in INSERT statements
  • foDoNotUpdate: field is not included in UPDATE statements
  • foDoNotSelect: field is not included in SELECT statements (can be used where previuosly foWriteOnly was used)
  • foReadOnly: alias for foDoNotInsert + 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;  
  // 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)

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.

PATREON Community

Remember to join PATREON community to get valuable informations, tutorials, insight, get priority support and more.

Enjoy!

– Daniele Teti

Comments

comments powered by Disqus