Web Core / XData Service Query Example v2

(Whoops, I guess because I marked the last one as 'solved' I can't add to it anymore?)

Alright, a bit more fiddling and I've replaced the WebCore side of the example with the following functions and everything seems to work just as I want now. So, to recap, I was looking to have a service endpoint from XData return a TStream that I could then use within WebCore as a dataset without having to know anything about the fields ahead of time. As I'm using FireDAC, the JSON coming through via the TStream already has everything needed, just a matter of parsing it.

My "Main Menu" example now has three functions in the client. These first two can be put somewhere like in a utility function unit or something that isn't specific to a form:

function GetQueryData(Endpoint: String; Dataset: TXDataWebDataSet):Integer;
var
  Conn:     TXDataWebConnection;
  Client:   TXDataWebClient;
  Response: TXDataClientResponse;

  JFDBS:       TJSObject;
  JManager:    TJSObject;
  JTableList:  TJSObject;
  JColumnList: TJSArray;
  JRowList:    TJSArray;

  StringFields:  Array of TStringField;
  IntegerFields: Array of TIntegerField;
  // Likely need a few more datatypes here

  i: Integer;
begin

  // Value to indicate the request was unsuccessful
  Result := -1;

  // Setup connection to XData Server
  Conn              := TXDataWebConnection.Create(nil);
  Conn.URL          := DM1.CarnivalCoreServer;          // could also be a parameter
  Conn.OnRequest    := PopulateJWT;                     // See below
  Client            := TXDataWebClient.Create(nil);
  Client.Connection := Conn;
  await(Conn.OpenAsync);

  // Make the Request 
  // Likely to have another version of function that includes more endpoint parameters
  try
    Response := await(Client.RawInvokeAsync(Endpoint, ['FireDAC']));
  except
    on Error: Exception do
    begin
      Client.Free;
      Conn.Free;
      console.log('...something is amiss...');
      exit;
    end;
  end;

  // Process the FireDAC-specific JSON that was returned
  JFDBS       := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']);
  JManager    := TJSObject(JFDBS['Manager']);
  JTableList  := TJSObject(TJSArray(JManager['TableList'])[0]);
  JColumnList := TJSArray(JTableList['ColumnList']);
  JRowList    := TJSArray(JTableList['RowList']);
  // Don't really want 'Original' in field names, so let's remove it from JSON first
  // Probably a better one-liner, but this seems to work
  for i := 0 to JRowList.Length - 1 do
    JRowList.Elements[i] := TJSObject(JRowList.Elements[i])['Original'];

  // We're assuming Dataset parameter is newly created and empty.  
  // First, add all the fields from JSON
  // NOTE: Very likely more datatypes need to be added here
  for i := 0 to JColumnList.Length-1 do
  begin
    if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'AnsiString') then
    begin
      // NOTE: Different datatypes may need different values set (eg: Size for strings)
      SetLength(StringFields, Length(StringFields) + 1);
      StringFields[Length(StringFields)-1] := TStringField.Create(Dataset);
      StringFields[Length(StringFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
      StringFields[Length(StringFields)-1].Size      := Integer(TJSObject(JColumnList.Elements[i])['Size']);
      StringFields[Length(StringFields)-1].Dataset   := Dataset;
    end
    else if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'Int32') then
    begin
      SetLength(IntegerFields, Length(IntegerFields) + 1);
      IntegerFields[Length(IntegerFields)-1] := TIntegerField.Create(Dataset);
      IntegerFields[Length(IntegerFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
      IntegerFields[Length(IntegerFields)-1].Dataset   := Dataset;
    end
    else
    begin
      console.log('ERROR: Field ['+String(TJSObject(JColumnList.Elements[i])['Name'])+'] has an unexpected datatype ['+String(TJSObject(JColumnList.Elements[i])['DataType'])+']');
    end;
  end;

  // Add the data and return the dataset as opened
  Dataset.SetJSONData(JRowList);
  Dataset.Open;

  // Just for fun
  Result :=  Dataset.RecordCount;

  // No dataset stuff to free as the dataset was created by the caller and 
  // all the fields created were created with that dataset as the parent

  Client.Free;
  Conn.Free;
end;

procedure PopulateJWT(Args: TXDataWebConnectionRequest);
begin
  Args.Request.Headers.SetValue('Authorization', JWT-From-Somewhere);
end;

With those out of the way, the previous RefreshMainMenu function is then much simpler, primarily just processing the dataset and not having to think about how it was retrieved:

procedure RefreshMainMenu;
var
  i: Integer;
  MenuGroup: TTreeNode;
  MenuGroupName: String;
  MenuDataset: TXDataWebDataSet;
  MenuRecords: Integer;
begin
  MenuDataset := TXDataWebDataset.Create(nil);
  MenuRecords := await(GetQueryData('ISomethingService.EndpointName', MenuDataset));

  if (MenuRecords = -1) then
  begin
     console.log('...Menu refresh not working...');
     exit;
  end;

  MenuDataset.First;
  MenuGroupName := '';
  MainMenuIndex.Text := '';
  MainMenuSearch.Text := '';
  treeMainMenu.BeginUpdate;
  treeMainMenu.Items.Clear;
  i := 0;
  while not(MenuDataset.EOF) do
  begin
    if (MenuDataset.FieldByName('GRP').AsString <> MenuGroupName) then
    begin
      MenuGroupName := MenuDataset.FieldByName('GRP').AsString;
      MenuGroup := treeMainMenu.Items.Add(MenuGroupName);
      SetLength(MainMenuNodes, i+1);
      MainMenuNodes[i] := MenuGroup;
      i := i + 1;
    end;
    SetLength(MainMenuNodes, i+1);
    MainMenuNodes[i] := treeMainMenu.Items.AddChild(MenuGroup,MenuDataset.FieldByName('NAM').AsString);
    i := i + 1;
    MainMenuIndex.Add(RightStr('00000'+MenuDataset.FieldByName('REF').AsString,5)+':'+MenuDataset.FieldByName('NAM').AsString);
    MainMenusearch.Add(RightStr('00000'+MenuDataset.FieldByName('REF').AsString,5)+':'+MenuDataset.FieldByName('NAM').AsString+';'+MenuDataset.FieldByName('DSC').AsString);
    MenuDataset.Next;
  end;
  treeMainMenu.EndUpdate;
  MenuDataset.Close;
  MenuDataset.Free;
end;

So thanks everyone for your help and ideas!

Awesome @AndrewSimard! Very nice piece of code and I'm sure it will be useful for many other users. Thank you.

Hi Andrew,

thx for this good example, nice peace of code.

br
Michael