Search Results for

    Show / Hide Table of Contents

    TXDataClient

    The TXDataClient object (declared in unit XData.Client) allows you to send and receive objects to/from a XData server in a high-level, easy-to-use, strong-typed way. From any platform, any development environment, any language, you can always access XData just by using HTTP and JSON, but if you are coding from Delphi client, TXDataClient makes it much easier to write client applications that communicate with XData server.

    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:

    uses {...}, XData.Client;
    
    {...}
    
    Client := TXDataClient.Create;
    Client.Uri := 'http://server:2001/tms/xdata';
    // <use client>
    Client.Free;
    

    The following topics explain how to use TXDataClient in details.

    Invoking Service Operations

    You can easily invoke service operations from a Delphi application using the TXDataClient class. Even though XData implements service operations using standards HTTP and JSON, which allows you to easily invoke service operations using HTTP from any client or platform, the TXDataClient makes it even easier by providing strong typing and direct method calls that in turn perform the HTTP requests under the hood.

    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.

    To invoke service operations, you just need to:

    1. Retrieve an interface using Service<I> method.

    2. Call methods from the interface.

    Here is an example of invoking the method Sum of interface IMyService, declaring in the topic "defining service interface":

    uses
      {...}
      MyServiceInterface,
      XData.Client;
    
    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.

    Client Memory Management

    Since method operations can deal with several types of objects, either Aurelius entities, plain old Delphi objects or even normal classes like TList<T> or TStream, it's important to know exactly how XData handles the lifetime of those objects, in order to avoid memory leaks or access violation exceptions due to releasing the same object multiple times.

    This is the behavior or TXDataClient when it comes to receiving/sending objects (there is a separated topic for server-side memory management).

    • 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.

    So consider the example below:

    var
      Customer: TCustomer;
      Invoices: TList<TInvoice>;
    
    {...}
    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:

    for Entity in Client.ReturnedEntities do {...}
    

    Working With CRUD Endpoints

    The following topics describe how to use TXDataClient to deal with TMS Aurelius CRUD Endpoints.

    Requesting a Single Entity

    To request a single entity, use the Get generic method passing the Id of the object as parameter:

    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:

    var
      InvoiceId: TGuid;
    begin
      { ... get invoice Id }
      Invoice := Client.Get<TInvoice, TGuid>(InvoiceId);
    end;
    

    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):

    Customer := TCustomer(Client.Get(TCustomer, 10)));
    

    Requesting an Entity List

    Use the List method to query and retrieve a list of entities from the server:

    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:

    Customers := Client.List<TCustomer>('$filter=(Name eq ''Paul'') or (Birthday lt 1940-08-01)&$orderby=Name desc');
    

    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:

    var
      TotalFishes: Integer;
      TotalCustomers: Integer;
    begin  
      TotalFishes := Client.Count(TFish);
      TotalCustomers := Client.Count(TCustomer, '$filter=(Name eq ''Paul'') or (Birthday lt 1940-08-01)&$orderby=Name desc');
    end;
    

    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
    

    Pay attention to client memory management to learn which objects you need to manually destroy. Client won't destroy objects passed to Delete 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.

    Using the Query Builder

    XData allows you to easily query entities using a full query syntax, either by directly sending HTTP requests to entity set endpoints, or using the List method of TXDataClient.

    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:

    Customers := Client.List<TCustomer>('$filter=(Name eq ''Paul'') or (Birthday lt 1940-08-01)&$orderby=Name desc');
    

    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:

    uses {...}, XData.QueryBuilder, Aurelius.Criteria.Linq;
    
    Customers := Client.List<TCustomer>(
      CreateQuery
        .From(TCustomer)
        .Filter(
          (Linq['Name'] = 'Paul') 
          or (Linq['Birthday'] < EncodeDate(1940, 8, 1))
        )
        .OrderBy('Name', False)
        .QueryString
      );
    

    Filter and FilterRaw

    The Filter method receives an Aurelius criteria expression to later convert it to the syntax of XData $filter query parameter. Please refer to Aurelius criteria documentation to learn more about how to build such queries. A quick example:

      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
    

    OrderBy and OrderByRaw

    Method OrderBy receives either a property name in string format, or an Aurelius projection. A second optional boolean parameter indicates if the order must be ascending (True, the default) or descending (False). For example:

      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
    

    Top and Skip

    Use Top and Skip methods to specify the values of $top and $skip query options:

      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.

    Subproperties

    If you need to refer to a subproperty in either Filter, OrderBy or Expand methods, just separate the property names using dot (.):

      CreateQuery.From(TCustomer)
        .Filter(Linq['Country.Name'] = 'Germany')
        .QueryString
    

    Results in $filter=Country/Name eq 'Germany'.

    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;
    {...}
    

    The following query will work ok:

      CreateQuery.From('Customer').Filter(Linq['the_name'] = 'Paul')
    

    While the following query will fail:

      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')
    

    Which will then result in the query string $filter=the_name eq 'Paul'.

    Client and Multi-Model

    When you create the TXDataClient object, it uses the default entity model to retrieve the available entity types and service operations that can be retrieved/invoked from the server. When your server has multiple models, though, you need to specify the model you are using when accessing the server. This is useful for the client to know which service interface contracts it can invoke, and of course, the classes of entities it can retrieve from the server. To do that, you pass the instance of the desired model to the client constructor:

    Client := TXDataClient.Create(TXDataAureliusModel.Get('Security'));
    

    See topic "Multiple servers and models" for more information.

    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;
    

    Legacy Basic authentication

    TXDataClient class provides you with the following properties for accessing servers protected with basic authentication.

    property UserName: string;
    property Password: string;
    

    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.

    Ignoring Unknown Properties

    TMS XData allows you work with the entity and DTO classes at client-side. Your client application can be compiled with the same class used in the server, and when a response is received from the server, the class will be deserialized at client-side.

    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 server then sends the JSON with an additional Foo property, but the client was not updated and it doesn't recognize such property, because it was compiled with an old version of TCustomer class.

    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;
    
    In This Article
    Back to top TMS XData v5.21
    © 2002 - 2025 tmssoftware.com