Problem with Async concurrency of Dataset

Im trying to wait for a variable called compId to be set before we can access it.
Right now we tried using a simple while loop to check peridiodically if the variable is assigned.

We tried using:

TWebTimer

AsyncSleep

The problematic function is: function TDataModule1.CompIdFromJWT(): string;
Note that we can access the compId value from the procedure TDataModule1.CompIdFromJWTDatasetAfterOpen(DataSet: TDataSet); if there is no concurrency.

unit Help;

interface

uses
  System.SysUtils, System.Classes, WEBLib.CDS, WEBLib.Cookies, WEBLib.Modules,
  WEBLib.Forms, Vcl.Forms, Data.DB, System.DateUtils, WEBLib.REST, WEBLib.Miletus,
  JS, WEBLib.ExtCtrls;

type
  TDataModule1 = class(TWebDataModule)
    CompIdFromJWTDataset: TWebClientDataSet;
    ConnectionCompIdFromJWT: TWebClientConnection;
    timer: TWebTimer;
    procedure ConnectionCompIdFromJWTBeforeConnect(Sender: TObject);
    [async]
    procedure CompIdFromJWTDatasetAfterOpen(DataSet: TDataSet);

  private
    { Private declarations }
  public
    compId: string;
    procedure setBearerHeader(requestRef: TWebClientConnection);
    function GetCookie(cookie_name: string): string;
    procedure SetCookie(cookie_name, value: String; minutes: UInt64);
    procedure BeforeConnectionAction(AForm: TWebForm);
    procedure BeforeConnectionActionSingle(ClientConn: TWebClientConnection);
    [async]
    function ReadIniFile(const FileName, Section, Key: string): string;

    [async]
    function CompIdFromJWT(): string;
  end;

var
  h: TDataModule1;

implementation

{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDataModule1.setBearerHeader(requestRef: TWebClientConnection);
begin requestRef.Headers.AddPair('authorization', 'Bearer ' + GetCookie('bearer'));
end;

procedure TDataModule1.CompIdFromJWTDatasetAfterOpen(DataSet: TDataSet);
begin CompIdFromJWTDataset.First;
  if Assigned(CompIdFromJWTDataset.FieldByName('value')) then begin
      compId := CompIdFromJWTDataset.FieldByName('value').Text;
    end;
end;

procedure TDataModule1.ConnectionCompIdFromJWTBeforeConnect(Sender: TObject);
begin ConnectionCompIdFromJWT.URI := Format('http://localhost:2001/tms/xdata/CrmService/CompIdFromJWT?clientToken=%s', [GetCookie('bearer')]);
end;

function TDataModule1.GetCookie(cookie_name: string): string;
var
  Cookies: TCookies;
  Cookie:  TCookie;
begin Result := '';
  Cookies    := TCookies.Create;
  try
    Cookies.GetCookies;
    Cookie := Cookies.Find(cookie_name);
    if Assigned(Cookie) then
      Result := Cookie.value;
  finally
    Cookies.Free;
  end;
end;

procedure TDataModule1.SetCookie(cookie_name, value: String; minutes: UInt64);
var
  Cookies: TCookies;
begin Cookies := TCookies.Create;
  Try
    Cookies.Add(cookie_name, value, IncMinute(Now, minutes));
    Cookies.SetCookies;
  Finally
    Cookies.Free;
  End;
end;

function TDataModule1.CompIdFromJWT(): string;
begin ConnectionCompIdFromJWT.Active := True;
  WriteLn('Before First');
  CompIdFromJWTDataset.Open;
  // while not(compId <> '') do begin
  // WriteLn(compId);
  // timer.Enabled := True;
  // end;
  WriteLn(compId);
  Result := compId;
end;

procedure TDataModule1.BeforeConnectionAction(AForm: TWebForm);
var
  i:          Integer;
  ClientConn: TWebClientConnection;
begin
  for i := 0 to AForm.ComponentCount - 1 do begin
      if AForm.Components[i] is TWebClientConnection then begin
          ClientConn := TWebClientConnection(AForm.Components[i]);
          // h.setBearerHeader(ClientConn);
          ClientConn.URI := Format('%s://%s:%s%s', ['http', 'localhost', '2001', ClientConn.URI])
        end;
    end;
end;

procedure TDataModule1.BeforeConnectionActionSingle(ClientConn: TWebClientConnection);
begin
  // h.setBearerHeader(ClientConn);
  WriteLn(ClientConn.URI);
  ClientConn.URI := Format('%s://%s:%s%s', ['http', 'localhost', '2001', ClientConn.URI])
end;

function TDataModule1.ReadIniFile(const FileName, Section, Key: string): string;
var
  IniFile: TMiletusINIFile;
begin Result := 'error';
  WriteLn('Entered read ini');
  try
    IniFile := TMiletusINIFile.Create(FileName);
    try
      WriteLn(TAwait.ExecP<boolean>(IniFile.SectionExists(Section)));
      if TAwait.ExecP<boolean>(IniFile.SectionExists(Section)) then begin
          WriteLn('Found section');
          if TAwait.ExecP<boolean>(IniFile.ValueExists(Section, Key)) then begin
              Result := TAwait.ExecP<String>(IniFile.ReadString(Section, Key, Result));
            end
          else
            raise Exception.CreateFmt('Key "%s" not found in section "%s"', [Key, Section]);
        end
      else
        raise Exception.CreateFmt('Section "%s" not found', [Section]);
    finally
      IniFile.Free;
    end;
  except
    on E: Exception do begin
        WriteLn(Format('Error reading INI file: %s', [E.Message]));
      end;
  end;
end;

end.

The DataSet.Open method is an aynch method but it does not have at this moment a variant returning a TJSPromise.
The AfterOpen event is triggered (asynchronously) after opening the dataset, so from this event, you can calculate compid.

My proposal would make life so much simpler TXDataWebDataset add a LoadAndAwait method - BIZ / BIZ Feature Requests - TMS Support Center (tmssoftware.com) :wink:

The AfterOpen is something we tried using, but still we get back to the same problem which we cannot wait for the AfterOpen to be executed and have results before reading the compId variable...

That's a fact, this needs to be implemented.

It's code all over the place when you have to use OnAfterOpen and you have a handful of datasets

1 Like

Vote here TXDataWebDataset add a LoadAndAwait method - BIZ / BIZ Feature Requests - TMS Support Center (tmssoftware.com)

@brunofierens Any update? This is a real roadblock which prevents implementation of security features...

Done

We are researching this.
I don't think it prevents anything, without promises, it just makes writing the code a bit more convoluted.

That is correct, you can do it all by using the AfterOpen, it is just that if you have a handful of datasets to open it does mean that the code can be complex, especially where not all the datasets need opening in every circumstance.

ApplyUpdatesAndWait is another one that would be really useful.

Yes, we are looking at all methods where it could be relevant.

1 Like