DataSnap 2010 – Enviando e recebendo objetos
Uma das perguntas frequentes dos usuários de Delphi 2009 e que utilizam DataSnap para criação de objetos é sobre a transferência de objetos entre cliente e servidor. No DataSnap 2009 estavamos limitados aos data types do dbxExpress, agora com o DataSnap 2010 que acompanha o Delphi 2010, isso é totalmente possível.
DataSnap 2010 traz o suporte a JSON (JavaScript Object Notation) que é uma formatação leve de troca de dados, totalmente independente de linguagem, futuramente vou comentar mais sobre JSON e suas vantagens, para começar este post irá mostrar como transferir objetos entre cliente e servidores DataSnap, sendo que ambos cliente e servidor são aplicações Delphi.
Para começar vamos definir uma classe chamada TCustomer.
unit Customer; interface uses DBXJSON, DBXJSONReflect, SysUtils; type TMaritalStatus = (msMarried, msEngaged, msEligible); TCustomer = class private FName: string; FAge: integer; FMaritalStatus: TMaritalStatus; public property Name: string read FName write FName; property Age: integer read FAge write FAge; property MaritalStatus: TMaritalStatus read FMaritalStatus write FMaritalStatus; function toString : string;override; end;
Para transferir objetos em DataSnap estes devem descender the TJSONObject, no caso de não ser um objeto descendente você terá que utilizar as classes TJSONMarshal e TJSONUnMarshal para efetuar a transformação dos objetos. Sendo assim os métodos abaixo irão efetuar a conversão dos mesmos.
unit Customer; function CustomerToJSON(customer: TCustomer): TJSONValue; var m: TJSONMarshal; begin if Assigned(customer) then begin m := TJSONMarshal.Create(TJSONConverter.Create); try exit(m.Marshal(customer)) finally m.Free; end; end else exit(TJSONNull.Create); end; function JSONToCustomer(json: TJSONValue): TCustomer; var unm: TJSONUnMarshal; begin if json is TJSONNull then exit(nil); unm := TJSONUnMarshal.Create; try exit(unm.Unmarshal(json) as TCustomer) finally unm.Free; end; end;
Com isso temos classe TCustomer pronta para trafegar entre cliente e servidor, assim sendo basta implementar um Server Method que retorne um TJSONValue a partir da transformação de TCustomer, como o exemplo abaixo.
// protected function TServerMethods.GetCustomer: TCustomer; begin Result := TCustomer.Create; Result.Name := 'Pedro'; Result.Age := 30; Result.MaritalStatus := msEligible; end; // public function TServerMethods.GetJSONCustomer(): TJSONValue; var myCustomer: TCustomer; begin myCustomer := GetCustomer; Result := CustomerToJSON(myCustomer); myCustomer.Free; end;
No lado cliente ao executar o método GetJSONCustomer será necessário efetuar a transformação de TJSONValue para TCustomer, utilizando o método JSONToCustomer.
var proxy: TServerMethodsClient; myJSONCustomer: TCustomer; begin try proxy := TServerMethodsClient.Create(SQLConnection1.DBXConnection); myJSONCustomer := JSONToCustomer(proxy.myJSONCustomer); Button1.Caption := myJSONCustomer.ToString; myJSONCustomer.Free; finally SQLConnection1.CloneConnection; proxy.Free; end; end;
Muito mais pode ser feito, como retornar Arrays de objetos, classes mais complexas, etc. Estarei abordando estes temas em futuros posts.
Download do código fonte
Olá Andreano, bem interessante esses novos recursos do Delphi 2010, a minha licença do novo Delphi deve estar chegando nessa semana, já estou aqui pirando para poder escrever meus serviços com datasnap. Alguns dias atrás eu escrevi um mini-artigo sobre exportação de dados de um dataset para um arquivo JSON utilizando versões anteriores ao Delphi 2010, utilizei a biblioteca lkjson, q é opensource. Se tiver interesse, segue meu blog: http://www.eversonnovka.com
[]’s
Oi Everson, não conheço em detalhes esta biblioteca mas parece interessante
Caro Andreano,
Ótimo post parabéns.
Uma pequena dúvida: Há algo pronto, no delphi 2010, para enviar/receber objetos mais complexos como por exemplo um TClientDataSet?
Obrigado.
Muito bom o artigo, estou querendo fazer algo parecido com um Browser, onde todos os meus formulários ficariam do lado do Servidor e eu teria no lado Cliente somente uma carcaça para ler esses objetos do Servidor e traduzir. Acho que isso é possível usando o JSON, vou pesquisar mais, de qq forma obrigado.
Boa tarde,
Teria algum exemplo para a conversão genérica de objetos para JSON?
Escrever dois métodos para para cada classe não me parece uma boa coisa. A conversão genérica de um objeto para JSON eu consegui, agora de JSON para um objeto está difícil…
Rafael, agora não tem um método genérico, mas você pode criar um utilizando generics 🙂
Para as próximas versões isso será transparente, mas você também terá a opção de controlar como converter.
Rafael acabei de publicar um post mostrando como fazer a conversão genérica.
Obrigado pelo comentário.
Tinha feito algo para parecido, também usando uma classe-base para herdar meus objetos, mas com generics ficou bem mais limpo.
Obrigado
Andreano.
Alguma consideração sobre a pergunto do Márcio Costa? Eu gostaria de tranferir objetos TClientDataSet ou TSQLQuery!
Oi Eduardo,
Na minha opinião não faz sentido, querer transferir TClientDataSet é a mesma coisa que transferir dados e isso ele já faz com o Provider, no caso de TSQLQuery é querer passar SQL isso você faz pelo ClientDataSet, server method ou outra forma.
Adreano,
Novamente lhe dando os parabéns pelo ótimo post, mas estou uma dúvida, terei que ter as classes tanto na aplicação cliente quanto na aplicação servidora?
Isso não seria um retrabalho?
Ter que refazer toda a estrutura de classes novamente ou no cliente ou no servidor.
Por exemplo, aqui tenho uma série de classes com toda as regras de negócios e mapeamento objeto relacional, vou ter que fazer isso tudo denovo no servidor, ou tem algum jeito de fazer as regras de negócio no servidor com as classes que já tenho e no cliente apenas chamar elas, de algum modo sem ter que reescrever boa partes delas?
Ola Andreano.
Primeiramente parabens pelo blog, muito bom msm.
Seguinte fiz uma implementacao neste genero utilizando as dicas do outro artigo pra deixar a conversao generica, mas estou com uma dúvida.
Como fica o gerenciamento da memoria no server, exemplo: Eu faco uma requisicao de uma lista, é feita a consulta instancia um JSonArray seta e devolve para a interface, mas a memoria ocupada por esta lista no server continua lah.
Tem alguma dica.
Mais uma vez parabéns.
Oi Juliano, obrigado.
Fica lá até você destruir o objeto do server, uma vez passado para o cliente é outra instância.
Certo, mas como faço para destruir os objetos do server, visto que só poderei destruir eles apos a conclusão da solicitação no entanto quando a solicitação é concluída nao tenho mais acesso as objetos instanciado, já que a execução retornou para o cliente.
Abraço
Muito bom mesmo esse post sobre datasnap, ainda estou me iniciando nisso mas ele demonstra o poder e a simplicidade de uso. Outro ponto é o uso do JSON, que já conhecia, mas no EXTJS que é todo baseado nessa notação.
Com toda certeza terei perguntas, mas vou explorar mais os códigos aqui.
Parabéns pelas dicas.
Olá Parabéns pelo Artigo,
Como eu testo o retorno deste desta aplicação servidora via http??
Exemplo fantastico do uso do DATASNAP, me ajudou muito na migração de uma sistema feio em delphi 2009.
mas gostaria de saber como enviar e receber dados de um campo BLOB do banco de dados, eu até recebo mas nao consigo enviar. o sistema sempre reclama falando que o JSON nao suporta ARRAY e nem DATA.
poderia passar um exemplo de como trabalhar com eses tipo de Dado usando o JSON e D2010 ?
obrigado
Olá Andreano, primeiramente otimo post, muito valido para mim.
Eu gostaria de saber se é possivel fazer o inverso enviar um JSON para o Servidor, usando REST, estou iniciando em REST, ta ficando interessando, só que hoje tenho a necessidade de retornar um Objeto para o Servidor, e até o momento só descobri como passar os parametros e no formato String … por exemplo …
http://127.0.0.1:8081/datasnap/rest/TSMTeste/GetTeste/AquiOParametro
no resto mais uma vez parabéns, não somente por esse post, e também pelos outros.
Obrigado.
Cleyton, pode refazer a pergunta, está confusa 🙂
Andreano,
parabéns pelo blog, sem dúvida alguma essas curtas postagem estão trazendo grandes resultados…hehehe
Assim quanto a isso eu só tenho a agradecer, pois é graças aos posts como este que nós programadores podemos estar aprendendo, nos atualizando e etc…
Bom, baseado neste teu tópico, eu fui tentar criar um exemplo, simples, claro, mas eu fiz mais para aprender. Eu criei um sistema de classes para fazer vendas/controle de estoque. minha idéia, o cliente quando quer fazer uma venda, ele chama uma função no servidor, e passa uma classe (TVenda) com as informações para o servidor proceder. Desse jeito eu consigo ter um controle de estoque bem consciente (controlando a concorrência…) bom mas o problema, foi que os produtos eu representei com uma classe TProduto, e a classe TVenda possuia uma TList , algo digamos, bem intuitivo…mas, na conversão de objetos JSON, essa lista, literalmente não converte. Eu não consegui encontrar alguma solução, por acaso você teria alguma idéia a respeito disso???
Muito obrigado, pela atenção…
Oi Cleiton,
Isso acontece porque esse tipo de objeto não é convertido automático, neste mesmo post eu menciono o post do Andrei que explica como fazer isso http://blogs.embarcadero.com/adrian/2009/08/19/json-types-for-server-methods-in-datasnap-2010/, de uma lida nele, neste caso a propriedade que é TList precisa de um conversor que você deve implementar.
Tive o mesmo problema transportando propriedades do tipo Data, DateTime. Alguma sugestão?
fui realizar alguns testes criando objeto descendendo de TJSONObject, so que o mesmo é uma classe selada. Minha versão esta desatualizada?
Boa tarde Andreano
Estou tendo o mesmo problema que o otto mencionou ao transportar um objeto que possui uma property do tipo double.
Poderia me dar alguma dica de como resolver esse problema?
Oi Marcelo,
Você está usando Delphi 2010? Eu testei aqui e não tive nenhum problema. Qual a mensagem de erro?
Andreano
Obrigado pela resposta utilizo o Delphi 2010 apesar de não ser indicado a mudança nos fontes da vcl resolvi o problema seguindo o exemplo desse poste http://www.expressolivre.net/openmail/index.php?msg_id=%22123336%22 e funcionol perfeitamente.
Agora estou com problema ao rodar a aplicação servidora em uma máquina com windows server 2003 sem farewall, testei em outras máquinas e funciona, na verdade não sei se a falta do firewall é o problema, mas, é o que difere das outras máquinas que testei.
Olá Andreano,
Estou com a necessidade de transpostar o ClientDataSet como objeto para trabalhar no meu servidor de aplicação.
É possível através de JSON? A princípio tenho erro de RTTI.
Obrigado!
Alexandre, não faz sentido transportar clientdataset como JSON, nesse caso use o Provider pra isso.
No meu caso gostaria de commitar vários CDS dentro de uma mesma transação, passando vários clients em um JSONArray. Irei tentar fazer isso via DBXReader. Eu utilizo isso com RemObjects, queria converter para o novo Datasnap. =)
Obrigado!
Oi Alexandre, você pode ter uma única transação ao fazer o applyupdates de vários ClientDataSet se eles tiverem conectados, da uma olhada em nested clientdataset
Parabéns Andreano….
Ok Andreano, vou explicar melhor …
Estou desenvolvendo um sistema, que tem um servidor DataSnap com Rest, até ai tudo bem, com seus exemplos e o dos amigos, consegui desenvolver sem problema, inclusive se comunicando com outras linguagens, por exemplo Flex.
Criei minha regra de negocio no meu servidor DataSnap, e utilizando HTTPService no Flex consigo pegar os dados do meu Servidor e popular os meus campos no Flex.
{
var ht:HTTPService = new HTTPService();
ht.url = “http://localhost:8081/datasnap/rest/TSMClient/GetClient/” + txtConsulta.text;
}
seria mais ou menos assim, passo por parâmetro o txtConsulta e ele retorna o que eu quero.
Só que surgiu a necessidade de passar um arquivo(bytes) = Upload, por exemplo, uma imagem do flex para o meu Servidor DataSnap, ai que está o meu problema, pq no caso desse exemplo que passei eu passo do Flex para o DataSnap como parametro um valor String, em outras linguagens, como por exemplo ASP.NET é utilizado Request para fazer essa operação, só que não encontrei nada parecido nos componentes DataSnap que possa me permitir fazer esse upload … realmente estava confuso anteriormente espero que tenha ficado mais claro … obrigado pelo retorno.
Boa tarde,
Criei um servidor datasnap que retorna uma lista de TCustomer agrupada com JsonArray e JsonValue, estou tentando consumir o resultado numa aplicação asp.net com delphi prism, ao gerar o proxy ele retorna a classe como object, alguma forma de realizar a transformação direto com .net ?
Dannyrooh, não tem jeito, você tem que transformar de JSON para o objeto correspondente.
Excelente post, me ajudou muito, porém tenho um problema, já citado acima. Criei uma classe que possui property do tipo Double e me ocorre um erro na conversão do objeto, impossibilitando a transferência do objeto para o cliente. Como posso resolver o problema ?
Att,
Eu consegui resolver o problema de transporte de datas e doubles entre o servidor DataSnap e a aplicação cliente utilizando um truque muito simples. Se você observar a estrutura de uma classe qualquer em formato JSON vai notar que o que é mapeado para o JSON não são as propriedades da classe em sí, mas sim as suas variáveis privadas. Sendo assim consegui resolver o problema som o seguinte truque:
TCliente = Class (TObject)
private
FID: Integer;
FNome: String;
FDataCadastro: String;
FValor: String;
function GetDataCadastro: TDateTime;
procedure SetDataCadastro(value: TDateTime);
function GetValor: Double;
procedure SetValor(value: Double);
….
public
property DataCadastro: TDateTime read GetDataCadastro write SetDataCadastro;
property Valor: Double read GetValor write SetValor;
….
end;
O truque é na implementação dos métodos privados:
function TCliente.GetDataCadastro: TDateTime;
begin
Result := StrToDateTime(FDataCadastro); // Vai bem alguma checagem extra aqui antes da conversão
end;
function TCliente.SetDataCadastro(value: TDateTime);
begin
FDataCadastro:= FormatDateTime(‘DD/MM/YYYY HH:NN:SS’, Value);
end;
function TCliente.GetValor: Double;
begin
Result := StrToFloatDef(FValor,0);
end;
function TCliente.SetValor(value: Double);
begin
FValor := FloatToStr(value);
end;
Dessa forma quando você atribuir ou obter um valor das propriedades DataCadastro e Valor, automaticamente eles serão convertidos de TDateTime e Double para String, e virce-versa. Como o JSON só armazena as informações dos campos privados das propriedades, as informações são armazenas como String e não há nenhum problema na conversão de volta na aplicação cliente.
Espero ter ajudado.
Olá Sr. Andreano Lanusse! Parabéns pela execelência de seu trabalho. Embora tenha procurado em várias fontes, não encontrei nenhum material editado sobre “Como construir uma aplicação do começo ao fim” com DataSnap, em 3 camandas.
Se poder me ajudar com qualquer informação de onde e como encontrar, serei imensamente grato.
Prezado Andreano,
Eu gostaria de criar metodo remoto onde eu possa passar um array de Delta do ClientDataSet + seu ProviderName
Para isso criei um class TDeltaApply Tentei TDeltaApplyArray : array of TDeltaApply e ele não aceita como metodo remoto esse tipo.
Então como posso para meu servidor uma array de delta e seu providername????
Boa noite Andreano!
Estava estudando esse seu exemplo de transferência de objetos do servidor para o cliente e surgiu uma dúvida: Utilizando esse seu exemplo da classe TCustomer, poderíamos ter uma propriedade public do tipo TJPEGImage ou TBitmap, algo como TCustomer.Photo?
Meu servidor iria definir o conteúdo dessa propriedade e passar o objeto via JSON para o cliente e o mesmo conseguir exibir em um TImage padrão, a imagem que está na propriedade Photo?
Sobre o post acima… percebi que o problema é na conversão de JSON para Object, utilizando o método JSONToObject.
Antes da conversão para JSON, um TImage consegue exibir a foto tranquilamente.
Na volta de JSON para o objeto TCustomer, ao que parece, o conteúdo de Photo fica comprometido.
Andreano… segue a página onde tento explicar detalhadamente o problema:
http://www.activedelphi.com.br/forum/viewtopic.php?t=69002
Caro Andreano!
Tentei realizar o teste que vc indicou, usando TStream para enviar a imagem para o cliente… Na aplicação servidor, a imagem é lida do Stream com sucesso mas, quando é enviado para o cliente, dá erro!
O post foi detalhado para a nova situação… grato pela sua ajuda!
Andreano!
Consegui resolver o problema trocando a classe TMemoryStream por uma TStringStream.
Mas, e quando não for possível fazer um processo como esse? E todo o projeto DataSnap já pronto parado por conta de um único tipo?
Testem direito esse exemplo. O método toString que ele chama é da classe tcustomer que foi compilada dentro do executável do cliente através da declaração no uses, em nenhum momento foi executado o tostring do servidor datasnap.
Só alterar o método tostring do tcustomer assim:
function TCustomer.ToString: string;
begin
Result := Self.Name + ‘ – Age: ‘ + IntToStr(Self.Age)+’ teste’;
end;
e compilar o server e não compilar o cliente, vai ver que o retorno será sem a string ‘teste’ que foi colocado no método.
@William,
Infelizmente você não entendeu o exemplo, o qual mostra o envio e recebimento de objetos no DataSnap.
TCustomer é o objeto usado como exemplo, e no caso ele é usado no server e client. Para representar e ter acesso ao TCustomer no cliente você TEM que ter a estrutura do mesmo no cliente.
O método ToString somente acessa a propriedade do objeto e não faz referência nenhum ao server.
Andreano,
Muito tem se falado a respeito dos objetos que ficam na memória, o que acaba sendo uma bomba relógio.
Eu utilizo o DataSnap (Rest + Json). Há remover esses objetos da memória?
Oi Magnaldo, tem sim, tudo depende como você configura o lifecycle dos objetos através do DSServerClass, aqui no blog tem artigos sobre isso
Prezado Andreano, Tudo bem?
Excelente post. Estou iniciando no desenvolvimento com DataSnap Rest Application, e me surgiu uma dúvida. Implementei um servidor que me retorna uma lista de DataSets (TFDJSONDataSets). Utilizei um exemplo disponibilizado na documentação da embarcadero, conforme segue abaixo:
FDQuery.Close;
Result := TFDJSONDataSets.Create;
TFDJSONDataSetsWriter.ListAdd(Result, FDQuery);
Após o fechamento da Query, eu ajusto o meu SQL e definindo os parâmetros para o mesmo.
Pergunta: Devo abrir minha Query antes de retorná-lá?
Fiz um teste, deixando com está, funcionou perfeitamente, mas apenas para um retorno de poucos registros, percebi que, se minha consultar retornar uma quantidade de informações tais como 50 registros, o dataset chega vazio no lado do cliente, como se não tivesse dados a ser retornado.
Quando eu abro a query no lado do servidor, os registros retornam sem problema algum. O que estou fazendo de errado? tenho que configurar alguma informação que defina o tamanho das informações que devem ser trafegadas?
Desde já agradeço a atenção
Boa Tarde, Adreano Lanusse
Posts com qualidade assim só encontro aqui no seu site, muito interessante e tem me ajudado bastante. Percebi que não é uma dúvida só minha, então resolvi perguntar. É possível transferir arquivos exibindo uma barra de status, tipo um download. o cliente solicitar um arquivo presente no servidor datasnap e mostrar o progresso da transferencia com uma progressbar, é possível?