Criando um contador de tempo de sessão em aplicativo web

Criando um contador de tempo de sessão em aplicativo web

Olá,

Realmente estou um pouco desapontado comigo mesmo, pois desde o último post se passou 3 anos… “Shame on you”… Mea culpa, minha vida mudou muito desde o último post, mudei de cidade, de emprego, enfim, nada justifica…

Voltando ao que interessa, gostaria de compartilhar com vocês uma solução bacana que utilizamos em um projeto que é o contador de tempo de sessão, bem parecido com aqueles cronômetros que existem em nossos internet bankings.

A solução basicamente é um mash up (uma mistura) de um User Controls e um objeto externo, ambos disponíveis no marketplace do GeneXus: Web.Config reader e SessionTime. A idéia é utilizar o user control Session Time, porém, ao invés de configurá-lo com um tempo fixo, obter o valor do tempo de sessão do web.config.

  1. A primeira coisa que precisa ser feita é o download e a instalação do U.C. e do objeto externo, caso necessite de ajuda nesse ponto, poderá encontrar a documentação aqui.
  2. O external object ConfigManager não necessita instalação, mas é necessário a importação dos objetos na sua KB, para isso, após efetuar o download, descompacte o arquivo webconfigreader.xpz para alguma pasta;
    • Vá no menu Knowledge Manager e selecione Import; Selecione o caminho onde foi posto o arquivo .xpz e selecione import.
    • Feito isso serão importados 1 External Object, 2 Files e 1 Webpanel de teste;
    • Se desejar, execute-a, para verificar se está tudo OK com o leitor de web.config:
    • Uma chave que pode ser lida sem problemas do web.config é a de nome DocumentType que por padrão está vindo com o valor HTML5.
  3. A seguir, necessitaremos incluir o user control em nossa masterpage, para que o mesmo fique sempre visível. eu recomendo adicioná-lo no cabeçalho da mesma: Sem títuloAo executar a aplicação podemos ver que agora existe um contador de sessão na masterpage:

    Sem título 2

    Também podemos notar 2 coisas:

    1. O tempo de sessão está sendo definido de forma manual, isso é, está sendo configurado diretamente no user control, ao invés de ser obtido do tempo de sessão real que o IIS nos oferece;
    2. O Layout está um pouco grande demais.
  4. Para corrigir o problema da integração com o IIS é que instalamos o external object Webconfig Reader, ele que será encarregado de obter o valor do tempo de sessão configurado na nossa aplicação. Para fazer isso, basta adicionar a seguinte linha no evento start da página onde está posicionado seu user control de tempo de sessão, no nosso caso, está na própria masterpage:
    ControlSessao1.Minutes = ConfigManager.getSessionStateTimeOut()
    
    Onde ControlSessao1 é o nome do controle utilizado na tela.
  5. Para ajustar o layout necessitaremos de um pouco de conhecimento em CSS, mas não é nada muito difícil: Todo user control é “renderizado” em tempo de execução, ou seja, no objeto gerado será feita uma referência ao código que deverá ser inserido no html, essa inserção será feita em tempo de execução. Logo, para alterar alguns detalhes do user control, basta editar o arquivo que está sendo referenciado, nesse caso se chama ControlSessaoRender.js e está na subpasta de nome ControlSessao, dentro da pasta Web de nossa aplicação.Uma maneira rápida é utilizar a configuração da fonte para que seja definida uma classe, ao invés do tipo de letra. Para isso, basta substituir a linha 40 do arquivo, onde diz:
    var buffer = '<a id="Sessao" href="#"><font face="' + this.FontFace + '" color="' +  this.FontColor + '" size="' + this.FontSize + '">'+ relogio + '</font></a>';

    por isso:

    var buffer = '<a id="Sessao" href="#"><font class="' + this.FontFace + '">'+ relogio + '</font></a>';

    O que estamos fazendo aqui é utilizando a propriedade FontFace do user control para que ao invés de configurar o tipo de letra, seja configurado a classe CSS a qual se deseja utilizar para esse texto.

    Também necessitaremos colocar o código a seguir, logo abaixo da linha que inserimos no evento start no item anterior:

    ControlSessao1.FontFace=ThemeClass:Label

    O que está acontecendo aqui é que estou dizendo que a fonte do controle de sessão agora receberá o nome da classe “Label”, notar que essa notação faz com que a classe passe a ter integridade referencial, ou seja, ao tentar remover a classe “Label” do meu objeto tema, ocorrerá um erro dizendo que ela está sendo referenciada. Nesse exemplo também poderia ser utilizado o código:

    ControlSessao1.FontFace="Label"

    Porém perderemos a integridade e caso essa classe fosse removida, nosso user control aparecerá desconfigurado.

    Assim, veremos agora como ficou nossa página:

    Sem título 3

  6. Como etapa opcional, complemento dizendo que ainda é possível melhorar um pouco mais, assim como no item anterior editamos somente a classe do texto, podemos editar o próprio texto, de forma que fique um pouco mais intuitivo, na linha 39 do mesmo arquivo ControlSessaoRender.js, temos o texto que está sendo utilizado:
    var relogio = this.tempo.getMinutes() + " Minutos : " + this.tempo.getSeconds() + " Segundos."

    Podemos, por exemplo, alterá-lo para:

    var relogio = “Tempo restante: “+ this.tempo.getMinutes() + ” min.  e ” + this.tempo.getSeconds() + ” seg.”

    O bacana é que a alteração do arquivo .JS é refletida automaticamente, basta recarregar a página que o texto aparece alterado.

    Finalmente, nossa página ficou assim:
    Sem título 4

 

Espero ter contribuído de alguma forma, qualquer dúvida ou problema que tiverem podem me deixar uma mensagem aqui embaixo, respondo ASAP.

Um abraço e até a próxima.

 

Anúncios

Acessando variáveis no web.config

Ola a todos.

Muito tempo se passou desde meu último post e peço desculpas, mas tudo na vida da gente acontece por alguma razão e desde minha última aparição muita coisa mudou, meu emprego, minha cidade atual, dentre outras, bueno, nada disso justifica eu sei, mas tive de repensar em um monte de coisas de um dia para o outro e acabei me esquecendo desse projeto, por isso as desculpas.

Recentemente estive trabalhando em um projeto realmente pequeno com apenas três tabelas no DBMS e fiquei me perguntando se realmente havia a necessidade de criar uma quarta tabela apenas para guardar parâmetros… Decidi que não, não queria.

Então lembrei que é comum no desenvolvimento tradicional (aka “a mão”) armazenar configurações de acesso rápido no arquivo web.config, então resolvi que iria fazer isso.

Tive alguns problemas quando tentei gravar valores no web.config utilizando a classe ConfigurationManager, depois fui descobrir que por motivos de segurança a gravação não foi implementada, o método para escrever até existe e ao ser executado não retorna erro, mas o web.config simplesmente não é atualizado. Fiquei um pouco confuso, mas depois analisando com calma vi que era uma sábia decisão… hehehehe

A solução foi mais simples que eu pensava, existe uma classe no C# que acessa diretamente a seção appSettings de dentro do web.config, a única coisa que faltava era criar o Wrapper, o External Object e utilizá-los.

O resultado final disso acabei utilizando como piloto para meu primeiro projeto no marketplace do GeneXus.

Resumindo, o que você precisa fazer é:

1 –  Baixar a extensão nessa URL: http://marketplace.genexus.com/product.aspx?web.config.reader
2 – Importar o arquivo “WebConfigReader.xpz” para a sua base de conhecimento;
3 – Certifique-se que o arquivo ConfigManager foi corretamente copiado para o diretório da aplicação <app>\bin; (Os arquivos ConfigManager.cs e ConfigManager.dll estão embutidos no .xpz);
4 – Efetue um build all;
5 – Execute a webpanel WebConfigReader para testar;

Obs.: Esse External Object foi criado utilizando GeneXus X Evolution 2 Upgrade 2, mas acredito que deva funcionar também na Evolution 1.

Sinta-se livre para utilizá-la onde achar melhor, e por favor, se precisar de ajuda não hesite em perguntar.

Se desejar, no .zip está o fonte e um .bat para auxiliar na compilação.

Abraços a todos e até a próxima.

Genexus 9 vs. Funções externas Java

 

Buenas…

Demorei um pouco, mas estou de volta. Hoje quero falar um pouco sobre funções externas, mais especificamente utilizando GeneXus 9, já que a versão X nos possibilita fazer isso de forma transparente…

Resumidamente, o que se deve fazer é:

1) Personalizar uma classe externa, para que o genexus consiga chamar o método execute() de forma transparente;
2) Compilar a classe externa na mesma package da aplicação;
3) Adicionar o call() no objeto genexus;

Vamos aos detalhes:

1)  Personalizar a classe externa:

Crie uma classe externa java, a que eu montei é uma classe para compactação de arquivos utilizando métodos nativos do java (Olhar no final desse post). O grande segredo aqui são 3 coisas:

  1. Ter um construtor específico que serve para o genexus utilizá-lo quando for instanciar o objeto.
  2. Ter um método execute(), que o genexus irá utilizar para realizar a chamada de fato.
  3. Ter um package name compatível com a aplicação, pois facilita a compilação tanto da classe externa quanto da aplicação genexus, visto que para essa última a classe já está automaticamente adicionada.

 

2) Compilar a classe externa:

Para compilar a classe externa é necessário colocá-la no mesmo diretório onde estão as demais classes geradas da aplicação (normalmente dentro do diretório da KB\diretório do modelo\web, levar em conta que se estiver utilizando um package name terá mais um ou dois subdiretórios dentro de web\). Após feito isso, utiliza-se o mesmo método que o genexus utiliza para compilá-las, ou seja:

  1. Abrir um prompt do DOS e posicioná-lo na pasta web\ do modelo da KB. Como o exemplo feito na minha KB:
    • Iniciar, Executar, CMD ;
    • D: ;
    • Cd D:\Projetos\MinhaKB\Data002\Web;
  2. Configurar a variável CLASSPATH do Java (disponível no menu de execução do GeneXus):
    • SET CLASSPATH=gxclassr.zip;GxUtils.jar;.;
  3. Executar:
    • callmake.bat “C:\Program Files\ARTech\GeneXus\GeneXus90\gxjava\GXJMake.exe” “C:\Program Files\Java\jdk1.6.0_16\bin\javac.exe” gxzip com\genexus\ -O

sendo

    • c:\Program Files\ARTech\GeneXus\GeneXus90 o caminho onde está o genexus 9 na minha máquina e
    • c:\Program Files\Java\Jdk1.6.0_16\ o caminho onde está o JDK.
    • gxzip: o nome da minha classe externa (arquivo .java)
    • com\genexus: o “package name” da minha aplicação.

E, finalmente,

 

3) Adicionar a chamada ao genexus

Essa é a parte mais simples, localize o evento onde deseja-se adicionar a chamada e inclua:

  • &Origem (string) = caminho absoluto de uma pasta ou arquivo a ser compactados;
    • Se for um arquivo, o .zip conterá somente esse arquivo.
    • Se for uma pasta, o .zip conterá todos os arquivos na pasta citada, com exceção de:
      • subpastas;
      • arquivos com extensão .zip, .7z e .jar
  • &Destino (String) = caminho absoluto para o arquivo .zip a ser gerado, se já existir será sobrescrito;
  • Call(‘gxzip’,&Origem, &Destino)

 

Bom, por hoje é só, espero ter sido claro como a água do rio Tietê e discreto como um elefante em uma loja de cristais… hehehe

Qualquer coisa não exite em não me chamar…

Um abraço e boa noite…

/*
               File: GXZip
        Description: Classe para compactação
             Author: Sandro M. Vianna
         Created on: 29/09/2010 16:24:55.75

*/

package com.genexus ;
import java.io.*;
import java.util.zip.*;
import com.genexus.*;

public class  gxzip {

    static final int BUFFER = 2048;

        //Esse construtor vazio é para ser utilizado pelo método main()
	public gxzip()
	{
	}

        //Aqui está o primeiro segredo, é necessário ter um construtor com exatamente esses parâmetros,
        //para que o genexus consiga "enxergar" como uma classe dele
	public gxzip(int remoteHandle, ModelContext context)
	{
	}

    //Deixei o método main para que a classe possa ser chamada pela linha de comandos,
    //Mas é completamente opcional...
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println(
   "Usar: java gxzip <arquivos> <novo arquivo compactado> ");
            return;
        }
        try {
            String filename = args[0];
            String zipfilename = args[1];
            gxzip list = new gxzip( );
            list.doZip(filename,zipfilename);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        //Aqui está o segundo segredo, esse método será utilizado no comando call() do gx
        //Aqui se está recebendo duas strings, apesar de serem recebidas como array de string.
        //Se utiliza sempre o indice 0
        public static void execute(String[] gxFiles, String[] gxZipFile ) {
	  try {
	    String filename = gxFiles[0];
            String zipfilename = gxZipFile[0];
            gxzip list = new gxzip( );
            list.doZip(filename,zipfilename);
	  } catch (Exception e) {
            e.printStackTrace();}
	}

        //Esse é o método que realiza de fato a compactação, é chamado tanto do método main() quanto do execute()
	public void doZip(String filename,String zipfilename) {
            try {
		File ft = new File(filename);
		String entryname, foldername = null;
		final String zipfile = zipfilename;

		if (ft.isFile()){
			//Obtenho o nome do arquivo e da pasta
			//int index = filename.lastIndexOf('\\')+1;
			//String entryname = filename.substring(index);
			//String foldername = filename.substring(0,index-1);
			entryname = ft.getName();
			foldername = ft.getParent();
		}else{
			entryname = "";
			foldername = filename;
			int index = filename.lastIndexOf('\\')+1;
			if (index == filename.trim().length()) foldername = foldername.substring(0,index-1);
		}

		//Obtenho os arquivos contidos na pasta
		File f = new File(foldername);
		String files[] = null;

		//Filtros para não carregar subpastas, arquivos compactados, e o próprio .zip
		FilenameFilter filter = new FilenameFilter() {
			public boolean accept(File f, String name) {
			File ftemp = new File(f.getPath()+'\\'+name);
			return !ftemp.isDirectory() &&
				!name.endsWith(".zip") &&
				!name.endsWith(".7z") &&
				!name.endsWith(".jar") &&
				!name.equals(zipfile);
				}
		};

		//Se foi configurado um nome de arquivo como origem, carrego somente ele na listagem
		if (entryname.length() > 0)
		{
			files = new String[1];
			files[0] = entryname;
		}else{
			//Senão, carrego a todos arquivos na pasta.
			files = f.list(filter);
                }

                byte[] buf = new byte[BUFFER];
		BufferedInputStream origin = null;
		ZipOutputStream s = new ZipOutputStream((OutputStream)new FileOutputStream(zipfilename));

                for (int i=0; i<files.length; i++) {

	        	FileInputStream fis = new FileInputStream(foldername+'\\'+files[i]);
         		origin = new BufferedInputStream(fis, BUFFER);
			s.setLevel(6);
			ZipEntry entry = new ZipEntry(files[i]);
			s.putNextEntry(entry);
			int count;
			while((count = origin.read(buf, 0, BUFFER)) != -1) {
				s.write(buf, 0, count);
			}
			origin.close();
		}
		s.finish();
		s.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}