quarta-feira, 10 de dezembro de 2014

Upload arquivos usando o AWS SDK for .NET (C#)

É bastante comum em diferentes tipos de projetos a necessidade de fazer upload de algum tipo de arquivo, sendo ele uma imagem ou algum outro. A forma mais fácil ou a mais utilizada seria fazer upload desse arquivo para uma determinada pasta do projeto, e se você estiver utilizando o IIS como servidor de pagina ou mesmo se for APACHE por exemplo de ambas as formas você estaria fazendo teu servidor entregar o conteúdo estático que nesse caso seria o arquivo que foi feito upload. Não vejo nenhum problema nisso quando falamos de poucos arquivos mas se a sua aplicação tiver uma necessidade maior de performance na entrega ou até mesmo a quantidade for bastante grande gerando um tamanho significante no disco do teu servidor você teria que optar por outra coisa.
Para isso a AMAZON AWS oferece um serviço bastante barato e rápido que pode ser utilizado para diversas outras funcionalidades. Apresentação do serviço no site oficial: http://aws.amazon.com/pt/s3/
A intenção desse post é exatamente mostrar como é simples a integração com o serviço "Simple Storage Service" ou como é conhecido AMAZON S3.

Primeiramente eu criei uma classe estática e nela vamos codificar o nosso método de uploadFile. A organização de onde ficaria melhor a classe fica a critério da arquitetura que você está trabalhando.
Antes de começar a codificar vamos fazer referencia ao SDK da amazon aws, você pode encontrar ele facilmente no Nuget.https://www.nuget.org/packages/AWSSDK/2.3.12 ou se você prefere por command.


   public static class UploadAmazon  
   {  
     private static IAmazonS3 client;  
     public static HttpStatusCode UploadFile(Stream file, string type, string nomeImagem)  
     {  
       using (client = AWSClientFactory.CreateAmazonS3Client("****key****", "***secret key****"))  
       {  
         PutObjectRequest request = new PutObjectRequest();  
         request.BucketName = "MeusArquivos";      //nome do bucket name  
         request.CannedACL = S3CannedACL.PublicRead;  //permissões  
         request.Key = "Arquivos/" + nomeImagem;    //diretorio e nome do arquivo  
         request.ContentType = type;         //type do arquivo  
         request.InputStream = file;         //Stream do arquivo  
         return client.PutObject(request).HttpStatusCode;  
       }  
     }  
   }  
Veja que o código é bastante simples =)
Apenas se atentar alguns detalhes, a propriedade "BucketName" eu coloquei fixa porém você poderia fazer uma verificação se esse "BucketName" se ele já não existe na sua storage, ficaria mais ou menos assim:
 private static bool IsCheckBucket(string bucketName)  
 {  
     ListBucketsResponse response = client.ListBuckets();  
     bool found = false;  
     foreach (S3Bucket bucket in response.Buckets)  
     {  
         if (bucket.BucketName == bucketName)  
         {  
           found = true;  
           break;  
         }  
      }  
      return found;  
  }  
(...) e para criar o BucketName
   public void CreateBucket(string bucketName)  
     {  
       if (!this.IsCheckBucket(bucketName))  
       {  
         client.PutBucket(new PutBucketRequest()  
         {  
           BucketName = bucketName,  
           UseClientRegion = true //Utiliza a mesma região que estiver como padrão no WS.  
         });  
       }  
     }  

Outra propriedade importante também seria "CannedACL" que indica o tipo de permissão que o arquivo terá no storage da amazon aws, nesse caso eu coloquei como "S3CannedACL.PublicRead" que esse arquivo poderá ser visto publicamente.
Para concluirmos você deve ter reparado que ao fazer a chamada da classe "AWSClientFactory" você precisa passar a ela a sua chave e também a chave secreta. Essas duas chaves você pode gerar através do painel de controle da Amazon AWS.
Ao acessar o console https://aws.amazon.com/ com o teu login, você vai ver no menu acima na aba escrita com seu nome a opção "Security Credencials".

Acesse:

Agora é só gerar a sua key e secret key e inserir na chamada.
Se obtiver sucesso no upload você pode acessar o console na opção de serviço S3, você vai perceber que o seu "BucketName" e dentro dele o arquivo enviado, ao clicar sobre o arquivo perceba nas propriedade que Amazon AWS gerou uma url publica de acesso ao seu arquivo que será algo parecido com isso: https://s3.amazonaws.com/MeusArquivos/Arquivos/nome_arquivo
Agora é só usufruir da performance que a storage vai te oferecer além disso com um valor muito pequeno.

Espero ter ajudado.
Abraço,

segunda-feira, 8 de dezembro de 2014

MongoDB + ASP.NET Web API 2

No dia-dia de um desenvolvedor é bem comum a necessidade de criar uma API com pequena ou baixa complexidade, ambientes que possam entregar informações a qualquer dispositivo e plataforma vem se tornando cada vez mais rotineiro.
Através dessa necessidade que a microsoft criou um Framework bastante fácil para desenvolver serviços HTTP http://www.asp.net/web-api





A primeira vez que tive contato com Web Api ainda na versão 1 fiquei muito animado porque consegui ver muitos ganhos principalmente em agilidade de desenvolvimento em relação aos serviços RESTful desenvolvidos com WCF até então.  
Para você que não teve a oportunidade de entrar nesse mundo de serviços RESTful te recomendo a começar com Web Api, é bastante fácil e com pouco programado você vai conseguir ver um resultado bastante interessante. Esse tutorial escrito por Mike Wasson http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api no site oficial do Asp Net vai perceber como é simples criar e consumir o teu serviço criado. Reforço a sua atenção no tutorial do Mike em que ele cria um client em Javascript para teste dos serviços, algo também muito utilizado ou talvez o mais utilizado ... fácil não? 



A minha intenção com esse post além de apresentar o Asp Net Web Api é mostrar a você que você pode também acoplar ele a um banco de dados NoSQL e tornar o seu serviço ainda mais rápido, é claro isso vai depender se a tua analise comporta um banco de dados NoSQL.

Sirva-se de um café e vamos codificar (...) Primeiramente iremos precisar criar um projeto Web Api. Eu optei por criar um projeto "Empty". 




Após criar o projeto já também criei um controller chamado "NOTICIA" e também um modelo também chamado "NOTICIA", perceba que me antecipei e já criei uma pasta chamada "MONGODB". A minha solution ficou mais ou menos assim ...

Se você executar o projeto vai ver que o serviço já esta funcionando porém você não implementou nenhum dos métodos no controller vai receber um erro ou se você optou por criar um controller já com as funções de read/write actions vai ter um template de alguns métodos previamente implementado, fica a sua escolha. 
Com o nosso serviço funcionando vamos agora implementar um pequeno repositório para facilitar a nossa vida ao escrever as nossas consultar no MongoDB.

 public interface IRepository<TEntity> where TEntity : EntityBase   
 {   
    bool Insert(TEntity entity);   
    List<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate);   
    List<TEntity> GetAll();   
    TEntity GetById(ObjectId id);   
 }   

 public abstract class EntityBase   
 {   
    [BsonId]   
    [BsonRepresentation(BsonType.ObjectId)]   
    public string Id { get; set; }   
 }   

Implementamos uma interface com os métodos que teremos disponíveis no repositório e também uma classe abstract para utilizar em nossos modelos, logo você vai perceber que não será necessário criar um atributo "Id" para nossos modelos sendo que apenas devemos herdar a nossa classe "EntityBase".

Exemplo:
 public class Noticia : EntityBase  
 {  
    public string Titulo { get; set; }  
    public string Conteudo { get; set; }  
    public string Autor { get; set; }  
 }  

Não devemos esquecer de baixar o pacote de DLL do drive do MongoDB para C# é bastante simples e pode ser baixado no
nuGet https://www.nuget.org/packages/mongocsharpdriver/1.9.2

   public class MongoDbRepository<TEntity> : IRepository<TEntity> where TEntity : EntityBase  
   {  
     private MongoDatabase _Database;  
     private MongoCollection<TEntity> _Collection;  
     public MongoDbRepository()  
     {  
       GetDatabase();  
       GetCollection();  
     }  
     private void GetDatabase()  
     {  
       var client = new MongoClient(GetConnectionString());  
       var server = client.GetServer();  
       _Database = server.GetDatabase(GetDatabaseName());  
     }  
     private string GetConnectionString()  
     {  
       return WebConfigurationManager.AppSettings["MongoDbConnectionString"].Replace("{DB_NAME}", GetDatabaseName());  
     }  
     private string GetDatabaseName()  
     {  
       return WebConfigurationManager.AppSettings["MongoDbDatabaseName"];  
     }  
     private void GetCollection()  
     {  
       _Collection = _Database  
         .GetCollection<TEntity>(typeof(TEntity).Name);  
     }  
     public bool Insert(TEntity entity)  
     {  
       entity.Id = ObjectId.GenerateNewId().ToString();  
       return _Collection.Insert(entity).Ok;  
     }  
     public List<TEntity> SearchFor(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)  
     {  
       return _Collection  
           .AsQueryable<TEntity>()  
             .Where(predicate.Compile())  
               .ToList();  
     }  
     public List<TEntity> GetAll()  
     {  
       return _Collection.FindAllAs<TEntity>().ToList();  
     }  
     public TEntity GetById(ObjectId id)  
     {  
       return _Collection.FindOneByIdAs<TEntity>(id);  
     }  
   }  
Se você já teve oportunidade de trabalhar com repositórios com o EF por exemplo vai perceber alguma semelhança pois utilizamos de expressões linq para acesso aos documentos. Os três métodos privados ficam responsáveis pela configuração do servidor do mongo.
Eu implementei buscando dentro do web.config apenas para uma melhor organização isso não impediria em nada no funcionamento se estivesse diretamente no código a connection do banco.


O servidor do MongoDB poderia estar localmente instalado como escrevi um tutorial a algum tempo falando especificamente sobre essa instalalção no windows. http://jr-encode.blogspot.com.br/2013/03/instal-mongodb-no-windows-como-servico.html.
O processo de instalação é bastante simples mas temos uma outra opção se você não quiser instalar o serviço do mongoDB na sua rede você pode estar utilizando um serviço que inicialmente é gratuito até um certo tamanho de banco e tendo a opção de escalar conforme sua necessidade. É um serviço muito bom pude utilizar em um projeto recentemente e não tive problemas. Você pode estar lendo mais a respeito https://mongolab.com/, após criar um login e senha no Mongolab você terá que criar um novo database.


Recomendo a você a criar um single-node que tem uma opção de até 0.5G gratuito, você terá que informar ao mongolab o nome do seu database a próxima pagina o mongolab irá mostrar sua URL de acesso ao database onde você terá que substituir na config do repositório, depois disso é SUCESSO, você terá um banco NoSQL online tendo a opção de escalar muito dependendo da necessidade e crescimento da sua API.
Com o nosso repositório configurado podemos agora codificar os métodos do nosso controller de noticia.

   public class NoticiaController : ApiController  
   {  
     private IRepository<Noticia> _RepNoticia;  
     public NoticiaController()  
     {  
       _RepNoticia = new MongoDbRepository<Noticia>();  
     }  
     /// <summary>  
     /// Retorna todas as noticias.  
     /// </summary>  
     /// <returns></returns  
     [Route("")]  
     public IEnumerable<Noticia> Get()  
     {  
       return _RepNoticia.GetAll();  
     }  
     /// <summary>  
     /// Retorna uma noticia filtrada pelo Id  
     /// </summary>  
     /// <param name="id">Id</param>  
     /// <returns></returns>  
     [Route("{id}")]  
     public Noticia Get(ObjectId id)  
     {  
       return _RepNoticia.GetById(id);  
     }  
     /// <summary>  
     /// Retorna todas as noticias filtrando por alguma palavra contida no titulo.  
     /// </summary>  
     /// <param name="titulo">Titulo</param>  
     /// <returns></returns>  
     [Route("titulo/{titulo}")]  
     public IEnumerable<Noticia> Get(string titulo)  
     {  
       return _RepNoticia.SearchFor(f => f.Titulo.Contains(titulo)).ToList();  
     }  
     /// <summary>  
     /// Grava uma noticia  
     /// </summary>  
     /// <param name="value"></param>  
     [Route("")]  
     public HttpResponseMessage Post(Noticia noticia)  
     {  
       try  
       {  
         _RepNoticia.Insert(noticia);  
         return Request.CreateResponse(HttpStatusCode.OK, "Noticia gravada com sucesso");  
       }  
       catch (Exception ex)  
       {  
         return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);  
       }  
     }  
   }  
As chamadas para o repositório fica de forma limpa e de fácil entendimento, basicamente temos quatro métodos de teste, são eles: Get (retorna todos), Get (retorna filtrado por id), Get (retorna todos por alguma palavra contida titulo), Post (Grava uma noticia). Outro detalhe que possa parecer diferente é a utilização de "RoutePrefix" na anotação do controller. Isso irá mudar a URL de acesso no router que deverá ser acessada agora assim: http://localhost/api/public/v1/noticias/   dessa forma você consegue padronizar melhor as chamadas da sua API, acredito que isso é mais uma boa pratica porém se você tiver uma maior quantidade de controller vai perceber que começa a ficar "complicada" a organização da API. Segue um outro interessante tutorial falando sobre "RouterPrefix" também escrito pelo Mike Wasson http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Outra dica interessante que costumo fazer é como geralmente utilizo retorno de formato JSON até porque é bem mais tranquilo para você trabalhar em Javascript por exemplo, removendo o retorno de XML e também formatando o retorno do JSON algo que vai te ajudar bastante na leitura em ambiente de desenvolvimento.

Global.asax.cs (Método register)

Espero ter ajudado, Fique a vontade para manter contato.
Abraço,

sexta-feira, 29 de março de 2013

Instalando MongoDB no Windows

Você que está antenado em novas tendencias já deve ter ouvido falar em NoSQL. Basicamente esse termo é utilizado para definir banco de dados não-relacionais, algo que rompe toda uma historia dos banco relacionais. 
Seu autor Carlo Strozzi tem uma frase que defini bem:
"é completamente distinto do modelo relacional e portanto deveria ser mais apropriadamente chamado "NoREL" ou algo que produzisse o mesmo efeito".
Aqueles que gostam de historia, te recomendo a dar uma navegada por esses sites:
O foco desse nosso POST é dar inicio a utilização de um banco de dados não-relacional, inicialmente irei mostrar como instalar o banco e configura-lo para ficar como um serviço do windows. 
Ao inicial o sistema operacional, juntamente com ele o serviço do nosso banco de dados. Venho estudando e utilizando em alguns projetos o banco "MongoDB". http://www.mongodb.org/.
MongoDB é uma aplicação de código aberto, de alta performance, sem esquemas, orientado à documentos.

Nesse mundo de NoSql ele é um cara bastante conhecido e utilizado por muita gente grande como Twitter, Facebook etc que por sua vez são sistemas que necessitam de alta escalabilidade nas consultas e utilização de compartilhamento de informações entre servidores. 
Suas bibliotecas são escritas em C++ e utiliza técnicas avançadas em recorrer totalmente a memoria para leitura dos dados.


Começamos fazendo o download da biblioteca que está disponível no site oficial http://www.mongodb.org/downloads.
Após teremos que criar duas pastas para apontar ao mongo aonde os arquivos do repositório será armazenado.

md data
md data\db
Depois de criada as pastas, teremos que navegar até a pasta em que baixamos e executar o Mongod.exe


Com a execução realidade com sucesso você poderá testar se o serviço está rodando corretamente. Vá até o seu navegador de preferencia e escreva a seguinte URL http://localhost:27017/





Se você reparar no gerenciador de processos da sua maquina vai ver um processo chamado mongod.exe. Muito fácil não é? isso já seria suficiente para começar a utilização do banco.
Mas como disse inicialmente vamos coloca-lo na inicialização do sistema operacional, para isso execute o seguinte comando:
C:\mongodb\bin\mongod.exe --config C:\mongodb\mongod.cfg --install

Fique atento se o arquivo mongod.cfg existe no diretório, caso contrario execute os seguintes comandos para criar o diretório de log.

md C:\mongodb\log
echo logpath=C:\mongodb\log\mongo.log > C:\mongodb\mongod.cfg


Agora o arquivo mongod.cfg foi gerado, após efetuado o procedimento com sucesso, execute novamente o comando para instalação do serviço.
Reinicie o sistema operacional e verá que o serviço será iniciado...

Acessando a URL http://localhost:28017/ terá um gerenciador bastante simplificado aonde é mostrado algumas atividades do banco.
Uma outra dica bastante produtiva seria a utilização de um gerenciador com uma interface gráfica um pouco mas amigável.
http://robomongo.org/ é uma biblioteca também de código aberto de muito fácil configuração e utilização, nela você terá um ambiente gráfico para gerenciar suas bases no MongoDB.


Apenas lembrando que testei esse procedimento de instalação nos sistema operacional windows 7 ultimamte 64 bits e windows server 2008 R2 Enterprise.

É isso ai! Espero ter ajudado.
Abraço =)

   "O sucesso nasce do querer, da determinação e persistência em se chegar a um objetivo. Mesmo não atingindo o alvo, quem busca e vence obstáculos, no mínimo fará coisas admiráveis."
José de Alencar

sábado, 17 de novembro de 2012

JQuery Mobile, Plante Aqui vs Google Maps API

Boa tarde caros "encodes" ...
sábado quente em Londrina pra variar! A um bom tempo sem escrever, prova de certificação bem próxima e o tempo vai ficando um pouco escasso, chega de desculpas baratas "HAHA" e vamos lá (...)
Post de hoje pretendo expor a vocês uma nova API que venho estudando e pude participar de um "Hackathon" com uma galera muito fera, que o intuito era apresentar essa API e suas funcionabilidades. Falando um pouco sobre a API, ela se chama Plante Aqui você pode encontrar mais sobre no Plante Aqui no Facebook ou se preferir no site oficial http://www.planteaqui.org/ lá você vai encontrar sobre a ideia da API e a proposta que a mesma está divulgando que é bastante interessante por sinal.
Aqui vou apresentar algo bastante básico e utilizar o recurso de consultas que a API (Plante Aqui) me fornece.
Após você ter dado uma lida nos links que repassei vamos explicar o nosso programa de exemplo:

1 - Desenvolver uma interface mobile com JQuery Mobile.
2 - Repassar os dados consultados a API(Plante Aqui).
3 - Com o retorno de sucesso da API(Plante Aqui) vamos utilizar a API(Google Maps) para mostrar os resultados em tela.

Considerando que você leu a ideia central do Plante Aqui a nossa interface cliente basicamente vai receber os dados de latitude e longitude informados pelo usuário que seria as coordenadas em que você quer saber se naquele ponto existe um local adequado para se plantar uma arvore se as coordenadas forem encontradas vamos repassar elas a nosso maps marcando o local.
Chega de papo e vamos codar =)
Começamos a implementar a parte visual:
 <!DOCTYPE html>  
 <html>  
 <head>  
      <meta charset="utf-8">  
      <title>Google Maps vs Plante Aqui</title>  
      <link rel="stylesheet" href="css/themes/default/jquery.mobile-1.2.0.css" />  
      <link rel="stylesheet" href="docs/_assets/css/jqm-docs.css" />  
      <script src="js/jquery.js"></script>  
      <script src="docs/_assets/js/jqm-docs.js"></script>  
      <script src="js/jquery.mobile-1.2.0.js"></script>  
 </head>  
 <script type="text/javascript">  
 </script>  
 <body>  
      <div class="ui-body ui-body-a">  
           <div data-role="fieldcontain">  
              <label for="latitude">Latitude:</label>  
              <input type="text" name="latitude" id="latitude" value="" />  
           </div>  
           <div data-role="fieldcontain">  
              <label for="longitude">Longitude:</label>  
              <input type="text" name="longitude" id="longitude" value="" />  
           </div>  
           <button type="button" data-theme="b" id="enviar" name="enviar" onclick="buscarEndereco();">Verificar Disponibilidade  
           </button>  
      </div>  
      <div id="map_canvas" style="width: 100%; height: 500px"></div>  
 </body>  
 </html>  
Como pode ver o código é bastante simples com JQuery Mobile.
O próximo passo seria implementar o codigo que será responsável em "desenhar" o mapa.
Não sei se você teve a oportunidade de trabalhar com alguma integração das API do google porém vai ver que é bastante simples de entender e aplicar nos seus programas as inúmeras funcionabilidades que a google apresenta.
Sobre o google maps em questão você vai encontrar a documentação aqui: https://developers.google.com/maps/documentation/javascript/v2/introduction neste link tem os exemplos de códigos e parametros que você pode utilizar para enriquecer teu programa.
Voltando a nosso exemplo implemente essa função.
   var map = null;  
   var geocoder = null;  
   function initialize() {  
    if (GBrowserIsCompatible()) {  
     map = new GMap2(document.getElementById("map_canvas"));  
     map.setCenter(new GLatLng(37.4419, -122.1419), 1);  
     map.setMapType(G_HYBRID_MAP);  
     map.setUIToDefault();  
     geocoder = new GClientGeocoder();  
    }  
   }  

Se você deu uma lida rápida no link que repassei sobre a API do Google Maps creio que ficou bastante fácil entender o meu código a seguir! "hehe" De modo geral eu instancio a classe GMap2() repassando para o construtor da classe o elemento HTML criado na minha pagina que irá receber o canvas do mapa gerado.
O restante do código se trata de configurações propriamente falando do mapa. O método SetMapType(G_HYBRID_MAP); por exemplo defini o tipo de mapa que será apresentando ao inicializar o programa, no meu caso eu escolhi o "SATÉLITE".
O nosso próximo passo seria adicionar a função a seguir no onload da pagina:
 <body onload="initialize()" onunload="GUnload()">  
...e também não podemos esquecer de adicionar a referencia a API do google:
 <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key="SUA_CHAVE"  
 type="text/javascript"></script>  
Perceba que no parametro "KEY" você deverá colocar a sua KEY gerada pela google no momento que você marcou a utilização de alguma API com o teu usuário google é claro.
Mais informações para obter a sua KEY:
https://developers.google.com/maps/documentation/javascript/tutorial
Fazendo isso você já verá algum resultado (...)

Vamos agora para a nossa próxima implementação que seria a função de busca das coordenadas informadas.
No evento onclick="" do button "Verificar Disponibilidade" vamos chamar a seguinte função:
 function buscarEndereco(button){  
           var iLatitude = $('#latitude').val();  
           var iLongitude = $('#longitude').val();  
           if(iLatitude != '' || iLongitude != ''){  
                $.getJSON('http://www.planteaqui.org/api/position?lat='+iLatitude+'&lon='+iLongitude+'',   
                     function(data) {  
                          // dados retornados do Plante Aqui  
                          point = new GLatLng(data[0].positions.lat,data[0].positions.lon);  
                          marker = new GMarker(point);  
                          map.addOverlay(marker);  
                     }  
                );  
           }else{  
                alert('Informe a Latitude e a Longitude para buscar!');  
           }  
  }  
Antes de explicarmos o nosso código vale ressaltar sobre a API Plante Aqui, pois se você a leu a documentação vai perceber que ela é baseada em REstFull desta forma utilizei a função $.getJSON(url,function() { } ); 
veja que a requisição é assíncrona e no segundo parâmetro você deve criar a sua função que receberá o retorno que no nosso caso é um JSON, após o retorno instancio a classe 
GLatLng(LATITUDE, LONGITUDE); 
repassando as coordenadas encontradas pela API Plante Aqui o retorno da função será guardada em uma VAR que será também repassada a classe GMarker(); que receberá as coordenadas retornando um ponto a ser marcado que por final será adicionado ao objeto principal do mapa. =)
Tranquilo né?
Pensando em ficar funcional para o usuário que estará fazendo a consulta não fico muito legal pois saber a latitude e longitude exata de um local é um pouquinho complicado "HAHA" porém este é o nosso primeiro exemplo utilizando API Plante Aqui, A ideia é evoluir e o próximo post que está praticamente pronto será implementado algumas alterações para corrigir isso. =)
A nível acadêmico alcançamos o nosso objetivo que seria mostrar como consumir uma API em RestFull e também a integração com o maps do google.
Fica o reforço da dica para você dar uma melhor olhada na API Plante Aqui veja que a documentação explica exatamente como "sugerir um local para se plantar" e outros métodos vale dizer também que a API está em constante alterações e novas melhorias vem ai.
Ficamos por aqui, com o agradecimento em especifico ao pessoal do Plante Aqui que abriu a oportunidade e a iniciativa.

Download do projeto completo
Até a próxima. :)

sexta-feira, 26 de outubro de 2012

MVC Helpers vs ExtJS 3.4 Sencha

Eai Caros "Encodes" (...)
Sexta-feira de muita chuva em Londrina é totalmente inspirador para um Post novo hehe, hoje vo tenta mostra a vocês a ideia de utilização de helpers no MVC 3.
Iniciando podemos começar falando um pouco do "RAZOR". Esse é o novo motor de visões de paginas adotado pela microsoft, esse novo "cara" segue o mesmo modelo de arquivos (.ASPX/.ASCX/.MASTER) que já estamos acostumados no WEB FORM.
Para uma mais refinada explicação vou utilizar uma definição feita em um ótimo blog que costumo visitar. (ScottGu's Blog
Compacto, Expressivo, e Fluente: o Razor minimiza o número de caracteres e teclas digitadas necessárias em um arquivo, e permite um fluxo de trabalho de codificação rápido e fluente. Ao contrário das sintaxes de outros modelos, você não precisa interromper sua codificação para explicitamente indicar blocos do servidor dentro do código HTML. O analisador é inteligente o suficiente para inferir isso a partir do seu código. Isto permite uma sintaxe muito compacta e expressiva, que é limpa, rápida e divertida para digitar.
Fácil para Aprender: o Razor é fácil de aprender e te permite ser produtivo rapidamente com um mínimo de conceitos. Você usa todas as suas habilidades existentes de linguagens de programação e HTML.
Não é uma nova linguagem: Nós conscientemente optamos por não criar uma nova linguagem imperativa com o Razor. Em vez disso, queríamos permitir que os desenvolvedores utilizassem seus atuais conhecimentos de linguagens de programação C#/VB (ou outras) com o Razor, entregando uma sintaxe de marcação para modelagem que permite um fluxo de trabalho de construção de código HTML bem legal com a sua linguagem de preferência.
Funciona com qualquer Editor de Texto: o Razor não necessita de uma ferramenta específica e te permite ser produtivo em qualquer editor de texto velho e simples (notepad funciona muito bem).
Tem ótimo suporte à Intellisense: o Razor foi projetado para não necessitar de uma ferramenta específica ou editor de código, mas ainda assim ele terá um excelente suporte para intellisense dentro do Visual Studio.
Unidade Testável: A nova implementação deste motor de visões suportará a capacidade de testar visões de maneira unitária (sem a necessidade de um controlador ou de um servidor web, e pode ser hospedado em qualquer projeto de teste unitário - não há necessidade de um app-domain [domínio de aplicação] especial).
Após entendermos algumas definições do Razor podemos partir para uma outra ideia que seria de criar o nosso próprio ajudante para criação de layouts.
O que vou mostrar a seguir seria basicamente utilizar o recurso das chamadas Razor em uma pagina HTML seguido de alguns parâmetros dinâmicos e gerando através do ExtJs (O que é ExtJs?) É um framework JavaScript de ricos recursos para desenvolvimento de layouts, ficando extremamente conhecido no Brasil, sendo agora de propriedade da SENCHA (http://www.sencha.com/) ele vem sendo atualizado constantemente a comunidade é ativa e nem preciso elogiar também a documentação !
Se você não conhece e tiver algum interesse em estudar mais ...

Docs - Versão 3.4 utilizada neste Post

Docs - Versão 4.1 (ATUAL)

Continuando (...) o que o ExtJs tem haver com o Razor? hehe na verdade como disse no começo vou agregar esse dois recursos para um ganho significativo na produtividade para a criação de uma Grid. Se você tem uma noção mais voltada para arquitetura pode estar pensando "Abstrair o que já está abstraído?" realmente um termo que me perguntei varias vezes até começar a trabalhar em algo do tipo, porém a PRODUTIVIDADE vs PADRONIZAÇÃO falou mais alto. Na verdade isso seria papo para uma longa discução mas ao final do POST seja você capaz de tirar suas próprias conclusões! Chega de "BLA BLA" e para manter o costume sirva-se de um café e vamos codar  ...

Primeiramente, criei um projeto PADRÃO utilizando um template já pronto do VS2010 e também uma pasta "Helpers".


Na pasta Helpers adiciona um classe chamada "HelperGrid.cs" essa classe irá receber e processar as chamadas vinda da pagina feita pelo Razor.
Vamos implementar a classe HelperGrid.cs, resumidamente nela eu implementei uma logica simples, aonde coloquei toda a ideia de parâmetros que uma Grid poderia receber dinamicamente sendo mais simplificado ainda, utilizei da minha imaginação e adicionei alguns métodos aonde enxerguei que a minha Grid poderia ser dinâmica. Segue o código:
 namespace MvcApplication1.Helpers  
 {  
   public static class HelperGrid  
   {  
     public static ExtGrid ExtGrid(this HtmlHelper helper, string id)  
     {  
       return new ExtGrid(helper, id);  
     }  
   }  
   public class ExtGrid  
   {  
     private HtmlHelper helper;  
     private string id;  
     private string url;  
     private string pkTabela;  
     private string tituloGrid;  
     private List<ColunasGrid> listaColunasGrid = new List<ColunasGrid>();  
     private string elementoRoot;  
     private int pageSizeGrid;  
     public ExtGrid(HtmlHelper helper, string id)  
     {  
       this.helper = helper;  
       this.id = id;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="url"></param>  
     /// <param name="pkTabela"></param>  
     /// <returns></returns>  
     public ExtGrid SetDataStore(string url, string pkTabela)  
     {  
       this.url = url;  
       this.pkTabela = pkTabela;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="root"></param>  
     /// <returns></returns>  
     public ExtGrid SetElementRoot(string root)  
     {  
       this.elementoRoot = root;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="pageSize"></param>  
     /// <returns></returns>  
     public ExtGrid SetPageSize(int pageSize = 20)  
     {  
       this.pageSizeGrid = pageSize;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="Name"></param>  
     /// <param name="Header"></param>  
     /// <param name="Tamanho"></param>  
     /// <param name="Visivel"></param>  
     /// <param name="Xtype"></param>  
     /// <returns></returns>  
     public ExtGrid Columns(string Name, string Header, int Tamanho, bool Visivel = true, string Xtype = null)  
     {  
       ColunasGrid colunasGrid = new ColunasGrid(Name, Header, Tamanho, Visivel, Xtype);  
       this.listaColunasGrid.Add(colunasGrid);  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     /// <param name="titulo"></param>  
     /// <returns></returns>  
     public ExtGrid SetTitulo(string titulo = null)  
     {  
       this.tituloGrid = titulo;  
       return this;  
     }  
     /// <summary>  
     ///   
     /// </summary>  
     public void Render()  
     {  
       GridXml gridXmlAtributos = new GridXml()  
       {  
         url = this.url,  
         pkTabela = this.pkTabela,  
         tituloGrid = this.tituloGrid,  
         elementoRoot = this.elementoRoot,  
         pageSize = this.pageSizeGrid  
       };  
       foreach (ColunasGrid column in this.listaColunasGrid)  
       {  
         GridColumnXml layoutColumnGrid = new GridColumnXml()  
         {  
           name = column.name,  
           header = column.header,  
           tamanho = column.tamanho,  
           visible = column.visivel,  
           xtype = column.xtype  
         };  
         gridXmlAtributos.columns.Add(layoutColumnGrid);  
       }  
       string xml;  
       try  
       {  
         xml = HelperUtil.Serialize<GridXml>(gridXmlAtributos);  
       }  
       catch (Exception ex)  
       {  
         throw new Exception("Erro ao serializar o XML" + ex.Message);  
       }  
       helper.ViewContext.Writer.Write("<div id=\"" + this.id + "\"");  
       helper.ViewContext.Writer.Write(" data-isGrid=\"true\"");  
       helper.ViewContext.Writer.WriteLine(">");  
       helper.ViewContext.Writer.WriteLine("<script type=\"text/xml\" id=\"" + this.id + "grid_xml\">");  
       helper.ViewContext.Writer.WriteLine(xml);  
       helper.ViewContext.Writer.WriteLine("</script>");  
       helper.ViewContext.Writer.WriteLine("</div>");  
     }  
   }  
   public class ColunasGrid  
   {  
     public string name { get; set; }  
     public string header { get; set; }  
     public int tamanho { get; set; }  
     public bool visivel { get; set; }  
     public string xtype { get; set; }  
     public ColunasGrid(string name, string header, int tamanho, bool visivel, string xtype)  
     {  
       this.name = name;  
       this.header = header;  
       this.tamanho = tamanho;  
       this.visivel = visivel;  
       this.xtype = xtype;  
     }  
   }  
   /// <summary>  
   ///   
   /// </summary>  
   [XmlType(TypeName = "gridLayout")]  
   public class GridXml  
   {  
     public string url;  
     public string pkTabela;  
     public string tituloGrid;  
     public string elementoRoot;  
     public int pageSize;  
     public string modeFilter;  
     public int charFiltro;  
     public List<GridColumnXml> columns;  
     public GridXml()  
     {  
       this.columns = new List<GridColumnXml>();  
     }  
     public readonly static XmlSerializer Serializer = new XmlSerializer(typeof(GridColumnXml));  
   }  
   /// <summary>  
   ///   
   /// </summary>  
   [XmlType(TypeName = "column")]  
   public class GridColumnXml  
   {  
     [XmlAttribute]  
     public string name;  
     [XmlAttribute]  
     public string header;  
     [XmlAttribute]  
     public int tamanho;  
     [XmlAttribute]  
     public bool visible;  
     [XmlAttribute]  
     public string xtype;  
   }  
 }  


Vale colocar algumas observações na parte do código:

       helper.ViewContext.Writer.Write("<div id=\"" + this.id + "\"");  
       helper.ViewContext.Writer.Write(" data-isGrid=\"true\"");  
       helper.ViewContext.Writer.WriteLine(">");  
       helper.ViewContext.Writer.WriteLine("<script type=\"text/xml\" id=\"" + this.id + "grid_xml\">");  
       helper.ViewContext.Writer.WriteLine(xml);  
       helper.ViewContext.Writer.WriteLine("</script>");  
       helper.ViewContext.Writer.WriteLine("</div>");  

Esse é o trecho que podemos dizer que é mais importante nessa logica, pois através do ViewContext que é disponibilizado pelo "helper" que seria basicamente todo conteúdo da pagina colocamos um pacote XML serializado que como você pode perceber no restante do código anterior é a nossa hierarquia de classes. Porque um pacote XML contido dentro de um <script> </script> ?  Isso é devido a renderização da pagina pois se escrevermos simplesmente o texto do XML serializado o browser irá tentar "fazer alguma coisa" com a string repassada prejudicando o nosso objetivo que como vamos ver adiante é capturar esse pacote via JavaScript   (...)

AHH, já ia esquecendo implemente uma outra classe chamada "HelperUtil.cs" nela coloquei o codigo responsável por serializar qualquer objeto repassado <T>, segue o codigo:
   public static class HelperUtil  
   {  
     public static string Serialize<T>(T value)  
     {  
       if (value == null)  
       {  
         return null;  
       }  
       XmlSerializer serializer = new XmlSerializer(typeof(T));  
       System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();  
       settings.Encoding = new System.Text.UnicodeEncoding(false, false);  
       settings.Indent = false;  
       settings.OmitXmlDeclaration = false;  
       using (StringWriter textWriter = new StringWriter())  
       {  
         using (System.Xml.XmlWriter xmlWriter = System.Xml.XmlWriter.Create(textWriter, settings))  
         {  
           serializer.Serialize(xmlWriter, value);  
         }  
         return textWriter.ToString();  
       }  
     }  
   }  

Ao chamar o metodo Render(); do nosso helper o pacote XML será escrito na pagina que foi feita a chamada, nesta VIEW o código é bastante reduzido pois ai que está a "graça" de tudo hehe nela será contida apenas os parâmetros requisitados para compor a nossa Grid e como estamos utilizando MVC a Url.Content do nosso controlador aonde a busca dos dados será feita.


Index.cshtml

 @using MvcApplication1.Helpers  
 @{   
   Html.ExtGrid("DIV_GRID_CONSULTA_PESSOA").SetDataStore(@Url.Content("~/Teste/GetAll"), "idPessoa").  
      SetTitulo("Consultar Pessoas").  
      SetElementRoot("Pessoa").  
      SetPageSize(20).  
      Columns("idPessoa", "Pessoa", 50, true, "Int").  
      Columns("nmPessoa", "Nome", 820, true, "String").  
      Render();  
 }  

ATENÇÃO: não se esqueça de adicionar o @using do seu helper pois é extremamente valido, sem ele você não terá acesso aos métodos disponibilizados no helper.

Teria uma outra maneira de implementar essa referencia diretamente no Web.config mas optei por fazer na VIEW para uma melhor visualização conceitual e também expressar a importância pois sem a referencia você não terá acesso a sua classe ajudante.

Seguindo a nossa chamada que foi feita a partir da nossa VIEW o pacote XML foi gerado como retorno na pagina, agora iremos montar no JavaScript como iria ficar a nossa Grid. Antes de "codarmos" o JS teremos que adicionar em nosso projeto a pasta contendo o Source da estrutura do ExtJs, enfatizando que disse no começo utilizei a versão 3.4, aproveitando também criamos uma outra pasta chamada 
"ext-js-helpers" para colocarmos o nosso JS que será responsável pela criação da Grid.
Agora o escopo do nosso projeto ficaria mais ou menos assim (...)

Vamos implementar o nosso JS ...
 jQuery(document).ready(function () {  
   var grids = jQuery("div[data-isGrid=\'true']");  
   grids.each(function (i, obj) {  
     var grid = new Grid(i, obj);  
   });  
 });  
 function Grid(index, rootDiv) {  
   
   var $this = this;  
   var id = $(rootDiv).attr('id');  
   var id_grid_panel = 'GRID_'+id;  
   // Vamos pegar o nosso XML...  
   var atributosGrid = $($("#" + id + "grid_xml").html());  
   // aqui vamos pegar os atributos do nosso xml  
   var url        = $(atributosGrid).find('url').text();  
   var pkTabela   = $(atributosGrid).find('pkTabela').text();  
   var tituloGrid = $(atributosGrid).find('tituloGrid').text();  
   var root       = $(atributosGrid).find('elementoRoot').text();  
   var pageSize   = parseInt($(atributosGrid).find('pageSize').text());  
   
   var columnsModel = [];  
   var columnsField = [];  
   
   $(atributosGrid).find('columns column').each(function (i, obj) {  
     columnsModel.push({  
       id: $(obj).attr("name"),  
       header: $(obj).attr("header"),  
       dataIndex: $(obj).attr("name"),  
       width: parseInt($(obj).attr("tamanho")),  
       sortable: true  
     });  
     columnsField.push($(obj).attr("name"));  
   });  

   // criamos um JsonStore...  
   var StoreGrid = new Ext.data.JsonStore({  
     url: url,  
     root: root,  
     totalProperty: 'total',  
     idProperty: pkTabela,  
     fields: columnsField  
   });  

   StoreGrid.load({ params: { start: 0, limit: pageSize } }); 
 
   var grid = new Ext.grid.GridPanel({  
     id: id_grid_panel,  
     width: 900,  
     height: 395,  
     title: tituloGrid,  
     store: StoreGrid,  
     viewConfig: { emptyText: 'Nenhum registro encontrado', deferEmptyText: false },  
     stripeRows: true,  
     // grid columns  
     columns: columnsModel,  
     bbar: new Ext.PagingToolbar({  
       pageSize: pageSize,  
       store: StoreGrid,  
       displayInfo: true,  
       displayMsg: 'Exibindo Tópicos {0} - {1} of {2}',  
       emptyMsg: "Nenhum tópico para mostrar"  
     })  
   });  
   grid.render(id);  
 }  

Veja que não tem segredo !
O código é basicamente implementado a partir do ExtJs para gerar a instancia do objeto Ext.grid.GridPanel e as propriedades vindas do pacote XML sendo capturado utilizando a bilbioteca do JQuery e abusando do comando Push() para montar as propriedades de Object esperado pelo ExtJs dinamicamente.
Após a correta implmentação não vamos esquecer de adicionar os scripts a nossa pagina ...



Com isso feito o resultado da nossa Grid seria mais ou menos isso !

Os dados gerados não são dados vindo de uma base "quente" e sim dados aleatórios (de um Model) serializados no método que informamos no  @Url.Content.
Perceba a requisição feita através do firebug no Firefox

Algo bastante importante para chamarmos atenção seria os metodos SetElementRoot() passado na VIEW que corresponde ao elemento PAI do JSON de retorno e também é claro os campos que como pode perceber o objeto que utilizei tinha muito mais propriedades do que mostrei na Grid porém exibi apenas o campo idPessoa e nmPessoa do mesmo, a ideia é exatamente essa deixar intuitivo para o desenvolvedor que string NAME repassada ao Helper corresponde ao nome da propriedade do objeto serializado.

É isso galera, espero que isso tenha sido útil e aqueles que lerem absorvam a ideia que quis repassar dessa junção de Helper e ExtJs. 
OBS: vale ressaltar novamente a quantidade de código na VIEW para a criação do mesmo, deixando seu projeto mais limpo e padronizado.

Abraço.  =))
Assim que tenho visto que não há coisa melhor do que alegrar-se o homem nas suas obras, porque essa é a sua porção; pois quem o fará voltar para ver o que será depois dele?
Eclesiastes 3:22 

quinta-feira, 18 de outubro de 2012

ASP.NET MVC Areas

Eai Caros "Encodes"  (...)
Estava a pensar nesses dias sobre um novo assunto para compartilhar com vocês e veio a mente um problema que tive algum tempo atrás em uma arquitetura web que estou trabalhando e resolvi compartilhar pois fiz varias pesquisas achei bons tutorias sobre o assunto porém a maioria deles em inglês, não é algo muito complicado porém se você nunca necessitou desse tipo de arquitetura e está disposto a implementar algo parecido vai perceber que fica um pouco chato no começo e muito simples e organizado de se trabalhar no passar do tempo do projeto.
Falando do "problema" necessariamente, quando você está trabalhando em um padrão de projeto em dotnet  mais especificamente MVC 3 que é o meu caso, você possui um padrão que o próprio visual studio cria para você ... seria mais ou menos assim:

 (OBS: apenas um parentes como você pode ver eu estou utilizando a versão 2012 do VS e apenas para nível de aprendizado e percepção de mudanças criei um projeto MVC4 mas para o assunto que estamos abordando não vai mudar nada praticamente na implementação)
Voltando (...)

Como pode ver existe o padrão de pastas CONTROLLERS, MODELS, VIEWS e o Global.asax este por sua vez é de extrema importância num projeto MVC pois ele é responsável pela manipulação de eventos e do ciclo de vida da aplicação. Basicamente ele contém uma tabela de rotas que é criada ao iniciar o aplicativo, mais detalhes sobre você pode dar uma lida no site oficial:
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/asp-net-mvc-routing-overview-cs
até ai estava tudo certo até que comecei a perceber que no projeto em que estou locado iria ter um problema de organização dessas pastas pois não iria ter apenas um conceito de regra dentro da hierarquia do projeto, deixando isso mais claro simulei apenas para nível conceitual um diagrama demonstrando esse possível ambiente:

Seria mais ou menos essa a ideia, como você pode ver tenho um site principal aonde o acesso seria publico sendo mais detalhista seria acessado por qualquer usuário sem nenhuma autenticação, porém os outros módulos seria de acesso especifico apenas para usuários credenciados e também devemos considerar que seria desenvolvido por uma equipe diferente. Foi nessa ideia que partir a pesquisar e verifiquei que o dotnet tem algo chamado de AREA. É dessa forma que ele representa os subdomínios da sua aplicação, com isso você poderia tranquilamente simular esse ambiente que colocamos a seguir tendo nele uma arquitetura de pastas na mesma visão do MVC porém dividido por áreas ficando assim muito mais organizado e limpo o desenvolvimento (...)
Pra você que curti uma vídeo aula:
http://www.asp.net/mvc/videos/mvc-2/how-do-i/aspnet-mvc-2-areas

Chega de "historinha" hehe, para não perder o costume, sirva-se de um belo café e vamos codar, irei mostras as alterações que implementei com base nesse diagrama e também a tela principal com alguns ActionLink fazendo o roteamento para as outras áreas.
Inicialmente esta seria a arquitetura já criada:

 Perceba que o visual studio já criou pra você classes de "Registros" separadamente. Isso quer dizer que quando o Global.asax for registrar a aplicação verá que necessita também de registrar as áreas separadamente (...)
Global.asax:

   // Note: For instructions on enabling IIS6 or IIS7 classic mode,   
   // visit http://go.microsoft.com/?LinkId=9394801  
   public class MvcApplication : System.Web.HttpApplication  
   {  
     public static void RegisterRoutes(RouteCollection routes)  
     {  
       routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
       routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });  
     }  
     protected void Application_Start()  
     {  
       AreaRegistration.RegisterAllAreas();  
       FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  
       RouteConfig.RegisterRoutes(RouteTable.Routes);  
     }  
   }  


As classes "PosVendaAreaRegistration.cs", "LojaAreaRegistration.cs", "FinanceiroAreaRegistration.cs" foram criadas automaticamente pelo VS quando criei a AREA.


Apenas na classe "PrincipalAreaRegistration.cs" fiz uma pequena alteração dizendo quem seria a pagina "ROOT" do projeto basicamente seria a pagina que será chamada inicialmente ao abrir a aplicação.

   public class PrincipalAreaRegistration : AreaRegistration  
   {  
     public override string AreaName  
     {  
       get  
       {  
         return "Principal";  
       }  
     }  
     public override void RegisterArea(AreaRegistrationContext context)  
     {  
       context.MapRoute("Root", "", new { controller = "Publico", action = "Index", id = UrlParameter.Optional });  
       context.MapRoute(  
         "Principal_default",  
         "Principal/{controller}/{action}/{id}",  
         new { action = "Index", id = UrlParameter.Optional }  
       );  
     }  
   }  

O trecho de codigo "context.MapRoute()" mapea o root da pagina que por sua vez aponta para o controller "Publico" e a View "Index" do projeto, ao executar a aplicação o resultado seria esse:
Como pode ver eu já implementei os links que irão chamar os outros módulos, perceba que ao clicar em cada um deles o roteamento é feito para a sua respectiva área chamando o "CONTROLLER" indicado no ActionLink, veja o fonte da Index:

 @{  
   Layout = null;  
 }  
 <!DOCTYPE html>  
 <html>  
 <head>  
   <meta name="viewport" content="width=device-width" />  
   <title>Index</title>  
 </head>  
 <body>  
   <div style="text-align:center">  
     <h2>Domínio Público</h2>  
     @Html.ActionLink("Area - Financeira", "Index", "Home", new { area = "Financeiro" }, null)  
     <br />  
     @Html.ActionLink("Area - Loja de Produtos", "Index", "Home", new { area = "Loja" }, null)  
     <br />  
     @Html.ActionLink("Area - Pós Venda", "Index", "Home", new { area = "PosVenda" }, null)  
   </div>  
 </body>  
 </html>  

Sem mistérios !  =))
Uma visão bastante interessante desse tipo de arquitetura seria o compartilhamento de _layouts e informações entres os "SITES" a grosso modo podemos ter uma percepção que temos vários ambientes de site dentro de apenas um, facilitando a manutenção e futuras melhorias no mesmo.
É isso ...
Espero ter ajudado.
Um abraço!