Quando trabalho com MVCBr o “model” é responsável em executar ações do aplicativo. Um aplicativo, mesmo os mais simples, poderá consumir um número considerável de Models para levar a cabo a execução de todas as ações.
Não será raro que sua aplicação requeira carregar uma dezena de “model” em um único “controller” – nem sempre irá utilizá-los de imediato ou todos ao mesmo tempo. Nada mais sensato postergar a criação do objeto para o momento que for necessário sua execução e concluído a ação liberá-lo da memória sem perder o acesso a classe para posterior execução de novas ações.

Postergar a criação do objeto é chamado de “Lazy Load” – carga tardia do objeto.

Estratégia

A estratégia para criar o objeto tardiamente é criar um “Patterns Adapter” para que faça o controle da classe a ser criada no momento que for necessária, ou seja, ao instânciar o “adapter” ele deve guardar qual a classe que irá criar somente quando for chamado para executar a ação requerida.

Para executar uma carga tardia da classe o MVCBr utiliza a classe TModelAdapterFactory como mostra a declaração a seguir:

[code]

TModelAdapterFactory<T: Class> = class(TModelFactory, IModel,
IModelAdapter<T>)
private
FInstanceClass: TComponentClass;
FInstance: T;
function GetInstance: T; virtual;
constructor CreateInternal; overload; virtual;
public
constructor Create; overload;
class function New(AController: IController)
: TModelAdapterFactory<T>; static;
destructor Destroy; override;
procedure Release; override;
property Instance: T read GetInstance;
procedure FreeInstance; virtual;
end;

[/code]

Criando o Adapter

Explorando o código do TModelAdapterFactory notará que a inicialização é permitida através da “class function New” que guarda as informações da sub-classe a ser criada tardiamente.

Durante a criação do adapter não haverá inicialização da sub-classe, que só poderá ser criada ao chamar a propriedade “Instance” – momento criar e retorna uma instância da sub-classe;

Exemplo:
[code]

/// declarando a variável para o Adapter – passando tipo da sub-classe
var LMeuAdapter : TModelAdapterFactory<TDataModuleXXX>;

…..
/// criando o adapter
LMeuAdpater := TModelAdapterFactory<TDataModuleXXX>.new( FController );

/// consumindo a sub-classe
LMeuAdapter.Instance.ExecutarMeuProcedimento; // exemplo de chamada de um método da sub-classe

[/code]

Sendo o adapter uma herança de “IModel”, em geral será utilizado como uma instância ligada a um controller no MVCBr – neste caso dispensa a declaração da variável e permite tratar o MODEL como outro MODEL comum ligado ao seu controller.

[code]
/// adicionando o model para o controller no CreateModels do Controller
add( TModelAdapterFactory<TDataModuleXXX>.New(FController) );

[/code]

Quando usar um Adapter

Há duas razões para querer utilizar um Adapter como um MODEL.
1. O primeiro deles é quando você quer importar uma classe para o MVCBr sem fazer as alterações da classe existente – irá permitir consumir a sub-classe como um MODEL mesmo quando a classe não implementa a interface de um MODEL – a sub-classe será acessível pela propriedade “Instance” ;
2. Quando um objeto requeira “instânciar” e “liberar” de forma formal sem interferência da contagem de referência. Casos práticos foram detectado necessidade da utilização do Adapter quando se trabalha com TDataModule – para melhorar o controle e liberação da instância;

Liberação temporária da Instância

Um bom motivo para utilizar carga tardia de um classe pode ser justificada para manter carga de memória somente dos objetos que sejam necessário para a ação e liberá-los tão logo termine sua utilização – para liberar a instância utilizar “FreeInstance” que libera somente a instância da sub-classe mantendo a Adapter ativo.

Git: Projeto MVCBr (quando deste artigo o código corrente Branch DEV); Localizar UNIT: MVCBr.Model para ver o código;

Os seguintes arquivos que devem ser configurados:
MVCBrServer.config
oData.ServiceModel.json
As configurações são as mesmas para o Servidor em Standalone ou em Serviço do Windows.
1º vamos configura o arquivo MVCBrServer.config, ele é responsável por informar ao servidor qual o drive de banco de dados a ser usado e o caminho da base de dados, que no exemplo vou mostrar usando uma base em Firebird.
[code lang=”pascal”]
{ "Config":
{ "WSPort":"8080",
"driverid":"FB",
"Server":"localhost",
"Database":"C:\\Diretorio_BD\\BD_NOME.FDB",
"user_name":"sysdba",
"Password":"masterkey",
"VendorLib":""
}
}
[/code]
Lembrando que o caminho da base deve ser utilizado duas barras “\\” ou barra invertida “/” para separação dos diretórios.
A estrutura do arquivo MVCBrServer.config:

  • "Config" conterá o objeto de parametrização com as seguintes propriedades:
    1. "WSPort" recebe o número da porta que o ServidorMVCBr irá responder;
    2. "driverid" recebe a sigla correspondente ao banco de dados utilizado (“FB”=Firebird, “PG”=PostgreSQL, “MYSQL”=MySQL e “MSSQL”=Microsoft SQL Server);
    3. "Server" recebe o ip ou hostname do servidor de banco de dados;
    4. "Database" recebe o caminho e o nome da sua base de dados;
    5. "user_name" recebe o nome do usuário que se conecta em sua base de dados;
    6. "Password" recebe a senha do usuário que se conecta em sua base de dados;
    7. "VendorLib" recebe o caminho e o nome da DLL-Dynamic-link library caso seu banco de dados exija para a conexão.

    2º vamos configura o arquivo oData.ServiceModel.json, ele é responsável por informar ao servidor qual os recursos serão disponibilizado.
    [code lang=”pascal”]
    {
    "@odata.context": "http://localhost:8080/OData/OData.svc",
    "__comment": "Services list all resource available to OData.Service",
    "OData.Services": [
    {
    "_comments_":"Products table service",
    "resource": "produtos",
    "collection": "produtos",
    "keyID": "codigo",
    "maxpagesize": 100,
    "fields": "produtos.codigo,produtos.descricao,produtos.unidade,produtos.grupo,produtos.preco",
    "method": "GET,PATCH,POST,PUT,DELETE",
    "relations": [{
    "resource": "grupos",
    "sourceKey": "grupo",
    "targetKey": "codigo",
    "join": "left join grupos on (produtos.grupo=grupos.grupo)"
    }]
    },
    {
    "_comments_":"Goups table service",
    "resource": "grupos",
    "collection": "grupos",
    "keyID": "codigo",
    "fields": "*",
    "method": "GET",
    "maxpagesize": 100,
    "relations": [{
    "resource": "produtos",
    "sourceKey": "codigo",
    "targetKey": "grupo",
    "join": "join produtos on (grupos.codigo=produtos.grupo)"
    }],
    "expands": [{
    "resource": "produtos",
    "filter": "grupos.codigo=:grupo"
    }]
    }
    ],
    "OData.Execute": [
    {
    "_comments_":"ExecSQL for exec_procedure",
    "resource": "exec_procedure",
    "procedure": "exec_procedure",
    "params": "filial,dcto,data",
    "returnRows": true
    }
    ]
    }
    [/code]
    Lembrando que tem um aplicativo para gerar o arquivo oData.ServiceModel.json, o GeradorMetadata, que está disponível junto dos fontes no GitHub no diretório MVCBr/MVCBrServer/GeradorMetadata (Projeto MVCBr).
    As propriedades do arquivo oData.ServiceModel.json:

  • "@odata.context" contem a URL do servidor OData, *obrigatório;
  • "__comment" descrição dos serviços do servidor OData, não é obrigatório;
  • "OData.Services" é um array de objetos que contem os serviços disponibilizados, cada item do array deve conter o objeto com as seguintes propriedades:
    1. "_comments_" uma descrição do serviço, não é obrigatório;
    2. "resource" conterá o nome do serviço que será disponibilizado, não se pode usar caracteres especiais pois esse nome será parte da URL e é case sensitivo, *obrigatório;
    3. "collection" conterá o nome da sua tabela do seu banco de dados, sendo que o nome do serviço pode ser totalmente diferente do nome de sua tabela, *obrigatório;
    4. "keyID" conterá o nome do campo chave da sua tabela, *obrigatório;
    5. "maxpagesize" conterá a quantidade de registro que será disponibilizado na consulta do serviço, não é obrigatório, mas recomendo 100% a utilização, pois se não tiver uma limitação, ao chamar o serviço sem filtro irá trazer todos os registro de sua tabela, com isso deixando seu cliente lento;
    6. "fields" conterá os campos de sua tabela que você quer disponibilizar, *obrigatório pelo menos um campo;
    7. "method" conterá quais método de requisição seu serviço poderá receber, “GET,PATCH,DELETE,PUT,POST”, *obrigatório pelo menos um método;
    8. "relations" é um array de objetos, que cada item terá um objeto com as seguinte propriedades:
      1. "resource" nome do serviço relacionado;
      2. "sourceKey" campo chave para relacionada ao serviço;
      3. "targetKey" nome do serviço com a relação;
      4. "join" sentença SQL da relação;
    9. "expands" é um array de objetos com os serviços disponibilizado, direciona os registros relacionados que devem ser recuperados no registro ou no conjunto que está sendo recuperado, cada item terá um objeto com as seguinte propriedades:
      1. "resource" nome do serviço, *obrigatório;
      2. "filter" Especifica uma expressão ou função que deve ser avaliada como verdadeira para que um registro seja retornado, *obrigatório;
  • "OData.Execute" é um array de objetos com os serviços disponibilizado, de acordo com um procedimento existente no seu banco de dados, cada item terá um objeto com as seguinte propriedades:
    1. "_comments_" uma descrição do serviço, não é obrigatório;
    2. "resource" conterá o nome do serviço que será disponibilizado, não se pode usar caracteres especiais pois esse nome será parte da URL e é case sensitivo, *obrigatório;
    3. "procedure" nome do seu procedimento na sua base de dados, *obrigatório;
    4. "params" parâmetro do seu procedimento, *obrigatório se tiver no seu procedimento;
    5. "returnRows" conterá “true” verdadeiro se o procedimento retorna registros e “false” falso para não retorna registros, *obrigatório;

    Vídeo mostrando como ServidorMVCBr funciona.

    1. Tudo bem, você já mostrou no #1 como um VIEW faz para abrir outro VIEW, mas no meu caso eu tenho um TLayout no form principal e queria mostrar meu segundo form dentro do TLayout, como fazer isto ?
      R. O TLayout é um componente do framework FMX. Dado a complexidade em embutir um formulário dentro de outro formulário quando se usa FMX – o caminho mais rápido para fazer isto é pegar um TLayout do VIEW2 e mostrar ele dentro de um TLayout da VIEW1. O MVCBr tem método pronto para fazer esta mágica com pouco código, mas precisa preparar o terreno antes:

      • No form principal VIEW1
        1. arrastar um TLayout e posicionar onde deseja que a o VIEW2 seja mostrado
        2. no ShowView do VIEW1 fazer a chamada para o VIEW2:
          [code lang=”pascal”]
          uses meuView2.Controller.interf ;

          function TNewMVCAppView.ShowView(const AProc: TProc<IView>): integer;
          var
          LController2: IMeuView2Controller;
          begin
          inherited;
          /// Carrega o controller do VIEW2
          LController2 := resolveController<IMeuView2Controller>;
          /// embute o Layout do VIEW2 dentro do Layout do VIEW1 (aqui vai a mágica)
          LController2.Embedded(Layout1);
          end;
          [/code]
      • No form do VIEW2
        1. arrastar um TLayout para dentro do VIEW2 e mudar a propriedade “align=client”, os componentes visuais serão arrastados para dentro do TLayout e posicionados a vontade
        2. Incluir na criação do objeto a interface ILayout (é ele que torna acessível o TLayout do VIEW2)
          [code]
          TMeuView2View = class(TFormFactory { TFORM } , IView, IThisAs<TMeuView2View>,
          IMeuView2View, IViewAs<IMeuView2View>, ILayout)
          [/code]
        3. implementar a função do ILayout para que retorne o TLayout a ser mostrado dentro do VIEW1
          [code]
          function TMeuView2View.GetLayout: TObject;
          begin
          result := Layout1;
          end;

          [/code]

    2. Como posso retornar para o VIEW1 uma cadeia de parâmetros que foram digitados no VIEW2 ? Exemplo: no VIEW2 eu digito alguns parâmetros que gostaria de formatar uma WHERE no SELECT que esta no VIEW1. Então como posso retornar estes dados do VIEW2 para o VIEW1 ?
      R. Lembrando que tudo no MVCBr é INTERFACE, então é preciso pensar em termos de interface. No VIEW2 você precisa de uma função que retorne os parâmetros formatados para serem utilizados no VIEW1. Exemplo: function GetWhereString: string;

      • No VIEW2 – Gerando os parâmetros
        [code]

        Type
        IParametrosView = interface(IView)
        [‘{838227AC-E658-4193-91B9-25790379DF49}’]
        // incluir especializacoes aqui
        function GetWhereString: string;
        end;
        …..

        procedure TParametrosView.FormShow(Sender: TObject);
        begin
        /// clear
        FWhere := ”;
        end;

        procedure TParametrosView.Button1Click(Sender: TObject);
        begin
        WhereBuilder; /// construtor da where
        close;
        end;

        /// construtor da where
        procedure TParametrosView.WhereBuilder;
        procedure add( AText:string );
        begin
        if FWhere <> ” then
        FWhere := FWhere + ‘ and ‘ ;
        FWhere := FWhere + AText;
        end;
        begin
        FWhere := ”;
        if LabeledEdit1.text<>” then
        add( ‘email_from like (‘+quotedStr(LabeledEdit1.text+’%’)+’)’);
        if LabeledEdit2.text<>” then
        add( ‘email_to like (‘+quotedStr(LabeledEdit2.text+’%’)+’)’);
        if LabeledEdit3.text<>” then
        add( ‘email_subject like (‘+quotedStr(LabeledEdit3.text+’%’)+’)’);
        end;

        /// function da interface do VIEW2
        function TParametrosView.GetWhereString: string;
        begin
        result := FWhere; /// retorna a where montada
        end;

        [/code]

      • No VIEW1, pegando os parâmetros digitados no VIEW2:
        [code]

        /// evento para receber os dados digitados no VIEW2
        procedure TNewMVCAppParametersView.ParametersApply(AView: IView);
        begin
        /// faz alguma coisa com os dados digitados no VIEW2
        Memo1.lines.text := (AView as IParametrosView).GetWhereString;
        end;

        /// abre a janela do VIEW2
        procedure TNewMVCAppParametersView.Button1Click(Sender: TObject);
        begin
        /// No ShowView chamar com os parametros:
        /// a interface do controller da VIEW2 – irá procurar na lista de controllers qual a view desejada
        /// nil para o segundo parâmetro (OnProcBeforeShow);
        /// método que irá receber o parâmetro (OnProcCloseEvent);
        ShowView(IParametrosController, nil, ParametersApply);
        end;

        [/code]

    Vou abordar hoje uma das maneira mais fáceis de inserir código malicioso em comando SQL. O título indicando o Firebird é só um direcionamento do teste, mas se aplica praticamente a todos os banco de dados relacional (ou quase todos).

    1. Um código Vulnerável

    Uma simples janela de “login” pode significar uma entrada a explorar, como veremos.

    Em geral uma janela de “login” pede o código/senha de usuário.

    Exemplos de “select” para validar o usuário:

    Caso 1:

    considere o select: ‘select codigo,nome from usuario where codigo=’ +edit1.text+’ and senha=’+edit2.text;

    como testar:

    1. digite uma aspas    no edit1.text e tente entrar; se o resultado for um erro, então você receberá informações sobre o que ocorreu e poderá indicar que o código do aplicativo é vulnerável.
    2. tente digitar no edit1.text:
      ' or 1=1 --

      note que com o select indicado o usuário ganhará acesso ao sistema.

    Caso 2:

    considere o select:    format(‘select codigo,nome from usuario where codigo=%s and senha=%s,[edit1.text,edit2.text]);

    • tente digitar no edit1.text: 
      ' or 1=1 --

     

    2. Como quebrar a vulnerabilidade

    Para quebrar a vulnerabilidade, o passo mais seguro é utilizar parâmetros nos “select” ou forçar as aspas por código, vejamos como fazer o correto:

    • ‘select codigo,nome from usuario where codigo=:codigo and senha=:senha’
    • ‘select codigo,nome from usuario where codigo=’+quotedstr(edit1.text)+’ and senha=’+quotedstr(edit2.text);

     

    Como usar o servidor MVCBr OData com javascript !!!

    Javascript possui tudo que é necessário para consumir RESTful – alias, não foi por lá que nasceu!

    O exemplo é bem simples com objetivo somente de mostrar como estruturar os artefatos para fazer a busca no servidor. Para o teste, o servidor estava instalado como um serviço windows respondendo na porta 8080 configurado com o banco padrão que vai de exemplo com o servidor OData do MVCBr – Firebird3;

     

    Classes construtura para OData Protocol:

    [code lang=”javascript”]
    class ODataBuilder{

    constructor(AResource) {
    this.BaseURL = "http://localhost:8080",
    this.Service = "/OData.svc",
    this.ServicePrefix = "/OData",
    this.ResourceParams = "",
    this.Top = 0,
    this.Skip = 0,
    this.Select = "",
    this.OrderBy = "",
    this.Filter = "",
    this.Params = "";
    this.Resource = "/" + AResource;
    }

    formatParams() {
    var rt = "";
    if (this.ResourceParams == "") {
    return rt;
    }
    rt = rt + "(" + this.ResourceParams + ")";
    }

    BaseURI() {
    return this.BaseURL + this.ServicePrefix + this.Service;
    }

    ResourceParams(AParams) {
    this.ResourceParams = AParams;
    }

    addParams(prm) {
    if (this.Params != "") { this.Params += "&" };
    this.Params += prm;
    };

    URI() {
    var rt = this.Resource + this.formatParams();
    this.Params = "";
    if (this.Top > 0) this.addParams("$top=" + this.Top.ToString());
    if (this.Skip > 0) this.addParams("$skip=" + this.Skip.ToString());
    if (this.Select != "") this.addParams("$select=" + this.Select);
    if (this.Filter != "") this.addParams("$select=" + this.Filter);
    return rt + "?" + this.Params;
    };

    ToString() {
    return this.BaseURI() + this.URI();
    }
    };
    [/code]

    Classe RestClient para comunicação com o servidor:

    [code lang=”javascript”]

    class RestClient {
    constructor(AODataBuilder) {
    this.Builder = AODataBuilder;
    this.ResponseCode = 0;
    this.ResponseContent = "";
    }
    ResponseCode() {
    return this.ResponseCode;
    }
    Content() {
    return this.ResponseContent;
    }

    GET(fn) {
    this.ResponseCode = 0;
    $.ajax({
    url: this.Builder.ToString(),
    type: ‘GET’,
    dataType: ‘json’,
    success: function (data) {
    this.ResponseContent = data;
    if (fn != null) {
    fn(data);
    }
    }
    });
    };

    };

    [/code]

    Exemplo construção da chamada HTML5:

    [code lang=”html”]
    <html>
    <head>
    <script src="./js/jquery.min.js"></script>
    <script src="./src/ODataBuilder.js"></script>
    <script src="./src/ODataBuilder.js"></script>
    <script src="./src/RestClient.js"></script>

    <script type="text/javascript">

    $(document).ready(function(){
    $(‘#ok’).click(function(){
    var builder = new ODataBuilder("produtos");
    var rc = new RestClient(builder).GET(
    function (data) {
    $(‘#content’).text( JSON.stringify(data) );
    });
    });
    });

    </script>
    </head>
    <body>

    <textarea rows="20" cols="80" id="content" name="content">

    </textarea>
    <button id="ok">Okey – Pedir dados proddutos</button>

    </body>
    </html>
    [/code]

    Exemplo completo no GIT

    1. Como inicializa o projeto  MVCBr ?

      R. No início no projeto (.dpr) o assistente inclui:
      [code]
      ApplicationController.Run(TLojaAppController.New,
      function: boolean
      begin
      // retornar True se o applicatio pode ser carregado
      // False se não foi autorizado inicialização
      result := true;
      end);
      [/code]

    2. No formulário principal, como abrir uma janela com outra VIEW?

      R. todo VIEW representa um formulário e é controlado por um “controller” – o “controller” é a porta de entrada para inicializar um formulário, assim é necessário criar o “controller”:
      [code]
      procedure TFormChildSampleView.Button1Click(Sender: TObject);
      var aController:IFormChildController;
      begin
      aController := resolveController<IFormChildController>;
      aController.ShowView();
      end;
      [/code]

    3. Tenho um MODEL com regras de negócio, como acessar os dados desses MODEL?

      R. O primeiro passo é tornar o MODEL visível para o “controller”, visto que o “controller” mantem uma lista de MODEL associado a ele mesmo.
      Para instânciar o MODEL dentro do “controller”:
      [code]
      /// Adicionar os modulos e MODELs personalizados
      Procedure TFormChildSampleController.CreateModules;
      begin
      // adicionar os seus MODELs aqui
      // exemplo: add( MeuModel.new(self) );

      add( TRegrasNegociosModel.new(self) );
      end;
      [/code]
      Na view você pode usar o “controller” para chamar o “MODEL”.

    4. Como chamar um MODEL de dentro de um VIEW?

      R. O MODEL é inicializado junto com o “controller” que mantem uma lista de MODELs ativos para acesso.
      [code]
      procedure TFormChildSampleView.Button2Click(Sender: TObject);
      var
      AModel:IRegrasNegociosModel;
      begin
      AModel := GetModel<IRegrasNegociosModel>;
      AModel.Validar(‘Show Model Validar()’);
      end;
      [/code]

    5. Quero embutir um VIEW dentro de um TABSHEET, como posso acessar os atributos do formulário ?

      R. você precisa inicialmente carregar o “controller” do VIEW – isto vai permitir acesso ao GETVIEW.This que retorna a instância do TFORM;
      [code]
      procedure TFormChildSampleView.Init;
      var
      ACtrl:IEmbededFormController;
      AForm:TForm;
      begin
      // incluir incializações aqui

      // embeded form…
      ACtrl := resolveController<IEmbededFormController>;
      AForm := TForm(ACtrl.GetView.This);
      AForm.parent := TabSheet1;
      AForm.BorderStyle := bsNone;
      AForm.Align := alClient;
      AForm.Show;

      end;

      [/code]

    6. Quando trabalho com FMX é comum utilizar TABCONTROL ou TLAYOUT para mostrar VIEWs, como um VIEW pode enviar uma mensagem para outro VIEW que já esta carregada ?

      R. para enviar uma mensagem para um VIEW específico, você precisa da “interface” do VIEW desejado – o que pode ser feito incluindo no USES da UNIT uma chamada para a UNIT de “interface” do VIEW.
      [code]
      procedure TFormChildSampleView.Button4Click(Sender: TObject);
      begin
      ApplicationController.ViewEvent(IEmbededFormView,’Message to: EmbededFormView’);
      end;
      [/code]
      Outra forma é se você conhece o “controller” alvo, pode enviar a mensagem diretamente para o “controller”.
      [code]
      procedure TFormChildSampleView.Button5Click(Sender: TObject);
      begin
      ResolveController<IEmbededFormController>.ViewEvent(‘Message via controller’);
      end;
      [/code]
      Para mandar mensagem para todos os VIEW é possível utilizar o ApplicationController:
      [code]
      procedure TFormChildSampleView.Button3Click(Sender: TObject);
      begin
      applicationController.ViewEvent( ‘generic message to all VIEW’ );
      end;
      [/code]

      GIT com Exemplo

    Nestes primeiros meses de 2017, o grupo MVCBr dedicou a maior parte do tempo em implementar um servidor OData que permite o acesso a base de dados utilizando protocolo RESTful via HTTP.

    FIREBIRD !!!

    Sim….. o servidor OData  implementado no MVCBr é um servidor que expõe recursos (resources) armazenados em um servidor FIREBIRD 3.0

    Simplificando, o servidor MVCBrServer é um servidor RESTful que utiliza o protocolo OData que suporta o Firebird3.

    Arquitetura do Servidor

    O servidor é um servidor implementado utilizando componentes INDY para fazer o processamento das requisições. Ao receber a chamada do CLIENTE o motor INDY passa para o framework  “Delphi MVC Framework” implementado pelo “Daniele Teti”. Seguindo os padrões RESTful, o servidor analisa o tipo de pacote que esta recebendo (GET, PUT, POST, PATCH, DELETE, OPTIONS) e passa a requisição para um PARSER OData implementado no framework MVCBr. Ao receber a requisição o PARSER prepara a requisição que será feito ao banco de dados (ex: select * from produtos), avalia o “metadata” contendo as regras de acesso e constrói a solicitação através da estrutura do FireDAC do Delphi, que remete ao driver o FIREBIRD. Recebendo o retorno tudo é empacotado utilizando JSON e devolvido para o cliente.

     

    O “metadata” – Onde o modelo relacional é descrito

    Junto com o projeto MVCBr irá encontrar um banco FIREBIRD3  (MVCBr.fdb)  que possui uma estrutura básica de teste utilizada no “framework”

    Configurar o “databases.conf” do firebird:

    #
    # Live Databases:
    #
    mvcbr=[path]/mvcbr.fdb
    

    Como configurar um resource

    Resource é o identificar a ser utilizado no HTTP para acessar um determinado recurso do banco de dados.
    Veja o exemplo:

        {
          "resource": "produtos",
          "collection": "produtos",
          "keyID": "codigo",
          "maxpagesize": 100,
          "fields": "*",
          "method": "GET,PATCH,POST,PUT,DELETE",
          "relations": [
            {
              "resource": "grupos",
              "sourceKey": "grupo",
              "targetKey": "codigo",
              "join": "left join grupos on (produtos.grupo=grupos.grupo)"
            }
          ]
        }
    

    • resource:  apelido para o URL utilizada no HTTP
    • collection: nome da tabela fisica no banco de dados
    • keyID: coluna de acesso rápido ás linhas da tabela    ex: http://…./OData/OData.svc/produtos(‘789112313311’)
    • maxpagesize: número máximo de linhas a retornar caso não seja indicado o comando   $top
    • fields: lista de colunas a retornar quando o comando  $select não for indicado
    • method: quais as permissões serão publicadas aos clientes
    • relations: quais relacionamento podem ser executados com “resource” corrente  ( é um DETAIL)
      • relations.resource: qual o apelido do relacionamento com o resource MASTER
      • relations.sourceKey: qual a coluna de recionamento no resource MASTER
      • relations.targetKey: qual a coluna de relacionamento no resource DETAIL
      • relations.join: utilizado para JOINs mais complexos ignorando “sourceKey” e “targetKey”

    Listagem completa do metadata de exemplo

    [code lang=”javascript”]
    {
    "@odata.context": "http://localhost:8080/OData/OData.svc",
    "__comment": "Services list all resource available to OData.Service",
    "OData.Services": [
    {
    "resource": "produtos",
    "collection": "produtos",
    "keyID": "codigo",
    "maxpagesize": 100,
    "fields": "*",
    "method": "GET,PATCH,POST,PUT,DELETE",
    "relations": [
    {
    "resource": "grupos",
    "sourceKey": "grupo",
    "targetKey": "codigo",
    "join": "left join grupos on (produtos.grupo=grupos.grupo)"
    }
    ]
    },
    {
    "resource": "grupos",
    "collection": "grupos",
    "keyID": "codigo",
    "fields": "*",
    "method": "GET,PATCH,DELETE,PUT,POST",
    "maxpagesize": 100,
    "relations": [
    {
    "resource": "produtos",
    "sourceKey": "codigo",
    "targetKey": "grupo",
    "join": "join produtos on (grupos.codigo=produtos.grupo)"
    }
    ]
    },
    {
    "resource": "fornecedores",
    "collection": "fornecedores",
    "maxpagesize": 100,
    "fields": "*",
    "keyID": "codigo"
    },
    {
    "resource": "clientes",
    "collection": "clientes",
    "keyID": "codigo",
    "method": "GET,POST,PATCH,UT,DELETE",
    "searchFields": "nome",
    "maxpagesize": 100,
    "fields": "*"
    "relations": [
    {
    "resource": "vendas",
    "join": "join vendas on (vendas.cliente=clientes.codigo)"
    },
    {
    "resource": "vendas_item",
    "join": "join vendas a on (clientes.codigo=a.cliente) join vendas_item b on (b.documento=a.documento)"
    }
    ]
    },
    {
    "resource": "vendas",
    "collection": "vendas",
    "maxpagesize": 100,
    "keyID": "documento",
    "fields": "*",
    "method": "GET,POST,PATCH,PUT,DELETE"
    },
    {
    "resource": "vendas_item",
    "collection": "vendas_item",
    "maxpagesize": 100,
    "keyID": "documento"
    "method": "GET,POST,PATCH,PUT,DELETE"
    }

    ]

    }
    [/code]

     

     

    Chegar a um CRUD é um grande desafio. Associar um MVC no desenvolvimento e a utilização de RESTful para persistir os dados (com OData) só faz crescer o desafio.

    O desafio vem do fato de tudo ser novidade e forçar a quebrar paradigmas antes impensáveis utilizando a ferramenta do dia-a-dia, o Delphi.

    Vamos ligar os componentes:

    TIdHTTPRestClientTIdHTTPRestClient é responsável em fazer a
    comunicação entre o cliente e o servidor RESTful.

    É o canal para buscar dados no servidor, bem como enviar dados para o servidor (é um componente de transporte).

    ver mais…

     

     

    Os preenchimento das propriedades do RestClient pode ser feito manualmente, ou ainda, de forma automática se for utilizado em conjunto com um TODataBuilder.

     

    ODataCom o TODataBuilder permite construir a soliciatação GET a ser enviada ao servidor OData.

    Ligar ao TODataBuilder a propriedade RestClient com o TIdHTTPRestClient. Com isto o comando construído no Builder será repassado (preenchido automático) no RestClient.  ver mais….

     

     

     

    O passo seguinte é ligar o TODataDatasetAdpater.

    TODataDatasetAdapterO Adapter recebe os dados do RestClient e liga com o Dataset.

    Ligar o Builder, Dataset e o ResponseJSON( RestClient).

    Para obter os dados com os servidor, chamar o método execute. ver mais…

    Feito estas ligações nos componentes, já é possível chamar o EXECUTE do Adapter que irá preencher o Dataset com a resposta do servidor.

    Para enviar um INSERT, DELETE ou UPDATE para o servidor ( ver exemplo).

    O código a seguir mostra como montar os dados a serem enviado para o servidor persistir as alterações.

    Os seguintes métodos do Adapter são importantes, como no exemplo:

    ODataDatasetAdapter1.AddRowSet(rctInserted, DataSet); /// põe na pilha um INSERT;
    ODataDatasetAdapter1.AddRowSet(rctModified, DataSet);  /// põe na pilha um UPDATE
    ODataDatasetAdapter1.AddRowSet(rctDeleted, DataSet);  /// põe na pilha um DELETE;
    ODataDatasetAdapter1.ApplyUpdates(nil, rmPATCH);  /// atualiza o servidor com os dados da pilha;
    ODataDatasetAdapter1.Execute;   /// faz um SELECT no banco de dados
    ODataDatasetAdapter1.ClearChanges;  /// limpa a pilha
    

     

    Código de exemplo que esta no projeto MVCBr.  no git

    [code lang=”pascal”]
    { //************************************************************// }
    { // // }
    { // Código gerado pelo assistente // }
    { // // }
    { // Projeto MVCBr // }
    { // tireideletra.com.br / amarildo lacerda // }
    { //************************************************************// }
    { // Data: 10/03/2017 21:21:54 // }
    { //************************************************************// }
    ///
    /// Uma View representa a camada de apresentação ao usuário
    /// deve esta associado a um controller onde ocorrerá
    /// a troca de informações e comunicação com os Models

    ….
    DBGrid1: TDBGrid;

    DataSource1: TDataSource;
    /// Componente de Envio de mensagem HTTP para o servidor OData RESTful
    IdHTTPRestClient1: TIdHTTPRestClient;
    /// Construção de URL a ser enviado ao servidor
    ODataBuilder1: TODataBuilder;
    /// Desempacota a resposta do servidor e preenche o Dataset
    ODataDatasetAdapter1: TODataDatasetAdapter;

    /// Dataset que recebe a resposta do servidor – preenchida com o Adpater
    FDMemTable1: TFDMemTable; /// FireDAC
    Button1: TButton; /// botão GET
    Button2: TButton; /// botão ApplayUpdates
    ….
    private
    FLoading: boolean; /// Flag para marcar se esta fazendo carga inicial da tabela.
    FState: TDataSetState; /// Guarda o STATE do Dataset a ser usando no AfterPost
    protected
    public
    { Public declarations }

    end;

    Implementation

    {$R *.DFM}

    …..
    /// No evento AfterPost do Dataset, armazena na lista de
    /// alterações a serem enviadas para o servidor OData RESTful
    procedure TRestODataAppView.FDMemTable1AfterPost(DataSet: TDataSet);
    begin
    if not FLoading then /// trata somente os registros alterados pelo usuário… (para não pegar os dados iniciais na carga da resposta do servidor)
    case FState of
    dsInsert: /// empilha os dados de INSERT – inserted
    ODataDatasetAdapter1.AddRowSet(rctInserted, DataSet);
    dsEdit: /// empilha os dados do POST – modified
    ODataDatasetAdapter1.AddRowSet(rctModified, DataSet);
    end;

    end;

    /// No ApplyUpdates do Dataset despachar para o servidor
    /// as alterações armazenadas para serem notificadas
    /// ao servidor
    procedure TRestODataAppView.FDMemTable1BeforeApplyUpdates(DataSet: TFDDataSet);
    begin
    // envia os dados alterados para o servidor OData RESTful
    ODataDatasetAdapter1.ApplyUpdates(nil, rmPATCH);
    end;

    procedure TRestODataAppView.FDMemTable1BeforeDelete(DataSet: TDataSet);
    begin
    /// empilha os dados a serem de DELETE – deleted a ser enviado ao servidor
    ODataDatasetAdapter1.AddRowSet(rctDeleted, DataSet);
    end;

    procedure TRestODataAppView.FDMemTable1BeforePost(DataSet: TDataSet);
    begin
    /// guarda o estado da linha (dsEdit, dsInsert) para uso no AfterPost
    FState := DataSet.State;
    end;

    procedure TRestODataAppView.FormCreate(Sender: TObject);
    begin
    /// um exemplo de como adicionar CustomHeaders a mensagem que será enviada ao servidor
    IdHTTPRestClient1.IdHTTP.Request.CustomHeaders.AddValue(‘token’, ‘abcdexz’);
    end;

    procedure TRestODataAppView.Button1Click(Sender: TObject);
    begin
    FLoading := true; /// controle para não gerar linhas de alterações na pilhas
    try
    /// executa um GET no servidor RESTful
    /// o servidor fazer um SELECT e devolve o conteúdo as linhas do SELECT
    ODataDatasetAdapter1.Execute;
    /// limpa alterações que estejam armazenadas
    ODataDatasetAdapter1.ClearChanges;
    finally
    FLoading := false;
    end;
    end;

    procedure TRestODataAppView.Button2Click(Sender: TObject);
    begin
    FDMemTable1.ApplyUpdates(); /// processa a alteração do Dataset
    end;

    [/code]

     

    O componente TODataDatasetAdapter é um construtor associado ao Dataset convertendo o JSON enviado pelo servidor em um DATASET navegável no formulário. Vejamos suas propriedades.

    TODataDatasetAdapter Onde:
    Builder: ligação para o TODataBuilder;
    Datasert: ligação para um TFDMemTable (aka. TClientDataset !!!);
    ResponseJSON: ligação com o TIdHTTPRestClient;
    ResponseType: no momento só aceita texto plano sem compactação – pureJSON;
    RootElement:  caminho onde se encontra o ARRAY de valores no JSON enviado pelo servidor (no OData o padrão é “value”);
    Introdução ao OData | TODataBuilder | TIdHTTPRestClient

     

    TODataBuilder é um construtor de URI para chamada ao OData Server.

    OData

    O construtor  é uma facilidade para criar a URI de chamada ao OData através da interface gráfica do Delphi onde:

    • BaseURL – Servidor e porta do OData;
    • Count – indica se é para retornar o número total de linhas do select – usado para paginação;
    • Expand – lista de Resource DETAIL   (não implementado no MVCBrServer neste momento);
    • Filter – filtro de contrução auxiliar para o cláusula WHERE de busca no banco de dados (aceita os operadores do OData);
    • Resource – lista de Collections/Tabelas e respectivos parâmetros aninhadas ( max 3 );
    • Select – lista de colunas a retornar pelo consulta no servidor;
    • Service – nome do serviço no servidor;
    • ServicePreffix – prefixo de composição da URI associado ao Serviço;
    • SkipRows – indica quantas linhas saltar antes de retornar o conjunto de linhas (default=0);
    • TopRows – indica a quantidade de linhas a serem retornadas (default=0  todas);

    O componente expõe o metódo “execute” que tem por atribuição preencher os parâmetros do TIdHTTPRestClient e chamar o seu “execute”;

    RestClient | Introdução ao OData