воскресенье, 17 ноября 2013 г.

Пример использования DataSnap Callback: запрос данных во время вызова серверного метода

Что это и для чего надо:
Работа с Datasnap  заключается в запросе от сервера данных и вызове серверных методов, например:
 - Запросить список товаров с сервера (dataset )
 - Создать новый документ
 - Добавить в него позиции
 - Закрыть документ

Иногда при добавлении позиции необходимо сделать дополнительный выбор, например выбрать партию товара или разрез.

    Это можно сделать через возврат кода ошибки (что-то вроде Prepare/Execute), чтобы клиент запросил пользователя, а затем попробовал снова выполнить операцию.
    Или же дать серверу возможность запросить клиента непосредственно во время операции все необходимые ему данные.

Первый вариант требует перечня кодов ошибок с соответствующей обработкой.
Второй вариант требует зарегистрировать клиенту функции обратного вызова, которые может вызывать сервер, а также при каждом обращении к серверу иметь в виду, что в ответ сервер может что-то потребовать от клиента (а это грозит блокировкой, если запрос требует реакции от пользователя). Для того, чтобы не было блокировок при однопоточном пользовательском интерфейсе, обращения к серверу нужно делать в отдельном потоке - тогда пришедший от сервера вызов можно синхронизировать с основным потоком (который уже не ждет возврата управления от исходного вызова серверного метода). А также не забывать сообщать серверу ID клиента.

Вызов:
procedure TForm2.btnRegisterWareClick(Sender: TObject);
begin
  TThread.CreateAnonymousThread(
    procedure()
    begin
      clmClient.ServerMethods1Client.RegisterWare(seWareID.Value, clmClient.DSClientCallbackChannelManager1.ManagerId)
    end).Start;
end;

Сама функция обратного вызова:
  TmyCallback = class(TDBXCallback)
  protected
    FSelectedString: Integer;
    procedure SelectString(const Arg: TJSONValue);
  public
    function Execute(const Arg: TJSONValue): TJSONValue; override;
  end;

{ TmyCallback }

function TmyCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  TThread.Synchronize(nil, procedure()  // запрос данных у пользователя - только в основном потоке
    begin
      SelectString(Arg);
    end);
  Result := TJSONNumber.Create(FSelectedString);
end;

procedure TmyCallback.SelectString(const Arg: TJSONValue);
var
  strs: TStringList;
  enum: TJSONPairEnumerator;
  val, str: string;
begin
  enum := TJSONObject(Arg).GetEnumerator;
  if Assigned(enum) then
  begin
    strs := TStringList.Create;
    try
      while enum.MoveNext do
      begin
        Val:= enum.Current.JsonString.Value;
        str := enum.Current.JsonValue.Value;
        strs.AddObject(str, TObject(val.ToInteger()));
      end;
      FSelectedString := TfrmSelectString.SelectString(strs);
    finally
      strs.Free;
    end;
  end;
  enum.Free;
end;

А вот что происходит на сервере:

procedure TServerMethods1.RegisterWare(ID: Integer; ClientID: string);
var
  Params, ParamsServ: TJSONObject;
  ResObj: TJSONValue;
  temp: TJSONValue;
begin
  ResObj := nil;
  Params := TJSONObject.Create;
  Params.AddPair(TJSONPair.Create('1', 'Размер 42'));
  Params.AddPair(TJSONPair.Create('2', 'Размер 43'));
  Params.AddPair(TJSONPair.Create('3', 'Размер 44'));
  ParamsServ := TJSONObject(Params.Clone);  //Делаем копию для себя, ибо ушедшую не вернуть
 ServerContainer1.DSServer1.NotifyCallback(ClientID, 'SelectString', Params, ResObj);
  if Assigned(ResObj) then
    begin
      temp := ParamsServ.GetValue(ResObj.Value);
      if Assigned(temp) then
        Form1.QueueLogMsg(Format('RegisterWare %d с разрезом %s', [ID, temp.ToString]))
      else
        Form1.QueueLogMsg(Format('RegisterWare %d без разреза', [ID]));
      ResObj.Free;
      ParamsServ.Free;
    end
  else
    Form1.QueueLogMsg(Format('RegisterWare %d без разреза', [ID]));
end;

И напоследок напоминание:
 - Локальные переменные процедур - не инициализируются
 - Всё, что вы передаете в функцию - можете попрощаться с ними и не освобождать
 - Всё, что вы получаете из функции - надо освободить (ведь тут ARC не работает (хотя, надо уточнить для мобилок))

С обратными вызовами можно делать что угодно - передавать в неё готовый набор данных или SQL-запрос, или имя справочника, из которого хранимая процедура вернет набор для выбора.

Код: https://github.com/VladimirSrednikh/datasnap-callback-with-ui

Так как пока с технологией жестко не определился, следующее изучение будет по TRealThinkClient

Комментариев нет: