Xdata User Guide
Xdata User Guide
TMS XData is a Delphi framework that allows you to create HTTP/HTTPS servers that expose
data through REST/JSON.
By using the concept of Service Operations, you create server-side methods (business logic) that
are mapped to endpoints in your API. Whenever an endpoint is requested, your method is
executed. XData has a very high-level and smooth learning curve allowing you to build the
service operations without having to worry about HTTP communication, HTTP methods, JSON
handling, among other low-level mechanisms. You just declare your methods using regular
Delphi types, tag them with attributes to properly bind the endpoints to it, define authorization
and authentication, multitenancy information, and everything is done automatically, including
full Swagger documentation output.
It is also optionally integrated with TMS Aurelius ORM in a way that creating automatic CRUD
endpoints based on applications with existing Aurelius mappings are just a matter of a few lines
of code. For the automatic CRUD endpoints XData defines URL conventions for adressing
resources, and it specifies the JSON format of message payloads. It is inspired on the OData
standard. Such conventions, with the benefit of existing Aurelius mapping, allow building a full
REST/JSON server with minimum writing of code. TMS XData uses TMS Sparkle as its core
communication library.
TMS XData is a full-featured Delphi framework that allows you to create REST/JSON servers,
using server-side actions named service operations, and optionally exposing TMS Aurelius
entities through REST endpoints. Consider that you have an Aurelius class mapped as follows:
[Entity, Automapping]
TCustomer = class
strict private
FId: integer;
FName: string;
FTitle: string;
FBirthday: TDateTime;
FCountry: TCountry;
public
property Id: Integer read FId write FId;
property Name: string read FName write FName;
property Title: string read FTitle write FTitle;
property Birthday: TDateTime read FDateTime write FDateTime;
property Country: TCountry read FCountry write FCountry;
end;
And the JSON representation of the customer will be returned in the body of the HTTP response:
{
"$id": 1,
"@xdata.type": "XData.Default.Customer",
"Id": 3,
"Name": "Maria Anders",
"Title": "Sales Representative",
"Birthday": "1980-05-20",
"Country": null
}
You can perform changes to objects through the REST interface, using a POST method to create
new objects, DELETE to remove objects, and PUT or PATCH to update the objects. The following
example will change the value of the FTitle property of the customer resource specified in the
previous example:
{
"Title": "Marketing Manager"
}
You can also perform queries on existing objects. The following example will retrieve all
customers with a country name equal to "USA", ordered by the customer's name.
The server will return a JSON array of objects containing all the filtered objects. You can use
query paging to restrict the number of objects returned in each request.
Also, you can use service operations to implement custom business logic that uses Aurelius
objects. By defining an interface and an implementation...
{...}
function TMyService.GetTopCustomersByState(const State: string): TList<TCustome>;
begin
Result := TXDataOperationContext.Current.GetManager.Find<TTCustomer>
.Where(TLinq.Eq('State', State) and TLinq.Eq('Category', TStatus.VIP))
.List;
end;
Client := TXDataClient.Create;
Client.Uri := 'http://server:2001/tms/xdata';
MyService := Client.Service<IMyService>;
TopNYCustomers := MyService.GetTopCustomersByState('NY');
// process customers
{
"State": "NY"
}
Please refer to the Introduction topic of this manual which lists all major topics to learn more
about XData and its features.
Features
Here is a list of main features of the TMS XData framework:
• Easily accessible from different client platforms. For example: .NET, Java, JavaScript (since it
is based on REST/JSON).
• Uses standard POST, GET, PUT and DELETE HTTP methods for data request and data
modification operations.
• Several databases supported at the back end: SQL Server, MySQL, PostgreSQL, Oracle,
Firebird, etc. (using TMS Aurelius).
◦ Built-in authentication mechanism with JWT (JSON Web Token) or Basic methods;
◦ Multiple applications/processes can share (respond to) the same port (at different
addresses/endpoints);
In this section:
Getting Started
Getting your first XData server and client applications running.
Service Operations
How to implement and use service operations to add business logic to your server and invoke it
from clients.
TXDataClient
Using TXDataClient object to send and receive objects to/from a XData server in a
straightforward way.
JSON Format
XData representation of different structures in JSON format.
Design-Time Components
Overview about XData components for design-time usage.
Server-Side Events
Events that can be used to implement additional server-side logic, customize XData behavior,
among other tasks.
OpenAPI Importer
Generating client for 3rd party APIs using the OpenAPI importer.
Other Tasks
How-tos and examples about basic tasks you can do with XData in code.
b. From the dialog that appears, navigate to Delphi Projects > TMS XData;
Done: a new project will be created, run it, and your server will be running at the address "http://
localhost:2001/tms".
Using the "XData Server Wizards" is just the more straightforward way to create a new server.
If you don't like wizards, you can simply create a new blank application and drop a couple of
design-time components to create your server.
If you don't like design-time components and you want to do it 100% from code, just create the
server manually.
b. From the dialog that appears, navigate to Delphi Projects > TMS XData;
c. Double click "TMS XData Service" to create a new service. Use the default settings for now.
Done: Two new units will be created, including two server-side sample methods: Sum and
EchoString. Your server is doing something!
Using "XData Service Wizard" is just the more straightforward way to add server-side logic.
If you don't like wizards, you can simply create your service operations manually from code,
creating a ServiceContract interface and ServiceImplementation class.
var
Client: TXDataClient;
MyService: IMyService;
SumResult: Double;
begin
Client := TXDataClient.Create;
Client.Uri := 'http://localhost:2001/tms/xdata';
SumResult := Client.Service<IMyService>.Sum(10, 5);
Client.Free;
end;
And that's it! You invoke XData methods as if they were regular procedures in your client
application. The MyInterface unit and IMyService interface were created in the previous step
above. You just reuse the same code so that in client you don't have to do anything else but call
the interface method.
Of course, there are much more advanced features you can use, so you can learn more about
TXDataClient.
{
"value": 15
}
Of course you can simply go to your web browser and navigate to address "http://localhost:
2001/tms/xdata/myservice/sum?a=10&b=5" to test it.
XData server is a standard REST/JSON server. Meaning you can access it from any client
application that can simply handle JSON and perform HTTP applications. That means virtually all
kinds of applications: desktop, web, mobile, IoT, etc., regardless if those applications were built in
C# .NET, Java, PHP, JavaScript, TypeScript, C/C++, Swift, etc.
You can receive and return parameters of many different types: primitive types like integers,
doubles, strings, guids, dates; complex types like object and arrays; and several specific
supported types like strings, streams, etc..
When using non-Delphi clients, it's interesting to learn how routing and parameter binding
works, so you know exactly how to invoke a service operation from a non-Delphi application. It's
also important to understand how JSON serialization works (how each Delphi type is represented
as JSON) to know how to build the JSON to be sent, and how to interpret the received JSON.
NOTE
Any XData HTTP server is based on the TMS Sparkle framework. According to the Sparkle
documentation, to use the Windows (http.sys) based server, you must first reserve the URL
your service will process its requests from. If you have installed TMS Sparkle on your computer,
the URL "http://+:2001/tms" is already reserved, so you can create your XData server under
that address (all examples in this documentation use the base address "http://server:2001/tms/
xdata". If you change your base URL to a different port, or to a URL that doesn't start with
"tms", you must reserve that URL otherwise your server might fail to start.
1. Choose File > New > Other and then look for the TMS XData category under "Delphi
Projects".
2. There you find several wizards to create a new XData Server Application:
◦ TMS XData VCL Server: Creates a VCL application that runs an XData server using
http.sys
3. Choose the wizard you want, double-click and the application will be created.
As soon as you execute your application, the server is run. The application generated uses the
design-time components. Simply learn about the components to configure them. For example,
you can drop a TFDConnection component and set it to the
TAureliusConnection.AdaptedConnection property to associate a database connection with the
XData server.
3. Associate the TXDataDBServer component with the dispatcher through the Dispatcher
property.
That's all you need. If you want to have a ready-to-use database connection pool, you can use
the following extra steps:
uses
{...}, Aurelius.Drivers.Interfaces, Aurelius.Drivers.Base,
Aurelius.Drivers.dbExpress;
var
ConnectionFactory: IDBConnectionFactory;
begin
ConnectionFactory := TDBConnectionFactory.Create(
function: IDBConnection
var
MyDataModule: TMyConnectionDataModule;
begin
MyDataModule := TMyConnectionDataModule.Create(nil);
Result := TDBExpressConnectionAdapter.Create(MyDataModule.SQLConnection1, M
yDataModule);
end
));
var
ConnectionPool: IDBConnectionPool;
ConnectionFactory: IDBConnectionFactory;
begin
{...}
ConnectionPool := TDBConnectionPool.Create(
50, // maximum of 50 connections available in the pool
// Define a number that best fits your needs
ConnectionFactory
);
uses
{...}, XData.Server.Module;
var
XDataModule: TXDataServerModule;
ConnectionPool: IDBConnectionPool;
begin
{...}
XDataModule := TXDataServerModule.Create('http://+:2001/tms/xdata', ConnectionP
ool);
uses
{...}, Sparkle.HttpSys.Server, XData.Server.Module;
var
XDataModule: TXDataServerModule;
Server: THttpSysServer;
begin
Server := THttpSysServer.Create;
Server.AddModule(XDataModule);
Server.Start;
end;
Later on, do not forget to destroy the Server object instance when the application finishes.
An extra note about this example: Since it is an in-memory database, the database will be empty
every time the server starts. Thus, before the server starts, the method
"UpdateDatabase(Connection)", which is declared in unit DatabaseUtils (also not listed here),
will be called to create the required tables and data. Such a thing is a regular TMS Aurelius
procedure and is out of the scope of this example as well. The purpose of this example is to
show how to start an XData server.
program SQLiteConsoleServer;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Aurelius.Drivers.Interfaces,
Aurelius.Drivers.SQLite,
Aurelius.Sql.SQLite,
Aurelius.Schema.SQLite,
Sparkle.HttpSys.Server,
XData.Aurelius.ConnectionPool,
XData.Server.Module,
AureliusEntities,
DatabaseUtils;
procedure StartServer;
var
Server: THttpSysServer;
Connection: IDBConnection;
begin
Server := THttpSysServer.Create;
try
Connection := TSQLiteNativeConnectionAdapter.Create(':memory:');
UpdateDatabase(Connection);
Server.AddModule(TXDataServerModule.Create('http://localhost:2001/tms/music',
TDBConnectionPool.Create(1,
function: IDBConnection
begin
Result := Connection;
end)));
Server.Start;
begin
StartServer;
end.
program DBExpressConsoleServer;
{$APPTYPE CONSOLE}
uses
System.SysUtils, SqlExpr, DBXMySQL,
Aurelius.Drivers.Base,
Aurelius.Drivers.Interfaces,
Aurelius.Drivers.dbExpress,
Aurelius.Sql.MySQL,
Aurelius.Schema.MySQL,
Sparkle.HttpSys.Server,
XData.Aurelius.ConnectionPool,
XData.Server.Module,
AureliusEntities in '..\common\AureliusEntities.pas';
procedure StartServer;
var
Server: THttpSysServer;
ConnFactory: IDBConnectionFactory;
begin
Server := THttpSysServer.Create;
try
// Example using dbExpress. Create a connection factory to use later
ConnFactory := TDBConnectionFactory.Create(
function: IDBConnection
var
SqlConn: TSQLConnection;
begin
StartServer;
end.
Thus, consider you have a data module with an existing configured database connection using
FireDac:
program FireDacConsoleServer;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Aurelius.Drivers.Base,
Aurelius.Drivers.Interfaces,
Aurelius.Drivers.FireDac,
Aurelius.Sql.MSSQL,
Aurelius.Schema.MSSQL,
Sparkle.HttpSys.Server,
XData.Aurelius.ConnectionPool,
XData.Server.Module,
AureliusEntities in '..\common\AureliusEntities.pas',
DBDataModuleUnit in 'DBDataModuleUnit.pas' {DBDataModule: TDataModule};
procedure StartServer;
var
Server: THttpSysServer;
begin
Server := THttpSysServer.Create;
try
Server.AddModule(TXDataServerModule.Create('http://localhost:2001/tms/music',
TDBConnectionPool.Create(15, TDBConnectionFactory.Create(
function: IDBConnection
var
DBDataModule: TDBDataModule;
begin
// Example using FireDac and Data Module
DBDataModule := TDBDataModule.Create(nil);
Result := TFireDacConnectionAdapter.Create(DBDataModule.FDConnection1,
DBDataModule);
end
))));
Server.Start;
begin
StartServer;
end.
Service operations are invoked via HTTP requests, providing the correct URL and sending/
receiving data via JSON payloads. This chapter explain hows to implement and use service
operations.
From the Delphi IDE, choose File > New > Other...
From the dialog that appears, navigate to Delphi Projects > TMS Business and choose the wizard
TMS XData Service.
That will launch the "New XData Service" dialog, which will provide you with the following
options:
• Service Name: Specifies the base name for the service contract interface and service
implementation class, as well the unit names to be generated.
• Add sample methods: If checked, the wizard will add some sample methods (service
operations) as an example.
• Use a specific model: If checked, adds a Model attribute to the service contract/
implementation with the name specified in the edit box.
After options are set, click Finish and the full source code with the service contract and service
implementation will be generated for you.
If you prefer to create the source code manually, or simply want to learn more about the
generated source code, refer to the following topics:
• Service Implementation
The presented source code samples are not complete units and only display the relevant piece of
code for understanding service operations usage. Some obvious code and keywords might have
been removed to improve readability. Here we will implement two server operations, one using
scalar types (Sum), and another using entities (FindOverduePayments).
This tutorial also assumes that you have already created your XData Server application.
a. A Service Contract interface, which describes the methods and how they will be accessible
from clients. This interface can be shared between server and client applications;
b. A Service Implementation class, which effectively implement the service contract. This is used
only by the server app.
The following steps explains how to use service operations, including the creation of mentioned
types. The XData Service Wizard automates steps 1 and 2 for you.
1. Define a service contract interface and declare the operations (methods) in it.
unit MyServiceInterface;
uses
{...}, XData.Service.Common;
type
[ServiceContract]
IMyService = interface(IInvokable)
['{F0BADD7E-D4AE-4521-8869-8E1860B0A4A0}']
function Sum(A, B: double): double;
function FindOverduePayments(CustomerId: integer): TList<TPayment>;
end;
initialization
RegisterServiceType(TypeInfo(IMyService));
end.
This will add the contract to the XData model. You can use the Model attribute to specify the
model where the contract belongs to:
uses
{...}, MyServiceInterface,
XData.Server.Module,
XData.Service.Common;
type
[ServiceImplementation]
TMyService = class(TInterfacedObject, IMyService)
private
function Sum(A, B: double): double;
function FindOverduePayments(CustomerId: integer): TList<TPayment>;
end;
implementation
initialization
RegisterServiceType(TMyService);
end.
It will automatically detect the service interfaces and implementations declared in the
application. Your operations can now be invoked from clients. When the operation is invoked,
the server creates an instance of the service implementation class, and invokes the proper
method passing the parameters sent by the client.
This tutorial assumes you created the xdata server at base address "http://server:2001/tms/
xdata".
var
Client: TXDataClient;
MyService: IMyService;
SumResult: double;
Payments: TList<TPayment>;
begin
Client := TXDataClient.Create;
Client.Uri := 'http://server:2001/tms/xdata';
MyService := Client.Service<IMyService>;
SumResult := MyService.Sum(5, 10);
try
Payments := MyService.FindOverduePayments(5142);
finally
// process payments
Payments.Free;
end;
Client.Free;
end;
5. If using non-Delphi applications: invoke the operation using HTTP, from any platform and/or
development tool.
{
"a": 5,
"b": 8
}
HTTP/1.1 200 OK
{
"value": 13
}
2. Create a GUID for the interface (Delphi IDE creates a GUID automatically for you using
Shift+Ctrl+G shortcut key).
6. Call RegisterServiceType method passing the typeinfo of your interface (usually you can do
that in initialization section of your unit).
When you add the unit where interface is declared to either your server or client application,
XData will automatically detect it and add it to the XData model as a service contract, and define
all interface methods as service operations. The client will be able to perform calls to the server,
and the server just needs to implement the interface. The source code below illustrates how to
implement the service interface.
interface
uses
System.Classes, Generics.Collections,
XData.Service.Common;
type
[ServiceContract]
IMyService = interface(IInvokable)
['{BAD477A2-86EC-45B9-A1B1-C896C58DD5E0}']
function Sum(A, B: double): double;
function HelloWorld: string;
end;
implementation
initialization
RegisterServiceType(TypeInfo(IMyService));
end.
You can use the Model attribute to specify the model where the contract belongs to. This way
you can have a server with multiple server modules and models.
Routing
Each service operation is associated with an endpoint URL and an HTTP method. In other words,
from clients you invoke a service by performing an HTTP method in a specific URL. The server
mechanism of receiving an HTTP request and deciding which service operation to invoke is
called routing.
NOTE
In all the examples here, the server base URL is assumed to be http://localhost:2001. Of course,
use your own base URL if you use a different one.
Default Routing
By default, to invoke a service operation you would perform a POST request to the URL
<service>/<action>, where <service> is the interface name without the leading "I" and <action>
is the method name. For example, with the following service interface declaration:
POST /MyService/Sum
Note that the parameters follow a specific rule, they might be part of the routing, or not,
depending on routing settings and parameter binding settings.
IMyService = interface(IInvokable)
[HttpGet] function Sum(A, B: double): double;
The above declaration will define that Sum method should be invoked using a GET request
instead of a POST request:
GET /MyService/Sum
You can use attributes for other HTTP methods as well. You can use attributes: HttpGet, HttpPut,
HttpDelete, HttpPatch and HttpPost (although this is not needed since it's default method).
[Route('Math')]
IMyService = interface(IInvokable)
[Route('Add')]
function Sum(A, B: double): double;
POST /Math/Add
You can use multiple segments in the route, in both interface and method. For example:
[Route('Math/Arithmetic')]
IMyService = interface(IInvokable)
[Route('Operations/Add')]
function Sum(A, B: double): double;
An empty string is also allowed in both interface and method Route attributes:
[Route('Math/Arithmetic')]
IMyService = interface(IInvokable)
[Route('')]
function Sum(A, B: double): double;
POST /Math/Arithmetic
And also, the way you bind parameters can affect the endpoint URL as well. For example, the
Route attribute can include parameters placeholders:
[Route('Math')]
IMyService = interface(IInvokable)
[Route('{A}/Plus/{B}')]
function Sum(A, B: double): double;
POST /Math/10/Plus/5
[ServiceContract]
[Route('')]
IRootService = interface(IInvokable)
['{80A69E6E-CA89-41B5-A854-DFC412503FEA}']
[HttpGet, Route('')]
function Root: TArray<string>;
end;
GET /
You can of course use other HTTP methods like PUT, POST, and the TArray<string> return is just
an example, you can return any type supported by XData, just like with any other service
operation.
When that happens XData will take into account the value specified in the RoutingPrecedence
property to decide if the endpoint should invoke the service operation, or behave as the
automatic CRUD endpoint.
By default, the automatic CRUD endpoints always have precedence, starting with the entity set
name. This means that if there is an entity set at URL Customers/ , any subpath under that path
will be handled by the automatic CRUD endpoint processor, even if it doesn't exist. For example,
Customers/Dummy/ URL will return a 404 error.
Parameter Binding
Service operation parameters can be received from the HTTP request in three different modes:
In the method declaration of the service contract interface, you can specify, for each parameter,
how they should be received, using the proper attributes. If you don't, XData will use the default
binding.
1. If the parameter is explicitly used in a Route attribute, the default mode will be FromPath,
regardless of HTTP method used:
[Route('orders')]
IOrdersService = interface(IInvokable)
[Route('approved/{Year}/{Month}')]
[HttpGet] function GetApprovedOrdersByMonth(Year, Month: Integer):
TList<TOrder>;
Year and Month parameters should be passed in the URL itself, in proper placeholders:
GET orders/approved/2020/6
2. Otherwise, if the HTTP method is GET, the default mode will be FromQuery:
Meaning Year and Month parameters should be passed as URL query parameters:
GET orders/approved/?Year=2020&Month=6
3. Otherwise, for all other HTTP methods, the default mode will be FromBody:
[Route('orders')]
IOrdersService = interface(IInvokable)
[Route('approved')]
[HttpPost] function GetApprovedOrdersByMonth(Year, Month: Integer): TList<TOrde
r>;
In this case, Year and Month parameters should be passed as properties of a JSON object in
request body:
POST orders/approved/
{
"Year": 2020,
"Month": 6
}
FromBody parameters
FromBody parameters are retrieved from a JSON object in the request body, where each name/
value pair corresponds to one parameter. The pair name must contain the parameter name (the
one declared in the method), and value contains the JSON representation of the parameter
value, which can be either scalar property values, the representation of an entity, or the
representation of a collection of entities.
As specified above, FromBody parameters are the default behavior if the HTTP method is not
GET and no parameter is explicitly declared in the Route attribute.
Example:
How to invoke:
{
"a": 5,
"b": 8
}
FromQuery parameters
FromQuery parameters are retrieved from the query part of the request URL, using name=value
pairs separated by "&" character, and the parameter value must be represented as URI literal.
NOTE
You can only use scalar property values, or objects that only have properties of scalar value
types.
If the Multiply operation of previous example was modified to respond to a GET request, the
binding mode would default to FromQuery, according to the following example.
You can add the [FromQuery] attribute to the parameter to override the default behavior.
Example:
How to invoke:
You can also use DTOs as query parameters, as long the DTO only have properties of scalar types.
In this case, each DTO property will be a separated query param. Suppose you have a class
TCustomerDTO which has properties Id and Name , then you can declare the method like this:
WARNING
Using objects as parameters in query strings might introduce a breaking change in TMS Web
Core applications using your XData server. If your TMS Web Core application was built a
version of TMS XData below 5.2, the connection to the XData server will fail.
FromPath parameters
FromPath parameters are retrieved from path segments of the request URL. Each segment is a
parameter value, retrieved in the same order where the FromPath parameters are declared in the
method signature. As a consequence, it modifies the URL address used to invoke the operation.
The following example modified the Multiply method parameters to be received from path.
Example:
How to invoke:
Parameter "A" was the first declared, thus it will receive value 5. Parameter "B" value will be 8. If
the parameters are explicitly declared in the Route attribute, FromPath mode is the default
mode. All the remaining parameters flagged with FromPath that were not included in the Route
attribute must be added as additional segments.
Example:
[Route('{A}/Multiply')]
[HttpGet] function Multiply(A: double; [FromPath] B: double): double;
How to invoke:
Method declaration:
procedure Process(
[FromPath] PathA: Integer;
[FromQuery] QueryA: string;
BodyA, BodyB: string;
[FromQuery] QueryB: Boolean;
[FromPath] PathB: string;
): double;
How to invoke:
{
"BodyA": "one",
"BodyB": "two"
}
• PathA: 5
• QueryA: queryvalue
• BodyA: one
• BodyB: two
• QueryB: true
• PathB: value
then clients must send the JSON entity representation of the customer directly in request body,
without the need to wrap it with the parameter name (C, for example).
In this case, clients can send the value of Version parameter in a name/value pair named "value",
in addition to the original "Version" name/value pair. Both can be used.
Supported Types
When defining service interfaces, there are several types you can use for both parameter and
return types of the operations.
Scalar types
You can use the regular simple Delphi types, such as Integer, String, Double, Boolean, TDateTime
and TGUID, and its variations (like Longint, Int64, TDate, etc.). The server will marshal such values
using the regular JSON representation of simple types. Variant type is also supported as long the
variant value is one of the supported simple types (String, Integer, etc.).
Examples:
type
TMyEnum = (First, Second, Third);
TMyEnums = set of TMyEnum;
Enumeration values are represented in JSON as strings containing the name of the value ("First",
"Second", according to the example above). Sets are represented as a JSON array of enumeration
values (strings).
Aurelius Entities
If you use Aurelius, then you can also use Aurelius entities. Aurelius entities are a special case of
simple objects. The mechanism is actually very similar to entity representation, with only minor
differences (like representation of proxies and associated entities). The entities will be marshaled
in the HTTP request using entity representation in JSON Format.
Examples:
Example:
TCriteriaResult
If you use Aurelius, you can return Aurelius TCriteriaResult objects, or of course a
TList<TCriteriaResult> as a result type of a service operation. This is very handy to implement
service operations using Aurelius projections.
For example:
The JSON representation for each TCriteriaResult object is a JSON object which one property for
each projected value. The following JSON is a possible representation of an item of the list
returned by the method above:
TStrings
Values of type TStrings are supported and will be serialized as JSON array of strings. Since
TStrings is an abstract type, you should only use it as property type in objects and you should
make sure the instance is already created by the object. Or, you can use it as function result, and
when implementing you should also create an instance and return it. You cannot receive TStrings
in service operation parameters.
TStream
You can also use TStream as param/result types in service operations. When you do that, XData
server won't perform any binding of the parameter or result value. Instead, the TStream object
will contain the raw content of the request (or response) message body. Thus, if you declare your
method parameter as TStream, you will receive the raw message body of the client request in the
server. If you use TStream as the result type of your method, the message body of the HTTP
response send back to the client will contain the exact value of the TStream. This gives you
higher flexibility in case you want to receive/send custom or binary data from/to client, such as
images, documents, or just a custom JSON format.
For obvious reasons, when declaring a parameter as TStream type, it must be the only input
parameter in method to be received in the body, otherwise XData won't be able to marshal the
remaining parameters. For example, the following method declaration is invalid:
// INVALID declaration
[HttpPost] procedure ReceiveDocument(Value: TStream; NumPages: Integer);
Since the method is Post, then NumPages will by default be considered to be received in the
request body (FromBody attribute), which is not allowed. You can, however, receive the
NumPages in the query part of from url path:
Value will contain the raw request body, and NumPages will be received from the URL query
string.
HTTP/1.1 200 OK
{
"value": 40
}
HTTP/1.1 200 OK
{
"result": True,
"Param1": 50,
"Param2": 30
}
then the server will return the JSON entity representation of the customer in the response body,
without wrapping it in the "value" name/value pair.
Default Parameters
You can declare service operations with default parameters. For example:
This is because the default value declared in the function prototype above is not available at
runtime, but at compile time. That's why it works when calling from Delphi clients: the compiler
will provide the default value automatically in the client call - but that's a client feature: the HTTP
request will always have all parameter values.
To make the above method also work from raw HTTP requests and do not complain about
missing parameters, you will need to inform (again) the default value using the [XDefault]
attribute. This way XData server will know what is the default value to be used in case the client
did not provide it:
For more than one parameter, just add one Default attribute for each default parameter:
procedure DoSomething(
[XDefault('Default')] Name: string = 'Default';
[XDefault(0)] Value: Integer = 0
);
You only have to add attributes to the method declaration in the service contact interface.
Attributes in methods in service implementation class will be ignored.
Service Implementation
Once you have defined the service interface, you need to write the server-side implementation of
the service. To do this you must:
2. Inherit the class from TInterfacedObject, or be sure the class implement automatic
referencing counting for the interface.
6. Call RegisterServiceType method passing the implementation class (usually you can do that
in initialization section of your unit).
When you create a TXDataServerModule to build your XData server, it will automatically find all
classes that implement interfaces in the model and use them when operations are invoked by
the client. When a client invokes the operation, the server will find the class and method
implementing the requested service through routing, will create an instance of the class, bind
input parameters, and invoke the method. If the method returns a value, the server will bind the
return values and send it back to the client. The instance of the implementation class will then be
destroyed.
unit MyService;
interface
uses
System.Classes, Generics.Collections,
MyServiceInterface,
XData.Service.Common;
type
[ServiceImplementation]
TMyService = class(TInterfacedObject, IMyService)
private
function Sum(A, B: double): double;
function HelloWorld: string;
end;
implementation
initialization
RegisterServiceType(TMyService);
end.
In general, all objects are automatically destroyed by XData at server-side. This means that when
implementing the service, you don't need to worry to destroy any object. However, it's important
to know how this happens, so you implement your code correctly, and also knowing that in
• Any object passed as parameter or returned in a function result (called param object or
result object);
• The context TObjectManager is also destroyed after the operation method returns;
• Any associated object that is also deserialized or serialized together with the param object
or result object.
Example
• function Result, of type TMyResult, being created and returned in the last line of service
implementation;
• Entity, of type TMyEntity, being created and then passed to context TObjectManager to be
saved in the database.
You don't need to destroy any of those objects. Whatever objects are passed as param (Param)
of function result are always destroyed by XData automatically. The context manager is
automatically destroyed by XData, and since you have added an entity to it, it will be destroyed
as well upon manager destruction (this is a TMS Aurelius feature, not XData).
Managed objects
To control what will be destroyed and avoid double destruction of same object instance (for
example, if the same object is received as parameter and returned as function result), XData
keeps a collection of object instances in a property named ManagedObjects, available in the
TXDataRequestHandler object. You can access that property using the operation context:
TXDataOperationContext.Current.Handler.ManagedObjects
Specific cases
As stated before, in general you just don't need to worry, since the above rules cover pretty
much all of your service implementation, especially if you use the context TObjectManager to
perform database operations instead of creating your own manager. The situations where
something might go wrong are the following:
1. If you create your own TObjectManager and save/retrieve entities with it that were also passed
as parameter or are being returned in function result.
This will cause an Access Violation because when you destroy your object manager, the entities
managed by it will be destroyed. But then XData server will also try to destroy those objects
because they were passed as parameter or are being returned. To avoid this, you must set your
TObjectManager.OwnsObjects property to false, or use the context TObjectManager so XData
knows that the objects are in the manager and don't need to be destroyed. The latter option is
preferred because it's more straightforward and more integrated with XData behavior.
2. If you are creating associated objects that are not serialized/deserialized together with your
object
For example, suppose your method returns an object TCustomer. This TCustomer class has a
property Foo: TFoo, but such property is not mapped in Aurelius as an association (or not
marked to be serialized if TCustomer is a PODO). You then create a TFoo object, assign it to Foo
property and return:
Since the Foo property will not be serialized by the server, XData will not know about such
object, and it will not be destroyed. So you must be sure to destroy temporary/non-mapped/
non-serializable objects if you create them (or add them to ManagedObjects collection as
explained above). This is a very rare situation but it's important that developers be aware of it.
TXDataOperationContext
To help you implement your service operations, XData provides you some information under the
context of the operation being executed. Such information is provided in a
TXDataOperationContext object (declared in unit XData.Server.Module ). The simplified
declaration of such object is as followed.
TXDataOperationContext = class
public
class function Current: TXDataOperationContext;
function GetConnectionPool: IDBConnectionPool;
function Connection: IDBConnection;
function GetManager: TObjectManager;
function CreateManager: TObjectManager; overload;
function CreateManager(Connection: IDBConnection): TObjectManager; overload;
function CreateManager(Connection: IDBConnection; Explorer: TMappingExplorer):
TObjectManager; overload;
function CreateManager(Explorer: TMappingExplorer): TObjectManager; overload;
procedure AddManager(AManager: TObjectManager);
function Request: THttpServerRequest;
function Response: THttpServerResponse;
end;
To retrieve the current instance of the context, use the class function Current.
Context := TXDataOperationContext.Current.GetManager;
Then you can use the context to retrieve useful info, like a ready-to-use, in-context
TObjectManager to manipulate Aurelius entities (using the GetManager function) or the
connection pool in case you want to acquire a database connection and use it directly (using
GetConnectionPool method).
This way your service implementation doesn't need to worry about how to connect to the
database or even to create a TObjectManager (if you use Aurelius) with proper configuration and
destroy it at the end of process. All is being handled by XData and the context object. Another
interesting thing is that when you use the TObjectManager provided by the context object, it
makes it easier to manage memory (destruction) of entity objects manipulated by the server,
since it can tell what objects will be destroyed automatically by the manager and what objects
need to be destroyed manually. See Memory Management topic for more information.
uses
{...}
XData.Server.Module;
Note that the database connection to be used will be the one provided when you created the
TXDataServerModule. This allows you keep your business logic separated from database
connection. You can even have several modules in your server pointing to different databases,
using the same service implementations.
You can, for example, set the content-type of a stream binary response:
Or you can check for a specific header in the request to build some custom authentication
system:
DefConnection := TXDataOperationContext.Current.Connection;
It's the same connection used by the default manager, and it's retrieved from the pool when
needed.
Additional managers
You might need to create more Aurelius managers in a service operation. You can of course do it
manually, but if by any chance you want to return one entity inside one of those managers, you
can't destroy the manager you've created because otherwise the object you want to return will
also be destroyed.
In these scenario, you can use the context methods CreateManager or AddManager. The former
will create a new instance of a TObjectManager for you, and you can use it just like the default
manager: find objects, save, flush, and don't worry about releasing it.
To create the manager you can optionally pass an IDBConnection (for the database connection),
or the model (TMappingExplorer), or both, or none. It will use the default Connection if not
specified, and it will use the model specified in the XData server (module) if model is not
specified.
If you want to create the manager yourself, you can still tell XData to destroy it when the request
processing is finished, by using AddManager.
TXDataOperationContext.Current.AddManager(Manager);
Result := Manager.Find<TAnimal>
.Where(TLinq.Eq('Name', Name)).UniqueResult;
end;
IMyService = interface(IInvokable)
[HttpGet] function List(Query: TXDataQuery): TList<TCustomer>;
TXDataQuery = class
strict private
[JsonProperty('$filter')]
FFilter: string;
[JsonProperty('$orderby')]
FOrderBy: string;
[JsonProperty('$top')]
FTop: Integer;
[JsonProperty('$skip')]
FSkip: Integer;
Which means clients can invoke the endpoint passing the same $filter , $orderby , $top and
$skip parameters accepted also by the automatic CRUD endpoint, like this:
/MyService/List/?$filter=Name eq 'Foo'&$orderby=Name&$top=10&$skip=30
Customer := Client.Service<IMyService>
.List('$filter=Name eq 'Foo'&$orderby=Name&$top=10&$skip=30');
Customer := Client.Service<IMyService>.List(
CreateQuery.From(TCustomer)
.Filter(Linq['Name'] eq 'Foo')
.OrderBy('Name')
.Top(10).Skip(30)
.QueryString
);
You can of course create the criteria and then modify it as you wish. Since you are implementing
a service operation, instead of using an automatic CRUD endpoint, you have full control of the
business logic you want to implement. You could even, for example, create a criteria based on a
query on a DTO object:
In the example above, even though you are creating a criteria for the TCustomer class, the query
will be validated from the TCustomerDTO class. This means XData will only accept queries that
use properties from TCustomerDTO . For example, if the filter string is $filter=Status eq 2 ,
even if the Status property exists in TCustomer , if it does not exist in class TCustomerDTO the
query will not be accepted and an error "property Status does not exist" will return to the client.
This chapter assumes you have existing, basic knowledge about TMS Aurelius and your
application already has some objects mapped to the database. Please refer to the TMS Aurelius
documentation, "Quick Start" section for a brief introduction.
The following are the topics related to automatic TMS Aurelius CRUD Endpoints.
To illustrate this, suppose you have an Aurelius entity TCustomer associated with a TCountry
entity, both declared in your application, you will have the following URL addresses published by
TMS XData:
• http://server:2001/tms/xdata/Customer?$filter=Country/Name eq
'USA'&$orderby=Name&$top=10 (retrieves top 10 customers in the USA, ordered by
name)
Furthermore, you can perform GET requests to retrieve entities, POST to create new entities,
PUT/PATCH to update existing entities, and DELETE to remove entities.
You can also easily execute service operations from Delphi code through the service interfaces
using a TXDataClient object instance:
var
Client: TXDataClient;
MyService: IMyService;
Customer: TCustomer;
begin
Client := TXDataClient.Create;
Client.Uri := 'http://server:2001/tms/xdata';
MyService := Client.Service<IMyService>;
Customer := Client.CustomerByName('Joe');
{...}
end;
Do not forget that you have to use those Aurelius entities anywhere in your server application
otherwise they will be removed by the linker, at the very least, you should call
RegisterEntity(TCustomer), for example, to make sure TCustomer will not be removed.
Note that the XData server module creates the EM based on a TMappingExplorer object, which
contains all the Aurelius mapping information.
Entity sets
XData creates an entity set for each entitytype, so that all entities can be easily accessible from
the server. The entity set associated with the class contains all entities (objects) of that class. By
default the name of the entity set is the name of the entity type associated to it. Thus, if you have
a mapped class named "TCustomer", XData will define an entity type "Customer" in the EM, and
will also define an entity set "Customer" that represents all entities of that type. You can prevent
the "T" from being removed from class name by using the entity model builder.
Properties
Every mapped property or field in an Aurelius class becomes either a property or navigation
property in the entity type. If the class property/field is an association, it becomes a navigation
property. If it's a scalar property, it becomes a single property. It's important to note that only
mapped class members become an entity type property. The name of the property in the EM will
be the name of the mapped class property or the name of mapped class field with the "F" prefix
removed. As an example, consider the following Aurelius mapping:
The above class will become an entity type named "TCustomer", with the following named
properties: Id, Name, Birthday, Country, Description, Sex and Photo. Fields like FId and FName
are considered because the class is marked with [Automapping] attribute, so all fields are
included (with "F" prefix removed). Also, properties Sex and Photo are also included because
they are explicitly mapped. Finally, fields FSex, FPhoto and FPhoto2 are not included because
they were excluded from Aurelius mapping because of the [Transient] attribute usage. You can
prevent the "F" from being removed of field names by using the entity model builder.
All properties will be created as simple properties, except "Country" which will be a navigation
property associated with the "TCountry" that is also an entity type created from TCountry class.
You can change those permissions individually by each entity set, by choosing which operations
can be performed in each entity set.
The EntitySetName parameter specifies the name of the entity set (note that it's not necessarily
the name of the class), or '*' for all entity sets and Permissions parameter defines the allowed
operations in that entity set. Valid values are (declared in unit XData.Module.Base ):
type
TEntitySetPermission = (List, Get, Insert, Modify, Delete);
TEntitySetPermissions = set of TEntitySetPermission;
const
EntitySetPermissionsAll =
[Low(TEntitySetPermission)..High(TEntitySetPermission)];
EntitySetPermissionsRead = [TEntitySetPermission.List,
TEntitySetPermission.Get];
EntitySetPermissionsWrite = [TEntitySetPermission.Insert,
TEntitySetPermission.Modify,
TEntitySetPermission.Delete];
TEntitySetPermission values
Name Description
TEntitySetPermission.List Allows clients to query entities from the entity set using query
criteria.
TEntitySetPermission.Get Allows clients to retrieve a single entity and its subproperties and
associated entities.
Examples
Module.SetEntitySetPermissions('Country',
EntitySetPermissionsAll - [TEntitySetPermission.Delete]);
For the History entity set, allows only querying entities, retrieve a single entity, or create a new
one:
Module.SetEntitySetPermissions('History',
[TEntitySetPermission.List, TEntitySetPermission.Get, TEntitySetPermission.Inse
rt]);
Allows all entities to be read-only by default (list and get only). You can override this setting by
setting permissions for a specific entity set:
Module.SetEntitySetPermissions('*', EntitySetPermissionsRead);
URL Conventions
This chapter describes what URL addresses are made available by the XData Server based on the
entity model. It explains the rules for constructing URL address that identify data and metadata
exposed by the server.
When you create the XData server, the root URL must be specified. Following the root URL part,
additional URL constructions might be added, like path segments, query or fragment parts. In
this manual, all examples consider the server root URL to be http://server:2001/tms/xdata/.
The URL used by the XData server for entity resources has three significant parts: root URL,
resource path and query options, according to the example below.
http://server:2001/tms/xdata/Customer?$top=2&$orderby=Name
\___________________________/\______/ \__________________/
| | |
Root URL resource path query options
http://server:2001/tms/xdata/MathService/Multiply
\___________________________/\__________/\______/
| | |
Root URL service operation
The following topics describe in details the parts of the URL as understood by the XData Server.
Resource Path
This topic defines the rules for resource path construction according to XData URL conventions.
Resources exposed by the XData server are addressable by corresponding resource path URL
components to enable interaction of the client with that resource aspect. To illustrate the
concept, some examples for resources might be: customers, a single customer, orders related to
a single customer, and so forth. Examples of addressable aspects of these resources as exposed
by the data model might be: collections of entities, a single entity, properties, and so on.
http://server:2001/tms/xdata/Customer
A query part can be appended to the URL address of an entity set, so entities in an entity set can
be filtered, ordered, etc., for example:
http://server:2001/tms/xdata/Customer?$order=Name
There is a specific chapter about all the query options you can use in the query part of the URL.
XData uses JSON Format to represent the entity set resources available in such addresses. To
know more about the format specification, please refer to the topic that explains JSON format
representation of a collection of entities.
You can suffix the address with $count segment to return the number of entities:
http://server:2001/tms/xdata/Customer/$count
By default the name of entity set is the name of the associated Aurelius class name without the
leading "T". Thus, if the class name is TCustomer, the entity set path will be "Customer". You can
change that name explicitly by adding URIPath attribute to the class:
type
[URIPath('Customers')]
TCustomer = class
When the attribute is applied, the entity set for TCustomer entities will be accessible in the
following address (considering server base url is server:2001/tms/xdata):
http://server:2001/tms/xdata/Customers
http://server:2001/tms/xdata/Customer(3)
The literals in key predicates can also represent other types than integer, such as strings or guids.
The following example retrieves the employee with id equals to "XYZ".
http://server:2001/tms/xdata/Employee('XYZ')
http://server:2001/tms/xdata/Person('Doe','John')
http://server:2001/tms/xdata/Person(LastName='Doe',FirstName='John')
XData uses JSON Format to represent the entity resources available in such URL addresses. To
know more about the format specification, please refer to the topic that explains JSON format
representation of a single entity.
Key as segments
http://server:2001/tms/xdata/Customer/3
http://server:2001/tms/xdata/Employee/XYZ
Compound keys can be passed one after order, each also separated by slashes:
http://server:2001/tms/xdata/Person/Doe/John
http://server:2001/tms/xdata/Customer(3)/Country
You can only have a maximum of one level of nested navigation property. The next address, for
example, trying to request the country associated with a customer that is associated to an order,
is not valid. To request such country, you should use the address in previous example
(Customer()/Country) or access the country directly using Country(<key>).
Note that it's also possible to retrieve collection of entities if the navigation property is a
collection. For example, the following URL addresses the collection of order items for a specific
order:
http://server:2001/tms/xdata/Order(41)/OrderItems
For such navigation properties that represents collections (many-valued associations), it's not
possible to use key predicates (to identify a single entity in the collection), or query options (to
filter or order the collection).
XData uses JSON Format to represent the entity resources available in such URL addresses. To
know more about the format specification, please refer to the topic that explains JSON format
representation of a single entity.
http://server:2001/tms/xdata/Customer(3)/Name
When accessing a simple property resource, it contains a JSON representation of the property
value. You can also alternatively access the raw value of property using $value suffix:
http://server:2001/tms/xdata/Customer(3)/Name/$value
The difference between the two address is that the latter ($value) contains the property value in
raw format (plain text), while the former, contains the property value wrapped in JSON format.
The only exception is a property address that represents a named stream (blob). In this case, the
resource will contain the raw value of the blob, without needing to add the "$value" suffix. As an
example, the following URL address contains the raw binary data representing the photo of a
customer:
http://server:2001/tms/xdata/Customer(3)/Photo
XData uses JSON Format to represent the individual property resources available in such URL
addresses. To know more about the format specification, please refer to the topic that explains
JSON format representation of an individual property.
http://server:2001/tms/xdata/Customer(3)/Photo
The stream property resource contains the raw (binary) value of the blob content. Content-type
is not specified. Unlike addressing individual properties, stream properties do not support the
$value suffix, since the stream property resource already provides the raw value of the property.
Counting Entities
You can append "$count" path segment to a url that retrieves an entity set or a navigation
property that returns a list of entities. That will retrieve the number of entities in that resource, in
text/plain format, instead of the entities themselves.
For example, the following url will return the total number of Customer entities:
http://server:2001/tms/xdata/Customer/$count
While the following url will return the number of Customer entities with name is equal to "John":
http://server:2001/tms/xdata/Customer/$count?$filter=Name eq 'John'
http://server:2001/tms/xdata/Order(41)/OrderItems/$count
http://server:2001/tms/xdata/$model
Query Options
When addressing resources, it's possible to include a query string in the URL to perform extra
operations on the resource being addressed, like filtering or ordering entities. Query options
start with ? character and can be provided in the format name=value, separated by the &
character. Many query options can be applied in a single URL. For example, the URL below
retrieves the first 10 customers which country is USA, ordered by name:
http://server:2001/tms/xdata/Customer?$filter=Country/Name eq
'USA'&$orderby=Name&$top=10
The following table lists all the query options that can be used in a query string:
Query
option Description
$skip Specifies the number of entities that the server should skip before returning
the requested entities.
$inlinecount Includes the total number of entities (without paging) in the JSON response.
$expand Expands associated objects in by forcing the server to include them inline in
JSON response.
$filter
The $filter query option can be used in a URI query string to specify a predicate by which entities
will be filtered from an entity set. The $filter option only applies when addressing entity sets. The
filter format is the following:
$filter=<boolean expression>
http://server:2001/tms/xdata/Customer?$filter=Name eq 'John'
To access a property of an associated entity (navigation property), you can use slashes to build
the path to the property name. The following example retrieves all customers which country is
USA:
http://server:2001/tms/xdata/Customer?$filter=Country/Name eq 'USA'
You can use logical operators and parenthesis to define multiple expressions and change
evaluation order:
The previous examples use string literals in the expressions. String literals are always defined
using single quotes. You can have literals of other types in the expression. Since the filter query
option is part of URL, you must always format the literals according to the rules defined in
Literals in URI section.
The following table lists the operators supported in filter expressions, and the order of
precedence from highest to lowest. Operators in the same category have equal precedence.
Unary -x Negate x
$orderby
The $orderby query option can be used in a URL query string to determine which values are used
to order the entities in the entity set. The $orderby option only applies for URI addressing entity
sets. The format is the following:
$orderby=<expression> [asc/desc]
$orderby=<expression> [asc/desc],<expression> [asc/
desc],...,<expression> [asc/desc]
Where <expression> must contain the value by which the entities must be order, which is mostly
the name of a property of sub property. Example:
http://server:2001/tms/xdata/Customer?$orderby=Name
The asc/desc identifier is optional and indicates if the entities must be sorted by the specified
expression values in ascending or descending order. If not specified, ascending order is used.
You can use slashes to access associations (navigation properties) and sort by values of such
associations. As an example, the following URI will retrieve all invoices sorted by the name of the
customer's country associated with that invoice, in descending order:
http://server:2001/tms/xdata/Invoice?$orderby=Customer/Country/Name desc
You can also order by multiple values, separated by comma. The following example lists all
customers by last name and for customers with the same last name, by first name:
http://server:2001/tms/xdata/Customer?$orderby=LastName,FirstName
$top
The $top query option can be used in a URI query string to specify the maximum number of
entities that will be returned by the server. The $top option only applies for URI addressing entity
sets. The format is the following:
$top=<integer>
The returned entities are always the first entities in the result set. Usually $top is used together
with $orderby query option so that the order by which the entities are returned are known. It is
also often used together with $skip query option to perform paging of results. In the following
example, the server will return the first 10 invoice entities ordered by Date, even if there are
more than 10 invoices in the server:
$skip
The $skip query option can be used in a URI query string to specify the number of entities that
will be skipped by the server before returning the results. The $skip option only applies for URI
addressing entity sets. The format is the following:
$skip=<integer>
The skipped entities are always the first entities in the result set. Usually $skip is used together
with $orderby query option so that the order by which the entities are returned are known. It is
also often used together with $top query option to perform paging of results. In the following
URI example, the server will return invoice entities ordered by Date in descending order, buy
skipping the 10 first invoices and starting in the 11th one:
http://server:2001/tms/xdata/Order?$orderby=Date desc&$skip=10
$inlinecount
The $inlinecount query option allows including the total number of entities in the JSON response
representing a collection of objects. The counting of entities ignores paging query options like
$top and $skip. It's useful when paging results, so you can get a single page but also retrieve the
total number of entities so the client can know in a single request how many entities are in total
even when requesting only a page.
The $inline option only applies for URI addressing entity sets. The format is the following:
$inlinecount=none|allpages
When using "none" (the default), no info is included. When using "allpages", the total number of
entities for all pages will be included in the JSON response, in a property named $xdata.count.
In the following example, the server will return the first 10 invoice entities ordered by Date, and
ask for the total number of entities available.
http://server:2001/tms/xdata/Order?$orderby=Date&$top=10&$inlinecount=allpages
The JSON result would be something like this (supposing the total number of entities is 142):
{
"@xdata.count": 142,
"value": [
<_list_of_Order_objects_here_>
]
}
http://server:2001/tms/xdata/Customer(3)
{
"$id": 1,
"@xdata.type": "XData.Default.Customer",
"Id": 3,
"Name": "Bill",
"[email protected]": "Country(10)"
}
Note the associated Country is represented as a reference (Country with Id = 10). This is very
handy when using Delphi client (TXDataClient) or even non-Delphi client where you just want to
modify the country reference without needing to retrieve the whole object.
However, in some cases you might want to have the full Country object inline. With the $expand
query option you can explicitly indicate which associated properties you want to be inline:
http://server:2001/tms/xdata/Customer(3)?$expand=Country
{
"$id": 1,
"@xdata.type": "XData.Default.Customer",
"Id": 3,
"Name": "Bill",
"Country": {
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
}
}
$expand applies to both single association and lists. It also forces any server-side proxy to be
loaded, so even if Country is lazy-loaded, $expand will retrieve the entity inline. XData optimizes
this retrieval process by forcing the proxy to be loaded eagerly in a single SQL statement (except
for lists).
Expanding blob properties
You can also use $expand to ask for the server to bring blob content inline in object
representation. By default, blob properties will be represented as a proxy reference:
If you want the blob content to be retrieved together with the object, you can ask to $expand
the property:
http://server:2001/tms/xdata/Invoice(3)?$expand=Data
"Data": "T0RhdGE"
http://server:2001/tms/xdata/Invoice(10)?$expand=Customer,Seller,Products
You can also expand subproperties (deeper levels) by using slash to specify subproperties. The
following example expands property Customer and also the Country property of such customer
(and Seller) property:
http://server:2001/tms/xdata/Invoice(10)?$expand=Customer/Country,Seller
$select
The $select query option allows fine-grained control on the fields present in the JSON response
of the XData server. You can specify the fields to be present using a comma-separated.
Considering the following request:
http://server:2001/tms/xdata/Customer(3)
{
"Id": 3,
"FirstName": "Bill",
"LastName": "Smith",
"Birthday": "1980-01-01",
"[email protected]": "Country(15)"
}
You can use $select query option to include just a few fields in response:
http://server:2001/tms/xdata/Customer(3)?$select=Id,FirstName,Birthday
Results in:
{
"Id": 3,
"FirstName": "Bill",
"Birthday": "1980-01-01"
}
http://server:2001/tms/xdata/Customer(3)?
$select=Id,FirstName,Birthday,Country,Country/Name&$expand=Country
Results in:
{
"Id": 3,
"FirstName": "Bill",
"Birthday": "1980-01-01",
"Country": {
"Name": "USA"
}
}
Built-in Functions
In addition to operators, a set of functions is also defined for use with the $filter or $orderby
system query options. The following sections describe the available functions.
Upper
The upper function returns the input parameter string value with all characters converted to
uppercase.
Example:
http://server:2001/tms/xdata/Customer?$filter=upper(Name) eq 'PAUL'
Lower
The lower function returns the input parameter string value with all characters converted to
lowercase.
Example:
http://server:2001/tms/xdata/Customer?$filter=lower(Name) eq 'paul'
Length
The length function returns the number of characters in the parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=length(Name) eq 15
Example:
http://server:2001/tms/xdata/Customer?$filter=substring(CompanyName, 2, 4) eq 'oogl'
Position
The position function returns the 1-based character position of the first occurrence of the first
parameter value in the second parameter value. If the string in first parameter is not found in the
string in second parameter, it returns zero.
Example:
http://server:2001/tms/xdata/Customer?$filter=position('jr', Name) gt 0
Concat
The concat function returns the string concatenation of the two string input parameters passed
to it.
Example:
Contains
The contains function returns True if the first string parameter contains the string specified by the
second string parameter. False otherwise.
The following example will return all customers which Name contains the string "Walker":
http://server:2001/tms/xdata/Customer?$filter=contains(Name, 'Walker')
StartsWith
The startswith function returns True if the first string parameter starts with the string specified by
the second string parameter. False otherwise.
The following example will return all customers which Name starts with the string "Paul":
http://server:2001/tms/xdata/Customer?$filter=startswith(Name, 'Paul')
EndsWith
The endswith function returns True if the first string parameter ends with the string specified by
the second string parameter. False otherwise.
http://server:2001/tms/xdata/Customer?$filter=endswith(Name, 'Smith')
Year
The year function returns the year component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=year(BirthDate) eq 1971
Month
The month function returns the month component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=month(BirthDate) eq 12
Day
The day function returns the day component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=day(BirthDate) eq 31
Hour
The hour function returns the hour component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=hour(BirthDate) eq 14
Minute
The minute function returns the minute component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=minute(BirthDate) eq 45
Second
The second function returns the second component of a single date/time parameter value.
Example:
http://server:2001/tms/xdata/Customer?$filter=second(BirthDate) eq 30
Custom Functions
Besides the built-in functions you can use in $filter and $orderby query options, you can also
register your own custom functions. Such functions will then be translated into Aurelius' LINQ
"SQL functions" that also need to be previously registered for the query to work.
http://server:2001/tms/xdata/Customer?$filter=unaccent(Name) eq 'Andre'
Requesting Data
XData server support requests for data via HTTP GET requests. You can follow URL conventions
to determine the correct URL to be requested according to data needed.
The resource path of the URL specifies the target of the request (for example, collection of
entities, entity, associated entity (navigation property), etc.). Additional query operators, such as
filter, sort, page are specified through query options. The format of returned data depends on
the data being requested but usually follows the specified XData JSON Format.
The following topics describe the types of data requests defined by XData and related
information.
Querying Collections
XData server supports querying collections of entities. The target collection is specified through a
URL that addresses an entity set (or an URL that addresses navigation properties representing a
collection of items, like items of an order for example), and query operations such as filter, sort,
paging are specified as system query options provided as query options. The names of all system
query options are prefixed with a dollar ($) character.
For example, to query all the existing Country objects in the server, you perform a GET request to
the respective entity set URL:
You can add query options to the URL to filter the collection of entities. The following GET
request returns the first 10 customers which country name is equals to "USA", ordered by
customer name:
GET http://server:2001/tms/xdata/Customer?$filter=Country/Name eq
'USA'&$orderby=Name&$top=10 HTTP/1.1
The following example retrieves the items of order with id equals to 10.
NOTE
You can only use query options on entity sets. When addressing navigation properties that
represent a collection (like the previous example), query options are not available.
If the navigation property does not exist on the entity indicated by the request URL, the service
returns 404 Not Found.
If the association terminates on a single entity (single-valued association), then behavior is the
same as described in topic "Requesting Single Entities". If no entity is related, the service returns
204 No Content.
Examples
The result format following the JSON format specification for individual properties, i.e., a JSON
object with a name/value pair which name is "value" and the value contains the actual property
value. The result for the above request might be, for example:
{
"value": "Silver Hammer XYZ"
}
If the property has the null value, the service responds with 204 No Content.
If the property doesn't exist the service responds with 404 Not Found.
You can alternatively request the raw value of the property. To retrieve the raw value of a
primitive type property, the client sends a GET request to the property value URL, which is the
property value URL suffixed with "$value":
The Content-Type of the response is text/plain and the response content is the property value in
plain text, formatted according to the JSON format specification for property values. The
response content of the above request might something like the following:
A $value request for a property that is null results in a 204 No Content response.
If the property doesn't exist the service responds with 404 Not Found.
The server will provide the binary blob content in response body, without specifying the content
type. It's up to your client application to determine the type of content and process it
accordingly. Note that this behavior is different from when you request individual properties,
which in this case provide a JSON representation of the property value.
If the property has the null value, or the stream content is empty, the service responds with 204
No Content, otherwise a sucessful 200 OK response is provided.
If the property doesn't exist the service responds with 404 Not Found.
xdata-expand-level
Clients can optionally include this header in the request to define the maximum depth for which
the associated entities will be expanded (serialized inline) in an entity JSON representation.
xdata-expand-level: 3
If XData-ExpandLevel is notpresent, the value 0 (zero) is assumed, which means all direct
associated entities will be represented as references. When the header is present and value is
higher, then all associated entities will be serialized inline, until the depth level specified by the
xdata-put-mode
Overrides the value of TXDataServerModule.PutMode property for the request. Please refer to
the property documentation for more info. Example:
xdata-put-mode: update
xdata-serialize-instance-ref
Overrides the value of TXDataServerModule.SerializeInstanceRef property for the request. Please
refer to the property documentation for more info. Example:
xdata-serialize-instance-ref: ifrecursive
xdata-serialize-instance-type
Overrides the value of TXDataServerModule.SerializeInstanceType property for the request.
Please refer to the property documentation for more info. Example:
xdata-serialize-instance-type: ifneeded
The read URL can be obtained by following the URL conventions, either that addresses a single
entity, or associated entities. The read URL can be also obtained from a response payload in
JSON format containing information about associated entities (through navigation properties of
the entity represented in the payload).
If no entity exists with the key values specified in the request URL, the service responds with 404
Not Found.
Examples
{
"$id": 1,
"@xdata.type": "XData.Default.Customer",
"Id": 55,
"Name": "Joseph",
"Birthday": "1980-05-20",
"Sex": "tsMale",
"Picture": null
}
Data Modification
XData Server supports Create, Update, and Delete operations for some or all exposed resources
(entities, individual properties, etc.). You can follow URL conventions to determine the correct
URL address of the resource path in order to perform the data modification operation.
Since XData Server follows REST/JSON architecture, such data modification operations are
performed by sending HTTP requests to the server using POST, PUT, PATCH or DELETE methods,
and when applicable, providing data in message payload using JSON format.
The following topics explains the several different methods of performing data modification in a
XData Server.
Create an Entity
To create an entity in a collection, clients must perform a POST request to that collection's URL,
which can be either the entity set URL address or a navigation property URL address that
represents a collection. The POST body must contain a single valid JSON representation of the
entity. The entity representation can be full (all possible properties) or partial (a subset of all
available properties for the entity type).
Properties in entity representation must be valid properties or navigation properties of the entity
type as specified in the entity model. Additional property values beyond those specified in the
entity type should not be sent in the request body. The request will fail if unable to persist all
property values specified in the request. Missing properties will be set to their default value. If a
missing property is required and does not have a default value in the database server, the
request will fail.
{
"@xdata.type": "XData.Default.Country",
"Name": "Brazil"
}
When creating an entity, the "xdata.type" annotation is not required. If present, it indicates the
type of the entity which should be created by the server. It can be useful when creating derived
entities, for example, even though you POST to an URL representing an entity set of base types
(for example, Animal), the entity being created might be a type inherited from the base type.
In the following example the request will create a Cat entity in entity set Animal. It's a valid
request. If the provided xdata.type annotation indicates a type that is different or not inherited
from the type of entity set being POST'ed, the request will fail.
{
"@xdata.type": "XData.Default.Cat",
"Name": "Wilbur",
"CatBreed": "Persian"
}
If "xdata.type" is not present, the created entity will have the same type as the base entity type of
the collection addressed by the URL. For example, if client POST to the url "Cat" without
"xdata.type" annotation present in payload, an entity of type Cat wil be created.
The entity representation in request payload must not contain associated entities as inline
content. If values for navigation properties should be specified, then they must be represented
as an association reference. For single-valued (single entity) navigation properties, this will
update the relationship (set the value of property to reference the associated object represented
in the association reference) after entity is created.
The following example creates a new City entity which is associated with a Country object of id 2.
{
"$id": 1,
"Name": "Frankfurt",
"[email protected]": "Country(2)"
}
Proxy values can be present in the entity payload (for example, when you are sending back a
modified JSON representation previously retrieved from the server, it's possible that proxy values
are present), but they are ignored by the server and navigation properties with proxy values that
were not modified.
On successful update, the server returns with a 201 Created success response and the JSON
representation of the updated entity in the message payload. The response will also contain a
Location header that contains the URL of the created entity. Note that the returned entity might
be different from the one sent by the client, since some properties might have been updated
automatically by XData Server and/or the database server.
Update an Entity
To update an entity, clients must perform a PATCH or PUT request to a single entity URL address,
providing the JSON representation of the entity in message payload of the request. The entity
representation can be full (all possible properties) or partial (a subset of all available properties
for the entity type).
PATCH method should be preferred for updating an entity as it provides more resiliency between
clients and services by directly modifying only those values specified by the client. The semantics
of PATCH, as defined in [RFC5789], are to merge the content in the request payload with the
entity's current state, applying the update only to those components specified in the request
body. Thus, properties provided in the payload will replace the value of the corresponding
property in the entity or complex type. Missing properties of the containing entity will not be
altered.
In the following example, only properties Name and DogBreed of resource Dog(1) will be
updated in the server.
{
"@xdata.type": "XData.Default.Dog",
"Id": 1,
"Name": "Willy",
"DogBreed": "Yorkshire"
}
PUT method can also be used, but you should be aware of the potential for data-loss for missing
properties in the message payload. When using PUT, all property values will be modified with
those specified in the request body. Missing properties will be set to their default values.
In the following example, properties Name and DogBreed will be updated, and any other existing
property of Dog will be set to its default value (strings will be blank, associations will be nil, etc.).
If one of missing properties is required and not present, the server might raise an error indicating
a required property should have a value.
{
"@xdata.type": "XData.Default.Dog",
"Id": 1,
"Name": "Willy",
"DogBreed": "Yorkshire"
}
Key and other non-updatable properties can be omitted from the request. If the request contains
a value for one of these properties the server will ignore that value when applying the update.
That's the reason why Id property is not updated in the above examples.
The entity must not contain associated entities as inline content. If values for navigation
properties should be specified, then they must be represented as an association reference. For
single-valued (single entity) navigation properties, this will update the relationship (set the value
of property to reference the associated object represented in the association reference).
The following example updates the Country association of a City object to the country with id
equals to 2.
{
"$id": 1,
"@xdata.type": "XData.Default.City",
"[email protected]": "Country(2)"
}
Proxy values can be present in the entity payload (for example, when you are sending back a
JSON representation previously retrieved from the server, it's possible that proxy values are
present), but they are ignored by the server and navigation properties with proxy values are not
modified.
For update operations (PUT and PATCH), the "xdata.type" annotation is not required. Since you
are updating an existing server resource, the type of resource is already known. Actually,
including "xdata.type" annotation will force the server to perform a type check, if the type
declared in the request doesn't match the type of server resource, an error occurs. So if you
include xdata.type annotation in payload, be sure it matches the type of server resource you are
trying to update.
On successful update, the server returns with a success response and the JSON representation of
the updated entity in the message payload. Note that the returned entity might be different
from the one sent by the client, since some properties might have been updated automatically
by XData Server and/or the database server.
The following example will delete the entity resource at address /tms/xdata/Dog(1) - which is a
Dog entity with id equals to 1:
Note that depending on cascades configured in the server, associated entities might also be
removed. If the entity being removed is associated by another entity that can't be removed in a
cascade operation, a constraint enforcement will fail and the server will return an error without
actually removing the entity.
On successful delete, the server responds with a 204 No Content response and no content in
message payload.
The request is an example of how to update the content of Photo property of the customer
identified by id equals to 3.
You can also use DELETE method to clear the blob content (remove any data):
On successful UPDATE or DELETE calls, the server will respond with a 204 No Content status code.
To start using a TXDataClient, you just need to instantiate it and set the Uri property to point to
root URL of the XData server:
{...}
Client := TXDataClient.Create;
Client.Uri := 'http://server:2001/tms/xdata';
// <use client>
Client.Free;
Another advantage is that you don't need to deal building or finding out the endpoint URL
(routing), with binding parameters or even create client-side proxy classes (when you use
Aurelius entities). The same service interface you defined for the server can be used in the client.
You can share the units containing the service interfaces between client and server and avoid
code duplication.
Here is an example of invoking the method Sum of interface IMyService, declaring in the topic
"defining service interface":
var
Client: TXDataClient;
MyService: IMyService;
SumResult: double;
begin
// Instantiate TXDataClient
Client := TXDataClient.Create;
// Set server Uri
Client.Uri := 'http://server:2001/tms/xdata';
// Retrieve IMyService inteface
MyService := Client.Service<IMyService>;
// call inteface methods
SumResult := MyService.Sum(5, 10);
end;
Note that the client won't destroy any object passed as parameters, and will only destroy entities
created by it (that were returned from the server), but no regular objects (like TList<T> or
TStream). See "Memory Management" topic for detailed information.
• Any object sent to the server (passed as a parameter) is not destroyed. You must handle
the lifetime of those objects yourself.
• Any object of type TStream or TList<T> returned from the server is not destroyed. You
must handle the lifetime of those objects yourself.
• Any other object returned from the server which type is not the ones mentioned in the
previous item is automatically destroyed by default.
{...}
Invoices := Client.Service<ISomeService>.DoSomething(Customer);
Customer.Free;
Invoices.Free;
Customer object is being passed as parameter. It will not be destroyed by the client and you
must destroy it yourself. This is the same if you call Post, Put or Delete methods.
Items object (TList<T>) is being returned from the function. You must destroy the list yourself,
it's not destroyed by the client. It's the same behavior for List method.
The TInvoice objects that are inside the list will be destroyed automatically by the client. You
must not destroy them. Also, the same behavior for Get and List methods - entities are also
destroyed by the client.
Alternatively, you can disable automatic management of entity instances at the client side, by
using the ReturnedInstancesOwnership property:
Client.ReturnedInstancesOwnership := TInstanceOwnership.None;
The code above will prevent the client from destroying the object instances. You can also retrieve
the list of all objects created by the client (that are supposed to be destroyed automatically)
using property ReturnedEntities, in case you need to destroy them manually:
Customer := Client.Get<TCustomer>(10);
State := Client.Get<TState>('FL');
The Id parameter is of type TValue, which has implicit conversions from some types like integer
and string in the examples above. If there is no implicit conversion from the type of the id, you
can use an overloaded method where you pass the type of Id parameter:
You can use the non-generic version of Get in case you only know the entity type at runtime (it
returns a TObject and you need to typecast it to the desired type):
var
Fishes: TList<TFish>;
begin
Fishes := Client.List<TFish>;
The TXDataClient.List<T> function will always create and retrieve an object of type TList<T>. By
default you must manually destroy that list object later, as explained in memory management
topic.
Optionally you can provide a query string to send to the server to perform filtering, order, etc.,
using the XData query options syntax:
Use the non-generic version in case you only know the type of the entity class at runtime. In this
case, the function will create and return an object of type TList<TObject>:
var
Fishes: TList<TObject>;
begin
Fishes := XClient.List(TFish);
You also can use Count method to retrieve only the total number of entities without needing to
retrieve the full entity list:
Creating Entities
Use TXDataClient.Post to create a new object in the server.
C := TCountry.Create;
try
C.Name := 'Germany';
Client.Post(C);
finally
C.Free;
end;
Pay attention to client memory management to learn which objects you need to manually
destroy. In this case, the client won't destroy the TCountry object automatically so you need to
destroy it yourself.
The client makes sure that after a successful Post call, the Id of the object is properly set (if
generated by the server).
Updating Entities
Use TXDataClient.Put to update an existing object in the server.
Customer := Client.Get<TCustomer>(10);
Customer.City := 'London'; // change city
Client.Put(Customer); // send changes
Pay attention to client memory management to learn which objects you need to manually
destroy. Client won't destroy objects passed to Put method. In the above example, though, the
object doesn't need to be destroyed because it was previously retrieved with Get, and in this
case (for objects retrieved from the server), the client will manage and destroy it.
Removing Entities
Use TXDataClient.Delete to delete an object from the server. The parameter must be the object
itself:
Customer := Client.Get<TCustomer>(10);
Client.Delete(Customer); // delete customer
For example, to query for customers which name is "Paul" or birthday date is lower then August
1st, 1940, ordered by name in descending order, you can write a code like this:
Alternatively to manually writing the raw query string yourself, you can use the XData Query
Builder. The above code equivalent would be something like this:
Customers := Client.List<TCustomer>(
CreateQuery
.From(TCustomer)
.Filter(
(Linq['Name'] = 'Paul')
or (Linq['Birthday'] < EncodeDate(1940, 8, 1))
)
.OrderBy('Name', False)
.QueryString
);
CreateQuery.From(TCustomer)
.Filter(Linq['Name'] = 'Paul')
.QueryString
Will result in $filter=Name eq Paul . You can also write the raw query string directly using
FilterRaw method:
CreateQuery.From(TCustomer)
.FilterRaw('Name eq Paul')
.QueryString
CreateQuery.From(TCustomer)
.OrderBy('Name')
.OrderBy('Id', False)
.QueryString
Results in $orderby=Name,Id desc . The overload using Aurelius project allows for more complex
expressions, like:
CreateQuery.From(TCustomer)
.OrderBy(Linq['Birthday'].Year)
.QueryString
Which results in $orderby=year(Birthday) . You can also write the raw order by expression
directly using OrderByRaw method:
CreateQuery.From(TCustomer)
.OrderByRaw('year(Birthday)')
.QueryString
CreateQuery.Top(10).Skip(30)
.QueryString
Results in $top=10&$skip=30 .
Expand
Specifies the properties to be added to $expand query option:
CreateQuery.From(TInvoice)
.Expand('Customer')
.Expand('Product')
.QueryString
Results in $expand=Customer,Product .
CreateQuery.From(TCustomer)
.Filter(Linq['Country.Name'] = 'Germany')
.QueryString
From
If your query refers to property names, you need to use the From method to specify the base
type being queried. This way the query builder will validate the property names and check their
types to build the query string properly. There are two ways to do so: passing the class of the
object being queries, or the entity type name:
CreateQuery.From(TCustomer) {...}
CreateQuery.From('Customer') {...}
Note that you can also specify the name of an instance type, ie., an object that is not necessarily
an Aurelius entity, but any Delphi object that you might be passing as a DTO parameter.
When you pass a class name, the query builder will validate agains the names of field and
properties of the class, not the final JSON value. For example, suppose you have a class mapped
like this:
TCustomerDTO = class
strict private
FId: Integer;
[JsonProperty('the_name')]
FName: string;
{...}
CreateQuery.From('Customer').Filter(Linq['the_name'] = 'Paul')
CreateQuery.From(TCustomerDTO).Filter(Linq['the_name'] = 'Paul')
Because the_name is not a valid property name for TCustomerDTO class. The correct query
should be:
CreateQuery.From(TCustomerDTO).Filter(Linq['Name'] = 'Paul')
Client := TXDataClient.Create(TXDataAureliusModel.Get('Security'));
Authentication Settings
For the HTTP communication, TXDataClient uses under the hood the Sparkle THttpClient. Such
object is accessible through the TXDataClient.HttpClient property. You can use all properties and
events of THttpClient class there, and the most common is the OnSendingRequest event, which
you can use to set custom headers for the request. One common usage is to set the
Authorization header with credentials, for example, a JSON Web Token retrieved from the server:
XDataClient.HttpClient.OnSendingRequest :=
procedure(Req: THttpRequest)
begin
Req.Headers.SetValue('Authorization', 'Bearer ' + vToken);
end;
TXDataClient class provides you with the following properties for accessing servers protected
with basic authentication.
Defines the UserName and Password to be used to connect to the server. These properties are
empty by default, meaning the client won't send any basic authentication info. This is equivalent
to set the Authorization header with property basic authentication value.
However, it might happen that your server and client classes get out of sync. Suppose you have a
class TCustomer both server and client-side. The server serializes the TCustomer, and client
deserializes it. At some point, you update your server adding a new property TCustomer.Foo. The
By default, the client will raise an exception saying Foo property is not known. This is the safest
approach since if the client ignore the property, it might at some point send the TCustomer back
to the server without Foo, and such property might get cleared in an update, for example.
On the other hand, this will require you to keep your clientes updated and in sync with the server
to work. If you don't want that behavior, you can simply tell the client to ignore properties
unknown by the client. To do this, use the IgnoreUnknownProperties property from TXDataClient:
XDataClient1.IgnoreUnknownProperties := True;
The following topics describes how each different structure is represented in JSON format by
XData, and additional useful info about it.
The name of the properties in JSON for a simple Delphi object will be the field names of the
object class, with the leading "F" removed from the name, if it exists.
The name of the properties in JSON for an Aurelius entity will be the same as property names
defined in the XData Model according to Aurelius Equivalence to Entity Model.
A Delphi object in payload will always have all properties, unless you explicitly change this
behavior by using JSON attributes to customize the serialization.
An Aurelius entity in a payload may be a complete entity, with all existing properties for that
entity type, or a partial entity update (for partial update operations), which do not list all
properties of the entity.
The following text illustrates how XData represents a simple Customer object (TCustomer class).
{
"$id": 1,
"Id": 55,
"Name": "Joseph",
"Birthday": "1980-05-20",
"Sex": "tsMale",
"Picture": null
}
In above JSON representation, Customer entity type contains simple properties Id (integer),
Name (string), Birthday (date), Sex (enumeration) and Picture (blob). The property values are
represented as direct JSON values. Note that XData also includes some metadata information in
the JSON object, like "$id" name which represents the object reference id. In some cases, XData
might include the "xdata.type" annotation which is needed for it to work properly with
polymorphism. The following topics describe more specific details about how entities and its
properties are represented in XData.
Data
Type Examples Description
When serializing objects, XData attributes an "instance id" for that object and serializes it. The
instance id is serialized as the first property of the object, with the name "$id". If during
serialization the serializer finds another reference to the same object, it won't serialize the object
again, but instead, will create a "instance reference" that refers to the instance id of the object
previously serialized.
For example, consider you have Product entity type which has a Category property that points to
a Category entity type. Suppose you have a list of two Product entities "Ball" and "Doll" that
point to the same "Toys" category. This is how such list would be serialized:
[
{
"$id": 1,
"@xdata.type": "XData.Default.Product",
"Id": 10,
"Name": "Ball",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
},
{
"$id": 2,
"@xdata.type": "XData.Default.Product",
"Id": 12,
"Name": "Doll",
"Category": {
"$ref": 2,
}
}
]
The TXDataClient deserializer also uses such notation to avoid duplicating objects and using the
same object instance for multiple references. When the deserializer finds a JSON object with a
"$ref" property it will try to find the object instance with the same "$id" value as the "$ref"
property value. Then it will use the reference to that object instead of creating a new one.
In the JSON example above, when creating the second Product instance and setting the
Category property, the deserializer will not create a new Category object. Instead, it will use the
same Category object created in the first product. Thus, the Category property of both Product
objects will point to the same Category object instance.
If the deserializer can't find an instance pointed by the "$ref" property, an error will be raised. All
other properties of a JSON object containing a "$ref" property will be ignored.
The rule of when object reference ($ref) will be used depend on the
TXDataServerModule.SerializeInstanceRef property. By default, it's always used whenever an
instance appears again. But it can be configured to only appear for recursive ocurrences (thus
avoiding an infinite loop). Please refer to the property documentation for more info. When used
only for recursive ocurrences, the example above will not include $ref, and the Category object
will be repeated in the response:
[
{
"$id": 1,
"@xdata.type": "XData.Default.Product",
"Id": 10,
"Name": "Ball",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
},
{
"$id": 2,
"@xdata.type": "XData.Default.Product",
"Id": 12,
"Name": "Doll",
"Category": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
}
]
Annotation "xdata.type"
All JSON objects in XData that represent an object might contain a metadata property named
"@xdata.type". If present it must appear before any regular (non-metadata) property, otherwise
an error is raised while deserializing the object. This property indicates the entity type of the
JSON object. This is used by the deserializer to know which class to be used to instantiate the
object. The value of this property is the name of the object class, or Aurelius entity type, prefixed
by "XData.Default".
When sending requests to the server, "@xdata.type" annotation is optional. If not present, XData
will try to infer the entity type from the context - for example, if your service operation is
expecting a parameter of type TCustomer then it will deserialize the JSON as a TCustomer
instance, if xdata.type annotation is missing. Thus, this annotation is mostly used when dealing
with polymorphism, so you be sure that the entity will be deserialized as the correct type.
An associated object is any object property that references a regular Delphi object.
An associated entity is any object property that references a TMS Aurelius entity through a
navigation property (association).
For example, you might have an Aurelius entity type Customer with a navigation property
Country, which target is the Country entity type. So the Country object is associated with the
Customer object through the Country property, and might be represented in JSON notation. The
equivalent Aurelius class would be something like this (parts of code removed):
TCustomer = class
{ code stripped }
property Country: TCountry;
end;
Here we describe the different ways to represent an associated entity/object in JSON format.
Please note that this is not related to representing object references, they are different concepts.
XData server responses will often use entity references when responding to resource requests.
This behavior might be affected by the XData-ExpandLevel request header. Some other classes
might also make use of such header transparently, like TXDataClient which automatically sets an
expand level to 3 (thus all non-proxy entities will be serialized inline until the 3rd level in object
tree).
Note that all the following options are only available for associated entities. For associated
objects, only inline representation is allowed.
This is the most common way to represent an associated entity and should be preferred over any
other method, if possible. It's somehow related to reference to an object instance, in a
programming language. It's represented by annotating the navigation property name with
"xdata.ref" sufix. The value of such property must be the canonical id of the associated object:
"[email protected]": "Country(10)"
In the simplified example above, the property Country is associated to the Country object with id
equals to 10.
Clients can use that value to retrieve further info about the associated object by using the rules
described in "canonical id" topic, performing an extra request to the server. Also, in update/insert
operations, clients can also use this format to tell the server how to update the association. In
the example above, if you send such object to the server in an update operation, the customer
object will have its Country property updated to point to the Country(10) object (country with id
equals to 10).
Entity/Object Inline
In some specific situations, the associated entity might be represented inline (for associated
objects, this is always the case). That would be represented as a JSON object representing the
whole object inline:
{
"Country": {
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
}
}
Such representation would usually be used by client applications that want to provide associated
entities that still do not have an id, thus can't be represented using the association reference
format. Or can be returned by the server, if the expand level is increased. You can also explicitly
ask for this format using the $expand query option.
To illustrate how association references are different from object references, you can have an
entity represented inline, but using an object reference. For example, if the country object
represented in the previous code (country of id 10 represented inline) was already present in the
JSON tree before, it could be represented as an object reference:
{
"Country": {
"$ref": 2
}
}
For performance reasons, the server might provide an object representation using proxy info
instead of an association reference. The format is similar to the association reference, but the
annotation is "xdata.proxy". The value must contain an URL (relative to server base URL) that
clients can use to retrieve the associated object using a GET request:
"[email protected]": "$proxy?id=52&classname='Customer'&classmember='Country'"
Association reference and proxy info are very similar, but they have two differences:
1. An association reference value has a very specific format (canonical id). Thus, association
references, in addition to be a valid relative URL to retrieve the associated object, it is also
in a specific format so that entity type and id can be parsed from the value, allowing you
to safely know the entity type and id of the associated object without performing a GET
request. Proxy info, in turn, is just a relative URL to retrieve the object, but the URL format
is not standard and no additional metadata info can be safely extracted from the URL.
Blob Representation
In general, binary values are represented in JSON notation as base64 value, following the rules
for serializing simple property values. The following JSON name/value pair is an example of a
binary property Data representation:
"Data": "T0RhdGE"
When the property type in Aurelius is declared as TBlob type, though, the name/value pair in
JSON notation might be declared using the "xdata.proxy" annotation after the property name,
which allows clients to load the blob content in a lazy (deferred) way. The value of such name/
value pair is the URL used to retrieve the blob content:
"[email protected]": "Customer(55)/Data"
If the property is a TBlob type, the property will always be represented like that, unless:
1. The object to which the property belongs is a transient object (in the example above, if the
customer object didn't have an id). In this case, the blob content will be represented inline as
base64 string.
2. The blob content is somehow available to the server when the object is retrieved from
database, and the blob content is empty. In this case, the property will be serialized normally
(without annotation), and value is "null":
"Data": null
• For entities, all class members (fields or properites) that are mapped using Aurelius will be
serialized. Any transient field or property will not be serialized.
• For objects, all class fields will be serialized. No property will be serialized.
You can override this default and choose, for both your entity and object classes, what property
will be included in JSON. The way to do that is different for entities and objects.
Aurelius Entities
For entities you can use attributes XDataProperty and XDataExcludeProperty (declared in unit
XData.Model.Attributes ). They have no arguments, and when put in a class member (field or
property) it imply indicates that such property will be added or removed from the JSON.
In the following example, even though FBirthday is the field being mapped to the database, the
final JSON will not include it, but instead will have three properties: Year, Month and Day.
[Entity, Automapping]
TCustomer = class
private
FId: Integer;
FName: string;
[XDataExcludeProperty]
FBirthday: TDateTime;
function GetDay: Integer;
function GetMonth: Integer;
function GetYear: Integer;
procedure SetDay(const Value: Integer);
procedure SetMonth(const Value: Integer);
procedure SetYear(const Value: Integer);
public
In the following example, final Json will have properties Id, PersonName, Birthday and
YearOfBirth. Field FTransient will not be serialized because of attribute JsonIgnore. Field FName
will be serialized with a different name (PersonName) and property YearOfBirth will also be
serialized because of the presence of attribute JsonProperty.
TDTOPerson = class
private
FId: Integer;
[JsonProperty('PersonName')]
FName: string;
FBirthday: TDateTime;
[JsonIgnore]
FTransient: string;
public
property Id: Integer read FId write FId;
property Name: string read FName write FName;
[JsonProperty]
property YearOfBirth: Integer read GetYearOfBirth;
property Birthday: TDateTime read FBirthday write FBirthday;
property Transient: string read FTransient write FTransient;
end;
JsonInclude attribute
You can also use JsonInclude attribute to specify which properties will be serialized to JSON
based on their values. JsonInclude attribute should be added to the class type and receive a
parameter of type TInclusionMode:
type
TMyEnum = (myFirst, mySecond, myThird);
"MyEnumProp": "myFirst"
You can change that value using the JsonEnumValues property, passing the new values in a
comma-separated string:
type
[JsonEnumValues('first,second,third')]
TMyEnum = (myFirst, mySecond, myThird);
"MyEnumProp": "first"
JsonNamingStrategy attribute
If you have a general rule for naming the properties in the final JSON, you can use
JsonNamingStrategy attribute instead of using a JsonProperty attribute for every single field/
property you want to define a name. You add this attribute to the class informing the naming
strategy to be used:
[JsonNamingStrategy(TCamelCaseNamingStrategy)]
TMySimpleClass = class
FFirstName: string;
property LastName: string;
• TDefaultNamingStrategy: This is the default strategy to be used in you don't specify one. It
will keep the property name as-is, field names will have leading "F" removed.
{
"FirstName": "Joe",
"LastName": "Smith"
}
• TCamelCaseNamingStrategy: Names will be camel case, with first letter in lower case. Field
names will have leading "F" removed before converting.
{
"firstName": "Joe",
"lastName": "Smith"
}
• TSnakeCaseNamingStrategy: Names will be snake case: all lower case with words
separated by underlines. Field names will have leading "F" removed before converting.
{
"first_name": "Joe",
"last_name": "Smith"
}
{
"FFirstName": "Joe",
"LastName": "Smith"
}
{
"fFirstName": "Joe",
"lastName": "Smith"
}
The converter must inherit from TCustomJsonConverter class and override methods ReadJson and
WriteJson, which will read and write the serialized JSON value. Here is a small example:
type
TSampleJsonConverter = class(TCustomJsonConverter)
protected
procedure ReadJson(const Reader: TJsonReader; var Value: TValue); override;
procedure WriteJson(const Writer: TJsonWriter; const Value: TValue);
override;
end;
{...}
{...}
type
TFoo = class
private
[JsonConverter(TSampleJsonConverter)]
FProp: Integer;
"FProp": "one"
"FProp": "two"
"FProp": 5
The converter also handles deserialization properly, i.e., if it reads value "one", it will set FProp as
1, and so on.
Collection of Objects
A collection of objects is represented as JSON array where each element is the representation of
an entity or the representation of an entity reference, or representation of any simple object type
supported by XData. An empty collection is represented as an empty JSON array.
When the server is responding to a request to an entity set resource address or a navigation
property that returns an entity collection, or any service operation that returns a list of arbitrary
objects, it wraps the collection in a JSON object with a name/value pair named "value":
{
"value": [
{
"$id": 1,
"@xdata.type": "XData.Default.Country",
"Id": 10,
"Name": "Germany",
},
{
"$id": 2,
"@xdata.type": "XData.Default.Country",
"Id": 13,
"Name": "USA",
}
]
}
Individual Properties
When performing requests to an URL that represents an individual property, such property is
represented as a JSON object (except for blob properties).
The property is represented as an object with a single name/value pair, whose name is "value"
and whose value is represented according to the XData JSON Format notation for property
values.
For example, when requesting the Name property of a TCustomer object (such as from address
"http://server:2001/tms/xdata/Customer(3)/Name"), result might be:
Note that when the URL is suffixed with "$value" segment or the property represents a blob
property, then the content is the raw value of the property (or binary value), not a JSON
representation.
Error Response
When an error happens in the XData server while processing a client request, the server might
provide information about the error in the HTTP message body as a single JSON object, with a
single name/value pair named "error" which value is also a JSON object. Such inline JSON object
might contain the following name/value pairs:
• "code", which provides a JSON string representing the error code for the error raised by
the server;
• "message", which provides a JSON string with a human-readable text describing the error.
Example:
{
"error": {
"code": "EntityNotFound",
"message": "Requested entity does not exist."
}
}
Canonical Id
The canonical id is a string representation that completely defines and identifies an Aurelius
entity. It's used by XData in several places in JSON format, like when using association
references.
The format of canonical id is "<entityset>(<id>)", where <entityset> is the name of the entity set
which contains the entity, and <id> is the id entity in URL literal representation. The entity set
must be the one which related entity type is the exact type of the entity being identifies. What
this means is that if an entity belongs to more than one entity set, the type of the entity set must
match the type of entity. For example, when dealing with inheritance between entity types, an
entity of type "Dog" might belong to entity sets "Dog", "Mammal" and "Animal". In this case, the
entity set must be "Dog".
"Invoice(5)"
"Customer('John')"
"InvoiceItem(15)"
http://server:2001/tms/xdata/Invoice(5)
http://server:2001/tms/xdata/Customer('John')
http://server:2001/tms/xdata/InvoiceItem(15)
Even though you can use the components at runtime, creating them from code, that would
usually be not necessary, as the components are just wrappers for the existing non-component
classes, like TXDataServerModule or IDBConnectionPool.
Since TMS XData is based on TMS Sparkle components, you might want to read about TMS
Sparkle design-time component usage, to learn more details about the architecture, like the
common features and middleware system usage.
From now on you can already create server-side business logic using service operations.
Optionally, if you wan to use TMS Aurelius and want to publish TMS Aurelius CRUD Endpoints,
follow the next steps:
8. Associated the TXDataServer component to the pool through the Pool property.
Now your XData server is able to publish database data automatically and you can also use the
TXDataOperationContext to use Aurelius to retrieve data from database.
You can also use the TAureliusConnection component to automatically generate Aurelius classes
from the database, right-clicking the component and choosing "Generate entities from the
database".
TXDataServer Component
TXDataServer component wraps the TXDataServerModule module. Basically all properties in the
component has a direct relation with the properties of the TXDataServerModule, in case of doubt
refer to TXDataServerModule reference to learn more details about each property.
ModelName: string Specifies the name of the model to be used to create the
TXDataServerModule instance.
DefaultExpandLevel: Integer All those property values will be used to set the a property
PutMode: TXDataPutMode with same name in the TXDataServerModule instance when
PostMode: TXDataPostMode it's created. Refer to TXDataServerModule topic to learn the
FlushMode: TXDataFlushMode purpose of each property.
ProxyLoadDepth: Integer
ProxyListLoadDepth: Integer
SerializeInstanceRef:
TInstanceRefSerialization
SerializeInstanceType:
TInstanceTypeSerialization
UnknownMemberHandling:
TUnknownMemberHandling
EntitySetPermissions: A collection where you can specify entity set permissions for
TEntitySetPermissionItems an entity set in particular. This will override the default entity
set permissions.
Events
Name Description
TXDataModuleEvent
TXDataModuleEvent = procedure(Sender: TObject; Module: TXDataServerModule) of obj
ect;
TGetPoolInterfaceEvent
TGetPoolInterfaceEvent = procedure(Sender: TObject; var Pool: IDBConnectionPool)
of object;
TXDataConnectionPool Component
TXDataConnectionPool component creates IDBConnectionPool instances using a
TAureliusConnection to create the IDBConnection interfaces.
Properties
Name Description
TPoolInterfaceEvent
TPoolInterfaceEvent = procedure(Sender: TObject; var Pool: IDBConnectionPool) of
object;
The XData Model describes all information the server provides, which are used to create
endpoints and to describe types and data received or returned by the server. This allows
additional flexibility, like for example, having a property name in XData to be different than the
property name in the mapped Aurelius class, or manually adding a service operation without
relying on ServiceContract/ServiceImplementation attributes.
The XData model in summary, describes the service operations available, the Aurelius entities
published from the CRUD endpoints, and the types it understands.
The following topics explain in more details what the XData model is about.
Services/Contracts
Services (specified by contracts) are a set of service operations, actions that are executed based
on a client request and HTTP method. The service contracts are defined by service interfaces
(contracts) and implemented by the server. For more information, see service operations.
Enum Type
Enum types are named scalar, primitive types that contain a list of name/value pairs which
indicates its valid values. For example, the enum type "TSex" might have two name/value pairs:
First is name "Male", with value 0, and second is name "Female", with value 1. Simple properties
which types are enum types can only receive such values.
Simple Property
A simple property is a property of a scalar type, belonging to an entity type. A simple property
can be of any primitive type as integer, boolean, string, etc., or enum types, and can have several
facets indicating for example if the property is nullable, or its maximum length. For example, an
entity type "TCustomer" can have a simple property "Name" of type string, and a simple property
"Sex" of enum type "TSex".
Navigation Property
A navigation property is a property that represents a relationship between two entity types. It
might relate to a single entity or a collection of other entities. It's the equivalent of an association
in Aurelius mapping, or a foreign key in a database definition. A navigation property has a target
property which points to another entity type. For example, an entity type "TOrder" might have a
navigation property named "Customer" which target type is the "TCustomer" entity type.
Using TXDataModelBuilder
XData server uses XData model to specify the data it will publish through the Rest/Json interface.
To work properly, the server module (TXDataServerModule object) needs a XData model,
represented by a TXDataAureliusModel to work properly.
However, you can create the TXDataServerModule without explicitly providing an entity model. In
this case, the server will create one internally, based on the default TMappingExplorer, using the
conventions of service operations and Aurelius CRUD endpoints. That's the most straightforward
and common way to use it, and you will rarely need to create an XData model yourself.
In the case you want to build your own model and create a server based on it (for example, when
you don't want classes and fields to have their "T" and "F" prefix removed from their names), you
can do it using the TXDataModelBuilder.
The following example creates a model based on the default mapping explorer, however it sets
properties UseOriginalClassNames and UseOriginalFieldNames to true to avoid the model
builder to remove the "T" prefix from class names when naming entity types, and remove "F"
prefix from field names when naming properties and navigation properties.
var
Builder: TXDataModelBuilder;
Model: TXDataAureliusModel;
Explorer: TMappingExplorer;
Module: TXDataServerModule;
begin
// model uses a TMappingExplorer instance with Aurelius mapping
// this example doesn't explain how to obtain one, please refer to Aurelius
documentation
Explorer := TMappingExplorer.DefaultInstance;
Model := TXDataAureliusModel.Create(Explorer);
try
Builder := TXDataModelBuilder.Create(Model);
try
Builder.UseOriginalClassNames := true;
Builder.UseOriginalFieldNames := true;
Builder.Build;
finally
Builder.Free;
end;
except
Model.Free; // destroy the model in case an error occurs
raise;
end;
When adding service operations to the model, the model builder will only take into
consideration those service interfaces that belong to the same model of the TMappingExplorer
instance you are using. For example, if your TMappingExplorer represents the model "Sample",
only service contracts marked for the "Sample" model will be included in the entity model.
You can use the methods and properties of TXDataModelBuilder to better define your XData
Model:
TXDataModelBuilder class
Declared in Unit XData.Aurelius.ModelBuilder .
UseOriginalClassNames: By default XData removes the leading "T" of the class name to
Boolean define the entity set name. So a class TCustomer will correspond
to the entity set "Customer". If you don't want this behavior and
wants the entity set name to be exactly the name of the class,
set this property to true.
UseOriginalContractNames: By default XData removes the leading "I" of the interface name
Boolean to define the url segment of a service operation. So an interface
ITestService will correspond to a service URL which path
segment will beign with "TestService". If you don't want this
behavior and wants the service operation base URL to be exactly
the name of the interface, set this property to true.
Methods
Name Description
function Creates a new entity set (CRUD endpoint) based on the class
AddEntitySet(AClass: TClass): defined by AClass. The class must be an Aurelius entity. This will
TXDataEntitySet automatically create the entity type associated with the entity
set.
procedure AddService<T> Adds a new service operation contract to the model. Example:
Model.AddService<IMyService>;
procedure Use this to remove an entity set already created by the model.
RemoveEntitySet(AClass: This is useful when you want to use the automatic creation of
TClass) entity sets, based on Aurelius entities, but then you want to
remove a few entity sets from the model.
For example, the following code creates two different TXDataServerModule modules, using two
different entity models:
XDataSampleModule := TXDataServerModule.Create('http://server:2001/tms/xdata/
sample',
SampleConnectionPool, TXDataAureliusModel.Get('Sample'));
XDataSecurityModule := TXDataServerModule.Create('http://server:2001/tms/xdata/
security',
SecurityConnectionPool, TXDataAureliusModel.Get('Security'));
HttpServer.AddModule(XDataSampleModule);
HttpServer.AddModule(XDataSecurityModule);
Note that this approach will filter all classes (entity types) belonging that specific model (for
example, XDataSampleModule will only publish entity types which classes are marked with
Model('Sample') attribute:
[Model('Sample')]
It will filter both service operations and Aurelius CRUD Endpoints according to the model name.
So XDataSampleModule will only have services operations which service interfaces are marked
with the Model('Sample') attribute.
• Restrict/change the data returned to the client by TMS Aurelius CRUD Endpoints, by
adding more filters to a query made by the user, for example;
• Implement additional server-side logic, for example, performing extra operations after a
resource is saved.
Events in XData are available in the Events property of the TXDataServerModule object. Such
property refers to a TXDataModuleEvents (declared in unit XData.Module.Events ) object with
several subproperties, each to them related to an event.
Read Using Events for more detailed info. You can also see real-world usage of events by
checking the Authentication Example using JSON Web Token (JWT).
Events in TXDataModuleEvents
General-purpose events
Name Description
OnModuleException Occurs when an exception is raised during request processing. You can
use it to provide custom error-handling.
Name Description
OnEntityGet Occurs after an entity is retrieved, right before being sent to the client.
Listeners are method references that receive a single object as a parameter. Such object has
several properties containing relevant information about the event, and differ for each event
type. Names of event properties, method reference type and arguments follow a standard. The
event property is named "On<event>", method reference type is "T<event>Proc" and parameter
object is "T<event>Args". For example, for the "EntityInserting" event, the respective names will
be "OnEntityInserting", "TEntityInsertingProc" and "TEntityInsertingArgs".
All events in XData are multicast events, which means you can add several events handlers
(listeners) to the same event. When an event occurs, all listeners will be notified. This allows you
to add a listener in a safe way, without worrying if it will replace an existing listener that might
have been set by other part of the application.
It's always safe to set the events before adding the module and running the server.
Example:
Module.Events.OnEntityGet.Subscribe(
procedure(Args: TEntityGetArgs)
begin
// code here
end
);
TEntityGetArgs Properties:
Name Description
OnEntityList Event
Occurs when the client queries an entity collection. This event is fired after the TCriteria object is
built based on the client request, and right before the criteria is actually executed to retrieve the
entities and sent to the client.
Example:
Module.Events.OnEntityList.Subscribe(
procedure(Args: TEntityListArgs)
begin
// code here
end
);
TEntityListArgs Properties:
Name Description
Criteria: TCriteria The Aurelius criteria built based on client request that will be
used to retrieve the collections. You can modify the request
here, adding extra filters, orders, etc., before it's executed and
results are sent to the client.
Example:
Module.Events.OnEntityInserting.Subscribe(
procedure(Args: TEntityInsertingArgs)
begin
// code here
end
);
TEntityInsertingArgs Properties:
Name Description
OnEntityInserted Event
Occurs after an entity is created. Note that, unlike OnEntityInserting Event, this event happens
after the transaction is committed. There is no way to rollback the insertion of the entity, and
any database operation here will be performed with no active transaction (unless you begin one
manually).
Example:
Module.Events.OnEntityInserted.Subscribe(
procedure(Args: TEntityInsertedArgs)
begin
// code here
end
);
TEntityInsertedArgs Properties:
Name Description
OnEntityModifying Event
Occurs right before an entity update. This event happens in the middle of a database transaction,
right before the entity is about to be updated in the database.
Module.Events.OnEntityModifying.Subscribe(
procedure(Args: TEntityModifyingArgs)
begin
// code here
end
);
TEntityModifyingArgs Properties:
Name Description
OnEntityModified Event
Occurs right after an entity update. Note that, unlike OnEntityModifying Event, this event
happens after the transaction is committed. There is no way to rollback the update of the entity,
and any database operation here will be performed with no active transaction (unless you begin
one manually).
Example:
Module.Events.OnEntityModified.Subscribe(
procedure(Args: TEntityModifiedArgs)
begin
// code here
end
);
TEntityModifiedArgs Properties:
Name Description
OnEntityDeleting Event
Occurs right before an entity delete. This event happens in the middle of a database transaction,
right before the entity is about to be deleted in the database.
Example:
TEntityDeletingArgs Properties:
Name Description
OnEntityDeleted Event
Occurs after an entity is deleted. Note that, unlike OnEntityDeleting event, this event happens
after the transaction is committed. There is no way to rollback the deletion of the entity, and any
database operation here will be performed with no active transaction (unless you begin one
manually).
Example:
Module.Events.OnEntityDeleted.Subscribe(
procedure(Args: TEntityDeletedArgs)
begin
// code here
end
);
TEntityDeletedArgs Properties:
Name Description
OnModuleException Event
Occurs when an unexpected exception is raised during server request processing. By default,
when that happens XData will send a response to the client with the property HTTP status code
(usually 500 but other codes might be provided as well) and a JSON response with details of the
error (a JSON object with properties ErrorCode and ErrorMessage). You can use this event to
change that behavior when a specific exception occurs.
Example:
TModuleExceptionArgs Properties:
Name Description
Exception: Exception The Exception object raised while processing the requested.
StatusCode: Integer The HTTP status code to be returned to the client. You can change
this property to tell XData to send a different status code.
ErrorCode: string The value of the ErrorCode property in the JSON response to be
sent to the client. You can modify this value.
ErrorMessage: string The value of the ErrorMessage property in the JSON response to
be sent to the client. You can modify this value.
Default value is SendError which means XData will send the HTTP
response to the client with the specified StatusCode and with a
JSON response that includes ErrorCode and ErrorMessage.
The third option is simply choose Ignore. Use this option if you
want to send a custom HTTP response yourself. In this case XData
will simply do nothing and finish processing request silently.
OnManagerCreate Event
Occurs when a TObjectManager instance is internally created by XData during request
processing. You can use this event to initialize settings in the object manager, or change some
behavior. For example, you can use this event to enable filters.
Example:
TManagerCreateArgs Properties:
Name Description
Manager: TObjectManager The TObjectManager instance that has just been created by XData.
The authentication/authorization mechanism in Sparkle is generic and can be used for any types
of HTTP server, not only XData. But this topic illustrates how to better use specific XData features
like server-side events and attributed-based authorization to make it even easier to secure your
REST API.
In this guide we will present the concept of JSON Web Token, then how to authenticate requests
(make sure there is a "user" doing requests) and finally how to authorize requests (make sure
such user has permissions to perform specific operations).
NOTE
Even though we are using JWT as an example, the authentication/authorization mechanism is
generic. You can use other type of token/authentication mechanism, and the authorization
mechanism you use will be exactly the same regardless what token type you use. Holger Flick's
book TMS Hands-on With Delphi shows a good example of authentication/authorization using
a different approach than JWT.
JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for
passing claims between parties in web application environment.
That doesn't say much if we are just learning about it. There is plenty of information out there, so
here I'm going directly to the point in a very summarized practical way.
aaaaaaaaaaa.bbbbbbbbbb.cccccccccc
It's just three sections in string separated by dots. Each section is a text encoded using base64-
url:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidG1zdXNlciIsImlzcyI6IlRNUyBYRGF0YSBTZXJ2ZXIiLCJhZG
If we decode each part of the JWT separately (remember, we have three parts separated by
dots), this is what we will have from part one (spaces and returns added to make it more
readable). It's the header:
And this is part two decoded, which is the payload or claims set:
{
"name":"tmsuser",
"iss":"TMS XData Server",
"admin":true
}
Finally the third part is the signature. It makes no sense to decode it here since it's just a bunch
of bytes that represent the hash of the header, the payload, and a secret that only the generator
of the JWT knows.
The payload is the JSON object that "matters", it's the actualy content that end-user applications
will read to perform actions. The header contains meta information the token, mostly the
hashing algorithm using to generate the signature, also present in the token. So, we could say
that a JWT is just an alternative way to represent a JSON object, but with a signature attached to
it.
What does it has to do with authentication and authorization? Well, you can think of the JWT as
a "session" or "context" for an user accessing your server. The JSON object in the payload will
contain arbitrary information that you are going to put in there, like permissions, user name, etc..
This token will be generated by your server upon some event (for example, an user "login"), and
then the client will resend the token to the server whenever he wants to perform any operation.
This would be the basic workflow:
1. Client performs "login" in the server by passing regular user credentials (user name and
password for example).
2. The server validates the credentials, generate a JWT with relevant info, using the secret,
and sends the JWT back to the client.
3. The client sends the JWT in next requests, passing the JWT again to the server.
4. When processing each request, the server checks if the JWT signature is valid. If it is, then
it can trust that the JSON Object in payload is valid and process actions accordingly.
Since only the server has the secret, there is no way the client can change the payload, adding
"false" information to it for example. When the server receives the modified JWT, the signature
will not match and the token will be rejected by the server.
For more detailed information on JSON Web Tokens (JWT) you can refer to https://jwt.io, the
Wikipedia article or just the official specification. It's also worth mentioning that for handling
JWT internally, either to create or validate the tokens, TMS XData uses under the hood the open
source Delphi JOSE and JWT library.
[ServiceContract]
ILoginService = interface(IInvokable)
['{BAD477A2-86EC-45B9-A1B1-C896C58DD5E0}']
function Login(const UserName, Password: string): string;
end;
Clients will send user name and password, and receive the token. Delphi applications can invoke
this method using the TXDataClient, or invoke it using regular HTTP, performing a POST request
passing user name and password parameters in the body request in JSON format.
WARNING
It's worth noting that in production code you should always use a secure connection (HTTPS)
in your server to protect such requests.
Now, users can simply login to the server by performing a request like this (some headers
removed):
{
"UserName": "writer",
"Password": "writer"
}
and the response will be a JSON object containing the JSON Web Token (some headers removed
and JWT modified):
HTTP/1.1 200 OK
Content-Type: application/json
{
"value":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoid3JpdGVyIiwic2NvcGUiOiJyZWFkZXI
gd3JpdGVyIiwiaXNzIjoiWERhdGEgU2VydmVyIn0.QdRTt6gOl3tb1Zg0aAJ74bepQwqm0Rd735FKToPE
yFY"
}
For further requests, clients just need to add that token in the request using the authorization
header by indicating it's a bearer token. For example:
NOTE
This authentication mechanism is a suggestion and is totally independent from the rest of this
guide. The JWT token could be provided in any other way: a different service, different server,
different sign-in mechanism (not necessarily username/password), or even a 3rd party token
provider.
At design-time, right-click the TXDataServer component, choose the option to manage the
middleware list, and add a JWT Middleware to it. The middleware has an OnGetSecret event
that you need to handle to pass to it the JWT secret our server will use to validate the signature
of the tokens it will receive.
If you are using the XData server module, all you need to do is to add a TJwtMiddleware and
inform the secret in the constructor:
{...}
Module.AddMiddleware(TJwtMiddleware.Create('secret'));
That's it. What this will do? It will automatically check for the token in the authorization header. If
it does exist and signature is valid, it will create the IUserIdentity interface, set its Claims
based on the claims in the JWT, and set such interface to the User property of THttpRequest
object.
WARNING
Regardless if the token exists or not and the User property is set or not, the middleware will
forward the processing of the request to your server. It's up to you to check if user is present
in the request or not. If you want the token to prevent non-authenticated requests to be
processed, set its ForbidAnonymousAccess to true.
If the token is present and is invalid, it will return an error to the client immediately and your
server code will not be executed.
Authorization
Now that we know how to check for authenticated requests, it's time to authorize the requests -
in other words, check if the authenticated client/user has permission to perform specific
operations.
Attribute-based Authorization
The easiest way to authorize your API is to simply add authorization attributes to parts of the
code you want to specify permissions.
Remember that a XData server has two mechanism for publishing endpoints: service operations
and automatic CRUD endpoints. Each of them has different ways to be authorized.
[ServiceContract]
IMyService = interface(IInvokable)
['{80A69E6E-CA89-41B5-A854-DFC412503FEA}']
[Authorize]
function Restricted: string;
end;
In the example above, the endpoint represented by the method NonRestricted will be publicly
accessible, while the method Restricted will only be invoked if the request is authenticated.
Otherwise, a 403 Forbidden response will be returned.
You can also apply the Authorize attribute at the interface level:
[ServiceContract]
[Authorize]
IMyService = interface(IInvokable)
['{80A69E6E-CA89-41B5-A854-DFC412503FEA}']
In this case, the attribute rule will be applied to all methods of the interface. In the example
above, both Restricted and AlsoRestricted methods will only be invoked if request is
authenticated.
AuthorizeScopes Attribute
You can use AuthorizeScope attribute in service operations if you want to allow them to be
invoked only if the specified scopes are present in user claims.
It will check for a user claim with name scope , and check its values. The scope values in scope
claim must be separated by spaces. For example, the scope claim might contain editor or
reader writer . In the last example, it has two scopes: reader and writer .
[AuthorizeScopes('admin')]
procedure ResetAll;
In the example above, the ResetAll method can only be invoked if the admin scope is present
in the scope claim.
[AuthorizeScopes('admin,writer')]
procedure ModifyEverything;
In the previous example, ModifyEverything can be invoked if the scope claim contain either
admin scope, or writer scope.
You can add multiple AuthorizeScopes attributes, which will end up as two requirements that
must be met to allow method to be invoked:
[AuthorizeScopes('publisher')]
[AuthorizeScopes('editor')]
procedure PublishAndModify;
For method PublishAndModify to be invoked, both scopes publisher and editor must be
present in the scope claim.
NOTE
Just like Authorize attribute and any other authorization attribute, you can apply
AuthorizeScopes attribute at both method and interface level. If you apply to both, then all
requirements set by the authorization attributes added to interface and method will have to
be met for the method to be invoked.
AuthorizeClaims Attribute
If you want to check for an arbitrary user claim, the use AuthorizeClaims attribute:
[AuthorizeClaims('admin')]
procedure OnlyForAdmins;
The OnlyForAdmins method will only be executed if the claim 'admin' is present in user claims.
You can also check for a specific claim value, for example:
[AuthorizeClaims('user', 'john')]
procedure MethodForJohn;
In this case, MethodForJohn will only be executed if claim user is present and its value is john .
As already described above, AuthorizeClaims attribute can be used multiple times and can be
applied at both method and interface level.
EntityAuthorize Attribute
You can also protect automatically created CRUD endpoints using authorization attributes. Since
those endpoints are based on existing Aurelius entities, you should apply those attributes to the
entity class itself.
[Entity, Automapping]
[EntityAuthorize(EntitySetPermissionsWrite)]
TCustomer = class
In the previous example, to invoke endpoints that modify the TCustomer entity (like POST , PUT ,
DELETE ), the request must be authenticated.
WARNING
The rules are applied by entity set permission. In the previous example, the read permissions
( GET a list of customer or a specific customer) are not specified, and thus they are not
protected. User don't need to be authenticated to execute them.
EntityAuthorizeScopes Attribute
Similarly to AuthorizeScopes, you can restrict access to CRUD endpoints depending on the
existing scopes using EntityAuthorizeScopes :
[Entity, Automapping]
[EntityAuthorizeScopes('reader', EntitySetPermissionsRead)]
[EntityAuthorizeScopes('writer', EntitySetPermissionsWrite)]
TArtist = class
In the previous example, read operations will be allowed if and only if the scope reader is
present. At the same time, the writer scope must be present to perform write operations.
That means that to perform both read and write operations, the scope claim must have both
reader and writer values: reader writer .
[Entity, Automapping]
[EntityAuthorizeScopes('reader,writer', EntitySetPermissionsRead)]
[EntityAuthorizeScopes('writer', EntitySetPermissionsWrite)]
TArtist = class
In the case above, if the scope just have writer , then it will be able to perform both read and
write operations, since read permissions are allowed if either reader or writer are present in
scope claim. Either approach is fine, it's up to you to decide what's best for your application.
[Entity, Automapping]
[EntityAuthorizeClaims('user', 'john', [TEntitySetPermissions.Delete]])]
TArtist = class
In the example above, only users with claim user equals john will be able to delete artists.
For example, suppose you have a service operation DoSomething that does an arbitrary action.
You don't want to allow anonymous requests (not authenticated) to perform such action. And
you will only execute such action is authenticated user is an administrator. This is how you would
implement it:
procedure TMyService.DoSomething;
var
User: IUserIdentity;
begin
User := TXDataOperationContext.Current.Request.User;
if User = nil then
raise EXDataHttpUnauthorized.Create('User not authenticated');
if not (User.Claims.Exists('admin') and User.Claims['admin'].AsBoolean) then
raise EXDataHttpForbidden.Create('Not enough privileges');
That applies to all entities. Of course, if you want to restrict the code to some entities, you can
check the Args.Entity property to verify the class of object being deleted and perform actions
accordingly.
Finally, another nice example for authorization and server-side events: suppose that every entity
in your application has a property named "Protected" which means only admin users can see
those entities. You can use a code similar to the one above to refuse requests that try to modify,
create or retrieve a protected entity if the requesting user is not admin.
But what about complex queries? In this case you can use the OnEntityList event , which will
provide you with the Aurelius criteria that will be used to retrieve the entities:
Module.Events.OnEntityList.Subscribe(procedure(Args: TEntityListArgs)
var
User: IUserIdentity;
IsAdmin: Boolean;
begin
User := Args.Handler.Request.User;
IsAdmin := (User <> nil) and User.Claims.Exists('admin');
if not IsAdmin then
Args.Criteria.Add(not Linq['Protected']));
end
);
The code above simply checks if the requesting user has elevated privileges. If it does not, then it
adds an extra condition to the criteria (whatever the criteria is) which filters only the entities that
are not protected. So non-admin users will not see the protected entities in the server response.
For now the importer can import Swagger 2.0 specification in JSON format.
The above code will parse the Swagger JSON, build a TCodeUnit object with the unit
information, then generate the file .pas file with the file name indicated by OutputFile .
// OnGetTypeName also allows you to provide a custom name for the DTO classes
Importer.OnGetTypeName :=
procedure(var TypeName: string; const Original: string)
begin
TypeName := 'T' + TypeName;
end;
var
Client: TXDataClient;
Request: TCreatePatchContactRequest;
begin
Client := TXDataClient.Create;
Client.Uri := 'https://api.infusionsoft.com/crm/rest/v2';
Request := TCreatePatchContactRequest.Create;
Request.FamilyName := 'Foo';
Client.Service<IKeapContact>.CreateContact(Request);
Request.Free;
Client.Free;
end;
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;
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;
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 ):
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, My
DataModule);
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.
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
AccessControlAllowOrigin: Specifies the accepted client hosts for which CORS will be
string 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.
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.
UnknownMemberHandling: Defines server behavior when receiving JSON from the client
TUnknownMemberHandling 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.
EnableEntityKeyAsSegment: When True, it's possible to address single entities by using the
Boolean URL format "/entityset/id" - in addition to the default "/
entityset(id)". Default is False.
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.
TUnknownMemberHandling
• TUnknownMemberHandling.Error: An InvalidJsonProperty error is raised if JSON contains
an invalid property. This is the default behavior.
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
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.
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
);
Alternatively, you can just provide an anonymous method that creates the IDBConnection
instead of providing the IDBConnectionFactory interface:
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
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:
// 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.
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
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
// The second parameter makes sure the data module will be destroyed
// when IDBConnection interface is released
Result := TDBExpressConnectionAdapter.Create(
MyDataModule.SQLConnection1, MyDataModule);
end
));
OpenAPI/Swagger Support
The XData server can optionally provide a JSON file containing the OpenAPI Specification (OAS,
formerly Swagger) for your whole server API. This opens up a lot of possibilities; usage of several
tools of the OpenAPI ecosystem is possible. The main one is Swagger UI: it is web front-end to
describe and test your API.
XDataServer1.SwaggerOptions.Enabled := True;
Alternatively, if you are using TXDataServerModule instead, you can just use the unit
XData.OpenAPI.Service and call the method RegisterOpenAPIService anywhere in your server
application:
{...}
RegisterOpenAPIService;
GET http://server:2001/tms/xdata/openapi/swagger.json
[ServiceContract]
[Route('arith')]
IArithmeticService = interface(IInvokable)
['{9E343ABD-D97C-4411-86BF-AD2E13BE71F3}']
[HttpGet] function Add(A, B: Double): Double;
[HttpGet] function Subtract(A, B: Double): Double;
[SwaggerExclude]
[HttpGet] function NotThisFunctionPlease: Integer;
end;
In the example above, methods Add and Subtract will be added to the documentation, but
method NotThisFunctionPlease will not appear.
Enabling Swagger UI
XData server can optionally publish and endpoint to provide a SwaggerUI web-based interface
that works as both a full documentation of your API as well a test interface. Swagger UI is a web-
based front-end to dynamically document and test your API. Quoting their website:
To enable SwaggerUI support in your server, just set the property SwaggerUIOptions.Enabled to
true in your TXDataServer component, either in object inspector or from code:
XDataServer1.SwaggerUIOptions.Enabled := True;
{...}
RegisterSwaggerUIService;
Using SwaggerUI
Once enabled, SwaggerUI is available in the address "/swaggerui" relative to your server base
path. Just open your browser and go to the address:
http://server:2001/tms/xdata/swaggerui
XDataModule.SwaggerOptions.AuthMode := TSwaggerAuthMode.Jwt;
Only available property is AuthMode and options are TSwaggerAuthMode.Jwt or None. When Jwt
is enabled, a new security definition named jwt requiring an Authorization header will be added
to all requests.
You can also configure the specification by passing parameters in the query part of the URL, and
they can be one of the following:
Name Description
ExcludeEntities Boolean parameter. Default is False. When True, the specification will not
contain the automatic CRUD operations on entities provided automatically
by XData (entity resources).
Example: /openapi/swagger.json?ExcludeEntities=True
ExcludeOperations Boolean parameter. Default is False. When True, the specification will not
contain any service operations.
Example: /openapi/swagger.json?ExcludeOperations=True
AuthMode String parameter. Options are "none" and "jwt". When jwt is specified, a
new security definition named jwt requiring an Authorization header will
be added to all requests.
Example: /openapi/swagger.json?authmode=jwt
Configuring SwaggerUI
The SwaggerUI interface can also be configured using the SwaggerUIOptions property of either
TXDataServer or TXDataServerModule. Available properties are:
Name Description
ShowFilter Boolean parameter. Default is False. When True, the SwaggerUI will display a
filter edit box to search for API operations.
DocExpansion Enumerated parameter. Specifies the default expand level of the listed API
operations.
Valid values are: TSwaggerUIExpansion = (List, None, Full)
Default value is List.
TryOutEnabled When True, SwaggerUI will already be in "try out" mode automatically. Default
is false, which requires clicking the button "Try out" to test the endpoint.
Examples:
XDataModule.SwaggerUIOptions.Filter := True;
XDataModule.SwaggerUIOptions.DocExpansion := TSwaggerUIExpansion.None;
/// <summary>
/// Retrieves the sine (sin) of an angle
/// </summary>
/// <remarks>
/// Returns the Sine (Sin) value of an angle radians value.
/// The value returned will be between -1 and 1.
/// If the angle is zero, the returned value will be zero.
/// </remarks>
/// <param name="Angle">
/// The angle in radians.
/// </param>
function Sin(Angle: Double): Double;
And Delphi IDE will automatically use it for Help Insight, showing you information about the
method on-the-fly. For example, if some developer is trying to use the Sin method of your API,
information will be conveniently displayed:
...
TXDataModelBuilder.LoadXmlDoc(XDataServer.Model);
Add the line at the beginning of your application, be it in your dpr file, or initialization section of
some unit, or in the OnCreate event of your TDataModule that contains your XData/Sparkle
components. In the first parameter you must provide the XData model you want to import XML
files to. In the example above, XDataServer is a TXDataServer component. If you are using multi-
model design, just provide the proper model there.
TXDataModelBuilder.LoadXmlDoc(XDataServer.Model, 'C:\MyXMLFiles');
Once you do that, if you check your Swagger documentation via Swagger-UI, you will see
something like this:
Note how the content of summary tag goes directly at the right of the GET button, as a summary
for the endpoint.
And the content of each param tag explains each respective parameter. Sweet!
But, if for some reason you want to use different documentation content for Delphi developers
and for REST API users, that's also possible. You can use the specific swagger tag to fill specific
parts of the documentation. You then use the name attribute to differentiate between swagger
tags.
And swagger tags with no name attribute (A), or name param-A (B), param-B (C) and remarks (D)
will be used exclusively for Swagger documentation:
By default, the name of the tag will be path segment of the interface service. But you can change
it using swagger tag with tag-name attribute.
The description of the tag by default is empty, but you can define it using the regular summary
tag, or optionally using the swagger tag with tag-description attribute.
Note that you can use Markdown syntax to format the final text. Here is what it will look like:
Even though the web application generated by TMS Web Core can run 100% stand alone in the
browser, in many scenarios it needs data to work with, and such data needs to be retrieved from
a server (and eventually sent back for modifications). Usually this data communication is done
through a REST API Server - the web client performs requests using HTTP(S), and send/receive
data in JSON format.
TMS XData is the ideal back-end solution for TMS Web Core. It not only allows you to create
REST API servers from an existing database in an matter of minutes, but is also provides the most
complete and high-level client-side framework for TMS Web Core, including Delphi design-time
support with visual components, a TXDataWebClient component that abstracts low-level HTTP/
JSON requests in a very easy and high-level way of use, and a dataset-like optional approach
that simply feels home to Delphi users but still uses modern REST/JSON requests behind the
scenes.
The following topics cover in details how to use TMS XData Web-Client Framework and make
use of TMS XData servers from TMS Web Core applications.
XDataWebConnection1.URL := 'http://localhost:2001/tms/music';
TIP
Even though the examples above and below will show setting properties from code, since
TXDataWebConnection is available at design-time, you can set most of the properties
described here at design-time in object inspector, including testing the connection.
DataWebConnection1.Connected := True;
XDataWebConnection1.Connected := True;
// The following line will NOT work because connection
// is still being established asynchronously
PerformSomeRequestToXDataServer();
To make sure the component is connected and you can start performing requests to XData
server, you should use OnConnect and OnError events:
From TMS Web Core 1.6 and on, you can also use the OpenAsync method using the await
keyword. This will ensure the next line will be executed after OpenAsync is complete, even
though it's executed asynchronously:
await(XDataWebConnection1.OpenAsync);
// The following line will be executed
// after the connection asynchronously established
PerformSomeRequestToXDataServer();
procedure OnConnect;
begin
WriteLn('XData server connected succesfully!');
PerformSomeRequest;
end;
begin
XDataWebConnection1.URL := 'http://localhost:2001/tms/music';
XDataWebConnection1.Open(@OnConnect, @OnError);
end;
As stated previously, OpenAsync is the equivalent to Open that can be used with await :
OnRequest event
OnRequest event is called before every request about to be sent to the XData server. This is an
useful event to modify all requests at once. For example, it's often used to add authentication
information the request, like a authorization token with a JWT header:
Those headers will by default not be saved in the DFM, meaning you will lose that information if
you close/open the project, or even the form unit. This is for security purposes. In the exceptional
case you want information to be saved (and thus also loaded at runtime), you can set the Persist
property to True.
Using TXDataWebClient
After setting up the connection, you can use TXDataWebClient component to communicate with
the TMS XData Server from a TMS Web Core application. Note that the TXDataWebConnection
must be previously connected. Performing operations with TXDataWebClient won't
automatically setup the connection.
However, there are several differences. The first and main one is that all requests are performed
asynchronously. This means that when you call TXDataWebClient methods, you won't have a
function result provided immediately to you, but instead you have to use event or callback to
receive the result. Here is, for example, how to retrieve an entity (GET request) from server, more
specifically retrieve an object artist, from entity set "Artist", with id 1:
Second, set the OnLoad event of the component and add the code there to be executed when
request is completed. Also can be done at design-time.
Finally, execute the method that perform the operation. In this case, Get method, passing the
entity set name (Artist) and the id (1).
When the request is complete, the OnLoad event will be fired, and the result of the request (if it
does return one) will be available in the Response.Result property. That property is of type
JSValue, which can be any valid value. You will have to interpret the result depending on the
request, if you are retrieving a single entity, that would be a TJSObject. If you are retrieving a list
of objects, then the value can be a TJSArray, for example. You can alternatively use ResultAsObject
or ResultAsArray.
TXDataWebClient component is very lightweight, so you can use as many components as you
want. That means that you can drop one TXDataWebClient component in the form for each
different request you want to perform, so that you will have one OnLoad event handler
separated for each request. This is the more RAD and straightforward way to use it. In the case
you want to use a single web client component for many requests, you can differentiate each
request by the request id.
As of TMS Web Core 1.6, you can also use the Async version of all methods. It's the same name
but with the Async suffix, and you can then use the await keyword so you don't need to work
with callbacks:
Using RequestId
Each response in the OnLoad event will have a request id. By default the request id is the name
of the performed operation, in lowercase. So it will be "get" for get requests, "list" for list
requests, etc.:
If you want to change the default request id, you can pass a different one as an extra parameter
to the request, and check for it in the OnLoad event:
Handling errors
If you don't specify anything, all request errors that might happen will fire the OnError event of
the associated TXDataWebConnection component. This way you can have a centralized place to
handle all request errors for that XData server.
But if you want to add error-handling code that is specific to a TXDataWebClient connection,
TXDataWebClient also provides an OnError event:
try
Resonse := await(XDataWebClient1.GetAsync('Artist', 1));
except
on E: Exception do ; // do something with E
end;
Using callbacks
Alternatively to using OnLoad and OnError events, you can use the request methods passing
callback as parameter. You can pass a callback for successful response, and optionally an extra
callback for error response (if you don't pass the error callback, it will fallback to the OnError
event).
procedure TForm1.GetArtistWithId1;
begin
XDataWebClient1.Get('Artist', 1, @OnSuccess, @OnError);
end;
Name Description
procedure Get(const EntitySet: Retrieves a single entity from the server (GET request), from
string; Id: JSValue) the specified entity set and with the specified id. Result will
be a TJSObject value.
procedure Get(const EntitySet, Retrieves a single entity from the server (GET request), from
QueryString: string; Id: JSValue) the specified entity set and with the specified id. Optionally
you can provide a QueryString parameter that must contain
query options to added to the query part of the request. For
get requests you would mostly use this with $expand query
option. Result will be a TJSObject value.
procedure List(const EntitySet: Retrieves a collection of entities from the specified entity set
string; const Query: string = '') in the server (GET request). You can provide a QueryString
parameter that must contain query options to added to the
query part of the request, like $filter, $orderby, etc.. Result
will be a TJSArray value.
procedure Post(const EntitySet: Inserts a new entity in the entity set specified by the
string; Entity: TJSObject) EntitySet parameter. The entity to be inserted is provided in
the Entity parameter.
procedure Put(const EntitySet: Updates an existing entity in the specified entity set. You
string; Entity: TJObject) don't need to provide an id separately since the Entity
parameter should already contain all the entity properties,
including the correct id.
procedure Delete(const Deletes an entity from the entity set. The id of the entity will
EntitySet: string; Entity: be retrieved from the Entity parameter. Since this is a remove
TJObject) operation, only the id properties are relevant, all the other
properties will be ignored.
For example, if in your server you have a service contract which is an interface named
"IMyService" which contains a method "Hello", that receives no parameter, you invoke it this way:
XDataWebClient1.RawInvoke('IMyService.Hello', []);
If the service operation provides a result, you can get it the same way as described above: either
using OnLoad event, or using callbacks:
begin
Client.RawInvoke('IMyService.Greet', ['My name'], @OnResult);
end;
Since we're in the web/JavaScript world, you must know in more details how results are returned
by service operations in JSON format.
As the example above also illustrates, you can pass the operation parameters using an array of
JSValue values. They can be of any type, inculding TJSObject and TJSArray values.
Just as the methods for the CRUD endpoints, you also have a RawInvokeAsync version that you
can use using await mechanism:
begin
Response := await(Client.RawInvokeAsync('IMyService.Greet', ['My name']);
GreetResult := string(TJSObject(Response.Result)['value']);
end;
Other properties
• property ReferenceSolvingMode: TReferenceSolvingMode
Specifies how $ref occurrences in server JSON response will be handled.
◦ rsAll: Will replace all $ref occurrences by the instance of the referred object. This is
default behavior and will allow dealing with objects easier and in a more similar way
as the desktop/mobile TXDataClient. It adds a small overhead to solve the
references.
◦ rsNone: $ref occurrences will not be processed and stay as-id.
Using TXDataWebDataset
In addition to TXDataWebClient, you have the option to use TXDataWebDataset to communicate
with TMS XData servers from web applications. It's even higher level of abstraction, at client side
you will mostly work with the dataset as you are used to in traditional Delphi applications, and
XData Web Client framework will translate it into REST/JSON requests.
Setting it up is simple:
XDataWebDataset1.Connection := XDataWebConnection1;
2. Set the value for EntitySetName property, with the name of the entity set in XData server you
want to manipulate data from. You can also set this at design-time, and object inspector will
automatically give you a list of available entity set names.
XDataWebDataset1.EntitySetName := 'artist';
3. Optionally: specify the persistent fields at design-time. As with any dataset, you can simply use
the dataset fields editor and add the desired fields. TXDataWebDataset will automatically retrieve
the available fields from XData server metadata. If you don't, as with any dataset in Delphi, the
default fields will be created.
And your dataset is set up. You can the use it in several ways, as explained below.
XDataWebDataset1.Load;
This will perform a GET request in XData server to retrieve the list of entities from the specific
entity set. Always remember that such requests are asynchronous, and that's why you use Load
method instead of Open. Load will actually perform the request, and when it's finished, it will
provide data to the dataset and only then, call Open method. Which in turn will fire AfterOpen
event. If you want to know when the request is finished and data is available in the dataset, use
the AfterOpen event.
NOTE
If you already have data in the dataset and want Load method to fully update existing data,
make sure the dataset is closed before calling Load method.
To filter out results, you can (and should) use the QueryString property, where you can put any
query option you need, including $filter and $top, which you should be use to filter out the
results server-side and avoiding retrieving all the objects to the client. Minimize the number of
data sent from the server to the client!
The datase also provides a property ServerRecordCount which might contain the total number of
records in server, regardless of page size. By default, this information is not retrieved by the
dataset, since it requires more processing at server side. To enable it, set ServerRecordCountMode:
XDataWebDataset1.ServerRecordCountMode := smInlineCount;
When data is loaded from the dataset (for example in the AfterOpen event), you can read
ServerRecordCount property:
procedure TForm1.LoadWithXDataClient;
begin
XDataWebClient1.List('artist', '$filter=startswith(Name, ''New'')',
@OnSuccess);
end;
But TXDataWebDataset you can have the modifications to be automatically and transparently
sent to the server. You just need to call ApplyUpdates:
XDataWebDataset1.ApplyUpdates;
This will take all the cached modifications (all objects modified, deleted, inserted) and will
perform the proper requests to the XData server entity set to apply the client modifications in
the server.
Other properties
Name Description
CurrentData: Provides the current value associated with the current row. Even though
JSValue CurrentData is of type JSValue for forward compatibility, for now
CurrentData will always be a TJSObject (an object).
EnumAsIntegers: This property is for backward compatibility. When True, fields representing
Boolean enumerated type properties will be created as TIntegerField instances.
Default is False, meaning a TStringField will be created for the enumerated
type property. In XData, the JSON representing an entity will accept and
retrieve enumerated values as strings.
Solving Errors
In the process of solving errors in web applications, it's important to always check the web
browser console, available in the web browser built-in developer tools. Each browser has its
own mechanism to open such tools, in Chrome and Firefox, for example, you can open it by
pressing F12 key.
You should also have in mind that sometimes the web application doesn't even show a visible
error message. Your web application might misbehave, or do not open, and no clear indication is
given of what's going on. Then, whenever such things happen or you think your application is
not behaving as it should, check the web browser console.
Here we will see common errors that might happen with web applications that connect to XData
servers.
As stated above, the first thing you should is open the browser console to check for more details
- the reason for the connection to fail. There are two common reasons for that:
CORS issue
The reason for the error might be related to CORS if in the browser console you see a message
like this:
This is caused because your web application files is being served from one host (localhost:8000 in
the example above), and the API the app is trying to access is in a different host (localhost:2001
in the example).
1. Modify either your web app or API server URL so both are in the same host. In the
example above, run your web application at address localhost:2001, or change your XData
API server URL to localhost:8000/tms/xdata.
1. Use HTTPS also in your XData server. It's very easy to associate your SSL certificate to your
XData server. If you don't have an SSL certificate and don't want to buy one, you can use a
free Let's Encrypt certificate in your XData server.
2. Revert your web app back to an HTTP server, so both are served through HTTP. Obviously,
for production environments, this is not a recommended option.
This wizard will connect to an existing XData server and extract meta information it with the list
of entities published by the server. With that information, it will create a responsive, Boostrap-
based TMS Web Core application that will serve as a front-end for listing entities published by
the server. The listing features include data filtering, ordering and paging.
To launch the wizard, go to Delphi menu File > New > Other..., then from the New Items dialog
choose Delphi Projects > TMS Business, and select wizard "TMS XData Web Application".
Once the server URL is provided and connecting, click Next to go to next page.
You can now compile and run the application, and of course, modify it as you want. This is a easy
and fast way to start coding with TMS Web Core and TMS XData backend. Here is a screenshot
of the generated application running in the browser, using the settings above:
Extra Resources
There are additional quality resources about TMS Web Core and TMS XData available, in
different formats (video training courses and books):
In this section:
What's New
Copyright Notice
Getting Support
Breaking Changes
• Improved: Validator attributes now can receive an option ErrorCode parameter in their
constructors.
• Improved: DTO validation messages now inform the full path of the property causing the
error. For example, instead of "field name must be required", it shows "field country.name
must be required", or "field customers[1].country.name must be required".
• Improved: Correct response status code (400) and better error message for invalid
parameter values sent via URL, including indicating the name of the offending parameter.
For example: Cannot deserialize value "x" from Url parameter "NumericId".
• Fixed: Deserialization of date time from JSON were raising wrong exceptions (Invalid
argument to date encode) for wrong date value, like "7777".
• Improved: WebCoreBlobClient demo now shows how to retrieve an image blob from
server using AJAX requests.
• Improved: Error message for JSON Schema generator now shows the position of JSON
document which caused the error.
• Improved: Incomplete JSON request bodies sent by the client now return HTTP status
code 400 (instead of 500).
• Improved: Better error message ("Missing parameter" instead of "Invalid JSON") when the
endpoint expects a single object parameter but no content body is sent.
• Improved: If all body parameters of an endpoint are optional (have default values), then
Swagger specification is not adding the single body parameter as required.
• Fixed: Web music demo was failing after login with "401 unauthorized" error.
• Fixed: Swagger documentation for DTO members were not being displayed when if the
members were declared in an ancestor class.
• Fixed: Some wrong JSON request bodies were still causing status code in response to be
500 instead of 400.
• Fixed: Some Access Violation error when lists were nil in DTOs.
• Fixed: TXDataClient not retrieving error message details in some situations (error 404 for
services that didn't return objects)
• Fixed: Service operations flagged as www-form-urlencoded were not decoding the plus
sign (+) as spaces from request body.
• Improved: For DTOs being sent in query params, attribute JsonInclude is not being taken
into consideration, so properties with default values are not included in query string.
• Improved: BREAKING CHANGE JWT is now requiring secret with a minimum size. If you
use a secret that is smaller than the minimum size, you should set JWT Middleware
property SkipKeyValidation to True.
• Improved: When JSON deserialization fails (for example, when a client sends wrong JSON
in request), the error message now shows the detailed position of the error (complete
path), making it easier for the client to know what part of the JSON was wrong. It also
returns status code 400.
• Improved: Entity validation now propagates the error message list to XData exception
handler and to clients as JSON response.
• Improved: RegularExpression validator now shows the regular expression in the error
message so users know what is the regex they have to match.
• Fixed: Validation of DTOs containing JSON classes (TJSONValue, TJElement) was causing
Access Validation.
• Fixed: Android/iOS XData client not correctly handling JSON responses (regression).
• Fixed: Eventual "Invalid Pointer Operation" (possibly causing database connections being
locked in the pool) when calling POST/PUT in CRUD endpoints. The combination to raise
the problem was rare, but could happen if the JSON sent by the client had errors and the
entity structure had lists.
http://server:2001/tms/xdata/Customer(3)?$select=Id,FirstName,Birthday
Results in
{
"Id": 3,
"FirstName": "Bill",
"Birthday": "1980-01-01"
}
• New: JWT (JOSE) for Delphi units updated. You can now use RSA and ECDSA signing
algorithms for both singing new JWT and also verify existing JWT signatures.
• Fixed: Swagger not working when service operations had PATCH methods.
• Fixed: TMS XData Web Application wizard was generating code that wasn't compilable
with latest versions of TMS Web Core.
• Fixed: Ordered dictionary converter was not creating new instances, only modifying
existing ones.
• Fixed: Serialization of TJSONObject boolean properties were failing when the value type
was TJSONBool , instead of TJSONTrue or TJSONFalse .
• Fixed: Serialization of polymorphic lists now correctly output the class name of the list
item if different from the base class.
• New: Now it's possible to close the HTTP response inside a XData service operation.
In this case, XData will not touch the HTTP response anymore.
• New: JsonManaged attribute allows flagging which associated objects in the DTO
should not be automatically destroyed by XData serializer/deserializer.
• New: Swagger now supports and generates documentation from comments in DTO
classes and properties.
• New: You can specify which DTO properties will be flagged as "required" in Swagger,
using the following XML comment:
• Improved: List and dictionary JSON serializers now automatically take OwnsObjects
property into account, and do not try to destroy the items if such property is set to true.
• Improved: Better error message when database connection pool was not properly
configured in XData server.
• Improved: JSON date-time deserialization now accepts dates in format YYYYMMDD and
time in format hhnnss .
• Improved: Swagger demo updated to show how to work with DTO documentation.
• Fixed: TXDataClient was sending float parameters via URL query with wrong formatting
depending on local settings (regression).
• Fixed: Music web demo was not compiling with latest Pas2JS/Web Core versions.
Customers := Client.List<TCustomer>(
CreateQuery
.From(TCustomer)
.Filter(
(Linq['Name'] = 'Paul')
or (Linq['Birthday'] < EncodeDate(1940, 8, 1))
)
.OrderBy('Name', False)
.Take(10).Skip(50)
.Expand('Country')
.QueryString
);
• New: Mechanism to accept XData query options in service operations. You can now
easily receive and process XData query syntax like $filter , $orderby , $top and $skip
in service operations, even for DTOs, and create Aurelius criteria from it:
{...}
• New: Service operations can now receive DTOs (Delphi objects) from the query
string of the request URL. You can now have service like this:
And have your Customer parameter be received from a request URL like
this: /CustomerService/FindByIdOrName?Id=10&Name='Paul' .
• Improved: Nullable types are now supported in plain Delphi objects (in addition to
existing support in Aurelius entities). When using plain Delphi objects as DTOs, like when
receiving or returning them in service operations, they are now properly serialized/
deserialized as JSON.
• Improved: Web Core stub for TXDataClientResponse was missing some properties.
• Fixed: XData enum values were ignoring JsonEnumValues attribute in Swagger Schema
and $filter queries.
• Fixed: Missing properties in XData entities created from Aurelius classes with
AbstractEntity attribute.
• Fixed: Swagger support was being enabled automatically in Linux projects. This was
causing an "AmbiguousActionError" exception when Swagger was also enabled in
TXDataServer component.
• Fixed: "DevTools failed to load source map" error message now removed from web
browser console log when executing TMS XData Music application.
Version 5.2.1
• Fixed: Query Builder wrongly creating enumeration literals around quotes ( 'tsMale' ).
• Fixed: Query Builder not finding properties from inherited classes when using From
method passing class types.
[AuthorizeScopes('admin,writer')]
procedure ModifyEverything;
[Authorize]
function Restricted: string;
end;
[Entity, Automapping]
[EntityAuthorizeScopes('reader', EntitySetPermissionsRead)]
[EntityAuthorizeScopes('writer', EntitySetPermissionsWrite)]
TArtist = class
• New: async/await methods in XData web client makes it even easier to build TMS
Web Core applications using XData as backend - the asynchronous requests to server
can be coded as if they were synchronous:
var
Response: TXDataClientResponse
GreetResult: string;
begin
await(XDataConnection.OpenAsync);
Response := await(Client.RawInvokeAsync('IMyService.Greet', ['My name']);
GreetResult := string(TJSObject(Response.Result)['value']);
end;
• New: Demo multitenancy shows how to create multitenant servers using XData in
an easy and straightforward way with the new TMultiTenantConnectionPool
implementation.
• Fixed: Deserialization of a JSON empty object would result in nil object, instead of an
empty (instantiated) object with default property values.
• Fixed: List serialization failed when list to be serialized was nil in some situations.
• Fixed: Batch operations not working in an optimized way when using connection
interfaces coming from XData connection pool.
• Fixed: TXDataWebDataset was not bringing ancestor persistent fields at design-time for
entity classes in a inheritance hierarchy.
• Fixed: SwaggerUI was sometimes displaying empty tags. This happened with Automatic
CRUD Endpoints, when no endpoint were set for an existing entity. Still, the tag for the
entity appeared.
• Fixed: Error "Cannot deserialize value from Url format" when invoking service operations
passing double values in URL - for example: ?a=1.6 ).
• New: SwaggerExclude attribute allows you to exclude specific endpoints from the
final Swagger API documentation.
• Improved: Service contract routing now allows replacing the root URL. Requests like "GET
/" or "POST /" can be now be configured to be routed to your own service
implementation.
• Improved: Swagger documents now take entity set permissions into account, omitting
Aurelius CRUD endpoints which are not available due to the entity set permissions
configuration.
• Improved: Parameters and function results of type TDate, in service contracts, will now
show in Swagger document as "date" instead of "date-time".
• Improved: EXDataClientException now contains property Info holding the JSON response
of the error.
• Fixed: Service operations returning objects and also with "out" params were returning
wrong JSON.
• Fixed: When loading objects using TXDataClient, proxied objects were not being loaded if
they were located at a depth level higher than the MaxEagerDepth configuration (default
to 3).
• Fixed: Wrong serialization of objects that indirectly inherited from List<T>. For example,
TList2 = class(TList1), TList1 = class(TList<TSomeClass>). Serialization of TList2 was wrong.
• New: Delphi 10.4 Sydney support. Full compatibility with the recently released new
Delphi version.
• New: XData server now returns a xdata-version header in each request. This will make
it easier to tell the available features of the XData server being connected to. Current
version returned is 2.
• Fixed: TXDataClient 404 error not raising exception when server methods did not return
objects (procedures, not functions).
• Fixed: Value endpoints (/$value) were not returning correct string values when the
content had double quotes.
• Fixed: Serialization of TArray<byte> and TBytes was not working correctly when the
application/server was compiled using runtime packages.
• Fixed: XData Web App Generator creating wrong persistent fields when XData server had
GUID field types.
• Fixed: TXDataWebClient not raising OnError events when an error happened during
RawInvoke call.
• Fixed: XData Web Application generator updated to work with latest Web Core 1.4.
• Fixed: XData Music Demo updated to work with latest Web Core 1.4.
• New: Support for TStream param mixed with other params. You can now have a
service operation that receives a TStream parameter to also receive more parameters, as
long as they are not defined to be in the request body (using FromBody parameter). This
way you can use the TStream to process the raw body of the request, but still receive other
parameters through the URL (either using FromQuery or FromPath attributes).
• New: Demo project showing how to use multiple models (multi-model design) in
XData.
• New: BasicAuth demo showing how to use Basic Authentication in both server and
client sides.
• Improved: Swagger doesn't include definition names for lists anymore (interface gets less
cluttered).
• Fixed: Swagger specification was wrong for entity sets (in automatic CRUD endpoint). It
was indicating an array of entities, but the correct format was an object with "value"
property and then array of entities, according to the specification.
• Fixed: Sending an object with a blob property set to null was not modifying the blob in
the database.
• Fixed: Generated XData Web Application was being created with title "TMS XData Music
Web Application". The word "Music" was removed from the title.
• Improved: SwaggerUI was displaying parameters as required even when they had default
values in service operations.
• Improved: TXDataWebDataset compatibility with TMS Web Core 1.3. There is a breaking
change between TMS Web Core 1.3 and 1.2 regarding dataset, which means this XData
version won't work with previous TMS Web Core versions (1.2 and lower).
• Fixed: TXDataClient and TXDataWebClient were building request URLs with double slashes
(//) in some requests. This didn't cause any errors but was wrong anyway.
• Fixed: TMS XData Music Demo failed to compile in TMS Web Core 1.3 (fixed in 4.9.1).
• Fixed: TMS XData Web Application wizard was generating code that didn't compile (fixed
in 4.9.1).
• Fixed: SQLiteConsoleServer demo (the "Music server" used in several demos, like the TMS
XData/Web Core Music Web Application) was not correctly importing Tracks into Albums.
• Improved: TXDataWebClient.Post now updates the key (id) properties of the object with
the generated value returned by the server. When calling TXDataWebClient.Post you pass
a Javascript object instance to the method, which will be serialized as JSON and sent to
the server to be inserted. If the server generates a new id for the object, then the
Javascript object instance passed to Post will have its id property automatically updated
with the id generated by the server. This has a good side effect on using
• Fixed: Swagger not including all service operation actions when the actions endpoints
were the same.
• New: Support for SwaggerUI provides a built-in web-based environment to test the
API from the browser.
• Improved: String and enumeration literals now can be sent in URL without being enclosed
by single quotes (e.g., CustomerByName?Name=John instead of CustomerByName?
Name='John').
• Improved: All demos refactored, now using the new non-visual components:
TXDataServer, TAureliusConnection, TXDataConnectionPool and
TSparkleHttpSysDispatcher, making them easier to understand for beginners and easier to
change them.
• Fixed: Type mismatch when passing an empty array to a service operation that receives
the dynamic array as var/out parameter.
• Fixed: XData Service wizard did not appear if there was no open project in IDE.
• Fixed: TXDataWebDataset now creates TFloatField field for XData properties of type
Currency, instead of TCurrencyField (unsupported in TMS Web Core clients).
• Fixed: XData Music Demo was issuing JSON Web Tokens with wrong expiration date/time.
• Fixed: Workaround for a bug in Delphi Rio causing JWT tokens to be always rejected due
to expiration (TJSONNumber.Null always returns true).
• Fixed: TXDataWebClient.RawInvoke did not allow to receive a full JSON object as a single
parameter.
• Fixed: XData Web App Application Wizard incorrectly created TExtendedField fields for
Int64 types on the server.
• Fixed: XData Service Wizard created new service units in the first project of a project
group. New services will be created in the currently active project now.
• New: Design-time support with a new set of components for XData: TXDataServer
and TXDataConnectionPool components.
• New: XData Service Operation wizard. Makes it very easy to create new service
operations (server-side business logic).
• Fixed: TXDataWebDataset did not set correct datetime values for the entity properties.
• Improved: XData Music Demo includes options for ordering, paging and filtering (search)
of listed records.
• Improved: "New TMS XData Server" Wizard adds commented code to include CORS and
Compress middleware. Uncomment the generated code to easily enable the features.
• Improved: URIPathSegment attribute with empty string can be inside a service contract; it
supports creating service operations at the "root" of the server.
• Fixed: Removed memory leaks in desktop client of the JWT Auth demo.
• Fixed: "New TMS XData Server" wizard updated with option to use new TMS Aurelius
native database drivers.
• Fixed: Servers with Int64 type in model were not supported when used from TMS Web
client (3.1.1).
• New: TXDataWebClient request methods (List, Get, Post, RawInvoke, etc.) now have
overloaded methods that accept callback functions.
• Improved: Dataset fields now supported in TXDataWebDataset. They will contain data (list
of objects) for associated lists (many-valued associations).
• Fixed: Exceptions that were not inherited from EXDataHttpException were wrongly
reporting an error code (regression).
• Fixed: Memory leaks when using mobile/Linux compilers (when using automatic reference
counting).
• Fixed: OnEntityModifying event was being fired before merge operation thus not
providing correct information about the object in manager and previous state. Breaking
change that can be disabled by using _FixEntityModifyingOnUpsert := False.
• Fixed: Deserialization of array of objects was keeping the same instance for all objects in
the array.
http://server:2001/tms/xdata/Customer?$filter=unaccent(Name) eq 'Andre'
• New: /$model built-in URL returns metadata for the whole XData API.
• New: MultiTentant Demo included in distribution shows how to use XData with multi-
tentant applications (multiple databases being accessed by the same server). The demo
shows how to specify the tentant by different URL or by using a custom HTTP header.
• Improved: A connection was being retrieved from the pool even when the context
manager was not being used by a service operation.
• Fixed: Service operations invoked via GET method using enumerated type parameters are
now supported.
• New: Several new types supported in service operations. Lots of new Delphi types can
now be (de)serialized to JSON, meaning that these types can be used in service
operations, either as input parameters, or as function results, or even as properties of
PODO objects sent/received. The new supported types include:
◦ Generic arrays: values of type TArray<T>, where T can be any supported type;
◦ Generics lists of primitive types: values of type TList<T>, where T can by any
supported type. In previous versions only lists of objects were supported;
◦ TStrings type.
• New: Support for default parameter values in service operations. You can now specify
default values as parameters in service operations, using the [XDefault] attribute, and
make them not required when invoking the service operations from non-Delphi clients
(default parameters were already supported in Delphi clients).
• New: JsonInclude attribute in PODO classes. You can configure how properties/fields
with default values will be serialized in PODO classes. This attribute makes it possible to
have a smaller JSON representation, by removing properties which value is null, zero,
empty string, etc. Usage example: [JsonInclude(TInclusionMode.NonDefault)].
• New: JsonEnumValues attribute. You can use this attribute to specify different values
when serializing enumerated values.
• Fixed: Error when using params of type TDate (TDateTime was not affected) in service
operations (Regression).
• Fixed: Design-time wizard icon not showing correctly in Delphi 10.2 Tokyo.
• New: JsonProperty and JsonIgnore attributes. When serializing regular Delphi objects
(DTO) to JSON, XData includes all class fields by default. You can use JsonProperty
attribute to add properties to the JSON (and/or change their name in JSON object) and
use JsonIgnore attribute to exclude fields from JSON.
• New: Support for passing parameters by reference in service operations. You can now
declare and use service operations (methods) that receive parameters by reference. A
server method like Swap(var A, B: Integer) is now supported for both server-side and
client-side (using TXDataClient or regular HTTP requests).
• New: Filter functions startswith, endswith and contains. You can now perform $filter
queries with those functions to search for substrings in entity fields. For example: ?
$filter=contains(Name, 'Walker') or startswith(Name, 'John').
• New: Filter function concat. You can now use concat function when performing queries
to concatenate two or more strings. For example: ?$filter=concat(concat(FirstName, ' '),
LastName) = 'James Smith'.
• Fixed: Using TStream as a parameter for service operations was causing "JSON converter
not found" error.
• Fixed: Location header of POST responses now uses host of the client request, not the
host configured in XData module.
Previous Versions
Version 2.4 (Mar-2017)
• New: Delphi 10.2 Tokyo Support.
• Fixed: Sporadic server error loading proxied lists sent by the client.
• Fixed: XData Server Wizard generating corrupted source code when TMS Component Pack
was installed.
• Fixed: RefCount property was wrongly being serialized in PODO objects from mobile
clients.
• New: Support for entities that have associations in ID (primary key containing foreign
keys).
• New: Support for Variant-type parameters in service operations when using GET HTTP
method.
• Improved: Errors on query syntax now return http code 400 instead of 500.
• Fixed: JSON Proxies (@xdata.proxy) sent by the client were not being solved when reading
such properties at server-side.
• Fixed: Association references (@xdata.ref) were not being solved when receiving entities in
service operations parameters.
• New: Server-Side Events allow subscribing listeners events and perform additional server-
side logic
• New: JWT (Json Web Token) authentication and Basic authentication, thanks to new TMS
Sparkle.
• New: Service operations can now receive and return TJSONAncestor objects (Delphi XE6
and up only). This allows full control over the JSON request and response.
• New: Service operations can now receive and return TCriteriaResult objects, making it easy
to return Aurelius query results that use projections.
• New: $inlinecount query option allow retrieving the total number of entities when using
paged results.
• Improved: All server-side operation (entity CRUD, service operation execution) are now
performed in database transactions.
• Fixed: Service operation using enumerated types as parameters or function results not
working properly.
• Fixed: EntitySet requests were not taking xdata-expandlevel header into consideration.
This is a breaking change.
• New: TXDataOperationContext Request and Response properties gives full control for
service implementations to customize the processing of client requests.
• Fixed: TXDataClient Get, Delete, Patch and Put operations were broken when using entities
with composite id.
• Fixed: POST requests not working correctly with entities with composite key.
• Fixed: Data Modification requests sending content-type header with parameter (for
example, ";charset=UTF8") were causing errors.
• Fixed: GET requests with query order was sometimes causing the same column to appear
multiple times in a "ORDER BY" SQL clause.
• New: Server support for "x-http-method-override" header (allowing clients to tunnel HTTP
methods to server through POST requests).
• Fixed: Issues with floating-point literals in Query URL with non-English server systems.
• Fixed: Service operations returning nil entities should respond with status code 404.
• New: Multi-Model design makes it easy to create multiple servers with different mapping,
types and service operations.
The component cannot be distributed in any other way except through the TMS Software web
site. Any other way of distribution requires a written authorization from the author.
No part of this help may be reproduced, stored in any retrieval system, copied or modified,
transmitted in any form or by any means electronic or mechanical, including photocopying and
recording for purposes others than the purchaser's personal use.
• Make sure to read this whole manual and any readme.txt or install.txt files in the
component distributions, if available.
• Search the TMS support forum and the TMS newsgroups to see if your question has not
been already answered.
• Specify which Delphi or C++Builder version you are using, and please also let us know
which OS you use.
• For registered users, use the special priority support email address (mentioned in
registration email) & provide your registration email & code. This will guarantee a fast
reply.
Getting support
For general information: [email protected]
Fax: +32-56-359696
For all questions, comments, problems, and feature request for our products:
[email protected]
IMPORTANT
All topics covered by this manual are officially supported and it's unlikely that future versions
will break backward compatibility. If this ever happens, all breaking changes will be covered in
this manual and guidelines to update to a new version will be described. However, it's
important to note that parts of the source code of this product that are undocumented are not
officially supported and are subject to change, which includes breaking backward
compatibility. In case you are using an unsupported/undocumented feature we will not
provide support for upgrading and will not officially support it.
Version 4.3
TXDataWebDataset now creates string fields (TStringField) for XData entity properties that are
enumerated types. In previous versions, integer fields were created. This is actually a bug fix.
Integer fields are used for enumerated types when using TMS Aurelius directly in desktop/
mobile applications, because an enumerated type in Delphi is, in the end, an ordinal (integer)
type.
For TMS Web Core, though, there are no entities, but a direct manipulation of the JSON returned
by XData. And in XData JSON representation, an enumerated type is represented as a string (the
enum name). For modifying or inserting such property in an entity, user need to provide the
string value.
This is a bug fix but breaks existing applications. If by any chance you need to keep the enum
fields as integers, set TXDataWebDataset.EnumAsIntegers property to true.
Version 3.0
This version is the first "official" (non-beta) support for TMS Web Core, and it has renamed two
key components:
If you have used a previous version and used the above components in TMS Web Core
applications, you might get an error "TXDataConnection not found" when opening the form
asking for Cancel or Ignore. Just click Cancel, close the project, and open the offending .pas/.dfm
in your preferred text editor (not Delphi - it can be Notepad, for example).
Then replace any occurrence of TXDataDataset with TXDataWebDataset, and any occurrence of
TXDataConnection with TXDataWebConnection, in both .pas and .dfm files. Save the files and this
will fix the issue.
Version 2.9
OnEntityModifying event was being fired before merge operation thus not providing correct
information about the object in manager and previous state. In case you want to go back to the
previous behavior, set _FixEntityModifyingOnUpsert global variable to false.
uses XData.Server.Module;
_FixEntityModifyingOnUpsert := False;
Version 1.5.2
This version fixes a bug that the header xdata-expandlevel was being ignored when returning
entity sets. Even though it's a bug fix, this is a breaking change as the server changed its
behavior.
Version 1.1
a. Example provided in Building The Entity Model topic has changed to illustrate how to correctly
build the model in version 1.1 (TXDataModelBuilder constructor and Build method changed the
signature).
b. Model is not owned by the TXDataServerModule class and must be now destroyed explicitly.
So in the following code:
now you need to destroy the model when the server is shutdown:
MyModel.Free;
Note this is not a critical issue because if there is a leak, it will only happen when the whole
server application is shutdown. Also, with the new multiple model feature, it's very rare that you
would need to create your own model explicitly.
// TXDataPutMode.Update
Manager.Update(ReceivedObject);
Manager.Flush;
// TXDataPutMode.Merge
Manager.Merge(ReceivedObject);
Manager.Flush;
{
"$id": 1,
"@xdata.type": "XData.Default.Product",
"Id": 10,
"Name": "Ball",
"Category1": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
},
"Category2": {
"$id": 2,
"@xdata.type": "XData.Default.Category",
"Id": 5,
"Name": "Toys"
}
}
In the example above, the client is sending a Product entity which has two category properties:
Category1 and Category2. Both properties are pointing to the same "Toys" category (id = 5) so
the PUT request is intending to set both product categories to "Toys".
With the previous behavior, the server would raise an error, because there are two different
instances of the same category in the product. With the new Merge behavior, the PUT request
will process just fine.
In any case, if you want to go back to the previous behavior you can just set
TXDataServerModule.PutMode property to TXDataPutMode.Update.