SOAP client for TS 4.0 in Delphi

Discuss problems installing or using TrackStudio.

SOAP client for TS 4.0 in Delphi

Postby Simon H » Tue Mar 29, 2011 5:54 pm

We have successfully implemented a SOAP interface to TS 4.0. It has been quite painful. Seeing other posts in this forum other are having grief too, particularly those not using Java. For good measure Delphi introduces its own gotchas. For anyone who might find it useful here are the results of our experience.

1. Overview and prerequisits.

A TrackStudio 4 installation is required. This post will assume that it is on http://localhost:8888/TrackStudio.

TS 4 uses SOAP 1.2 (which is different from TS 3.5). You need a 1.2 compatible tool

We used Delphi 2010. Our understanding is that this is the first Delphi version to support SOAP 1.2. There are various posts on the web about SOAP 1.2 with earlier Delphi versions, but it looks difficult.

I will take this is several stages
- Import WSDLs
- Proof of concept code
- Production quality class

Once a reader has successfully authenticated (as explained here) there are plenty clues in the imported WSDLs to help with using the interface and writing your SOAP client.

2. Import WSDLs

The WDSLs are the definitions of the SOAP interface. They are imported from the SOAP server, i.e. your instance of TrackStudio. Although the URL occurs in the imported files is is a namespace only, there being no connection to the website.

Must have a VCL application open in order to use the WSDL Importer.

In Delphi
File -> New -> Other -> Delphi Projects -> WebServices -> WSDL Importer
Components -> Import WSDL......

If Web Services does not exist
Component -> Install packages -> Embarcadero SOAP Components -> check
If get error "dbExpress140.bpl does not exist" then run dbpack_setup.exe. I found it in:
C:\Documents and Settings\All Users\Application Data\{2D559015-4C05-4AE5-8C8B-7E13E1EAB09D}\db pack installer\mFileBagIDE.dll\bag\
The serial no required is "Digital rights" not "Serial Number(s) from your Delphi purchase confirmation.

In WSDL Importer (if icon is disabled, open a VCL application)
URL is case-sensitive and like
Everything else can be left as defaults or blank.
Proceed to Finish.

A unit "User1.pas" should have been created and be showing in the Delphi IDE.

Near the bottom of the file comment out the line
Code: Select all
//InvRegistry.RegisterInvokeOptions(TypeInfo(User), ioDocument);

Please don't ask how I discovered this as the answer includes non-diplomatic language.

Save the new unit into your project.
Tip: Create a subfolder within the project for WSDL imports.

Repeat steps 2.2 to 2.6 for any other interfaces that you may need. Replace "User" in the URL with the interface name, e.g. http://localhost:8888/TrackStudio/services/Find?wsdl. I imported the following interfaces, but there may be others available:
Note that they are case sensitive in the URL

3. Proof of concept application

The first action of any TrackStudio SOAP client is to call User.authenticate to get a SessionID which is used in all subsequent calls.
The next call obtains a UserID, which is frequently used.
Finally to prove that correct information is obtained the child users of UserID are obtained and displayed.

Code: Select all
  User1, SOAPHTTPClient;

procedure TMainPage.TestButtClick(Sender: TObject);
  LSessionID, LUserID: string;
  LChildren: Array_Of_userBean;
  i: integer;

  // Login to SOAP for TrackStudio 4.0

  // Get RIO
  LRIO := THTTPRIO.Create(nil);
  LRIO.URL := 'http://localhost:8888/TrackStudio/services/User'

  // Authenticate
  LSessionID := (LRIO as User).authenticate(<username>,<password>);
  ShowMessage('Authenticate return: ' + LSessionID);

  // Get UserID
  LUserID := (LRIO as User).getUserID(LSessionID);
  ShowMessage('User ID: ' + LUserID);

  // Get names of child users
  LChildren := (LRIO as User).getChildrenUsers(LSessionID, LUserID);

  // Display the names
  for i := 0 to High(LChildren) do
    ShowMessage('Child: ' + LChildren[i].name_);

Replace <username> and <password> with values for a valid TrackStudio login

The first few lines could be alternatively written as, which does exactly the same thing.

Code: Select all
  LUser: {User1.}User;
  LUser := GetUser();
  //  or GetUser(false, 'http://localhost:8888/TrackStudio/services/User')
  LSessionID := LUser.authenticate('username','password');

3.3 Freeing the THTTPRIO

The following is copied from the THTTPRIO help

// There is no need to explicitly free a THTTPRIO instance.
// If the THTTPRIO object is created with an Owner, then that Owner handles the
// freeing of the THTTPRIO instance. If the THTTPRIO object is created without an
// Owner, then it is automatically freed when the reference count on its interface
// drops to zero.

Calling LRio.Free will cause an exception, but not necessarily when Free is called.

3.4 Exception classes

An Exception class is declared in the imported WSDL (User1.pas). If the normal Delphi Exception is used in your unit you might get a compiler error. To fix use "SysUtils.Exception" for Delphi exceptions and "User1.Exception" is you use the TrackStudio SOAP class.

4 Production quality class

Here are some essential methods from our production code. The functionality is authenticating and posting a message to a task. This extract will not compile alone, but readers might find some ideas useful.

Code: Select all
unit PostTrackStudioU;


  Rio, SOAPHTTPClient, SysUtils
    <units removed>
  {Trackstudio definitions}
  , Find1, Message1, Step1, Task1, User1

// From Help for THTTPRIO
// There is no need to explicitly free a THTTPRIO instance.
// If the THTTPRIO object is created with an Owner, then that Owner handles the
// freeing of the THTTPRIO instance. If the THTTPRIO object is created without an
// Owner, then it is automatically freed when the reference count on its interface
// drops to zero.
// Error handling
// Individual posts are trapped as it is possible that individual posts will
// fail because they are to tasks that the user may not post to.  In this
// case the error and task number are logged in FErrorMessage.  Any other
// errors (for example login to TrackStudio) are trapped in TCustomPost.Post()
// and the whole post would be abandoned.


  TPostTrackStudio = class(TCustomPost)
    FSessionID: string;
    FUserID: string;
    FMessageID: string;  // ID for Time Message in TrackStudio
    procedure CreateTimeMessage(ATaskID: integer; AUserID: widestring;
      ADate: TDatetime; AHours: double; const ANarrative: string);
    function GetRio(const AService: string): THTTPRIO;
    procedure Login;
    function PostRow: boolean; override;
    property SessionID: string read FSessionID;
    property UserID: string read FUserID;


{ TPostTrackStudio }

function TPostTrackStudio.GetRio(const AService: string): THTTPRIO;
{Return a new THTTPRIO with URL set for the required
TrackStudio service}
  LBaseURL: string;
  LBaseURL := MainData.PostingFileName.asString;
  if (Trim(LBaseURL) = '') then
    raise EException.Create('URL for Trackstudio not entered in Settings');

  Result := THTTPRIO.Create(nil);

  Result.URL := LBaseUrl + '/services/' + AService;

procedure TPostTrackStudio.Login;
  LPassword: string;
  {Login to SOAP server.  Save session ID in FSessionID}

  // Blank vars in case of error
  FSessionID := '';
  FUserID := '';

  // Validate login and password
  LLogin := MainData.PostingLogin.AsString;
  LPassword := MainData.PostingPassword.AsString;
  FMessageID := MainData.PostingMessageID.AsString;
  if (Trim(LLogin) = '') or (Trim(LPassword) = '') or (Trim(FMessageID) = '') then
    raise EException.Create('Login and Password for Trackstudio not entered in Settings');

  // Get URL for relevant service
  // See comment about freeing at the top
  LRIO := GetRIO('User');

  // Login
  FSessionID := (LRIO as User).authenticate(LLogin, LPassword);

  // Get ID of current user
  if (FSessionID <> '') then
    FUserID := (LRIO as User).getUserID(FSessionID);

function TPostTrackStudio.PostRow: boolean;

     FDataset.FieldByName('Task').AsInteger  // TaskID
     , FUserID
     , CurrentDate  // Assuming everything is for today at the moment
     , FDataset.FieldByName('Duration').AsDatetime * 24 // Duration field s in days, AHours parameter is in hours
     , 'GetNarrative(). This is the text for the notes field in the message.'

    // Success
    Result := true;
    // Log the error then continue processing.  Assume there is a problem with
    // this particular post.
    on e: SysUtils.exception do
      AddToErrorMessage('Task ' + FDataset.FieldByName('Task').Text + ': ' + e.Message);

      // Signal failure
      Result := false;

procedure TPostTrackStudio.CreateTimeMessage(ATaskID: integer; AUserID: widestring;
  ADate: TDatetime; AHours: double; const ANarrative: string);
  LTask: TaskBean;
  // Get taskid
  // See comment about freeing THTTPRIO at the top
  LRIO := GetRio('Task');  // Get RIO for relevant service

  // Create message
  LTask := (LRIO as Task).findTaskByNumber(FSessionID, IntToStr(ATaskID));

  if (LTask = nil) then
    Exit;  // Task not found

  // Create message
  // See comment about freeing THTTPRIO at the top
  LRIO := GetRio('Message');  // Get RIO for relevant service

  // Create message
  (LRIO as {$IFDEF TS3}Message1.Message{$ELSE}Message4.Message_{$ENDIF}).createMessage(
   , LTask.ID
   , FMessageID
   //, '4028818b1ea831fc011eb566ae6e0023'  // ID of "Time" message type, obtained using Step service getAvailableMstatusList() method
   , ANarrative
   , Round(AHours*3600)    // TS4 takes an interger interpreted as seconds.
   , LTask.handlerUserId   // If don't set this handler is changed to blank
   , ''
   , ''
   , ''
   , -1
   , 0
   , false);

Simon H
Posts: 51
Joined: Mon Aug 13, 2007 12:23 pm

Return to TrackStudio Support

Who is online

Users browsing this forum: No registered users and 1 guest