Other Tasks
This chapter explains basic tasks you can do with XData in code. It basically explains how to setup and start the server and how to do some basic configuration using the available classes and interfaces. The following topics describe the most common XData programming tasks and main classes and interfaces.
Creating an XData Server
TMS XData Server is based on the TMS Sparkle framework. The actual XData Server is a Sparkle server module that you add to a Sparkle HTTP Server.
Please refer to the following topics to learn more about TMS Sparkle servers:
To create the XData Server, just create and add a
XData Server Module (TXDataServerModule class,
declared in unit XData.Server.Module
) to the Sparkle HTTP Server. The
following code illustrates how to create and run the server. Note that
the code that creates the XData server module is not displayed here. You
should refer to the "XData Server Module"
topic to learn about how to create the module.
uses
{...},
Sparkle.HttpSys.Server, XData.Server.Module;
function CreateXDataServerModule(const BaseUrl: string): TXDataServerModule;
begin
// Create and return the TXDataServerModule here,
// using the BaseUrl as the server address
end;
var
Module: TXDataServerModule;
Server: THttpSysServer;
begin
Server := THttpSysServer.Create;
Module := CreateXDataServerModule('http://server:2001/tms/xdata');
Server.AddModule(Module);
Server.Start;
ReadLn;
Server.Stop;
Server.Free;
end;
The code above will create and start an XData server that will receive and respond to HTTP requests at the address "http://server:2001/tms/xdata".
TXDataServerModule
To create an XData server, you need to add a TXDataServerModule object to the Sparkle HTTP Server. As with any Sparkle module, it will have a base (root) URL associated with it, and it will respond to requests sent to any URL which matches the root URL of the module.
The TXDataServerModule is the main XData class, because it is the one
which receives and handles all HTTP requests. So in a way, that is the
class that implements the XData server. Although it is a very important
class, its usage is very simple. You need to create the class using one
of the overloaded constructors (TXDataServerModule is declared in unit
XData.Server.Module
):
constructor Create(const ABaseUrl: string); overload;
constructor Create(const ABaseUrl: string; AConnectionPool: IDBConnectionPool); overload;
constructor Create(const ABaseUrl: string; AConnectionPool: IDBConnectionPool;
AModel: TXDataAureliusModel); overload;
constructor Create(const ABaseUrl: string; AConnection: IDBConnection); overload;
constructor Create(const ABaseUrl: string; AConnection: IDBConnection;
AModel: TXDataAureliusModel); overload;
In summary, you must provide the base URL and optionally an IDBConnectionPool interface so that the server can retrieve IDBConnection interfaces to operate with the database (if database connectivity is desired). For example:
XDataServerModule := TXDataServerModule.Create('http://server:2001/tms/xdata',
TDBConnectionPool.Create(50,
TDBConnectionFactory.Create(
function: IDBConnection
var
MyDataModule: TMyDataModule;
begin
MyDataModule := TMyDataModule.Create(nil);
Result := TFireDacConnectionAdapter.Create(MyDataModule.FDConnection1, MyDataModule);
end
)));
The example above creates the server with the root URL "http://server:2001/tms/xdata" providing a connection pool of a maximum of 50 simultaneous connections. The database connection used will be a FireDac TFDConnection component named FDConnection1, declared in a data module named TMyDataModule. That is all you need to set up for your XData server to run and to expose your Aurelius objects.
The first overloaded constructor, used in the previous example, takes just two parameters: the base url and the connection pool. The other overloaded versions are just variations that provide different settings.
If you have multiple Aurelius mapping models in your application, you can optionally use an entity model different from default, or explicitly build an entity model and provide it in the constructor for specific mapping. This allows for better control which classes and mapping settings are available from the XData server. If the model is not provided, XData uses the default entity model which in turn uses the default Aurelius model. Note that the model passed to the constructor will not be owned by the server module and must be destroyed manually. For example, the following code creates a module using the "Sample" model:
XDataServerModule := TXDataServerModule.Create('http://server:2001/tms/xdata',
MyConnectionPool, TXDataAureliusModel.Get('Sample'));
There are also versions of the Create constructor that receive an IDBConnection interface instead of an IDBConnectionPool. Those are easy-to-use variations that internally create a connection pool with a single connection (no simultaneous connections available). It is an easy approach for testing and debug purposes, but should not be used in production environments because performance might not be ideal.
Properties
Name | Description |
---|---|
UserName: string Password: string |
TXDataServerModule provides these properties to specify UserName and Password required by the server using Basic Authentication. By default, these values are empty which means no authentication is performed and any client can access server resources and operations. When basic authentication is used, be sure to use HTTP secure (HTTPS) if you do not want your user name or password to be retrieved by middle-man attacks. If you do not use it, both user name and password are transmitted in plain text in HTTP requests. These properties provide a very limited basic authentication mechanism. For a more advanced variant, you should use the TMS Sparkle built-in Basic Authentication mechanism. |
AccessControlAllowOrigin: string | Specifies the accepted client hosts for which CORS will be enabled. If you want to accept any client connection, set this property value to '*'. This will enable CORS in the server including proper responses to preflighted requests. |
DefaultExpandLevel: integer | Defines the minimum level that associated entities will be expanded (included inline) in JSON responses. Default value is 0 meaning that all associated entities will be represented as references unless specified otherwise. Clients can override this value by using xdata-expand-level header. |
Events: TXDataModuleEvents | Container for server-side events. |
PutMode: TXDataPutMode | Defines how PUT will be implemented at server side with Aurelius: Either TXDataPutMode.Update or TXDataPutMode.Merge method (default). You will rarely need to change this property unless to ensure backward compatibility with older versions. This property value can be overridden in a specific request by using xdata-put-mode HTTP Request header. |
SerializeInstanceRef: TInstanceRefSerialization | Controls how instance reference ($ref) will appear in JSON response. See below for options. This property value can be overridden in a specific request by using xdata-serialize-instance-ref HTTP Request header. |
SerializeInstanceType: TInstanceTypeSerialization | Controls whenever the entity/object type appears in the JSON response (property annotation "xdata.type"). See below for options. This property value can be overridden in a specific request by using xdata-serialize-instance-type HTTP Request header. |
UnknownMemberHandling: TUnknownMemberHandling | Defines server behavior when receiving JSON from the client with a property that is not known for that request. For example, JSON representing an entity with a property that does not belong to that entity. See below for options. |
RoutingPrecedence: TRoutingPrecedence | Specifies which route to use when there is a conflict between URL endpoints defined by automatic CRUD endpoints and service operations . See below for options. |
EnableEntityKeyAsSegment: Boolean | When True, it's possible to address single entities by using the URL format "/entityset/id" - in addition to the default "/entityset(id)". Default is False. |
SwaggerOptions: TSwaggerOptions SwaggerUIOptions: TSwaggerUIOptions |
Provide access to configure Swagger and SwaggerUI behavior. See more information at OpenAPI/Swagger Support. |
TInstanceRefSerialization
TInstanceRefSerialization.Always: $ref is always used if the same instance appears again in the JSON tree. This mode is more optimized for use with TXDataClient, and is the default option.
TInstanceRefSerialization.IfRecursive: $ref is only used if the instance appears as an associated object of itself. If the instance appears in a non-recursive way, $ref is not used and the instance is fully serialized inline instead. This mode is more suited for JavaScript and other non-Delphi clients so those clients do not need to resolve the $ref objects.
TInstanceTypeSerialization
TInstanceTypeSerialization.Always: The xdata.type annotation always appears in JSON responses.
TInstanceTypeSerialization.IfNeeded: The xdata.type annotation is only present if the entity/object type is a descendant of the expected type. For example, suppose a GET request is performed in url Customers. For any entity in JSON that is of type Customer, the annotation will not present. If the entity in JSON is a descendant of Customer (e.g., DerivedCustomer) then xdata.type apppears. Basically, it means that xdata.type is implicit when absent and is of the expected type of the request/specification.
TUnknownMemberHandling
TUnknownMemberHandling.Error: An InvalidJsonProperty error is raised if JSON contains an invalid property. This is the default behavior.
TUnknownMemberHandling.Ignore: Invalid properties in JSON will be ignored and the request will be processed.
TRoutingPrecedence
TRoutingPrecedence.Crud: The URL from automatic CRUD endpoint will be used if there is a URL conflict with service operation endpoints. This is the default behavior.
TRoutingPrecedence.Service: The URL from service operation will be used if there is a URL conflict with automatic CRUD endpoints.
Methods
Name | Description |
---|---|
procedure SetEntitySetPermissions(const EntitySetName: string; Permissions: TEntitySetPermissions) | Specify the permissions for a specified entity set. |
IDBConnectionPool Interface
The IDBConnectionPool interface is used by the XData server module to retrieve an IDBConnection interface used to connect to a database. As client requests arrive, the XData server needs an IDBConnection interface to connect to the database server and execute the SQL statements. It achieves this by trying to acquire an IDBConnection interface from the IDBConnectionPool interface. Thus, providing an IDBConnectionPool interface to the XData server module is mandatory for this to work.
The IDBConnectionPool interface is declared in the unit
Aurelius.Drivers.Interfaces
and contains a single GetConnection method:
IDBConnectionPool = interface
function GetConnection: IDBConnection;
end;
The easiest way to create an IDBConnectionPool interface is by using the TDBConnectionPool class which implements the IDBConnectionPool interface. To instantiate it, you need to call the Create constructor passing an IDBConnectionFactory interface which will be used by the connection pool to create a new connection if needed. Alternatively, you can pass an anonymous method that creates and returns a new IDBConnection interface each time it is called.
Also, you need to specify the maximum number of IDBConnection interfaces
(database connections) available in the pool. Whenever a client request
arrives, the XData server acquires an IDBConnection from the pool, does
all the database processing with it, and then returns it to the pool. If
many simultaneous requests arrive, the pool might run out of available
connections when the number of acquired IDBConnection interfaces reaches
the maximum number specified. If that happens, the client request will
wait until a new connection is available in the pool. TDBConnectionPool
class is declared in the unit XData.Aurelius.ConnectionPool
.
The following code illustrates how to create an IDBConnectionPool interface. It uses a function named CreateMyConnectionFactory which is not shown in this example. To learn how to create such interface, please refer to IDBConnectionFactory topic.
uses
{...}, Aurelius.Drivers.Interfaces, XData.Aurelius.ConnectionPool;
var
ConnectionPool: IDBConnectionPool;
ConnectionFactory: IDBConnectionFactory;
begin
ConnectionFactory := CreateMyConnectionFactory; // Creates IDBConnectionFactory
ConnectionPool := TDBConnectionPool.Create(
50, // maximum of 50 connections available in the pool
// Define a number that best fits your needs
ConnectionFactory
);
// Use the IDBConnectionPool interface to create the XData server module
end;
Alternatively, you can just provide an anonymous method that creates the IDBConnection instead of providing the IDBConnectionFactory interface:
uses
{...}, Aurelius.Drivers.Interfaces, XData.Aurelius.ConnectionPool;
var
ConnectionPool: IDBConnectionPool;
begin
ConnectionPool := TDBConnectionPool.Create(
50, // maximum of 50 connections available in the pool
function: IDBConnection
var
SQLConn: TSQLConnection;
begin
// Create the IDBConnection interface here
// Be sure to also create a new instance of the database-access component here
// Two different IDBConnection interfaces should not share
// the same database-access component
// Example using dbExpress
SQLConn := TSQLConnection.Create(nil);
{ Define SQLConn connection settings here, the server
to be connected, user name, password, database, etc. }
Result := TDBExpressConnectionAdapter.Create(SQLConn, true);
end
));
// Use the IDBConnectionPool interface to create the XData server module
end;
If you do not need a pooling mechanism but just want one database connection to be created for each time someone asks for a connection from the pool, you can use the TDBConnectionFactory class. It also implements the IDBConnectionPool interface:
var
ConnectionPool: IDBConnectionPool;
begin
ConnectionPool := TDBConnectionFactory.Create(
function: IDBConnection
var
MyDataModule: TMyDataModule;
begin
// Creates a datamodule which contains a
// TSQLConnection component that is already configured
MyDataModule := TMyDataModule.Create(nil);
// The second parameter makes sure the data module will be destroyed
// when IDBConnection interface is released
Result := TDBExpressConnectionAdapter.Create(
MyDataModule.SQLConnection1, MyDataModule);
end
));
end;
IDBConnectionFactory Interface
The IDBConnectionFactory interface is used to create an IDBConnectionPool interface used by the XData module. As client requests arrive, the XData server needs to retrieve an IDBConnection interface from the IDBConnectionPool so it can perform operations on the database. The connection pool creates a new IDBConnection by calling IDBConnectionFactory.CreateConnection method.
The IDBConnectionFactory interface is declared in unit
Aurelius.Drivers.Interfaces
, and it contains a single CreateConnection
method:
IDBConnectionFactory = interface
function CreateConnection: IDBConnection;
end;
The easiest way to create such an interface is using the
TDBConnectionFactory class which implements the IDBConnectionFactory
interface. To create a TDBConnectionFactory object, you just need to
pass an anonymous method that creates and returns a new IDBConnection
interface each time it is called. The TDBConnectionFactory class is
declared in the unit Aurelius.Drivers.Base
.
The IDBConnection interface is part of the TMS Aurelius library used by XData. You can refer to the TMS Aurelius documentation to learn how to create the IDBConnection interface.
In the following example, the factory will create an IDBConnection pointing to a Microsoft SQL Server database using a dbExpress connection. You can connect to many other database servers (Oracle, Firebird, MySQL, etc.) using many different database-access components (FireDac, dbExpress, UniDac, ADO, etc.). Please refer to Database Connectivity topic on TMS Aurelius documentation to learn about all those options. Regardless of what you use, the structure of the following code will be the same. What will change only is the content of the function: IDBConnection.
uses
{...}, Aurelius.Drivers.Interfaces, Aurelius.Drivers.Base,
Aurelius.Drivers.dbExpress;
var
ConnectionFactory: IDBConnectionFactory;
begin
ConnectionFactory := TDBConnectionFactory.Create(
function: IDBConnection
var
conn: TSQLConnection;
begin
// Create the IDBConnection interface here
// Be sure to also create a new instance of the database-access component here
// Two different IDBConnection interfaces should not share
// the same database-access component
// Example using dbExpress
conn := TSQLConnection.Create(nil);
conn.DriverName := 'MSSQL';
conn.GetDriverFunc := 'getSQLDriverMSSQL';
conn.VendorLib := 'sqlncli10.dll';
conn.LibraryName := 'dbxmss.dll';
conn.Params.Values['HostName'] := 'server';
conn.Params.Values['Database'] := 'master';
conn.Params.Values['User_Name'] := 'sa';
conn.Params.Values['Password'] := 'password';
conn.LoginPrompt := false;
Result := TDBExpressConnectionAdapter.Create(SQLConn, true);
end
));
// Use the ConnectionFactory interface to create an IDBConnectionPool interface
end;
It is possible that you already have your database-access component configured in a TDataModule and you do not want to create it in code. In this case, you can just create a new instance of the data module and return the associated IDBConnection to the component. But you must be sure to destroy the data module as well (not only the database-access component) to avoid memory leaks:
var
ConnectionFactory: IDBConnectionFactory;
begin
ConnectionFactory := TDBConnectionFactory.Create(
function: IDBConnection
var
MyDataModule: TMyDataModule;
begin
// Creates a datamodule which contains a
// TSQLConnection component that is already configured
MyDataModule := TMyDataModule.Create(nil);
// The second parameter makes sure the data module will be destroyed
// when IDBConnection interface is released
Result := TDBExpressConnectionAdapter.Create(
MyDataModule.SQLConnection1, MyDataModule);
end
));
// Use the ConnectionFactory interface to create an IDBConnectionPool interface
end;
OpenAPI/Swagger Support
Note
Documentation about OpenAPI, SwaggerUI and Redoc has been moved to its own chapter: OpenAPI Support.