Quantcast
Channel: André Alves de Lima
Viewing all 210 articles
Browse latest View live

Serializando e desserializando JSON com C# e VB.NET

$
0
0

O formato JSON (JavaScript Object Notation) é atualmente o formato mais utilizado para troca de informações nas comunicações entre aplicações e APIs. Antigamente tínhamos uma predominância do XML para esse tipo de operação, mas o JSON acabou dominando a cena já faz um bom tempo.

Com essa popularização do JSON, não é raro termos que fazer a serialização ou desserialização de objetos nesse formato dentro das nossas aplicações. No .NET Framework temos nativamente a classe DataContractJsonSerializer, que implementa justamente essa funcionalidade. Porém, existem diversas outras bibliotecas que também implementam a serialização de JSON com C# e VB.NET.

No artigo de hoje eu vou mostrar um exemplo de serialização e desserialização de dados JSON utilizando a classe DataContractJsonSerializer e as bibliotecas Json.NET (Newtonsoft) e Jil. Será que tem muita diferença? Vamos conferir?

Criando uma estrutura de exemplo

Para vermos como funciona a serialização e desserialização de dados em JSON, primeiramente temos que criar uma estrutura de dados. Uma das coisas que mais me irrita em artigos que demonstram esse tipo de funcionalidade é que, na maioria das vezes, o autor utiliza uma estrutura de dados muito simples. Uma classezinha com algumas propriedades e pronto. Esse é o exemplo que você encontra aos montes por aí.

Não me leve a mal, eu sou 100% a favor de utilizarmos exemplos simples em artigos técnicos. Porém, muitas vezes alguns autores levam isso ao extremo. Nesse cenário, por exemplo, é importante entendermos como a serialização funciona quando temos classes relacionadas. Outro problema interessante de estudarmos é a utilização de propriedades do tipo DateTime, que normalmente costumam causar problemas na serialização e desserialização de objetos.

Pensando nisso, para esse artigo eu resolvi criar uma estrutura de dados um pouco mais complexa. Entretanto, ao mesmo tempo ela é simples o suficiente para conseguirmos entender o que está sendo feito. A estrutura lida com Pedidos e Itens de Pedido, além de algumas informações de “lookup” (Vendedor, Produto e Categoria do Produto). Veja só o diagrama abaixo:

E aí, conseguiu entender? Acho que não é tão difícil assim, não é mesmo? Além dos relacionamentos entre as classes, nós temos também campos do tipo DateTime, para verificarmos qual é o comportamento de cada biblioteca na hora de serializar e desserializar as informações desse tipo.

Agora que já temos a nossa estrutura definida, vamos criar um projeto do tipo “Console Application” e vamos adicionar cada uma das classes no projeto:

    // C#
    public class CategoriaProduto
    {
        public int CategoriaProdutoId { get; set; }
        public string Descricao { get; set; }
    }
    public class Produto
    {
        public int ProdutoId { get; set; }
        public CategoriaProduto CategoriaProduto { get; set; }
        public string Descricao { get; set; }
        public decimal Preco { get; set; }
    }
    public class Vendedor
    {
        public int VendedorId { get; set; }
        public string Nome { get; set; }
        public DateTime DataNascimento { get; set; }
    }
    public class Pedido
    {
        public int PedidoId { get; set; }
        public DateTime DataPedido { get; set; }
        public Vendedor Vendedor { get; set; }
        public IEnumerable<ItemPedido> ItensPedido { get; set; }
    }
    public class ItemPedido
    {
        public int ItemPedidoId { get; set; }
        public Pedido Pedido { get; set; }
        public Produto Produto { get; set; }
        public decimal Quantidade { get; set; }
        public decimal Valor { get; set; }
    }
' VB.NET
Public Class CategoriaProduto
    Public Property CategoriaProdutoId As Integer
    Public Property Descricao As String
End Class
Public Class Produto
    Public Property ProdutoId As Integer
    Public Property CategoriaProduto As CategoriaProduto
    Public Property Descricao As String
    Public Property Preco As Decimal
End Class
Public Class Vendedor
    Public Property VendedorId As Integer
    Public Property Nome As String
    Public Property DataNascimento As DateTime
End Class
Public Class Pedido
    Public Property PedidoId As Integer
    Public Property DataPedido As DateTime
    Public Property Vendedor As Vendedor
    Public Property ItensPedido As IEnumerable(Of ItemPedido)
End Class
Public Class ItemPedido
    Public Property ItemPedidoId As Integer
    Public Property Pedido As Pedido
    Public Property Produto As Produto
    Public Property Quantidade As Decimal
    Public Property Valor As Decimal
End Class

Em seguida, vamos até a classe “Program” (ou “Module1” no VB.NET) para adicionarmos um método que fará a criação de uma lista de Pedidos com dados fictícios:

        // C#
        private static List<Pedido> CriarListaDePedidos()
        {
            var lista = new List<Pedido>();

            var categoria1 = new CategoriaProduto() { CategoriaProdutoId = 1, Descricao = "Categoria 1" };
            var categoria2 = new CategoriaProduto() { CategoriaProdutoId = 1, Descricao = "Categoria 1" };
            var produto1 = new Produto() { ProdutoId = 1, CategoriaProduto = categoria1, Descricao = "Produto 1", Preco = 1.5m };
            var produto2 = new Produto() { ProdutoId = 2, CategoriaProduto = categoria2, Descricao = "Produto 2", Preco = 2.5m };
            var vendedor1 = new Vendedor() { VendedorId = 1, Nome = "Vendedor 1", DataNascimento = new DateTime(2016, 11, 16) };
            var vendedor2 = new Vendedor() { VendedorId = 2, Nome = "Vendedor 2", DataNascimento = new DateTime(2016, 11, 17) };
            var pedido1 = new Pedido() { PedidoId = 1, DataPedido = DateTime.Now, Vendedor = vendedor1 };
            var pedido2 = new Pedido() { PedidoId = 2, DataPedido = DateTime.Now, Vendedor = vendedor2 };
            var itemPedido1 = new ItemPedido() { ItemPedidoId = 1, Pedido = pedido1, Produto = produto1, Quantidade = 1, Valor = 1.5m };
            var itemPedido2 = new ItemPedido() { ItemPedidoId = 2, Pedido = pedido1, Produto = produto2, Quantidade = 2, Valor = 5 };
            var itemPedido3 = new ItemPedido() { ItemPedidoId = 3, Pedido = pedido2, Produto = produto1, Quantidade = 3, Valor = 4.5m };
            var itemPedido4 = new ItemPedido() { ItemPedidoId = 4, Pedido = pedido2, Produto = produto2, Quantidade = 4, Valor = 10 };
            pedido1.ItensPedido = new[] { itemPedido1, itemPedido2 };
            pedido2.ItensPedido = new[] { itemPedido3, itemPedido4 };

            lista.Add(pedido1);
            lista.Add(pedido2);

            return lista;
        }
    ' VB.NET
    Private Function CriarListaDePedidos() As List(Of Pedido)
        Dim Lista = New List(Of Pedido)

        Dim Categoria1 = New CategoriaProduto() With {.CategoriaProdutoId = 1, .Descricao = "Categoria 1"}
        Dim Categoria2 = New CategoriaProduto() With {.CategoriaProdutoId = 1, .Descricao = "Categoria 1"}
        Dim Produto1 = New Produto() With {.ProdutoId = 1, .CategoriaProduto = Categoria1, .Descricao = "Produto 1", .Preco = 1.5D}
        Dim Produto2 = New Produto() With {.ProdutoId = 2, .CategoriaProduto = Categoria2, .Descricao = "Produto 2", .Preco = 2.5D}
        Dim Vendedor1 = New Vendedor() With {.VendedorId = 1, .Nome = "Vendedor 1", .DataNascimento = New DateTime(2016, 11, 16)}
        Dim Vendedor2 = New Vendedor() With {.VendedorId = 2, .Nome = "Vendedor 2", .DataNascimento = New DateTime(2016, 11, 17)}
        Dim Pedido1 = New Pedido() With {.PedidoId = 1, .DataPedido = DateTime.Now, .Vendedor = Vendedor1}
        Dim Pedido2 = New Pedido() With {.PedidoId = 2, .DataPedido = DateTime.Now, .Vendedor = Vendedor2}
        Dim ItemPedido1 = New ItemPedido() With {.ItemPedidoId = 1, .Pedido = Pedido1, .Produto = Produto1, .Quantidade = 1, .Valor = 1.5D}
        Dim ItemPedido2 = New ItemPedido() With {.ItemPedidoId = 2, .Pedido = Pedido1, .Produto = Produto2, .Quantidade = 2, .Valor = 5}
        Dim ItemPedido3 = New ItemPedido() With {.ItemPedidoId = 3, .Pedido = Pedido2, .Produto = Produto1, .Quantidade = 3, .Valor = 4.5D}
        Dim ItemPedido4 = New ItemPedido() With {.ItemPedidoId = 4, .Pedido = Pedido2, .Produto = Produto2, .Quantidade = 4, .Valor = 10}
        Pedido1.ItensPedido = New ItemPedido() {ItemPedido1, ItemPedido2}
        Pedido2.ItensPedido = New ItemPedido() {ItemPedido3, ItemPedido4}

        Lista.Add(Pedido1)
        Lista.Add(Pedido2)

        Return Lista
    End Function

E agora vamos criar um outro método que fará a impressão de uma lista de Pedidos. Nós utilizaremos esse método para conferirmos se o resultado da desserialização está correto ou não:

        // C#
        private static void ImprimirPedidos(List<Pedido> pedidos)
        {
            foreach (var pedido in pedidos)
            {
                Console.WriteLine("==========");
                Console.WriteLine("Pedido {0}:", pedido.PedidoId);
                Console.WriteLine("Data = {0:G}, Vendedor = {1}, Data Nascimento Vendedor = {2:G}", pedido.DataPedido, pedido.Vendedor.Nome, pedido.Vendedor.DataNascimento);
                Console.WriteLine("Itens do Pedido:");
                foreach (var item in pedido.ItensPedido)
                {
                    Console.WriteLine("Produto = {0}, Categoria Produto = {1}, Valor Unitário = {2}, Quantidade = {3}, Valor Total = {4}", item.Produto.Descricao, item.Produto.CategoriaProduto.Descricao, item.Produto.Preco, item.Quantidade, item.Valor);
                }
                Console.WriteLine("==========");
            }
        }
    ' VB.NET
    Private Sub ImprimirPedidos(Pedidos As List(Of Pedido))
        For Each Pedido In Pedidos
            Console.WriteLine("==========")
            Console.WriteLine("Pedido {0}:", Pedido.PedidoId)
            Console.WriteLine("Data = {0:G}, Vendedor = {1}, Data Nascimento Vendedor = {2:G}", Pedido.DataPedido, Pedido.Vendedor.Nome, Pedido.Vendedor.DataNascimento)
            Console.WriteLine("Itens do Pedido:")
            For Each Item In Pedido.ItensPedido
                Console.WriteLine("Produto = {0}, Categoria Produto = {1}, Valor Unitário = {2}, Quantidade = {3}, Valor Total = {4}", Item.Produto.Descricao, Item.Produto.CategoriaProduto.Descricao, Item.Produto.Preco, Item.Quantidade, Item.Valor)
            Next
            Console.WriteLine("==========")
        Next
    End Sub

Por fim, no nosso método “Main“, vamos criar uma lista de Pedidos e, logo em seguida, vamos imprimi-la na tela para ver se tudo está funcionando corretamente:

        // C#
        static void Main(string[] args)
        {
            var lista = CriarListaDePedidos();

            Console.WriteLine("==========");
            Console.WriteLine("Dados originais:");
            ImprimirPedidos(lista);
            Console.WriteLine("==========");
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        Dim Lista = CriarListaDePedidos()

        Console.WriteLine("==========")
        Console.WriteLine("Dados originais:")
        ImprimirPedidos(Lista)
        Console.WriteLine("==========")
        Console.ReadLine()
    End Sub

Execute a aplicação e veja o resultado:

Agora que já temos os nossos dados de exemplo, vamos ver como podemos serializar e desserializar esses dados no formato JSON utilizando 3 bibliotecas diferentes.

Opção 1: DataContractJsonSerializer

A primeira classe que vamos utilizar vem do próprio .NET Framework. A classe DataContractJsonSerializer pode ser utilizada justamente para fazermos a serialização e desserialização de dados no formato JSON. Para utilizarmos essa classe, nós primeiramente temos que adicionar uma referência ao assembly “System.Runtime.Serialization“:

O construtor da classe DataContractJsonSerializer espera o tipo de dados que deverá ser serializado ou desserializado. Uma vez criada uma instância dessa classe, nós podemos utilizar os métodos WriteObject (para serializarmos um objeto) ou ReadObject (para desserializarmos um objeto). Ambos os métodos trabalham com Streams para fazer a gravação e leitura do resultado. Dessa forma, se você quiser escrever os dados em memória, você poderia trabalhar com uma MemoryStream. Se, por outro lado, você quiser escrever os dados em um arquivo, você terá que trabalhar com um FileStream.

Vamos criar um método que receberá a nossa lista de Pedidos e fará a serialização dos dados, guardando o resultado dentro do arquivo “pedidos1.json“:

        // C#
        private static void SerializarDataContractJsonSerializer(List<Pedido> pedidos)
        {
            using (var stream = new System.IO.FileStream("pedidos1.json", System.IO.FileMode.Create))
            {
                var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Pedido>));
                serializer.WriteObject(stream, pedidos);
            }
        }
    ' VB.NET
    Private Sub SerializarDataContractJsonSerializer(Pedidos As List(Of Pedido))
        Using Stream = New System.IO.FileStream("pedidos1.json", System.IO.FileMode.Create)
            Dim Serializer = New System.Runtime.Serialization.Json.DataContractJsonSerializer(GetType(List(Of Pedido)))
            Serializer.WriteObject(Stream, Pedidos)
        End Using
    End Sub

Simples, não? Primeiro, criamos uma FileStream apontando para o arquivo “pedidos1.json” (que será criado caso ainda não exista ou substituído caso já exista). Em seguida, criamos um DataContractJsonSerializer passando o tipo “lista de Pedidos“, que é justamente o tipo que queremos serializar. Por fim, chamamos o método WriteObject passando a Stream e a lista de Pedidos.

Agora que já temos o código correspondente à serialização, vamos implementar um outro método que carregará o conteúdo do arquivo “pedidos1.json” e fará a desserialização utilizando a classe DataContractJsonSerializer:

        // C#
        private static List<Pedido> DeserializarDataContractJsonSerializer()
        {
            using (var stream = new System.IO.FileStream("pedidos1.json", System.IO.FileMode.Open))
            {
                var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Pedido>));
                return (List<Pedido>)serializer.ReadObject(stream);
            }
        }
    ' VB.NET
    Private Function DeserializarDataContractJsonSerializer() As List(Of Pedido)
        Using Stream = New System.IO.FileStream("pedidos1.json", System.IO.FileMode.Open)
            Dim Serializer = New System.Runtime.Serialization.Json.DataContractJsonSerializer(GetType(List(Of Pedido)))
            Return DirectCast(Serializer.ReadObject(Stream), List(Of Pedido))
        End Using
    End Function

Como você pode perceber, grande parte do código é idêntica à serialização. As únicas diferenças são a abertura do arquivo em modo leitura (e não escrita) e a utilização do método ReadObject, ao invés do método WriteObject.

Agora que nós já criamos os métodos para fazer a serialização e desserialização dos dados via DataContractJsonSerializer, vamos adicionar as suas chamadas no nosso método “main“:

            // C#
            Console.WriteLine("==========");
            Console.WriteLine("Resultado DataContractJsonSerializer:");
            SerializarDataContractJsonSerializer(lista);
            ImprimirPedidos(DeserializarDataContractJsonSerializer());
            Console.WriteLine("==========");
            Console.ReadLine();
        ' VB.NET
        Console.WriteLine("==========")
        Console.WriteLine("Resultado DataContractJsonSerializer:")
        SerializarDataContractJsonSerializer(Lista)
        ImprimirPedidos(DeserializarDataContractJsonSerializer())
        Console.WriteLine("==========")
        Console.ReadLine()

Vamos executar a aplicação para ver se o nosso código está funcionando corretamente? Como nada funciona “de primeira” no mundo do desenvolvimento de software, veja só a exceção que receberemos com o código atual:

Esse erro acontece porque nós temos uma referência cíclica na nossa estrutura de dados. Se você reparar, a classe Pedido tem uma lista de Itens de Pedido e a classe Item de Pedido tem uma referência ao Pedido (pai). Não existe uma maneira do serializador resolver essa referência cíclica automaticamente, então, ele acaba disparando essa exceção.

Se quisermos manter essa estrutura de referência cíclica, nós teremos que, de alguma forma, indicar para o serializador qual a propriedade que deve ser ignorada na serialização, de forma que a referência cíclica possa ser resolvida. No nosso caso, nós podemos ignorar a propriedade que faz o link do Item de Pedido com o Pedido pai, que é a propriedade “Pedido” dentro da classe “ItemPedido“. Para indicarmos que essa propriedade deve ser ignorada na serialização, nós temos que decorá-la com o atributo IgnoreDataMember:

    // C#
    public class ItemPedido
    {
        public int ItemPedidoId { get; set; }
        [System.Runtime.Serialization.IgnoreDataMember]
        public Pedido Pedido { get; set; }
        public Produto Produto { get; set; }
        public decimal Quantidade { get; set; }
        public decimal Valor { get; set; }
    }
' VB.NET
Public Class ItemPedido
    Public Property ItemPedidoId As Integer
    <System.Runtime.Serialization.IgnoreDataMember()>
    Public Property Pedido As Pedido
    Public Property Produto As Produto
    Public Property Quantidade As Decimal
    Public Property Valor As Decimal
End Class

Agora sim. Se executarmos o projeto novamente teremos o resultado esperado:

Note também que o arquivo “pedidos1.json” foi gerado com sucesso no diretório da aplicação:

Opção 2: Json.NET (Newtonsoft)

A segunda biblioteca de serialização de JSON que eu quero mostrar para vocês neste artigo é a Json.NET (também conhecida como Newtonsoft). Para instalá-la no nosso projeto, utilizamos o NuGet, procurando por “Json” ou através do comando “Install-Package Newtonsoft.Json” no Package Manager Console (se ficar com alguma dúvida na utilização do NuGet, confira este artigo):

Você perceberá rapidamente que essa biblioteca possui alguns métodos utilitários que facilitam muito a nossa vida. Com a classe “JsonConvert” (do namespace Newtonsoft.Json), nós conseguimos rapidamente serializar um objeto através do método “SerializeObject” e desserializar um conteúdo JSON através do método “DeserializeObject“. Veja só como fica o código para serializar e desserializar a nossa lista de Pedidos com o Json.NET:

        // C#
        private static void SerializarNewtonsoft(List<Pedido> pedidos)
        {
            using (var streamWriter = new System.IO.StreamWriter("pedidos2.json"))
            {
                var json = Newtonsoft.Json.JsonConvert.SerializeObject(pedidos);
                streamWriter.Write(json);
            }
        }
        private static List<Pedido> DeserializarNewtonsoft()
        {
            var json = System.IO.File.ReadAllText("pedidos2.json");
            return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Pedido>>(json);
        }
    ' VB.NET
    Private Sub SerializarNewtonsoft(Pedidos As List(Of Pedido))
        Using StreamWriter = New System.IO.StreamWriter("pedidos2.json")
            Dim Json = Newtonsoft.Json.JsonConvert.SerializeObject(Pedidos)
            StreamWriter.Write(Json)
        End Using
    End Sub
    Private Function DeserializarNewtonsoft() As List(Of Pedido)
        Dim Json = System.IO.File.ReadAllText("pedidos2.json")
        Return Newtonsoft.Json.JsonConvert.DeserializeObject(Of List(Of Pedido))(Json)
    End Function

Em seguida, vamos adicionar o código que faz a chamada dessas funcionalidades dentro do método “main“:

            // C#
            Console.WriteLine("==========");
            Console.WriteLine("Resultado Newtonsoft:");
            SerializarNewtonsoft(lista);
            ImprimirPedidos(DeserializarNewtonsoft());
            Console.WriteLine("==========");
            Console.ReadLine();
        ' VB.NET
        Console.WriteLine("==========")
        Console.WriteLine("Resultado Newtonsoft:")
        SerializarNewtonsoft(Lista)
        ImprimirPedidos(DeserializarNewtonsoft())
        Console.WriteLine("==========")
        Console.ReadLine()

Se executarmos o nosso projeto, veremos que o resultado é idêntico ao da classe DataContractJsonSerializer:

Opção 3: Jil

Por fim, vamos testar a biblioteca chamada Jil, que é famosa por ser mais performática que as outras bibliotecas. Inclusive o Carlos dos Santos já escreveu um artigo uns tempos atrás demonstrando um exemplo simples de serialização e desserialização com essa biblioteca. Confira o artigo aqui.

Porém, o exemplo do artigo do Carlos dos Santos abordou somente propriedades do tipo int e string. Vamos conferir se o Jil não tem alguma complicação com outros tipos de dados, como DateTime?

Primeiramente, temos que adicionar a referência através do NuGet. Você pode fazer isso procurando por “Jil” na tela de gerenciamento de pacotes do NuGet ou digitando “Install-Package Jil” no Package Manager Console (mais uma vez, se ficar com alguma dúvida sobre a utilização do NuGet no Visual Studio, confira este artigo):

A serialização e desserialização de dados com a biblioteca Jil também é extremamente simples. Através da classe “JSON“, podemos serializar objetos através do método “Serialize” e desserializar um conteúdo JSON através do método “Deserialize“:

        // C#
        private static void SerializarJil(List<Pedido> pedidos)
        {
            using (var streamWriter = new System.IO.StreamWriter("pedidos3.json"))
            {
                var json = Jil.JSON.Serialize(pedidos);
                streamWriter.Write(json);
            }
        }
        private static List<Pedido> DeserializarJil()
        {
            var json = System.IO.File.ReadAllText("pedidos3.json");
            return Jil.JSON.Deserialize<List<Pedido>>(json);
        }
    ' VB.NET
    Private Sub SerializarJil(Pedidos As List(Of Pedido))
        Using StreamWriter = New System.IO.StreamWriter("pedidos3.json")
            Dim Json = Jil.JSON.Serialize(Pedidos)
            StreamWriter.Write(Json)
        End Using
    End Sub
    Private Function DeserializarJil() As List(Of Pedido)
        Dim Json = System.IO.File.ReadAllText("pedidos3.json")
        Return Jil.JSON.Deserialize(Of List(Of Pedido))(Json)
    End Function

Em seguida, adicionamos as chamadas para essas funcionalidades no método “main“:

            // C#
            Console.WriteLine("==========");
            Console.WriteLine("Resultado Jil:");
            SerializarJil(lista);
            ImprimirPedidos(DeserializarJil());
            Console.WriteLine("==========");
            Console.ReadLine();
        ' VB.NET
        Console.WriteLine("==========")
        Console.WriteLine("Resultado Jil:")
        SerializarJil(Lista)
        ImprimirPedidos(DeserializarJil())
        Console.WriteLine("==========")
        Console.ReadLine()

Agora vamos executar a aplicação para compararmos os resultados da biblioteca Jil com as outras bibliotecas. Veja só a diferença nos campos DateTime:

Eu particularmente achei muito confuso o jeito que a biblioteca Jil lida com DateTimes. Pesquisei extensivamente no repositório da biblioteca e não encontrei nenhum jeito de configurá-la de maneira que os DateTimes sejam serializados e desserializados mantendo as informações de fuso horário e horário de verão. A única maneira de fazer com que o valor fique idêntico ao das outras bibliotecas é alterar as nossas propriedades DateTime para DateTimeOffset nas classes Vendedor e Pedido, aí sim os valores de fuso horário são mantidos:

    // C#
    public class Vendedor
    {
        public int VendedorId { get; set; }
        public string Nome { get; set; }
        public DateTimeOffset DataNascimento { get; set; }
    }
    public class Pedido
    {
        public int PedidoId { get; set; }
        public DateTimeOffset DataPedido { get; set; }
        public Vendedor Vendedor { get; set; }
        public IEnumerable<ItemPedido> ItensPedido { get; set; }
    }
' VB.NET
Public Class Vendedor
    Public Property VendedorId As Integer
    Public Property Nome As String
    Public Property DataNascimento As DateTimeOffset
End Class
Public Class Pedido
    Public Property PedidoId As Integer
    Public Property DataPedido As DateTimeOffset
    Public Property Vendedor As Vendedor
    Public Property ItensPedido As IEnumerable(Of ItemPedido)
End Class

Outras opções? Qual é a melhor?

Obviamente, essas não são as únicas bibliotecas de serialização de JSON no .NET – existem inúmeras outras bibliotecas. Essas foram as 3 opções que eu notei que são as mais utilizadas no mercado. Entretanto, se você pesquisar no Google por comparação de bibliotecas JSON no .NET, você encontrará dezenas de opções. Para mais informações, confira estes links:

Most efficient way to parse JSON in C#

Binary and Json benchmarks updated

Concluindo

Existem diversas opções de classes e bibliotecas para fazermos a serialização e desserialização de JSON no .NET. Neste artigo você conferiu como utilizar a classe DataContractJsonSerializer, bem como as bibliotecas Json.NET (Newtonsoft) e Jil.

E você, já precisou fazer o parse de JSON nas suas aplicações? Qual biblioteca você acabou utilizando? Como foi a sua experiência? Conte mais detalhes nos comentários deste post!

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Image by Linux Screenshots used under Creative Commons
https://www.flickr.com/photos/xmodulo/14636491710

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Serializando e desserializando JSON com C# e VB.NET appeared first on André Alves de Lima.


Apresentação DevWeek 2016 – Ferramentas gratuitas para geração de relatórios no .NET

$
0
0

Na noite de ontem eu participei do DevWeek 2016, evento 100% online e gratuito que está acontecendo durante toda essa semana, organizado pelo pessoal do Canal .NET.

A minha apresentação foi sobre ferramentas gratuitas para geração de relatórios no .NET. Apresentei o básico das ferramentas Crystal Reports e Report Viewer. Confira a gravação abaixo:

Links mencionados

Os principais links que eu mencionei durante a apresentação foram:

Suporte ao Crystal Reports no Visual Studio 2013

Aprenda a instalar o Crystal Reports no Visual Studio. Apesar do artigo ser voltado para o Visual Studio 2013, o processo é idêntico para o Visual Studio 2015, a única diferença é a versão que você terá que instalar.

Cadê o Report Viewer no Visual Studio 2015?

A Microsoft decidiu tirar o Report Viewer da instalação típica do Visual Studio a partir da versão 2015. Nesse artigo, veja como ativá-lo durante a instalação ou até mesmo se você já tiver instalado o Visual Studio sem ele.

Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll

Existe um problema de compatibilidade do Crystal Reports a partir do .NET 4.0 que faz com que esse erro aconteça ao executar a aplicação. Esse erro pode ser facilmente corrigido com um ajuste no arquivo app.config. Nesse artigo eu mostro como fazer esse ajuste.

Qual é melhor – Crystal Reports ou Report Viewer?

Uma vez apresentadas as duas ferramentas, a dúvida que fica é: qual é o melhor? Qual eu devo utilizar em cada situação? Essas são as dúvidas que eu tento responder nesse artigo / vídeo.

Perguntas

Essas foram as perguntas que foram feitas no final da apresentação:

[Alexandre Rodrigues da Silva] Como saber como o relatório ficará em tempo de execução?

No Crystal Reports é fácil, uma vez que nós temos a aba “Preview” que mostra exatamente como o relatório ficará em tempo de execução. No Report Viewer, se estivermos trabalhando com relatórios server side (publicados no SQL Server Reporting Services), também temos essa aba “Preview” que mostra o resultado do relatório. Porém, para relatórios locais do Report Viewer, infelizmente não temos uma maneira de visualizar o resultado do relatório. A única opção é criarmos um formulário de testes e executar a aplicação.

[Vinicius Veras] Como gerar um relatório com 2 páginas / 2 vias?

Esse é um tema que eu quero demonstrar no ano que vem. Infelizmente, que eu saiba, o único jeito é no modo “gambiarra“. Basicamente você pode criar uma coluna virtual no seu Data Source (por exemplo “Via“). Aí você duplica os dados do DataSet / coleção, uma vez utilizando “Via” = 1 e outra vez utilizando “Via” = 2. Por fim, você agrupa o relatório por essa coluna e adiciona quebra de página entre as instâncias do grupo.

[Vinicius Takeushi] Só funciona com SQL Server?

Não, tanto o Crystal Reports quanto o Report Viewer funcionam com qualquer banco. Como você manda um DataSet ou coleção de objetos para o relatório, não interessa de qual banco as informações estão vindo. Inclusive na demonstração eu não utilizei nenhum banco (fiz com dados aleatórios em memória).

[Paulo Dias] O que o Crystal Reports tem que o Report Viewer não tem (e vice-versa)?

Veja o vídeo que linkei acima para um comparativo entre as duas ferramentas.

[Pietro NET] Qual tem melhor performance?

Sinceramente, não sei. Essa é uma análise que eu também quero fazer para publicar aqui no site. Eu particularmente acho o Report Viewer mais rápido na hora de carregar o relatório. O Crystal Reports tem um “lag” bem grande na hora do carregamento, principalmente a primeira vez que você está exibindo o relatório depois de reiniciar o computador. Mas, eu sinceramente não sei como as duas ferramentas se comportariam em um cenário onde o relatório seja extremamente complexo ou onde o relatório esteja sendo alimentado com muitos registros.

Baixe o projeto e o PPT

Como prometido, você pode baixar o projeto aqui (com o código tanto em C# quanto em VB.NET) e o PPT aqui.

Obrigado!

Antes de me despedir, quero agradecer imensamente o pessoal do Canal .NET pela oportunidade de ter participado no evento e também todo mundo que acompanhou o evento, tanto ao vivo quanto a gravação. Qualquer dúvida é só deixar aí nos comentários ou entrar em contato por e-mail. E aproveita para se inscrever na minha newsletter utilizando o formulário abaixo!

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Apresentação DevWeek 2016 – Ferramentas gratuitas para geração de relatórios no .NET appeared first on André Alves de Lima.

Fazendo o download e upload de arquivos em um servidor FTP com C# e VB.NET

$
0
0

O FTP é um protocolo muito antigo, porém muito utilizado até os dias de hoje. Em algumas aplicações, pode ser pode ser que precisemos acessar um servidor FTP para baixarmos um arquivo ou até mesmo para fazermos um upload de algum arquivo relacionado ao nosso aplicativo.

No .NET Framework temos à nossa disposição a classe FtpWebRequest, que implementa todas as funcionalidades necessárias para manipularmos servidores FTP com C# e VB.NET. Porém, se quisermos fazer algo que vá além do upload e download de arquivos, o código acaba ficando um tanto quanto complexo.

Para substituir o FtpWebRequest, podemos utilizar uma infinidade de bibliotecas que implementam as funcionalidades de acesso a servidores FTP no .NET. Uma dessas bibliotecas é a FluentFTP, implementada 100% com código gerenciado e muito simples de ser utilizada.

No artigo de hoje, eu vou mostrar para você como utilizar a classe FtpWebRequest para fazer o upload e download de arquivos, bem como a biblioteca FluentFTP para outras atividades mais complexas, como criação, deleção e listagem de diretórios.

Preparando o ambiente de testes

É obvio que, para testarmos as funcionalidades deste artigo, precisaremos ter acesso a um servidor FTP. Eu poderia utilizar alguns servidores FTP que eu tenho disponíveis (como o servidor onde eu hospedo este site ou o servidor da empresa), porém ambos não permitem acesso anônimo e eu não queria correr o risco de acidentalmente compartilhar o meu usuário e senha no meio dos códigos de exemplo. Por isso, resolvi configurar um outro servidor FTP local com suporte a acesso anônimo.

Não sei se você sabe (eu não sabia), mas nós podemos configurar um servidor FTP com as próprias funcionalidades do Windows. Para conseguirmos fazer isso, temos que habilitar o IIS e criarmos um novo “FTP Site“. Eu segui as instruções deste tutorial e configurei um servidor FTP local com suporte a acesso anônimo:

How to set up and manage an FTP server on Windows 10

Se mesmo assim você achar esse processo muito complicado, saiba que existem outras opções mais simples ainda. Nesta thread do SuperUser (StackOverflow de infra) o pessoal da comunidade discutiu sobre as principais opções para configurarmos um servidor FTP no Windows com o mínimo esforço possível:

Dead-simple FTP server for Windows?

Caso você já tenha um servidor FTP acessível para fazer os testes, você obviamente não precisa seguir os passos apresentados nos links acima.

O que o FtpWebRequest tem a nos oferecer?

Uma vez tendo um servidor FTP para testarmos as funcionalidades, vamos começar a criar o nosso projeto de testes. Para simplificar as coisas, vamos criar um projeto do tipo “Console Application“. A ideia dos testes é fazermos as principais operações no nosso servidor FTP: upload, download, deleção de arquivos, criação de diretórios, deleção de diretórios e listagem dos arquivos.

O próprio .NET Framework conta com uma classe que implementa a manipulação de servidores FTP. Essa classe se chama FtpWebRequest, localizada dentro do namespace System.Net. Existem dois exemplos na própria documentação do MSDN mostrando como fazer upload e download de arquivos com o FtpWebRequest. Resolvi coloca-los à prova para ver se eles realmente funcionam (e também para convertê-los para VB.NET, uma vez que eles só estão disponíveis em C# na documentação).

Para testarmos o upload de arquivo, precisamos de um arquivo de teste. Na maioria dos exemplos que você encontra por aí, os autores trabalham com arquivos texto. Eu resolvi ser diferente e vou testar a funcionalidade de upload e download utilizando um arquivo PDF, mais especificamente este aqui, que é um guia de licenciamento do Visual Studio 2015, feito pela própria Microsoft. Se o link do Microsoft Download Center não estiver funcionando, você pode baixar o PDF diretamente aqui no meu site. Coloque o arquivo PDF dentro da pasta bin/debug do projeto.

Seguindo o exemplo de upload de arquivos da documentação no MSDN, o código ficaria assim:

        // C#
        private static void Upload(string arquivo, string destino)
        {
            var request = (System.Net.FtpWebRequest)System.Net.WebRequest.Create("ftp://localhost/" + destino);
            request.Method = System.Net.WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");

            using (var stream = new System.IO.StreamReader(arquivo))
            {
                var conteudoArquivo = Encoding.UTF8.GetBytes(stream.ReadToEnd());
                request.ContentLength = conteudoArquivo.Length;

                var requestStream = request.GetRequestStream();
                requestStream.Write(conteudoArquivo, 0, conteudoArquivo.Length);
                requestStream.Close();
            }

            var response = (System.Net.FtpWebResponse)request.GetResponse();
            Console.WriteLine("Upload completo. Status: {0}", response.StatusDescription);
            response.Close();
        }
    ' VB.NET
    Private Sub Upload(Arquivo As String, Destino As String)
        Dim Request = DirectCast(System.Net.WebRequest.Create(Convert.ToString("ftp://localhost/") & Destino), System.Net.FtpWebRequest)
        Request.Method = System.Net.WebRequestMethods.Ftp.UploadFile
        Request.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")

        Using Stream = New System.IO.StreamReader(Arquivo)
            Dim ConteudoArquivo = Text.Encoding.UTF8.GetBytes(Stream.ReadToEnd())
            Request.ContentLength = ConteudoArquivo.Length

            Dim RequestStream = Request.GetRequestStream()
            RequestStream.Write(ConteudoArquivo, 0, ConteudoArquivo.Length)
            RequestStream.Close()
        End Using

        Dim Response = DirectCast(Request.GetResponse(), System.Net.FtpWebResponse)
        Console.WriteLine("Upload completo. Status: {0}", Response.StatusDescription)
        Response.Close()
    End Sub

Veja que o código não é tão simples assim. A classe FtpWebRequest, como o próprio nome já diz, trabalha com Requests e Responses. Dessa forma, para fazermos o upload de um arquivo para o servidor FTP, temos que abrir um Request no endereço desejado e temos que escrever o conteúdo do arquivo em bytes na RequestStream. A chamada no método “main” ficaria assim:

        // C#
        static void Main(string[] args)
        {
            Upload("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf");
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        Upload("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf")
        Console.ReadLine()
    End Sub

Execute a aplicação e veja o resultado retornado pelo método:

Mas, será que o upload foi realmente feito com sucesso? Ao navegar até a pasta raiz do meu servidor FTP, o arquivo realmente estava lá:

Porém, ao abrir o arquivo, o seu conteúdo estava em branco:

Pesquisando um pouco sobre esse efeito, acabei encontrando esta thread no StackOverflow, que fala justamente sobre esse problema. Aquele código disponibilizado na documentação do MSDN só funciona corretamente para arquivos texto, portanto, temos que ajustá-lo para que ele funcione para qualquer tipo de arquivo. A diferença não é tão grande assim. Basicamente, a única coisa que precisamos alterar é o jeito que lemos o arquivo de origem. Ao invés de lermos o arquivo utilizando um StreamReader, nós temos que extrair diretamente os bytes do arquivo com o método ReadAllBytes:

        // C#
        private static void Upload(string arquivo, string destino)
        {
            var request = (System.Net.FtpWebRequest)System.Net.WebRequest.Create("ftp://localhost/" + destino);
            request.Method = System.Net.WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");

            var conteudoArquivo = System.IO.File.ReadAllBytes(arquivo);
            request.ContentLength = conteudoArquivo.Length;

            var requestStream = request.GetRequestStream();
            requestStream.Write(conteudoArquivo, 0, conteudoArquivo.Length);
            requestStream.Close();

            var response = (System.Net.FtpWebResponse)request.GetResponse();
            Console.WriteLine("Upload completo. Status: {0}", response.StatusDescription);
            response.Close();
        }
    ' VB.NET
    Private Sub Upload(Arquivo As String, Destino As String)
        Dim Request = DirectCast(System.Net.WebRequest.Create(Convert.ToString("ftp://localhost/") & Destino), System.Net.FtpWebRequest)
        Request.Method = System.Net.WebRequestMethods.Ftp.UploadFile
        Request.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")

        Dim ConteudoArquivo = System.IO.File.ReadAllBytes(Arquivo)
        Request.ContentLength = ConteudoArquivo.Length

        Dim RequestStream = Request.GetRequestStream()
        RequestStream.Write(ConteudoArquivo, 0, ConteudoArquivo.Length)
        RequestStream.Close()

        Dim Response = DirectCast(Request.GetResponse(), System.Net.FtpWebResponse)
        Console.WriteLine("Upload completo. Status: {0}", Response.StatusDescription)
        Response.Close()
    End Sub

Agora sim. Se executarmos novamente o projeto, veremos que o PDF será gerado corretamente dentro do servidor FTP:

OK, conseguimos implementar o upload de arquivo com sucesso utilizando o FtpWebRequest. Agora vamos testar o download utilizando o código da documentação do MSDN, com a única diferença que, ao invés de mostrarmos o conteúdo do arquivo no console, nós salvaremos o arquivo com o mesmo nome no diretório da aplicação:

        // C#
        private static void Download(string caminho)
        {
            var request = (System.Net.FtpWebRequest)System.Net.WebRequest.Create("ftp://localhost/" + caminho);
            request.Method = System.Net.WebRequestMethods.Ftp.DownloadFile;

            request.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
            var response = (System.Net.FtpWebResponse)request.GetResponse();

            var responseStream = response.GetResponseStream();
            using (var memoryStream = new System.IO.MemoryStream())
            {
                responseStream.CopyTo(memoryStream);
                var conteudoArquivo = memoryStream.ToArray();
                System.IO.File.WriteAllBytes(caminho, conteudoArquivo);
            }

            Console.WriteLine("Download Complete, status {0}", response.StatusDescription);
            response.Close();
        }
    ' VB.NET
    Private Sub Download(Caminho As String)
        Dim Request = DirectCast(System.Net.WebRequest.Create(Convert.ToString("ftp://localhost/") & Caminho), System.Net.FtpWebRequest)
        Request.Method = System.Net.WebRequestMethods.Ftp.DownloadFile

        Request.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
        Dim Response = DirectCast(Request.GetResponse(), System.Net.FtpWebResponse)

        Dim ResponseStream = Response.GetResponseStream()
        Using MemoryStream = New System.IO.MemoryStream()
            ResponseStream.CopyTo(MemoryStream)
            Dim ConteudoArquivo = MemoryStream.ToArray()
            System.IO.File.WriteAllBytes(Caminho, ConteudoArquivo)
        End Using

        Console.WriteLine("Download Complete, status {0}", Response.StatusDescription)
        Response.Close()
    End Sub

Não podemos esquecer de adicionar a chamada no método “main“:

        // C#
        static void Main(string[] args)
        {
            Upload("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf");
            Console.ReadLine();
            Download("arquivo.pdf");
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        Upload("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf")
        Console.ReadLine()
        Download("arquivo.pdf")
        Console.ReadLine()
    End Sub

Execute o projeto e veja que o arquivo será baixado do servidor FTP com sucesso:

Desafios do FtpWebRequest

O upload e download de arquivos são as operações mais básicas que podemos realizar em um servidor FTP. Muitas vezes, somente essas duas operações já resolvem as necessidades do nosso projeto. Porém, existem outras operações que são tão importantes quanto o upload e download, como criação de diretórios, deleção de arquivos, listagem do conteúdo de uma pasta, etc. Será que essas outras operações também podem ser implementadas com o FtpWebRequest?

Teoricamente, sim – essas outras operações podem ser implementadas com o FtpWebRequest. Porém, o código acaba ficando um tanto quanto complexo. Veja só esta thread no StackOverflow sobre a criação de diretórios com o FtpWebRequest. Ou esta outra thread sobre a deleção de arquivos. Como você pode perceber, o código fica complicado de entender. Por isso, na minha opinião, se a sua aplicação precisa fazer mais do que um simples upload ou download de arquivos de um servidor FTP, vale a pena investigar algumas alternativas à classe FtpWebRequest.

Procurando por alternativas ao FtpWebRequest, acabei encontrando esta outra thread no StackOverflow. Várias sugestões são dadas naquela discussão, porém a que mais se destacou foi a biblioteca FluentFTP.

Conhecendo a biblioteca FluentFTP

A biblioteca FluentFTP (conhecida anteriormente como System.Net.FtpClient) é uma biblioteca desenvolvida 100% com código gerenciado e implementa todas as funcionalidades que você possa imaginar relacionadas ao acesso a servidores FTP. Ela é open source (o seu código está publicado no GitHub) e é disponibilizada através da licença MIT (extremamente permissiva).

Para instalarmos o FluentFTP no nosso projeto, basta utilizarmos o NuGet (procurando por “FluentFTP” ou digitando o comando “Install-Package FluentFTP” no Package Manager Console – caso tenha dúvidas sobre a utilização do NuGet no Visual Studio, confira este artigo):

Uma vez instalada a biblioteca, vamos ver como podemos fazer para implementarmos o upload de um arquivo.

As classes do FluentFTP ficam dentro do namespace “FluentFTP” e a principal classe que vamos utilizar é a FtpClient. Com ela nós conseguiremos fazer todas as operações no nosso servidor FTP. O upload de arquivo, por exemplo, é feito através do método OpenWrite, que cria uma Stream onde teremos que escrever o conteúdo do arquivo (como fizemos com o FtpWebRequest que vimos anteriormente).

Tomando como base o exemplo disponibilizado no GitHub da biblioteca, esta foi a minha primeira tentativa de implementação do upload de um arquivo:

        // C#
        private static void UploadFluent(string arquivo, string destino)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "ftp://localhost";
                using (var stream = client.OpenWrite(destino))
                {
                    var conteudoArquivo = System.IO.File.ReadAllBytes(arquivo);
                    stream.Write(conteudoArquivo, 0, conteudoArquivo.Length);
                }
            }
        }
    ' VB.NET
    Private Sub UploadFluent(Arquivo As String, Destino As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "ftp://localhost"
            Using Stream = Client.OpenWrite(destino)
                Dim ConteudoArquivo = System.IO.File.ReadAllBytes(Arquivo)
                Stream.Write(ConteudoArquivo, 0, ConteudoArquivo.Length)
            End Using
        End Using
    End Sub

E, obviamente, temos que adicionar a chamada no método “main“:

        // C#
        static void Main(string[] args)
        {
            UploadFluent("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf");
            Console.WriteLine("Upload FluentFTP completo");
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        UploadFluent("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf")
        Console.WriteLine("Upload FluentFTP completo")
        Console.ReadLine()
    End Sub

Porém, ao testar esse código, acabei recebendo este erro:

Isso acontece porque a biblioteca FluentFTP espera que o endereço do servidor FTP não tenha o prefixo “ftp://“. Ou seja, no meu caso que o servidor está localizado no “localhost“, eu deveria passar somente “localhost“, e não “ftp://localhost“:

        // C#
        private static void UploadFluent(string arquivo, string destino)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                using (var stream = client.OpenWrite(destino))
                {
                    var conteudoArquivo = System.IO.File.ReadAllBytes(arquivo);
                    stream.Write(conteudoArquivo, 0, conteudoArquivo.Length);
                }
            }
        }
    ' VB.NET
    Private Sub UploadFluent(Arquivo As String, Destino As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Using Stream = Client.OpenWrite(destino)
                Dim ConteudoArquivo = System.IO.File.ReadAllBytes(Arquivo)
                Stream.Write(ConteudoArquivo, 0, ConteudoArquivo.Length)
            End Using
        End Using
    End Sub

Depois dessa alteração, a mensagem de erro mudou:

Dessa vez o problema é que não passamos as credenciais para o acesso ao servidor FTP. Eu achei que, devido ao servidor suportar conexões anônimas, eu não precisaria passar as credenciais para o cliente. Mas, se repararmos bem, com o FtpWebRequest nós também temos que passar as credenciais, senão o código não funciona.

Com o FluentFTP, nós passamos as credenciais para o FtpClient através da propriedade Credentials:

        // C#
        private static void UploadFluent(string arquivo, string destino)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                using (var stream = client.OpenWrite(destino))
                {
                    var conteudoArquivo = System.IO.File.ReadAllBytes(arquivo);
                    stream.Write(conteudoArquivo, 0, conteudoArquivo.Length);
                }
            }
        }
    ' VB.NET
    Private Sub UploadFluent(Arquivo As String, Destino As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            Using Stream = Client.OpenWrite(destino)
                Dim ConteudoArquivo = System.IO.File.ReadAllBytes(Arquivo)
                Stream.Write(ConteudoArquivo, 0, ConteudoArquivo.Length)
            End Using
        End Using
    End Sub

Agora sim, ao executarmos o projeto novamente, o upload do arquivo será realizado com sucesso. A implementação da funcionalidade de download é extremamente parecida. A única diferença é que, ao invés de utilizarmos o método OpenWrite, nós utilizamos o método OpenRead e gravamos o conteúdo no disco através de um FileStream:

        // C#
        private static void DownloadFluent(string caminho)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                using (var stream = client.OpenRead(caminho))
                {
                    using (var fileStream = new System.IO.FileStream(caminho, System.IO.FileMode.Create))
                    {
                        stream.CopyTo(fileStream);
                    }
                }
            }
        }
    ' VB.NET
    Private Sub DownloadFluent(Caminho As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            Using Stream = Client.OpenRead(Caminho)
                Using FileStream = New System.IO.FileStream(Caminho, System.IO.FileMode.Create)
                    Stream.CopyTo(FileStream)
                End Using
            End Using
        End Using
    End Sub

Não podemos esquecer de adicionar a chamada no método “main“!

        // C#
        static void Main(string[] args)
        {
            UploadFluent("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf");
            Console.WriteLine("Upload FluentFTP completo");
            Console.ReadLine();
            DownloadFluent("arquivo.pdf");
            Console.WriteLine("Download FluentFTP completo");
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        UploadFluent("Visual Studio 2015 Licensing Whitepaper - November-2016.pdf", "arquivo.pdf")
        Console.WriteLine("Upload FluentFTP completo")
        Console.ReadLine()
        DownloadFluent("arquivo.pdf")
        Console.WriteLine("Download FluentFTP completo")
        Console.ReadLine()
    End Sub

Pronto! Com isso nós temos as funcionalidades de upload e download de arquivos implementada com a biblioteca FluentFTP. Vamos agora partir para algumas funcionalidades mais interessantes?

Deletando um arquivo do servidor

Como mencionei anteriormente, para deletarmos um arquivo do servidor FTP com o FtpWebRequest, temos que fazer um certo malabarismo. Já com a biblioteca FluentFTP, essa funcionalidade pode ser implementada de maneira extremamente simples. Basta chamarmos o método “DeleteFile” da classe FtpClient passando o caminho do arquivo que queremos deletar:

        // C#
        private static void DeletarArquivoFluent(string caminho)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                client.DeleteFile(caminho);
            }
        }
    ' VB.NET
    Private Sub DeletarArquivoFluent(Caminho As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            Client.DeleteFile(Caminho)
        End Using
    End Sub

Criando e deletando diretórios

A criação e deleção de diretórios também pode ser implementada de maneira muito simples através da biblioteca FluentFTP. Basta utilizarmos os métodos CreateDirectory e DeleteDirectory:

        // C#
        private static void CriarDiretorioFluent(string diretorio)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                client.CreateDirectory(diretorio);
            }
        }

        private static void DeletarDiretorioFluent(string diretorio)
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                client.DeleteDirectory(diretorio, true, false);
            }
        }
    ' VB.NET
    Private Sub CriarDiretorioFluent(Diretorio As String)
        Using client = New FluentFTP.FtpClient()
            client.Host = "localhost"
            client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            client.CreateDirectory(diretorio)
        End Using
    End Sub

    Private Sub DeletarDiretorioFluent(Diretorio As String)
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            Client.DeleteDirectory(Diretorio, True, False)
        End Using
    End Sub

Note que temos a opção de fazermos a deleção recursiva na chamada do método DeleteDirectory. Para isso, nós temos que passar “true” para o parâmetro “force” (que é o segundo parâmetro desse método).

Listando o conteúdo de um diretório

Por fim, vamos ver uma última funcionalidade bem interessante da biblioteca FluentFTP: a listagem dos arquivos de um diretório. O método “GetListing” da classe FtpClient implementa essa funcionalidade na biblioteca FluentFTP. Porém, esse método não faz a listagem recursiva do diretório, ou seja, se quisermos listar recursivamente todos os arquivos de um diretório, nós teremos que implementar a recursividade no lado do nosso aplicativo.

Não se assuste. Isso não é nem um pouco complicado de ser feito. Basta criarmos um método que chama ele mesmo caso o item do FTP seja do tipo “Directory“. Veja só como fica o código:

        // C#
        private static void ListarConteudoFluent()
        {
            using (var client = new FluentFTP.FtpClient())
            {
                client.Host = "localhost";
                client.Credentials = new System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br");
                ListarConteudoFluentRecursivo(client, string.Empty);
            }
        }

        private static void ListarConteudoFluentRecursivo(FluentFTP.FtpClient client, string caminho)
        {
            var arquivos = client.GetListing(caminho);
            foreach (var arquivo in arquivos)
            {
                if (arquivo.Type == FluentFTP.FtpFileSystemObjectType.Directory)
                {
                    ListarConteudoFluentRecursivo(client, arquivo.FullName);
                }
                else
                {
                    Console.WriteLine("{0} || {1} b || {2:G}", arquivo.FullName, arquivo.Size, arquivo.Created != DateTime.MinValue ? arquivo.Created : arquivo.Modified);
                }
            }
        }
    ' VB.NET
    Private Sub ListarConteudoFluent()
        Using Client = New FluentFTP.FtpClient()
            Client.Host = "localhost"
            Client.Credentials = New System.Net.NetworkCredential("anonymous", "contato@andrealveslima.com.br")
            ListarConteudoFluentRecursivo(Client, String.Empty)
        End Using
    End Sub

    Private Sub ListarConteudoFluentRecursivo(Client As FluentFTP.FtpClient, Caminho As String)
        Dim Arquivos = Client.GetListing(Caminho)
        For Each Arquivo In Arquivos
            If Arquivo.Type = FluentFTP.FtpFileSystemObjectType.Directory Then
                ListarConteudoFluentRecursivo(Client, Arquivo.FullName)
            Else
                Console.WriteLine("{0} || {1} b || {2:G}", Arquivo.FullName, Arquivo.Size, If(Arquivo.Created <> DateTime.MinValue, Arquivo.Created, Arquivo.Modified))
            End If
        Next
    End Sub

Veja que, na hora de acessarmos a data do arquivo, temos duas propriedades à nossa disposição: “Created” e “Modified“. Porém, nem todos os servidores FTP suportam a data de criação dos arquivos. Portanto, ao invés de simplesmente acessarmos diretamente a propriedade “Created“, nós verificamos se ela não é igual a DateTime.MinValue. Caso ela seja igual a DateTime.MinValue, isso significa que o servidor não informa a data de criação do arquivo, então, nós utilizamos a propriedade “Modified“, que retorna a data da última modificação do arquivo.

Eu copiei a pasta “Debug” do projeto para dentro do meu servidor FTP e o resultado da listagem dos arquivos foi este:

Concluindo

O .NET Framework conta com uma classe que implementa as funcionalidades necessárias para acessarmos um servidor FTP através dos nossos aplicativos. Essa classe, chamada FtpWebRequest, é muito simples de ser utilizada para fazermos upload e download de arquivos em um servidor FTP com C# e VB.NET. Porém, para operações mais complexas, o código acaba ficando bem difícil de entender.

Uma ótima alternativa à classe FtpWebRequest é a biblioteca FluentFTP. Essa biblioteca possui métodos muito simples para fazermos upload, download e deleção de arquivos, criação, deleção e listagem de diretórios, entre outras funcionalidades.

No artigo de hoje você conferiu como utilizar a classe FtpWebRequest para fazer o upload e download de arquivos, bem como a biblioteca FluentFTP para implementar as outras funcionalidades mais complexas.

Você já precisou trabalhar com FTP nas suas aplicações? Como é que você acabou implementando essas funcionalidades? Utilizou o FtpWebRequest ou o FluentFTP? Ou talvez até mesmo uma outra biblioteca diferente? Conte-nos mais detalhes na caixa de comentários!

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Image by Pixabay used under Creative Commons
https://pixabay.com/en/computer-file-network-server-156949/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Fazendo o download e upload de arquivos em um servidor FTP com C# e VB.NET appeared first on André Alves de Lima.

Nos vemos em 2017!

$
0
0

Olá querido(a) leitor(a)! Muito obrigado por ter acompanhado os artigos nesse ano de 2016!

Depois de ter publicado sem falta um artigo por semana durante todo esse ano, chegou aquela época em que eu tiro uma merecida pausa das postagens.

Nos vemos novamente em 2017, mais especificamente no dia 11/01/2017, quando publicarei a clássica revisão de 2016 e a lista de metas para 2017.

Até lá, eu e minha família desejamos a todos vocês boas festas e um próspero ano novo!

Família Lima
André, Larissa, Sophie e Thomas

The post Nos vemos em 2017! appeared first on André Alves de Lima.

Revisão de 2016, metas para 2017

$
0
0

Bem vindo(a) à primeira publicação de 2017! Para não perder o costume, o primeiro post do ano será uma revisão do ano passado e estabelecimento de metas para este novo ano.

Você ainda não definiu as suas novas metas? Dá uma olhada aí nas minhas. Quem sabe eu acabo te inspirando com os meus aprendizados desse ano que passou.

Aprendizado de 2016: quase tive um burnout

O ano de 2016 foi muito tenso e agitado. Começamos o ano com a Larissa grávida de 5 meses do nosso segundo filho, o Thomas. Como vocês já devem imaginar, tivemos muitas consultas no ginecologista, controles para ver se tudo estava certo e, no começo de maio, nasceu esta belezinha:

Aqui na Alemanha os pais de filhos pequenos têm alguns benefícios. Um deles é a possibilidade de tirar uma licença para cuidar o filho recém-nascido. Não vou entrar em detalhes aqui sobre esse benefício, mas, só para vocês terem uma ideia, eu poderia ter tirado uma licença parcialmente remunerada durante um ano, além de outros anos não remunerado. Claro que eu não fiz isso, mas eu tirei os meses de junho e julho para ajudar em casa com o Thomas.

Porém, não pense que eu fiquei em casa “só ajudando a Larissa em casa” (o que já é um trabalhão enorme). Além disso, eu resolvi pegar esse tempo para dar um gás nas coisas aqui do site também. Dei uma boa evoluída no curso sobre Report Viewer, mas infelizmente não consegui termina-lo.

Depois da volta ao trabalho em agosto, os meus dias tinham mais ou menos essa estrutura:

– 04:30 – despertador
– 04:30 – 05:00 – meditação, reflexões, pequeno exercício
– 05:00 – 06:00 – trabalho no site
– 06:00 – 06:30 – trabalho no site (se a Sophie ainda estivesse dormindo)
– 06:30 – 07:40 – café e banho
– 07:40 – 08:00 – levar Sophie no Kindergarten e ida ao trabalho
– 08:00 – 17:00 – trabalho na Savcor com 1 hora de almoço
– 17:00 – 19:30 – janta, tempo com crianças, colocar crianças pra dormir
– 19:30 – 20:00 – exercício (corrida 2km + exercícios de fisio)
– 20:00 – 21:00 – trabalho no site
– 21:00 – 21:30 – leitura
– 21:30 – dormir

Isso quando não tinha algum compromisso à noite, como as aulas de alemão que temos todas as terças-feiras. E isso também trabalhando super focado utilizando a Técnica Pomodoro (mais informações sobre essa técnica aqui).

Agora repita isso por alguns meses, além de finais de semana recheados de coisas para fazer (coisas que ficam acumuladas durante a semana, como levar o lixo na estação de lixo reciclável, comprar roupas para as crianças, etc). Junte também nesse bolo um projeto super tenso que começou na Savcor em outubro com prazo de entrega curtíssimo e que ocasionou no cancelamento de todas as férias de final de ano da empresa toda.

Sabe qual é o resultado de tudo isso? Quase tive um burnout!

Nunca passei por um ano em que eu fiquei mais doente como em 2016. Eu tive gripes, resfriados ou dores de garganta fortes praticamente uma vez por mês entre fevereiro e dezembro. Isso sem falar no stress constante em casa (no casamento) por causa disso tudo.

No começo de dezembro, no auge da última gripe forte do ano, eu e a Larissa tivemos uma conversa sobre tudo isso que estava acontecendo. Para nós, estava óbvio que todas essas doenças estavam acontecendo porque estávamos muito sobrecarregados. Por causa disso, resolvi pegar mais leve com o site em 2017. Vou focar somente em continuar produzindo conteúdo e respondendo os leitores, mas sem criar novos produtos. Além disso, estou dando uma pausa na utilização da Técnica Pomodoro. Isso vai atrasar um pouco o processo de transformar o site em um negócio de verdade, mas, fazer o quê. Paciência é uma virtude.

Outro aprendizado: diversificar as publicações

Ironicamente no ano em que eu mais me dediquei ao site (e quase acabei entrando em colapso por causa disso), a Microsoft entendeu que as minhas contribuições não foram suficientes para a renovação do meu MVP. Eu conto a história em detalhes neste post, mas basicamente o motivo dado pela Microsoft foi que eu deveria ter diversificado mais os canais de publicação. Ou seja, o fato de eu ter produzido somente material em formato de artigo (além do e-book sobre Report Viewer que eu publiquei no final de 2015) foi o motivo de eu não ter conseguido renovar o prêmio.

Independente do MVP, eu já tinha sacado que o mundo está se voltando para o formato em vídeo. Não é à toa que o Youtube é a segunda maior ferramenta de busca do mundo, perdendo somente para o Google. Juntando isso ao fato que eu realmente gosto de ensinar no formato de vídeo aulas, eu resolvi que vou diversificar um pouco as publicações, produzindo alguns conteúdos em formato de artigo e outros conteúdos em formato vídeo (darei mais detalhes sobre isso na seção de metas para 2017).

Revisão das metas de 2016

Agora que já falei um pouco dos aprendizados de 2016, vamos ver como ficou o resultado das metas que eu tinha estabelecido?

Ler um livro por mês(OK)

Meta cumprida. Consegui ler um livro por mês. Foram estes aqui:

Alguns foram boas escolhas, outros nem tanto. Se você se interessar por algum desses livros, entra em contato comigo que eu te dou mais detalhes. Percebi que os relatos dos livros que eu li não tiveram muito sucesso aqui no site em publicações passadas, portanto não vou me estender muito nesse ponto.

Evento social pelo menos uma vez por mês(não OK)

Não cumpri, mas, sabe de uma coisa? Tudo bem. Não é legal ficar forçando compromissos. Temos que encontrar com pessoas e combinar passeios somente quando temos vontade, e não ter que ficar lembrando de fazer isso todo mês.

Meditar de segunda a sexta-feira(OK)

Alguns dias falharam, mas estou feliz com o resultado. É visível a diferença na minha paciência comparando os dias em que eu medito com os dias em que eu não medito. Parece que a meditação te dá aquele 1 segundo a mais antes de reagir frente a uma situação difícil. Em 2017 eu vou continuar meditando de segunda a sexta-feira sem sombra de dúvida.

Reestruturar a página “Sobre” do site(OK)

Feito! Você já deu uma olhada na minha nova página “Sobre”? O que achou?

Criar página de consultoria no site(não OK)

Vai ficar para 2017.

Criar página de download de lead magnets(não OK)

Também vai ficar para 2017.

Lançar mais dois produtos(não OK)

Não consegui terminar nem um produto. Caminhei bem com o curso sobre Report Viewer: consegui finalizar todas as capturas de tela e tenho 18 capítulos já editados e finalizados. Ficou faltando a narração e edição de 5 capítulos, além dos capítulos de abertura e fechamento. A ideia é tentar finalizar esse curso em 2017 para não perder todo esse trabalho que eu já tive até aqui.

Metas para 2017

Metas de 2016 revisadas, agora é hora de estabelecer as metas de 2017. Sem mais delongas, aí vão elas.

– Pegar mais leve com o trabalho para evitar burnout: como mencionei anteriormente, quase tive um burnout no final do ano passado. Não quero repetir o mesmo erro este ano, portanto vou pegar mais leve com o trabalho, tanto na empresa quanto no site. Vou focar somente na produção de conteúdo, sem trabalhar no desenvolvimento de novos produtos (tirando o curso sobre Report Viewer que eu gostaria de finalizar e publicar). Além disso, vou dar uma pausa na Técnica Pomodoro (só vou utiliza-la para atividades do site, e não mais na Savcor).

– Um artigo a cada 15 dias, um vídeo a cada 15 dias (1 conteúdo por semana): continuo com a meta de produzir 52 novos conteúdos este ano (um por semana). Porém, agora será um artigo intercalado com um vídeo. Ou seja, artigo uma semana, vídeo na outra semana, e por aí vai.

– Finalizar o curso sobre Report Viewer: falta pouco para finalizar o curso sobre Report Viewer (eu diria que estou com uns 80-85% do curso concluído). Seria excelente se eu conseguisse um tempo para finaliza-lo em 2017.

– Aprender o básico de desenvolvimento web ou mobile: toda a minha carreira foi voltada a desenvolvimento de aplicações desktop. Vou continuar focando nisso, mas sinto que eu preciso urgentemente aprender o básico sobre desenvolvimento web ou mobile. Ainda não decidi qual vai ser, mas quero aprender o básico de uma dessas plataformas em 2017.

– Correr pelo menos 6km por semana e competir na corrida da cidade: lá por volta de abril do ano passado eu comecei a praticar corrida. Eu cheguei em um nível no qual eu estava conseguindo correr 2km por dia, cinco vezes por semana. Esse ritmo estava um pouco demais e no final do ano acabei parando por causa dos meus resfriados recorrentes (e também por causa do frio – correr dentro de casa na bicicleta elíptica no inverno com neve não é tão atraente quanto dar uma corrida no lago da cidade no verão). Esse ano quero retomar as corridas num nível mais aceitável (pelo menos 6km por semana) e quero competir na modalidade 10km da corrida aqui da cidade onde eu moro, que acontecerá no dia 20/05/2017.

– Ler um livro por mês: como de costume, quero ler 12 livros em 2017. Já decidi exatamente quais serão os livros e em qual ordem eles serão lidos:

– Ask Gary Vee (Gary Vaynerchuk)
– The Slight Edge (Jeff Olson)
– A história do mundo para quem tem pressa (Emma Marriot)
– The compound effect (Darren Hardy)
– The one thing (Gary Keller)
– A história do Brasil para quem tem pressa (Marcos Costa)
– Made to Stick: Why Some Ideas Survive and Others Die (Chip Heath e Dan Health)
– Produtividade para quem quer tempo (Gerônimo Theml)
– What If?: Serious Scientific Answers to Absurd Hypothetical Questions (Randall Munroe)
– The Thank You Economy (Gary Vaynerchuk)
– The Sleep Revolution (Arianna Huffington)
– Ghost Boy (Martin Pistorius)

Como o Thomas ainda está dormindo no nosso quarto, está complicado de ficar lendo na cama (por causa da luz acessa). Por isso, vários dos livros que eu comprei para esse ano foram no Kindle (contra a minha vontade, para ser bem sincero – eu gosto mesmo é de livro no formato físico!).

Obrigado por acompanhar!

Antes de me despedir, eu gostaria de agradecer por vocês terem acompanhado essa minha revisão e estabelecimento de metas. Espero que eu consiga inspirar algumas pessoas a não cometerem os mesmos erros que eu cometi.

E você, já deu uma revisada em como foi o ano de 2016 para você? Já estabeleceu as suas metas para esse ano? Agora é a hora, não deixe passar! Cada vez mais o ano passa voando e quando a gente menos percebe já é final de ano novamente.

Até a próxima!

André Lima

Image by Pixabay used under Creative Commons
https://pixabay.com/en/archery-arrow-goal-sports-focus-472932/

The post Revisão de 2016, metas para 2017 appeared first on André Alves de Lima.

Exibindo vídeos do Vimeo e Youtube no Windows Forms e WPF

$
0
0

Hoje em dia é bastante comum gravarmos vídeos mostrando o funcionamento dos nossos aplicativos. Esses vídeos normalmente servem muito bem para o processo de vendas, já que o potencial comprador consegue entender se a aplicação vai suprir ou não as necessidades do seu negócio.

Porém, não é só para isso que servem esses vídeos. Muitas vezes esse tipo de gravação também pode ser útil para treinarmos os usuários que compraram a nossa aplicação. Já pensou que legal seria se publicássemos esses vídeos no Youtube e depois incorporássemos dentro do próprio aplicativo na janela de ajuda? Bem legal, não é mesmo?

Eu tive essa ideia quando um leitor me perguntou como exibir vídeos do Vimeo no Windows Forms. Fui pesquisar e descobri que não existe um componente pronto para isso, mas nós podemos facilmente reproduzir esses tipos de vídeo dentro de um controle WebBrowser.

No artigo de hoje eu vou mostrar para você como exibir vídeos do Vimeo e Youtube no Windows Forms e WPF.

Vimeo no Windows Forms

Como eu mencionei anteriormente, não existe um controle que podemos utilizar especialmente para exibirmos vídeos do Vimeo ou Youtube no Windows Forms ou WPF. A alternativa nesse caso é utilizarmos um controle WebBrowser com o código de embed do vídeo.

Vamos começar criando um projeto do tipo “Windows Forms Application” e, dentro do formulário, vamos adicionar um controle WebBrowser:

Em seguida, no construtor do formulário (ou no evento “Load” caso você esteja trabalhando com VB.NET), temos que configurar a propriedade DocumentText do WebBrowser com um código HTML que exiba o “iframe” contendo o código de embed do Vimeo. Para isso, precisamos primeiramente descobrir o código de embed no site do Vimeo. Você consegue encontrar o código abrindo o vídeo desejado no browser e clicando no botão de compartilhar:

Na janela que se abre, você encontrará o código de embed. Porém, eu recomendo que você customize o código de forma que ele fique o mais simples e enxuto possível. Fazemos isso clicando no botão “Show options” e desmarcando as opções desnecessárias (como “Show text link underneath this video“):

Agora sim, temos o código embed que utilizaremos na nossa aplicação:

Com o código embed em mãos, podemos exibi-lo no nosso controle WebBrowser. Porém, não basta somente configurarmos a propriedade DocumentText do controle com o código de embed. Temos que criar um documento HTML propriamente dito (com as tags html, head, title e body). Veja como podemos montar essa estrutura:

        // C#
        public Form1()
        {
            InitializeComponent();

            var cabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>";
            var codigoEmbed = @"<iframe src=""https://player.vimeo.com/video/182970337\"" width=""640"" height=""360"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>";
            webBrowser1.DocumentText = string.Format(cabecalhoHtml, codigoEmbed);
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim CabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>"
        Dim CodigoEmbed = "<iframe src=""https://player.vimeo.com/video/182970337\"" width=""640"" height=""360"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"
        WebBrowser1.DocumentText = String.Format(CabecalhoHtml, CodigoEmbed)
    End Sub

Simples, não? Vamos executar a aplicação para vermos o resultado?

Como você pode perceber, apesar do vídeo ter sido carregado e reproduzido corretamente, alguns scripts do mecanismo do Vimeo não são executados com sucesso no controle WebBrowser do .NET. Como esses scripts não interferem em nada na reprodução do vídeo, o mais fácil é desabilitarmos essas mensagens de erro do controle WebBrowser. Faremos isso configurando a propriedade “ScriptErrorsSuppressed” como “true” antes de setarmos o DocumentText:

            // C#
            webBrowser1.ScriptErrorsSuppressed = true;
            webBrowser1.DocumentText = string.Format(cabecalhoHtml, codigoEmbed);
        ' VB.NET
        WebBrowser1.ScriptErrorsSuppressed = True
        WebBrowser1.DocumentText = String.Format(CabecalhoHtml, CodigoEmbed)

E com essa pequena alteração, ao executarmos o projeto novamente, nós não receberemos mais aquela mensagem de erro que recebemos anteriormente:

OK, o vídeo foi carregado e conseguimos reproduzi-lo, mas será que não dá para dar uma melhorada nesse player? Por exemplo, será que nós não conseguimos ajustar o tamanho do player de forma que ele ocupe todo o espaço disponível do WebBrowser? Sim, isso é possível!

Para fazermos isso, basta configurarmos os elementos “width” e “height” do código embed, levando em consideração o tamanho do nosso controle WebBrowser. Veja só como fica o código:

            // C#
            var cabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>";
            var codigoEmbed = @"<iframe src=""https://player.vimeo.com/video/182970337\"" width=""{0}"" height=""{1}"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>";
            codigoEmbed = string.Format(codigoEmbed, webBrowser1.Width - 50, webBrowser1.Height - 50);
            webBrowser1.ScriptErrorsSuppressed = true;
            webBrowser1.DocumentText = string.Format(cabecalhoHtml, codigoEmbed);
        ' VB.NET
        Dim CabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>"
        Dim CodigoEmbed = "<iframe src=""https://player.vimeo.com/video/182970337\"" width=""{0}"" height=""{1}"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"
        CodigoEmbed = String.Format(CodigoEmbed, WebBrowser1.Width - 50, WebBrowser1.Height - 50)
        WebBrowser1.ScriptErrorsSuppressed = True
        WebBrowser1.DocumentText = String.Format(CabecalhoHtml, CodigoEmbed)

E veja só o resultado:

Nota: como o tamanho do player não leva em consideração os controles, temos que descontar alguns pixels para que não tenhamos barras de rolagem no WebBrowser. No código acima, eu descontei 50 pixels na altura e no comprimento.

Youtube no Windows Forms

Agora que já vimos como exibimos vídeos do Vimeo, vamos ver como fazer o mesmo para vídeos do Youtube? A ideia é exatamente a mesma. Nós só temos que pegar o código de embed do vídeo desejado e substituirmos na variável “codigoEmbed“. Por exemplo:

            // C#
            var cabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>";
            var codigoEmbed = @"<iframe width=""{0}"" height=""{1}"" src=""https://www.youtube.com/embed/WwC-WOqUaIk"" frameborder=""0"" allowfullscreen></iframe>";
            codigoEmbed = string.Format(codigoEmbed, webBrowser1.Width - 50, webBrowser1.Height - 50);
            webBrowser1.ScriptErrorsSuppressed = true;
            webBrowser1.DocumentText = string.Format(cabecalhoHtml, codigoEmbed);
        ' VB.NET
        Dim CabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>"
        Dim CodigoEmbed = "<iframe width=""{0}"" height=""{1}"" src=""https://www.youtube.com/embed/WwC-WOqUaIk"" frameborder=""0"" allowfullscreen></iframe>"
        CodigoEmbed = String.Format(CodigoEmbed, WebBrowser1.Width - 50, WebBrowser1.Height - 50)
        WebBrowser1.ScriptErrorsSuppressed = True
        WebBrowser1.DocumentText = String.Format(CabecalhoHtml, CodigoEmbed)

Veja o resultado:

E no WPF, como fica?

O controle WebBrowser do WPF é um pouquinho diferente do controle no Windows Forms. A ideia continua sendo a mesma, porém os nomes de algumas propriedades e métodos são diferentes.

A primeira grande diferença que conseguimos notar é que não existe a propriedade “ScriptErrorsSuppressed“! Infelizmente a Microsoft não disponibiliza uma maneira de desabilitarmos as mensagens de script no WebBrowser do WPF. Para conseguirmos desabilitar essas mensagens, temos que recorrer a alternativas sinistras, conforme demonstrado nesta thread do StackOverflow. Uma outra alternativa seria utilizarmos um WindowsFormsHost com um controle WebBrowser do Windows Forms dentro dele.

Outra diferença é que as propriedades “Width” e “Height” do controle WebBrowser retornarão a altura e comprimento desejados, e não os valores reais. Os valores reais ficam armazenados nas propriedades “ActualWidth” e “ActualHeight“. Porém, os valores para essas propriedades só serão calculados quando os controles já tiverem sido desenhados. Isso quer dizer que nós não podemos colocar o código de carregamento do WebBrowser no construtor da janela, mas sim, teremos que utilizar o evento Load da janela.

Por fim, para carregarmos um HTML no controle WebBrowser do WPF, nós chamamos o método “NavigateToString” passando o código HTML desejado.

Para demonstrar tudo isso, vamos criar um novo projeto do tipo “WPF Application” e vamos ajustar o XAML da janela dessa forma:

<Window x:Class="VimeoEYoutube.WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:VimeoEYoutube.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800" Loaded="Window_Loaded">
    <Grid>
        <WebBrowser x:Name="WebBrowser1" Navigated="WebBrowser1_Navigated"/>
    </Grid>
</Window>

Em seguida, vamos para o code-behind, onde adicionaremos o seguinte código:

        // C#
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            var cabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>";
            var codigoEmbed = @"<iframe src=""https://player.vimeo.com/video/182970337\"" width=""{0}"" height=""{1}"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>";
            codigoEmbed = string.Format(codigoEmbed, WebBrowser1.ActualWidth - 50, WebBrowser1.ActualHeight - 50);
            WebBrowser1.NavigateToString(string.Format(cabecalhoHtml, codigoEmbed));
        }

        private void WebBrowser1_Navigated(object sender, NavigationEventArgs e)
        {
            SetSilent(WebBrowser1, true);
        }

        public static void SetSilent(WebBrowser browser, bool silent)
        {
            if (browser == null)
                throw new ArgumentNullException("browser");

            // get an IWebBrowser2 from the document
            IOleServiceProvider sp = browser.Document as IOleServiceProvider;
            if (sp != null)
            {
                Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
                Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");

                object webBrowser;
                sp.QueryService(ref IID_IWebBrowserApp, ref IID_IWebBrowser2, out webBrowser);
                if (webBrowser != null)
                {
                    webBrowser.GetType().InvokeMember("Silent", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.PutDispProperty, null, webBrowser, new object[] { silent });
                }
            }
        }


        [System.Runtime.InteropServices.ComImport, System.Runtime.InteropServices.Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
        private interface IOleServiceProvider
        {
            [System.Runtime.InteropServices.PreserveSig]
            int QueryService([System.Runtime.InteropServices.In] ref Guid guidService, [System.Runtime.InteropServices.In] ref Guid riid, [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)] out object ppvObject);
        }
    ' VB.NET
    Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Dim CabecalhoHtml = "<html><head><title></title></head><body>{0}</body></html>"
        Dim CodigoEmbed = "<iframe src=""https://player.vimeo.com/video/182970337\"" width=""{0}"" height=""{1}"" frameborder=""0"" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>"
        'Dim codigoEmbed = "<iframe width=""{0}"" height=""{1}"" src=""https://www.youtube.com/embed/WwC-WOqUaIk"" frameborder=""0"" allowfullscreen></iframe>"
        CodigoEmbed = String.Format(CodigoEmbed, WebBrowser1.ActualWidth - 50, WebBrowser1.ActualHeight - 50)
        'WebBrowser1.ScriptErrorsSuppressed = true;
        WebBrowser1.NavigateToString(String.Format(CabecalhoHtml, CodigoEmbed))
    End Sub

    Private Sub WebBrowser1_Navigated(sender As Object, e As NavigationEventArgs)
        SetSilent(WebBrowser1, True)
    End Sub

    Public Shared Sub SetSilent(Browser As WebBrowser, Silent As Boolean)
        If Browser Is Nothing Then
            Throw New ArgumentNullException("Browser")
        End If

        ' get an IWebBrowser2 from the document
        Dim sp As IOleServiceProvider = TryCast(browser.Document, IOleServiceProvider)
        If sp IsNot Nothing Then
            Dim IID_IWebBrowserApp As New Guid("0002DF05-0000-0000-C000-000000000046")
            Dim IID_IWebBrowser2 As New Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E")

            Dim WebBrowser As Object
            sp.QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, WebBrowser)
            If WebBrowser IsNot Nothing Then
                WebBrowser.GetType().InvokeMember("Silent", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.PutDispProperty, Nothing, WebBrowser, New Object() {Silent})
            End If
        End If
    End Sub


    <System.Runtime.InteropServices.ComImport, System.Runtime.InteropServices.Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)> _
    Private Interface IOleServiceProvider
        <System.Runtime.InteropServices.PreserveSig> _
        Function QueryService(<System.Runtime.InteropServices.In> ByRef guidService As Guid, <System.Runtime.InteropServices.In> ByRef riid As Guid, <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)> ByRef ppvObject As Object) As Integer
    End Interface

Perceba que a ideia do código é exatamente a mesma do que fizemos no Windows Forms. As diferenças estão somente nos detalhes.

Execute a aplicação e veja o resultado:

Para reproduzir um vídeo do Youtube, basta substituir o código de embed apontando para o vídeo desejado (exatamente como fizemos no Windows Forms). Aproveito também para deixar um link para este projeto no CodeProject, no qual o autor implementou um player do Youtube para o WPF (utilizando o controle WebBrowser por trás dos panos, como fizemos neste artigo).

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana onde eu falo sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Concluindo

Reproduzir vídeos do Vimeo ou Youtube nas nossas aplicações Windows Forms ou WPF não é uma tarefa muito difícil. Apesar de não termos controles específicos que implementem essa funcionalidade, graças aos códigos de embed, nós conseguimos facilmente utilizar um controle WebBrowser para reproduzirmos qualquer vídeo que quisermos.

No artigo de hoje você aprendeu a exibir vídeos dessas duas plataformas tanto no Windows Forms quanto no WPF. Você viu também como desabilitar a mensagem de erro de script disparada ao carregarmos um vídeo do Vimeo no controle WebBrowser. Por fim, você aprendeu a ajustar automaticamente o código de embed para que o player ocupe o espaço todo do WebBrowser.

E aí, será que essa funcionalidade não pode ser útil no seu aplicativo? Imagine que você tenha gravado e publicado no Youtube um tutorial mostrando o funcionamento da sua aplicação. Que tal incorporar esse vídeo na janela de ajuda do seu aplicativo? Uma boa ideia, não é mesmo? Me avisa nos comentários se você gostou desse artigo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Exibindo vídeos do Vimeo e Youtube no Windows Forms e WPF appeared first on André Alves de Lima.

Criando um instalador com o Visual Studio 2015

$
0
0

Uns tempos atrás eu escrevi um artigo mostrando rapidamente como criar instaladores para aplicativos .NET. Nesse artigo eu mostrei três ferramentas que podemos utilizar para gerar instaladores das nossas aplicações: Installer Projects do Visual Studio, InnoSetup e InstallShield. Como esse é um dos artigos mais acessados aqui no meu site, eu resolvi me aprofundar um pouco em cada uma dessas ferramentas. Confira no vídeo abaixo a criação de um instalador no Visual Studio 2015 utilizando os Installer Projects:

Adicionando os Installer Projects no Visual Studio 2015

Como mencionei no vídeo, se você não conseguir encontrar os projetos de instalação no Visual Studio 2015, não se preocupe, isso é completamente normal. Até o Visual Studio 2012, os projetos de instalador eram adicionados automaticamente durante a configuração do Visual Studio. Porém, a partir do Visual Studio 2013 nós temos que baixar uma extensão a parte para instalá-los.

Você pode baixar os Installer Projects para Visual Studio 2013 aqui e para o Visual Studio 2015 aqui. Outra opção é abrir a tela de “Extensions and Updates” diretamente no Visual Studio (menu “Tools“), clicar em “Online” e procurar por “Installer“.

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Criando um instalador com o Visual Studio 2015 appeared first on André Alves de Lima.

Passando um DataSet não tipado para o Report Viewer

$
0
0

O grande problema ao criarmos relatórios do Report Viewer com DataSets tipados é que, caso nós utilizemos DataAdapters vinculados às nossas DataTables, existem grandes chances dos dados do relatório virem de um banco de dados incorreto quando executarmos a aplicação no computador dos nossos clientes. Isso pode ser facilmente contornado alterando manualmente a string de conexão no computador do cliente ou até mesmo alterando a string de conexão via código em tempo de execução.

Mas, que tal passarmos um DataSet não tipado para o Report Viewer? Dessa forma nós ficaríamos totalmente flexíveis na hora de carregarmos os dados dos nossos relatórios. Nós poderíamos, por exemplo, carregar os dados através de um TableAdapter ou DataReader. Poderíamos também carregar os dados de algum outro lugar que não fosse o banco de dados (um arquivo texto, uma planilha do Excel, etc).

No artigo de hoje eu vou mostrar como você pode alterar a string de conexão dos DataSets tipados em tempo de execução (caso você deseje trabalhar com DataSets tipados). Além disso, eu vou ensinar também como passar DataSets não tipados para o Report Viewer.

Criando o relatório com DataSet tipado ou classe

Infelizmente, as únicas maneiras (nativas) de criarmos relatórios no Report Viewer é através de um DataSet tipado ou classe (além das outras opções mais exóticas, como através de serviços ou listas do SharePoint). Dessa forma, mesmo que a gente decida passar um DataSet não tipado para o Report Viewer em tempo de execução, nós teremos que obrigatoriamente criar um DataSet tipado ou classe para conseguirmos desenhar a estrutura do relatório. Existe também uma gambiarra onde editamos o arquivo rdlc manualmente, mas esse truque eu vou deixar para um próximo artigo.

Dito isso, vamos começar o nosso projeto de exemplo criando um novo projeto do tipo “Windows Forms Application“. Poderia ser WPF ou poderia ser web e o resultado seria o mesmo, mas vamos trabalhar com Windows Forms para facilitar a demonstração.

Dentro desse projeto, vamos analisar as duas possibilidades que temos para desenharmos o nosso relatório: DataSet tipado ou classe. A primeira opção (DataSet tipado) é a mais óbvia. Vamos adicionar um novo DataSet tipado ao projeto, dando o nome de “DataSetPessoa“:

Dentro desse DataSet tipado, vamos adicionar uma nova DataTable, chamada “Pessoa“, contendo as colunas “Id“, “Nome” e “Sobrenome“, sendo que a coluna “Id” é auto-incremento e chave primária:

E com isso nós temos o DataSet que nós poderíamos utilizar para criar o nosso relatório. Porém, essa não é a sistemática que a maioria das pessoas utiliza na criação de DataSets tipados. O mais comum é, ao invés de criar as DataTables manualmente, os programadores acabam arrastando as tabelas de um banco de dados existente para dentro do DataSet. O problema disso é que a string de conexão fica gravada no arquivo app.config do projeto, e muitas pessoas têm dificuldade em mudar posteriormente essa string de conexão em tempo de execução, fazendo com que o relatório pegue os dados do banco errado.

Para entendermos esse cenário, vou criar um outro DataSet tipado (“DataSetPessoa2“) e, na janela “Server Explorer“, eu vou criar uma conexão com um banco de dados do Microsoft Access já existente (você pode baixar o arquivo mdb aqui). Esse banco já possui uma tabela chamada “Pessoa” com a estrutura que precisamos para o nosso relatório, então eu vou arrastar essa tabela para dentro do DataSet tipado:

O resultado disso tudo é uma DataTable contendo um DataAdapter atachado, que tem todas as informações necessárias para carregar os dados daquele banco que selecionamos no Server Explorer:

Por fim, a terceira maneira de termos uma estrutura de dados para desenharmos o nosso relatório é através de uma classe. Vamos, então, adicionar uma nova classe no nosso projeto, dando o nome de “Pessoa“:

    // C#
    public class Pessoa
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Sobrenome { get; set; }
    }
' VB.NET
Public Class Pessoa
    Public Property Id As Integer
    Public Property Nome As String
    Public Property Sobrenome As String
End Class

Nesse ponto, não esqueça de compilar o projeto, senão essa classe não será detectada pelo Report Viewer nos próximos passos.

É importante frisar que nós não precisamos de toda essa parafernalha no nosso projeto. Nós só temos que escolher uma das três opções que eu apresentei acima: ou DataSet tipado com a tabela criada manualmente, ou DataSet tipado com a tabela vindo de um banco de dados existente ou uma classe de dados. Não faz sentido utilizar mais de uma sistemática no mesmo projeto. Eu só fiz dessa forma nesse artigo para mostrar todas as possibilidades existentes.

Agora chegou a hora de, finalmente, criarmos o nosso relatório. Vamos adicionar um novo relatório do Report Viewer no nosso projeto, dando o nome de “RelatorioPessoa“:

Nesse relatório, vamos adicionar uma nova fonte de dados, clicando com o botão direito em “Datasets” e escolhendo a opção “Add Dataset“:

Na janela Dataset Properties, o Report Viewer automaticamente detectará os dois DataSets tipados que temos no nosso projeto (“DataSetPessoa” e “DataSetPessoa2“):

Se quiséssemos trabalhar com a classe de dados (ao invés dos DataSets tipados), teríamos que clicar em “New“, escolher a opção “Object” como tipo do DataSource e depois encontraríamos a classe “Pessoa” na lista de classes do projeto:

Porém, ao invés disso, vamos trabalhar com o modelo que a maioria das pessoas utiliza e que, infelizmente, é o modelo que traz mais problemas: o DataSet tipado que tem uma DataTable com adapter atachado (no nosso caso, o “DataSetPessoa2“):

Agora que já temos o DataSet no relatório, vamos adicionar uma tabela bem simples que listará as informações da tabela “Pessoa“:

Pronto! Esse será o relatório que utilizaremos no exemplo deste artigo. É claro que poderíamos trabalhar com uma estrutura bem mais complexa e um design bem mais detalhado, porém a ideia do artigo é mostrar o envio dos dados para o relatório, e não o relatório em si. Se você quiser dicas para criar relatórios mais complexos, dá uma olhada no meu histórico de artigos sobre o Report Viewer que tem bastante coisa interessante já publicada.

Alterando a string de conexão do DataSet tipado em tempo de execução

Com o relatório finalizado, vamos ajustar o formulário do nosso projeto para exibirmos o relatório com os seus respectivos dados. A primeira coisa que temos que fazer é adicionarmos um controle do Report Viewer no formulário, docka-lo (para que ele ocupe o espaço todo do formulário) e temos também que escolher o relatório que será exibido:

Como criamos o relatório apontando para o DataSet tipado que tinha um adapter atachado, ao escolhermos esse relatório no controle, o Visual Studio criará automaticamente no formulário uma instância do DataSet, um BindingSource e uma instância do Adapter:

E, se olharmos no code-behind do formulário, no evento “Load“, o Visual Studio já colocou o código para fazer o carregamento do DataSet utilizando o Adapter. E qual é o problema disso? O problema é que o Adapter utilizará a string de conexão que está armazenada no arquivo app.config da aplicação. Veja só como ficou a string de conexão no projeto que eu criei:

Essa string de conexão criada pelo Visual Studio está errada e, se eu tentar executar o projeto, veja só o erro que eu vou acabar recebendo:

Obviamente, na hora de fazermos o deployment desse projeto, esse caminho do arquivo será inválido também. E isso pode acontecer não só com o Microsoft Access, mas com qualquer banco de dados (quando trabalhamos com o SQL Server, o nome da instância pode ser diferente no cliente, por exemplo).

A solução nesse caso é alterarmos a string de conexão em tempo de execução. E como é que podemos fazer isso? É simples. Só precisamos abrir o arquivo de configuração (utilizando a classe ConfigurationManager), alteramos a string de conexão e salvamos o arquivo novamente. Porém, um detalhe importante é que essa classe fica no assembly System.Configuration, que não é referenciado por padrão quando criamos um novo projeto. Portanto, precisamos adicionar manualmente a referência para esse assembly:

Adicionada a referência, podemos utilizar a classe ConfigurationManager para alterar a nossa string de conexão. Veja como fica o código de forma que a string de conexão aponte para o arquivo Banco1.mdb no mesmo diretório da aplicação:

        // C#
        private void AlterarStringDeConexao()
        {
            var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None);
            var connectionStrings = config.ConnectionStrings;
            foreach (System.Configuration.ConnectionStringSettings connectionString in connectionStrings.ConnectionStrings)
            {
                connectionString.ConnectionString = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\\Banco1.mdb", Environment.CurrentDirectory);
            }
            config.Save(System.Configuration.ConfigurationSaveMode.Modified);
            System.Configuration.ConfigurationManager.RefreshSection("connectionStrings");
        }
    ' VB.NET
    Private Sub AlterarStringDeConexao()
        Dim Config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None)
        Dim ConnectionStrings = Config.ConnectionStrings
        For Each ConnectionString As System.Configuration.ConnectionStringSettings In ConnectionStrings.ConnectionStrings
            ConnectionString.ConnectionString = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\Banco1.mdb", Environment.CurrentDirectory)
        Next
        Config.Save(System.Configuration.ConfigurationSaveMode.Modified)
        System.Configuration.ConfigurationManager.RefreshSection("connectionStrings")
    End Sub

Nota: obviamente, caso a sua aplicação utilize mais de uma string de conexão ao mesmo tempo, você teria que lidar com essa especificidade nesse método.

Agora nós só temos que adicionar a chamada para esse método antes do carregamento da DataTable:

        // C#
        private void Form1_Load(object sender, EventArgs e)
        {
            AlterarStringDeConexao();

            this.PessoaTableAdapter.Fill(this.DataSetPessoa2.Pessoa);

            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        AlterarStringDeConexao()

        Me.PessoaTableAdapter.Fill(Me.DataSetPessoa2.Pessoa)

        Me.ReportViewer1.RefreshReport()
    End Sub

Ao executarmos novamente o projeto, veremos que os dados serão carregados corretamente através do arquivo mdb especificado na string de conexão:

Criando e passando um DataSet não tipado

Muito bem. Nós já vimos como criar o relatório com DataSet tipado (ligado ou não a um banco de dados via TableAdapter) e classe de dados. Já vimos também como carregar o relatório através de um DataSet tipado que contenha um Adapter, inclusive alterando a string de conexão em tempo de execução. Agora chegou a hora de vermos como podemos carregar esse relatório que criamos utilizando um DataSet não tipado.

Um DataSet não tipado é um DataSet criado diretamente via código. Para o caso do Report Viewer, nós nem precisamos de um DataSet em si, mas somente, uma DataTable (um DataSet seria mais ou menos um agrupamento de DataTables). Dessa forma, a primeira coisa que temos que fazer é criarmos uma DataTable com as respectivas colunas:

            // C#
            var dataTable = new DataTable();
            dataTable.Columns.Add("Id", typeof(int));
            dataTable.Columns.Add("Nome");
            dataTable.Columns.Add("Sobrenome");
        ' VB.NET
        Dim DataTable = New DataTable()
        DataTable.Columns.Add("Id", GetType(Integer))
        DataTable.Columns.Add("Nome")
        DataTable.Columns.Add("Sobrenome")

Nota: os nomes das colunas da DataTable devem bater exatamente com os nomes das colunas no DataSet do relatório, inclusive as letras maiúsculas e minúsculas.

Em seguida, temos que carregar essa tabela de alguma maneira. Num sistema “de verdade“, você teria que carregar os dados do banco de dados, provavelmente utilizando um TableAdapter ou DataReader. Como esse não é o foco do artigo (não importa de onde você esteja pegando os dados), eu vou simplesmente criar algumas linhas manualmente:

            // C#
            dataTable.Rows.Add(1, "André", "Lima");
            dataTable.Rows.Add(2, "Fulano", "de Tal");
            dataTable.Rows.Add(3, "Beltrano", "da Silva");
            dataTable.Rows.Add(4, "Zé", "Ninguém");
        ' VB.NET
        DataTable.Rows.Add(1, "André", "Lima")
        DataTable.Rows.Add(2, "Fulano", "de Tal")
        DataTable.Rows.Add(3, "Beltrano", "da Silva")
        DataTable.Rows.Add(4, "Zé", "Ninguém")

Por fim, a única coisa que está faltando é passarmos essa DataTable para o nosso relatório. Veja só como fazemos isso:

            // C#
            this.reportViewer1.LocalReport.DataSources.Clear();
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", dataTable));
        ' VB.NET
        Me.ReportViewer1.LocalReport.DataSources.Clear()
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DataTable))

Atenção! O nome do DataSet (primeiro parâmetro do construtor da classe ReportDataSource) deve bater exatamente com o nome definido no DataSet dentro do relatório, inclusive letras maiúsculas e minúsculas.

Pronto! Execute o projeto e veja que o relatório será exibido corretamente. Aqui vai o resultado do código completo da criação da DataTable e carregamento do relatório:

        // C#
        private void Form1_Load(object sender, EventArgs e)
        {
            var dataTable = new DataTable();
            dataTable.Columns.Add("Id", typeof(int));
            dataTable.Columns.Add("Nome");
            dataTable.Columns.Add("Sobrenome");

            dataTable.Rows.Add(1, "André", "Lima");
            dataTable.Rows.Add(2, "Fulano", "de Tal");
            dataTable.Rows.Add(3, "Beltrano", "da Silva");
            dataTable.Rows.Add(4, "Zé", "Ninguém");

            this.reportViewer1.LocalReport.DataSources.Clear();
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", dataTable));

            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim DataTable = New DataTable()
        DataTable.Columns.Add("Id", GetType(Integer))
        DataTable.Columns.Add("Nome")
        DataTable.Columns.Add("Sobrenome")

        DataTable.Rows.Add(1, "André", "Lima")
        DataTable.Rows.Add(2, "Fulano", "de Tal")
        DataTable.Rows.Add(3, "Beltrano", "da Silva")
        DataTable.Rows.Add(4, "Zé", "Ninguém")

        Me.ReportViewer1.LocalReport.DataSources.Clear()
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DataTable))

        Me.ReportViewer1.RefreshReport()
    End Sub

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Concluindo

A principal dificuldade que os programadores enfrentam no desenvolvimento de relatórios com o Report Viewer é acertar no carregamento dos dados. Muitas vezes, ao utilizarmos DataSets tipados, a string de conexão acaba apontando para o lugar errado.

No artigo de hoje você aprendeu a alterar a string de conexão em tempo de execução (para o caso de você estar utilizando DataSets tipados com Adapters) e aprendeu também a passar um DataSet não tipado para o Report Viewer. Dessa forma você tem total flexibilidade na hora de carregar os dados dos seus relatórios.

O que achou? Gostou desse artigo? Escreva um comentário deixando a sua opinião e compartilhe esse artigo com algum amigo que possa se beneficiar desse aprendizado!

Até a próxima!

André Lima

Image by Juhan Sonin used under Creative Commons
https://www.flickr.com/photos/juhansonin/5135576565

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Passando um DataSet não tipado para o Report Viewer appeared first on André Alves de Lima.


Ativando o Report Viewer no Visual Studio 2015

$
0
0

Um dos artigos mais visualizados aqui no meu site é o artigo onde eu mostro como ativar o Report Viewer no Visual Studio 2015. Como algumas pessoas aprendem melhor com vídeo (ou preferem conteúdo em vídeo), resolvi aproveitar a popularidade desse tema para gravar um vídeo super rápido mostrando como ativar o Report Viewer no Visual Studio 2015. Confere aí:

E cadê o Report Viewer no Visual Studio 2017?

Como expliquei no vídeo, a Microsoft está mudando a estratégia de distribuição do Report Viewer a partir do Visual Studio 2017. A ideia é que o controle para utilização nos nossos projetos será distribuído via pacote do NuGet e a experiência de design de relatórios será distribuída através de uma extensão do Visual Studio. Eu detalhei tudo no artigo onde eu explico que a Microsoft não está descontinuando o Report Viewer.

Assine a minha newsletter

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você terá acesso aos projetos de exemplo utilizados nos artigos, receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Ativando o Report Viewer no Visual Studio 2015 appeared first on André Alves de Lima.

Não reinvente a roda! Utilize componentes prontos no desenvolvimento da sua aplicação

$
0
0

Muitos desenvolvedores não sabem da existência de bibliotecas comerciais e acabam constantemente “reinventando a roda” na hora de desenvolver as suas aplicações. Eu mesmo, lá por volta de 2005, no começo da minha carreira, não conhecia tais bibliotecas e pacotes de componentes. Ou seja, eu desenvolvia as aplicações da pequena empresa onde eu trabalhava utilizando os componentes nativos do Windows Forms.

Foi só depois de participar de uma mesa redonda no TechEd (em 2006, se não me engano) que eu ouvi falar da DevExpress e fui pesquisar exatamente o que ela tinha a oferecer. Começamos a utilizar os componentes de avaliação e a produtividade foi às alturas. Não dá para comparar a quantidade de funcionalidades disponíveis nos controles de bibliotecas comerciais com as funcionalidades dos controles nativos do .NET Framework.

É justamente por causa desse grande impacto que essa simples informação fez no desenvolvimento das nossas aplicações que eu quero compartilhar com você as 4 principais empresas que desenvolvem bibliotecas de componentes para aplicações .NET (entre outras plataformas).

Só para deixar claro, não existem somente essas empresas que vendem componentes prontos para o desenvolvimento de aplicações .NET. Essas são, na minha opinião, as 4 empresas mais conhecidas do segmento. Caso você utilize componentes de alguma outra empresa, deixe o seu comentário no final do artigo.

Ah, outro detalhe: esse não é um post patrocinado. Ou seja, não estou ganhando absolutamente nada dessas empresas para fazer essa divulgação. Só estou divulgando porque, como falei anteriormente, essa informação teve um grande impacto nas aplicações que desenvolvemos dali para a frente.

DevExpress

A DevExpress foi a primeira empresa desse ramo que eu tive contato. Na empresa onde eu trabalhava na época eu baixei os componentes de avaliação e fiquei maravilhado com as suas funcionalidades. O destaque vai para o famoso grid, que possui nativamente as funcionalidades de ordenação, remoção e adição de colunas, filtros, mestre/detalhe, drilldown, agrupamentos, entre outras:

E não é só o grid que faz sucesso. O pacote de componentes da DevExpress conta também com controles de layout (possibilitando que o usuário altere o layout das telas e salve para a próxima execução), gráficos, ribbon, skins, entre outros. Dá uma olhada nas plataformas suportadas no momento:

E quanto é que custa tudo isso? Bom, não é barato. No momento da escrita deste artigo (fevereiro de 2017), se você for comprar as plataformas separadamente (por exemplo, só Windows Forms ou só WPF ou só ASP.NET), elas custarão entre $500 e $900, dando o direito de suporte e atualizações por um ano. Depois disso, se você quiser continuar com o suporte e novas atualizações, você tem que renovar a cada ano (por um preço menor).

Existe também a opção de você comprar os componentes de todas as plataformas .NET (Windows Forms, ASP.NET, WPF e UWP) por $1500. Por fim, tem a opção “super master blaster” onde você compra todos os componentes possíveis da DevExpress (inclusive os componentes para HTML5 e JavaScript) pela bagatela de $2200.

Link: http://www.devexpress.com/
Fundada em: 1998
Versão Trial: 30 dias

Telerik

A Telerik é a empresa mais conhecida no meio dos desenvolvedores, uma vez que ela costuma patrocinar diversos eventos e ações da comunidade. Por exemplo, a Telerik foi patrocinadora do podcast .NET Rocks por anos e anos (hoje em dia, ironicamente, é a DevExpress que patrocina). Eu até tenho um bonequinho ninja da Telerik na minha mesa da empresa, que eu ganhei no primeiro TechEd que eu participei (a Telerik normalmente tinha estandes e patrocinava esse tipo de evento).

Um dos componentes mais conhecidos da Telerik é o seu grid, que é tão poderoso quanto o da DevExpress:

Além disso, o pacote de componentes da Telerik também conta com diversos outros controles bem interessantes, como gráficos, editor RichText, controle visualizador de PDF, etc. As plataformas suportadas também vão desde o Windows Forms até o UWP, passando por WPF, ASP.NET e Xamarin:

E o preço? No momento da escrita deste artigo (fevereiro de 2017), plataformas separadas estão saindo entre $400 e $1000, também com updates e suporte por 1 ano. Já a opção com todas as plataformas (tirando Xamarin) sai por $1500 e o pacote completo custa $2200.

Link: http://www.telerik.com/
Fundada em: 2002
Versão Trial: 30 dias

Infragistics

Confesso a você que logo quando eu comecei a participar de eventos da comunidade (lá por volta de 2004, 2005) eu ouvi bastante o nome “Infragistics“, mas nunca tive curiosidade sequer de procurar o que isso queria dizer. Eu tenho a impressão que as pessoas sempre reclamavam da Infragistics, mas pode ser que eu esteja errado. Com o passar dos anos eu acabei descobrindo que a Infragistics era, na verdade, mais uma empresa que desenvolvia componentes para auxiliar no desenvolvimento de aplicações.

Alguns projetos legados aqui na empresa onde eu trabalho utilizam controles da Infragistics, principalmente o grid:

Assim como as outras empresas apresentadas neste artigo, a Infragitics também suporta uma variedade de plataformas, como Windows Forms, WPF, ASP.NET, Xamarin e UWP:

Componentes de uma plataforma específica custam, no momento da escrita deste artigo (fevereiro de 2017), entre $500 e $900. Já o pacote com os componentes para todas as plataformas sai por $1500.

Link: http://www.infragistics.com/
Fundada em: 1989
Versão Trial: 30 dias

ComponentOne

Eu confesso que tive pouca experiência com os controles da ComponentOne, mas dentre as quatro opções apresentadas neste artigo, ela parece ser a menos robusta. No passado eu cheguei a utilizar de leve os componentes de acesso a dados da ComponentOne, que permitiam o compartilhamento “fácil” de schemas de bancos de dados diferentes. Isso é primordial caso a sua aplicação tenha que rodar apontando para bancos de dados diferentes (como SQL Server e Oracle, por exemplo).

Dando uma olhada nas imagens de exemplo dos controles da ComponentOne, já dá para perceber que ele fica em uma categoria um pouco inferior quando comparamos com os controles da DevExpress, Telerik ou Infragistics. Por exemplo, veja só o grid da ComponentOne:

As plataformas suportadas são basicamente as mesmas das outras empresas:

Eu imaginei que os preços da ComponentOne seriam menores, mas não, eles ficam na casa de $900 a $1100. Existe um combo de componentes .NET desktop + web que sai por $1500 e um outro combo que inclui também os componentes HTML5, JavaScript e desenvolvimento móvel por $2000.

Link: http://www.componentone.com/
Fundada em: 2000
Versão Trial: 30 dias

Concluindo

A grande mensagem que eu quero passar com este artigo é: se a sua aplicação tem uma demanda por controles complexos, não reinvente a roda! Existem diversas empresas que desenvolvem controles comerciais que podemos utilizar nas nossas aplicações, como as quatro que eu apresentei neste artigo.

Não importa qual delas você escolha, o que importa é que você não fique perdendo tempo e dinheiro desenvolvendo controles por conta própria. Faça uso desses excelentes controles desenvolvidos por terceiros, que contam com suporte e atualizações constantes.

Vamos fazer uma conta rápida aqui? Imagine que você tenha um desenvolvedor ganhando R$3000 por mês na sua empresa. Aí você pega esse funcionário e coloca ele para desenvolver uma funcionalidade extra no controle de grid do .NET. Se esse desenvolvedor trabalhar duas semanas e meia no desenvolvimento e suporte desse grid, o custo sairia basicamente o mesmo se você tivesse pago os $600, que é o que custa em média o pacote de controles para uma única plataforma das empresas abordadas nesse artigo. Não precisa nem falar o que compensa mais, não é mesmo?

Se você ainda não utiliza esses tipos de controles nos seus projetos ou na sua empresa, faça o download das versões trial, teste e veja você mesmo a quantidade de tempo que a sua empresa conseguiria reduzir no desenvolvimento das suas aplicações. Depois volta aqui e fala pra gente os resultados na caixa de comentários. E se você já utiliza ou utilizou esses ou outros componentes pagos nas suas aplicações, conta pra gente como é que foi a sua experiência também.

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Não reinvente a roda! Utilize componentes prontos no desenvolvimento da sua aplicação appeared first on André Alves de Lima.

Ativando o Crystal Reports no Visual Studio 2015

$
0
0

No meu último vídeo eu mostrei como ativar o Report Viewer no Visual Studio 2015. Aproveitando essa onda de relatórios, no vídeo de hoje eu resolvi mostrar como ativar o Crystal Reports no Visual Studio 2015.

O Crystal Reports é uma ferramenta de geração de relatórios poderosíssima e super conhecida no mercado. Existe uma versão gratuita praticamente sem limitações e que pode ser instalada em conjunto com o Visual Studio (inclusive na edição Community!). Veja só como ativá-lo neste vídeo:

O procedimento é muito simples. Basta acessarmos a página de downloads do Crystal Reports para Visual Studio e baixarmos o “Support Pack” mais atual. A instalação é muito tranquila, no famoso estilo “next, next, finish“.

Na última etapa da instalação o instalador vai perguntar se você quer instalar a runtime do Crystal Reports (que é necessária para conseguirmos visualizar os relatórios em tempo de execução). Eu costumo pular essa etapa e instalar manualmente as runtimes (colunas “MSI 32 Bit” e “MSI 64 Bit” da página de downloads). Faço isso de forma manual, primeiro a 32 bits e depois a 64 bits, porque antigamente existia um bug que, se você instalasse primeiro a runtime 64 bits, você não conseguiria instalar a runtime 32 bits depois. Não sei se esse bug já foi corrigido, mas, para evitar problemas, eu continuo fazendo essa instalação manual.

E no Visual Studio 2017?

No momento da gravação desse vídeo (fevereiro de 2017), o Visual Studio 2017 ainda não era suportado. Porém, como a SAP já colocou uma informação na página de downloads falando que essa versão ainda não é suportada, acredito que com um próximo “Support Pack” eles adicionarão o suporte ao Visual Studio 2017. Tudo indica que o processo de instalação continuará o mesmo.

Assine a minha newsletter

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você terá acesso aos projetos de exemplo utilizados nos artigos, receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Ativando o Crystal Reports no Visual Studio 2015 appeared first on André Alves de Lima.

Falei algumas besteiras sobre o PostgreSQL, veja aqui as correções

$
0
0

No final de 2015 eu estava “na onda” de fazer transmissões ao vivo pelo Periscope. Eu fazia uma transmissão por semana, extraía o conteúdo e publicava no meu canal do Youtube. No total foram 10 semanas de transmissões, e você pode conferir a gravação da maioria das transmissões nesta playlist.

Uma das transmissões que eu fiz foi um breve comparativo entre SQL Server, MySQL e PostgreSQL. Confessando aqui para você, desses três bancos o único que eu tenho experiência sólida e posso falar com propriedade é o SQL Server. Já trabalhei em alguns projetos “de brincadeira” com o MySQL e implementei uma integração aqui na empresa com um banco PostgreSQL. Dessa forma, a base para o comparativo do MySQL e PostgreSQL foi o site DB-Engines.com. A junção da minha pouca experiência com o PostgreSQL mais alguns dados incorretos (ou incompletos) do DB-Engines resultou em algumas besteiras no comparativo.

Aparentemente o vídeo caiu em alguma comunidade de PostgreSQL e algumas pessoas vieram me corrigir nos comentários do vídeo, em especial o Fabio Telles Rodriguez e o Juliano Atanazio. A ideia desse post é sumarizar as correções para que eu possa linká-lo no vídeo e evitar confusões para outras pessoas que assistam no futuro.

Vamos pronunciar o nome corretamente?

Antes de tudo, vamos começar pelo básico? Como é que se pronuncia PostgreSQL? Eu nunca trabalhei com o PostgreSQL no Brasil – o único projeto que eu trabalhei com ele foi aqui na Alemanha. Nessa época, todas as pessoas que eu tive contato pronunciavam o nome assim: “postgrê“. Aparentemente essa não é a forma correta (ou mais utilizada), que seria algo como “post” – “grés” – “kiu-él” (o “Q” e o “L” finais com som em inglês).

Eu não ligo muito quando as pessoas pronunciam algum termo técnico errado – não acho que isso influencie no quanto a pessoa sabe sobre aquela tecnologia ou não. Mas, de qualquer forma, eu sempre busco pronunciar as coisas da forma correta. Agora que já aprendi a pronúncia certa do PostgreSQL, vou me esforçar para utilizá-la.

Agora, um adendo: não sou só eu que tenho dificuldade em saber a pronúncia correta do PostgreSQL. Tem até uma pergunta no Quora onde a galera discute qual seria a pronúncia correta, além de uma pesquisa feita pelo próprio site do PostgreSQL e, pasmem: um arquivo mp3 no site do PostgreSQL mostrando a pronúncia certa!

O PostgreSQL tem suporte a XML, sim!

Agora que todo mundo já sabe pronunciar PostgreSQL corretamente, vamos começar as correções realmente técnicas desse vídeo. Acho que o fato de eu ter falado que o PostgreSQL não tem suporte a XML foi o principal fator que disparou a indignação dos entusiastas do PostgreSQL com o meu vídeo. Eu me baseei nas informações apresentadas no comparativo MySQL x PostgreSQL do site DB-Engines:

Até hoje o site mostra um quadrado em branco na parte do suporte ao XML, que eu erroneamente deduzi que significasse um “não“. Ao invés de conferir mais à fundo antes de falar algo errado no vídeo, eu simplesmente disse que o PostgreSQL não tem suporte a campos XML, informação que está completamente errada, uma vez que ele tem esse suporte desde 2008!

O PostgreSQL é um mundo sem dono?

Ai, ai, ai. Cada coisa que a gente fala e depois se arrepende. Nesse vídeo eu falei que o PostgreSQL é um “mundo sem dono“. Pelo que eu me lembro, na minha cabeça eu falei isso porque ele é um banco de dados open source, que aceita contribuições da comunidade. Mas que coisa mais feia de se falar, hein André? O fato de algo ser open source não tem nenhuma relação com a ferramenta ser um “mundo sem dono“. Retiro o que disse!

Repetindo aqui o que o Juliano Atanazio comentou no meu vídeo: “[O PostgreSQL] é open source sim, mas não é casa da mãe Joana. Existe um grupo de desenvolvedores, o core team, que é conhecido como PGDG (Postgresql Global Development Group) […] e é extremamente rigoroso com relação a aceitação de código […]. Não é qualquer coisa que passa e tudo é exaustivamente testado. É um trabalho muito sério.“.

A ferramenta de administração é difícil de utilizar?

Veja bem, eu estou acostumado a trabalhar com o SQL Server e a sua ferramenta de gerenciamento, o SQL Server Management Studio. Já trabalhei bastante também com o Oracle e o PL/SQL Editor no passado e eu vou te dizer: até hoje eu não consegui encontrar uma ferramenta de banco de dados mais intuitiva do que o SQL Server Management Studio.

Dito isso, eu confesso que sofri um bocado quando tive que trabalhar com o PostgreSQL naquele projeto que eu mencionei anteriormente. Comparando com o que eu estou acostumado, a ferramenta padrão de administração do PostgreSQL é, sim, muito mais difícil e menos intuitiva.

Porém, essa é uma discussão sem fundamento. Quem trabalhou com o PostgreSQL e a sua ferramenta de administração por 10, 15 anos certamente vai achar de início o SQL Server Management Studio complicado. Existem até pessoas que preferem ser mais “hard core” e trabalham direto em linha de comando, como o Juliano Atanazio. Com o tempo, qualquer ferramenta que você utilize repetidamente você inevitavelmente acaba dominando e atingindo o auge da sua produtividade.

Vale a pena mencionar que existem também outras ferramentas comerciais para fazer o gerenciamento do PostgreSQL, como o EMS SQL Manager, também mencionado pelo Juliano Atanazio e que tem suporte não só ao PostgreSQL, mas também para os principais bancos de dados relacionais do mercado.

O PostgreSQL só serve para trabalhar com dados geográficos?

Pode parecer que eu dei a entender no vídeo que você só deve trabalhar com o PostgreSQL se você estiver trabalhando com dados geográficos. Isso está obviamente errado. Não preciso nem falar que o PostgreSQL é, sim, um banco dados robusto e que não serve somente para trabalhar com dados geográficos.

Polêmica: MySQL é mais robusto que PostgreSQL?

A grande polêmica dessa história toda é: entre MySQL e PostgreSQL, qual é o mais robusto? No vídeo eu acabei caindo na besteira de falar que o MySQL é mais robusto que o PostgreSQL. De onde é que eu tirei essa informação? Do nariz! Em momento algum eu deveria ter dado uma opinião dessas sem conhecer profundamente as duas ferramentas.

Além disso, essa é uma discussão que nunca vai chegar a lugar algum. Quem defende o MySQL sempre vai achar argumentos para dizer que o MySQL é mais robusto, e o mesmo vale para quem defende o PostgreSQL. O ideal é conhecer as duas opções (entre outras) e decidir qual é a melhor em cada cenário.

Links de referência

Agora vou deixar para vocês alguns links de referência do PostgreSQL postados nos comentários do vídeo:

Matriz de recursos do PostgreSQL
Política de versionamento do PostgreSQL (com histórico de versões e datas de EOL)
Outro site com matriz comparativa de bancos de dados (veja só, nessa matriz eu poderia ter visto que o PostgreSQL suporta, sim, campos XML)
Lista de e-mails brasileira sobre PostgreSQL
Grupo no Facebook sobre PostgreSQL
Savepoint (site do Fabio Telles Rodriguez onde ele posta várias coisas sobre PostgreSQL)

Conclusão e lição aprendida

A conclusão e lição que podemos tirar dessa situação é que temos que tomar cuidado com o que falamos sobre tecnologias que não conhecemos profundamente. Eu não tinha experiência suficiente para falar sobre o PostgreSQL da forma que eu falei. Acabei me baseando completamente nas informações incompletas do site DB-Engines e falei diversas besteiras.

Entretanto, se você me perguntar se eu estou arrependido de ter feito aquela transmissão e publicado no meu canal do Youtube, eu te diria que não! Muita gente cai no espectro oposto e não fala nada na internet com medo de estar falando besteira. Eu errei nas informações, fui corrigido nos comentários e fiz esse post esclarecendo os pontos que estavam incorretos. Não vejo nenhum problema nisso, e você?

Assine a minha newsletter

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Falei algumas besteiras sobre o PostgreSQL, veja aqui as correções appeared first on André Alves de Lima.

Acessando a webcam no .NET com a biblioteca AForge

$
0
0

Uma das possibilidades que temos à nossa disposição para fazermos o acesso à webcam no .NET é através da biblioteca AForge. Essa biblioteca é muito fácil e intuitiva, muito simples de utilizar. Uns tempos atrás eu escrevi um artigo onde eu mostrei como tirar fotos com a webcam no C#, onde eu utilizei tanto a biblioteca AForge quanto a biblioteca DirectShow.NET. Como esse é um dos artigos mais populares do meu site, eu resolvi gravar um vídeo expandindo essa ideia, focando somente na biblioteca AForge, que é a mais utilizada:

Instalação da biblioteca AForge pelo NuGet

No artigo que eu escrevi tempos atrás, eu só mostrei como podemos baixar a biblioteca manualmente no seu site. Hoje em dia a utilização do NuGet se tornou praticamente padrão nos projetos .NET, por isso, faz todo o sentido partirmos para essa estratégia.

Para adicionarmos a referência à biblioteca AForge pelo NuGet, temos que primeiramente procurar por “AForge“. Uma vez listadas as opções, instale o item “AForge.Video.DirectShow“:

Outra opção é utilizarmos o Package Manager Console, executando o comando “Install-Package AForge.Video.DirectShow“.

Namespace

Todas as classes relacionadas à webcam no AForge estão localizadas no namespace “AForge.Video.DirectShow“, dessa forma, para facilitar as coisas, eu sugiro que você adicione uma referência a esse namespace utilizando a cláusula “using AForge.Video.DirectShow” no topo do seu formulário.

Listando as webcams

A biblioteca AForge serve para trabalharmos com diversas coisas no .NET (como áudio e vídeo). O acesso à webcam é somente uma das possibilidades que temos à nossa disposição. Para listarmos as webcams, utilizamos a classe “FilterInfoCollection“, passando “FilterCategory.VideoInputDevice” como parâmetro no construtor.

Instanciando uma webcam

No AForge, a webcam pode ser manipulada através da classe “VideoCaptureDevice“. O construtor dessa classe espera um “moniker string“, que seria algo como um “id” do dispositivo de captura. Essa informação pode ser recuperada através dos objetos retornados pelo “FilterInfoCollection” mencionado logo acima.

O evento NewFrame

A classe “VideoCaptrureDevice” possui um evento chamado “NewFrame“. Estando a câmera ligada, esse evento será disparado cada vez que um novo frame for capturado pela webcam. Isso quer dizer que, caso o frame rate da câmera seja, por exemplo, 40fps, esse evento será disparado 40 vezes por segundo.

Nos argumentos desse evento, temos acesso ao frame capturado (que é uma imagem). Com o frame capturado, podemos cloná-lo para, por exemplo, exibirmos em um controle do tipo PictureBox.

Evitando consumo desnecessário de memória

Uma vez que estamos clonando as imagens retornadas pelo evento “NewFrame“, o consumo de memória pode ficar rapidamente muito alto. O .NET só descartará as imagens que não estão sendo mais utilizadas quando ele perceber que nós não precisamos mais delas. Isso pode demorar 10, 15, até 30 segundos dependendo da situação, o que pode potencialmente levar a um estouro de memória.

Para contornarmos esse problema, o ideal é chamarmos um “Dispose” na imagem atual do PictureBox antes de atualizarmos com a imagem nova. Isso é uma maneira de dizermos para o .NET que não precisamos mais daquela imagem velha, fazendo com que a sua memória seja recuperada quase que imediatamente.

Ligando e desligando a câmera

Para sabermos se a câmera deve ser ligada ou desligada, utilizamos a propriedade “IsRunning“, que retornará verdadeiro caso a câmera já esteja ligada ou falso caso contrário. A câmera pode ser ligada através do método “Start” ou desligada através do método “Stop“. É importante que nós lembremos de limpar a imagem do PictureBox quando desligarmos a câmera, caso contrário ela ficará com o último frame capturado antes do desligamento.

Capturando a imagem da webcam

Estando a câmera ligada, nós podemos salvar o frame atual através do método “Save” da imagem que está sendo exibida no PictureBox. Nós podemos utilizar um caminho fixo para a imagem ou, melhor ainda, nós podemos utilizar um “SaveFileDialog” para perguntar para o usuário onde é que ele quer salvar a imagem.

Um problema muito interessante que vamos encontrar ao utilizarmos a classe “SaveFileDialog” é que o PictureBox continuará sendo atualizado com a imagem da webcam enquanto o usuário escolhe o caminho onde ele quer salvar a imagem. O resultado disso é que a imagem que será salva não necessariamente será a mesma imagem de quando o usuário clicou no botão “Capturar“.

Esse problema pode ser resolvido se pausarmos a captura enquanto o usuário escolhe o caminho, restaurando a captura logo após o confirmação ou cancelamento do diálogo. Isso pode ser feito removendo o “hook” do evento “NewFrame” e depois adicionando novamente, tudo isso dentro de um bloco try-finally para evitarmos inconsistências caso uma exception seja lançada no meio do caminho.

Desligue a câmera antes de fechar o formulário!

Em algumas situações (não muito raras) a biblioteca AForge disparará uma exception caso o formulário (ou aplicativo) seja fechado com a câmera ligada. Para evitar esse problema, temos que fazer um “override” do método “OnFormClosing“, onde verificaremos se a câmera está ligada e, caso positivo, nós desligamos a câmera antes que o formulário seja efetivamente fechado.

O código completo

Aqui vai o código completo do exemplo demonstrado nesse vídeo:

        // C#
        private VideoCaptureDevice videoSource;

        public FormCaptura()
        {
            InitializeComponent();

            var videoSources = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (videoSources != null && videoSources.Count > 0)
            {
                videoSource = new VideoCaptureDevice(videoSources[0].MonikerString);
                videoSource.NewFrame += VideoSource_NewFrame;
            }
        }

        private void VideoSource_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
        {
            if (pbWebcam.Image != null)
                pbWebcam.Image.Dispose();
            pbWebcam.Image = (Bitmap)eventArgs.Frame.Clone();
        }

        private void btLigarDesligar_Click(object sender, EventArgs e)
        {
            if (videoSource.IsRunning)
            {
                videoSource.Stop();
                pbWebcam.Image = null;
            }
            else
            {
                videoSource.Start();
            }
        }

        private void btCapturar_Click(object sender, EventArgs e)
        {
            if (pbWebcam.Image != null)
            {
                try
                {
                    videoSource.NewFrame -= VideoSource_NewFrame;

                    using (var dialog = new SaveFileDialog())
                    {
                        dialog.DefaultExt = "png";
                        dialog.AddExtension = true;

                        if (dialog.ShowDialog() == DialogResult.OK)
                        {
                            pbWebcam.Image.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png);
                        }
                    }
                }
                finally
                {
                    videoSource.NewFrame += VideoSource_NewFrame;
                }
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            if (videoSource.IsRunning)
            {
                videoSource.Stop();
            }
            base.OnFormClosing(e);
        }
    ' VB.NET
    Private VideoSource As VideoCaptureDevice

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim VideoSources = New FilterInfoCollection(FilterCategory.VideoInputDevice)
        If VideoSources IsNot Nothing AndAlso VideoSources.Count > 0 Then
            VideoSource = New VideoCaptureDevice(VideoSources(0).MonikerString)
            AddHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame
        End If
    End Sub

    Private Sub VideoSource_NewFrame(sender As Object, eventArgs As AForge.Video.NewFrameEventArgs)
        If PbWebcam.Image IsNot Nothing Then
            PbWebcam.Image.Dispose()
        End If
        PbWebcam.Image = DirectCast(eventArgs.Frame.Clone(), Bitmap)
    End Sub

    Private Sub BtLigarDesligar_Click(sender As Object, e As EventArgs) Handles BtLigarDesligar.Click
        If VideoSource.IsRunning Then
            VideoSource.Stop()
            PbWebcam.Image = Nothing
        Else
            VideoSource.Start()
        End If
    End Sub

    Private Sub BtCapturar_Click(sender As Object, e As EventArgs) Handles BtCapturar.Click
        If PbWebcam.Image IsNot Nothing Then
            Try
                RemoveHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame

                Using dialog = New SaveFileDialog()
                    dialog.DefaultExt = "png"
                    dialog.AddExtension = True

                    If dialog.ShowDialog() = DialogResult.OK Then
                        PbWebcam.Image.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png)
                    End If
                End Using
            Finally
                AddHandler VideoSource.NewFrame, AddressOf VideoSource_NewFrame
            End Try
        End If
    End Sub

    Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
        If VideoSource.IsRunning Then
            VideoSource.[Stop]()
        End If
        MyBase.OnFormClosing(e)
    End Sub

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Acessando a webcam no .NET com a biblioteca AForge appeared first on André Alves de Lima.

Como utilizar o Report Viewer no Visual Studio 2017?

$
0
0

Você está tentando utilizar o Report Viewer no Visual Studio 2017 e não está conseguindo? Já ativou o SQL Server Data Tools na instalação do Visual Studio e mesmo assim nada do Report Viewer aparecer? E agora, será que a Microsoft realmente descontinuou o Report Viewer?

Não se assuste, felizmente o Report Viewer ainda não foi descontinuado. O que acontece é que a Microsoft está mudando o modelo de distribuição do Report Viewer a partir do Visual Studio 2017. Eu até já falei sobre isso nesse outro artigo. A novidade é que a experiência de design de relatórios para o Visual Studio 2017 ficou pronta. Vamos ver como ficou o resultado?

Versão em vídeo

Esse assunto está quentíssimo, então eu resolvi gravar também esse tutorial em formato vídeo, que você confere abaixo:

O novo modelo de distribuição do Report Viewer

Mais uma vez a Microsoft alterou o modelo de distribuição do Report Viewer com o Visual Studio. Até o Visual Studio 2013 ele era instalado junto com a instalação típica do Visual Studio. Depois, a partir do Visual Studio 2015, nós precisávamos habilitar o SQL Server Data Tools na hora da instalação. Agora, com o Visual Studio 2017, ele foi totalmente removido da instalação do Visual Studio.

Eu já expliquei em detalhes o novo modelo de distribuição do Report Viewer no Visual Studio 2017 e os motivos da Microsoft ter feito essa alteração nesse outro artigo, mas, resumindo mais uma vez: a experiência de design dos relatórios será ativada através de uma extensão do Visual Studio (essa é a novidade do artigo de hoje) e o controle em si será distribuído através do NuGet.

Desenhando relatórios do Report Viewer no Visual Studio 2017

A grande novidade do artigo de hoje é que a extensão utilizada para ativarmos a experiência de design de relatórios do Report Viewer no Visual Studio 2017 ficou pronta. Com o Visual Studio 2017 instalado, se tentarmos adicionar um novo item dentro de um projeto, nós não encontraremos a categoria “Report” e tampouco encontraremos o item correspondente ao Report Viewer se fizermos uma pesquisa nos templates instalados:

E como é que podemos instalar a extensão que adiciona o Report Viewer? Simples! Uma opção é baixa-la diretamente no Visual Studio Marketplace, procurando pelo nome “Microsoft Rdlc Report Designer for Visual Studio“. A outra opção é abrirmos a janela de “Extensions and Updates” no Visual Studio:

Aí clicamos na categoria “Online“, procuramos por “rdlc” e instalamos o item “Microsoft Rdlc Report Designer for Visual Studio“:

Independente da opção que você escolher, a instalação será agendada para ser executada uma vez que todas as instâncias do Visual Studio sejam fechadas. Ao fechar todas as instâncias, essa janela será exibida:

A única coisa que temos que fazer nesse ponto é clicar em “Modify” e seguir as instruções do instalador. Uma vez concluída a instalação, se abrirmos o nosso projeto novamente e tentarmos adicionar um novo item, nós encontraremos os itens “Report” e “Report Wizard” dentro da categoria “Visual C# Items” (ou “Visual Basic Items“):

Nota: uma coisa que eu não gostei é que esses itens não foram organizados dentro de uma categoria separada. No Visual Studio 2015 eles eram separados na categoria “Reporting”. Mas, essa é somente a primeira versão. Quem sabe a Microsoft não melhora isso mais para frente.

Se você adicionar um novo item do tipo “Report” no seu projeto, você conseguirá desenhar os seus relatórios normalmente, da mesma forma que você conseguia fazer com o Visual Studio 2015. Aparentemente, o designer continua idêntico ao designer do Visual Studio 2015 (pelo menos essa foi a minha primeira impressão).

Controle do Report Viewer em projetos Windows Forms

No Visual Studio 2015, se quiséssemos adicionar o controle do Report Viewer nos nossos projetos, bastava arrastarmos o controle da caixa de ferramentas para dentro do formulário e pronto, missão cumprida! Porém, a partir do Visual Studio 2017, você não conseguirá encontrar o controle do Report Viewer na caixa de ferramentas. Isso acontece porque ele será distribuído via NuGet.

Dito isso, a primeira coisa que temos que fazer é adicionarmos a referência no nosso projeto. Para isso, abrimos a tela do NuGet, procuramos por “reportviewercontrol” e instalamos o item correspondente:

Em seguida, nós podemos adicionar um novo item na caixa de ferramentas apontando para a dll do Report Viewer que acabou de ser baixada. Fazemos isso clicando com o botão direito na área vazia da caixa de ferramentas e escolhendo a opção “Choose Items“:

Na janela “Choose Toolbox Items“, clicamos no botão “Browse“, navegamos até a pasta “packages” onde o Report Viewer foi baixado no nosso projeto e escolhemos o arquivo “Microsoft.ReportViewer.WinForms.dll“:

Feito isso, basta ativarmos o item “ReportViewer” e clicarmos em “OK” para que o controle do Report Viewer seja adicionado na nossa caixa de ferramentas.

A partir daqui a experiência continua como no Visual Studio 2015: basta arrastarmos o controle para dentro do nosso formulário e customizá-lo conforme precisarmos.

E no Web Forms?

O esquema para utilizarmos o novo controle do Report Viewer no Web Forms segue a mesma linha do Windows Forms. Precisamos adicionar o pacote do NuGet, só que dessa vez temos que prestar atenção para adicionarmos o pacote correto, referente do Web Forms:

Uma vez adicionada a referência, uma página web será aberta com um pequeno tutorial mostrando como adicionar o controle do Report Viewer no seu web form:

Você consegue acessar essa página diretamente no GitHub do Reporting Services, através deste link.

Em resumo, primeiramente você precisa adicionar uma referência à dll do Report Viewer no cabeçalho do seu web form:

<%@ Register assembly="Microsoft.ReportViewer.WebForms, Version=14.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" namespace="Microsoft.Reporting.WebForms" tagprefix="rsweb" %>

Em seguida, fazemos exatamente como estamos acostumados com a versão anterior do Report Viewer. Ou seja, adicionamos um ScriptManager e um controle do Report Viewer logo em seguida:

        <div>
            <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
            <rsweb:ReportViewer ID="ReportViewer1" runat="server" SizeToReportContent="True" />
        </div>

Por fim, no code-behind do web form, nós carregamos o relatório:

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                ReportViewer1.LocalReport.ReportPath = Server.MapPath("~/Report1.rdlc");
                ReportViewer1.LocalReport.Refresh();
            }
        }

O destaque aqui fica para o novo controle desenvolvido pelo time do Reporting Services, escrito totalmente em HTML5:

E no MVC?

Não existe suporte nativo ao Report Viewer no MVC. Dito isso, uma opção que nós temos para contornar essa limitação é adicionarmos um web form no nosso projeto MVC e trabalharmos como se estivéssemos em um projeto Web Forms puro.

Obviamente essa opção não é das melhores, uma vez que perdemos toda a separação de camadas que temos no MVC, com models, views e controllers. Foi pensando nisso que algumas pessoas já desenvolveram bibliotecas que implementam uma certa gambiarra por trás dos panos que possibilita a utilização do Report Viewer no estilo de desenvolvimento do MVC.

A biblioteca que eu costumo utilizar para exibir relatórios do Report Viewer no MVC é a “ReportViewer for MVC“. Se você se interessar por esse assunto, eu já expliquei como utilizá-la neste outro artigo. O problema é que essa biblioteca não tem sido mais atualizada e, por consequência, muito provavelmente não será atualizada para a versão mais nova do Report Viewer.

O meu plano é copiar essa biblioteca para o GitHub e atualizá-la para que ela aponte para essa versão mais nova do Report Viewer. Com isso eu vou conseguir também corrigir uns bugs dessa biblioteca relacionados ao carregamento de sub-relatórios (que você pode encontrar mais informações aqui).

Assim que eu conseguir fazer isso, eu volto aqui e edito esse post com mais informações.

E no .NET Core?

Até o momento, a Microsoft ainda não se pronunciou sobre uma possível implementação do Report Viewer que seja compatível com o .NET Core. Se você tentar adicionar a referência do Report Viewer via NuGet em um projeto .NET Core, você receberá um erro:

Porém, temos várias pessoas da comunidade discutindo sobre esse assunto nessa issue no GitHub do ASP.NET. Vale a pena acompanhar essa issue para ficar por dentro das novidades.

Concluindo

Com o lançamento de cada edição nova do Visual Studio surge aquela dúvida cruel para desenvolvedores de relatórios: será que a Microsoft descontinuou o Report Viewer? Para a nossa sorte, esse dia ainda não chegou.

Apesar do susto que tivemos com o Report Viewer não aparecendo no Visual Studio 2017 ao tentarmos adicioná-lo seguindo os mesmos passos do Visual Studio 2015, não precisamos ficar preocupados. O que aconteceu é que a Microsoft mudou completamente o modelo de distribuição do Report Viewer a partir do Visual Studio 2017, disponibilizando a experiência de design por meio de uma extensão e o controle através do NuGet.

No artigo de hoje você aprendeu todos os passos necessários para desenhar relatórios do Report Viewer no Visual Studio 2017, bem como a utilização do seu novo controle em projetos Windows Forms e Web Forms, além de uma visão da utilização no ASP.NET MVC e .NET Core.

Agora é seguir essas instruções e continuar desenvolvendo os nossos relatórios para satisfazer os nossos clientes. Aproveita e dá uma olhada na categoria do Report Viewer aqui do site para encontrar diversos outros tutoriais sobre essa ferramenta!

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como utilizar o Report Viewer no Visual Studio 2017? appeared first on André Alves de Lima.

Salvando arquivos no banco de dados com C# e ADO.NET

$
0
0

Uma das primeiras coisas que aprendemos depois de dominarmos o básico de uma linguagem de programação é como fazemos para manipularmos bancos de dados com essa linguagem. Afinal de contas, temos que salvar os dados da nossa aplicação em algum lugar, não é mesmo? Esse lugar costuma ser um banco de dados.

Com o ADO.NET nós podemos executar sentenças SQL nos mais diversos bancos de dados. Basta termos instalado o provider específico do banco de dados e pronto, tudo deve funcionar sem problema algum. Uma vez que aprendemos a estrutura básica das classes do ADO.NET, nós já conseguimos fazer o CRUD das nossas aplicações e todo mundo fica contente. Isso é, até que chega a hora em que temos que salvar um arquivo no banco de dados. Aí bate aquele desespero: como é que passamos um arquivo na sentença SQL?

Pois bem, nós não passamos! Basta utilizarmos a funcionalidade de parâmetros do ADO.NET, passando a representação binária do arquivo em um parâmetro do comando. Com isso o ADO.NET vai se virar para mandar o arquivo para o banco de dados, sem que tenhamos que ficar fazendo malabarismos com a sentença SQL (para mais informações sobre parâmetros no ADO.NET, confira este artigo).

No vídeo de hoje eu vou mostrar para você como salvar (e recuperar) arquivos no banco de dados com C# e ADO.NET puro. Caso você esteja trabalhando com Entity Framework na sua aplicação, confira este outro artigo onde eu mostro o equivalente para o Entity Framework.

O banco de dados

A princípio, vamos começar com um banco de dados do SQL Server extremamente simples. Ele terá somente uma tabela onde armazenaremos os arquivos. Nessa tabela, teremos um ID auto incremento (chave primária), uma coluna para armazenarmos o nome do arquivo e outra coluna para armazenarmos a representação binária dele:

CREATE TABLE Arquivos (
	ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
	NomeArquivo VARCHAR(255) NULL,
	Arquivo IMAGE NULL)

Interface do usuário

No exemplo desse vídeo, iremos trabalhar com um projeto do tipo “Windows Forms Application“. Porém, os mesmos conceitos poderiam ser totalmente aplicados em outros tipos de projetos, como WPF, Web Forms, MVC, etc.

Na interface do usuário teremos um grid (“dgvArquivos” – com duas colunas “ID” e “NomeArquivo“) e dois botões (“btSalvar” e “btAbrir“):

Carregando o grid

A primeira coisa que temos que fazer é carregarmos o grid. Separaremos o código de abertura de conexão em um método próprio porque ele será utilizado em várias partes do exemplo. Além disso, dessa forma fica mais fácil substituir o banco de dados mais para frente:

        public FormArquivos()
        {
            InitializeComponent();

            CarregarGrid();
        }
        private void CarregarGrid()
        {
            try
            {
                using (var conn = AbrirConexao())
                {
                    conn.Open();
                    using (var comm = conn.CreateCommand())
                    {
                        comm.CommandText = "SELECT ID, NomeArquivo FROM Arquivos";
                        var reader = comm.ExecuteReader();
                        var table = new DataTable();
                        table.Load(reader);
                        dgvArquivos.DataSource = table;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        private IDbConnection AbrirConexao()
        {
            return new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=Testes;Integrated Security=True");
        }

É importante ressaltar que, em uma aplicação “de verdade“, o ideal seria separar o código em camadas distintas (acesso a dados, lógica de negócio, interface do usuário, etc). Além disso, a string de conexão deveria estar armazenada em um arquivo de configuração, e não digitada diretamente no código. Nesse exemplo nós estamos fazendo tudo dentro do formulário para não complicarmos muito o exemplo e focarmos no tema do artigo.

Salvando o arquivo

Para salvarmos o arquivo, temos que executar um comando de “INSERT” no banco. Porém, como é que fazemos para passar a representação binária do arquivo no comando “INSERT“? É aí que mora todo o segredo: nós não passamos! Ao trabalharmos com a funcionalidade de parâmetros do ADO.NET, nós só precisamos passar o array de bytes com o conteúdo do arquivo em um parâmetro do comando. Aí o próprio ADO.NET já se vira para mandar essa informação corretamente para o banco de dados, independente do banco que estivermos utilizando:

        private void btSalvar_Click(object sender, EventArgs e)
        {
            try
            {
                var arquivo = EscolherArquivo();

                if (!string.IsNullOrWhiteSpace(arquivo))
                {
                    SalvarArquivo(arquivo);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        private string EscolherArquivo()
        {
            var retorno = string.Empty;

            using (var dialog = new OpenFileDialog())
            {
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    retorno = dialog.FileName;
                }
            }

            return retorno;
        }
        private void SalvarArquivo(string arquivo)
        {
            using (var conn = AbrirConexao())
            {
                conn.Open();
                using (var comm = conn.CreateCommand())
                {
                    comm.CommandText = "INSERT INTO Arquivos (NomeArquivo, Arquivo) VALUES (@NomeArquivo, @Arquivo)";
                    ConfigurarParametrosSalvar(comm, arquivo);
                    comm.ExecuteNonQuery();
                    CarregarGrid();
                }
            }
        }
        private void ConfigurarParametrosSalvar(IDbCommand comm, string arquivo)
        {
            comm.Parameters.Add(new SqlParameter("NomeArquivo", Path.GetFileName(arquivo)));
            comm.Parameters.Add(new SqlParameter("Arquivo", File.ReadAllBytes(arquivo)));
        }

Abrindo o arquivo

Para recuperarmos o arquivo do banco de dados, nós criamos um comando com um “SELECT” passando como parâmetro o “ID” da linha selecionada no grid. Em seguida, nós chamamos o método “ExecuteScalar“, que retornará o array de bytes com o arquivo que estava armazenado no banco.

Uma vez tendo o array de bytes em mãos, você pode fazer o que quiser com ele. No exemplo do vídeo, nós salvamos o arquivo na pasta “TEMP” e utilizamos a classe “Process” para abrirmos o arquivo com a aplicação padrão da sua extensão:

        private void ConfigurarParametrosAbrir(IDbCommand comm)
        {
            comm.Parameters.Add(new SqlParameter("ID", dgvArquivos.CurrentRow.Cells["ID"].Value));
        }
        private void btAbrir_Click(object sender, EventArgs e)
        {
            try
            {
                using (var conn = AbrirConexao())
                {
                    conn.Open();
                    using (var comm = conn.CreateCommand())
                    {
                        comm.CommandText = "SELECT Arquivo FROM Arquivos WHERE (ID = @ID)";
                        ConfigurarParametrosAbrir(comm);
                        var bytes = comm.ExecuteScalar() as byte[];
                        if (bytes != null)
                        {
                            var nomeArquivo = dgvArquivos.CurrentRow.Cells["NomeArquivo"].Value.ToString();
                            var arquivoTemp = Path.GetTempFileName();
                            arquivoTemp = Path.ChangeExtension(arquivoTemp, Path.GetExtension(nomeArquivo));
                            File.WriteAllBytes(arquivoTemp, bytes);
                            Process.Start(arquivoTemp);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

Trocando o banco de dados

Como separamos o código da abertura do banco e criação dos seus parâmetros, nós podemos facilmente trocar de banco de dados alterando o tipo do objeto de conexão e parâmetro. Por exemplo, para utilizarmos um banco SQLite ao invés do SQL Server, nós poderíamos trocar de SqlConnection para SQLiteConnection e de SqlParameter para SQLiteParameter:

        private IDbConnection AbrirConexao()
        {
            return new SQLiteConnection(@"Data Source=db.db");
        }
        private void ConfigurarParametrosSalvar(IDbCommand comm, string arquivo)
        {
            comm.Parameters.Add(new SQLiteParameter("NomeArquivo", Path.GetFileName(arquivo)));
            comm.Parameters.Add(new SQLiteParameter("Arquivo", File.ReadAllBytes(arquivo)));
        }
        private void ConfigurarParametrosAbrir(IDbCommand comm)
        {
            comm.Parameters.Add(new SQLiteParameter("ID", dgvArquivos.CurrentRow.Cells["ID"].Value));
        }

É claro que esse código só funcionará se adicionarmos a referência às dlls do SQLite através do NuGet e se tivermos o arquivo “db.db” no diretório da aplicação (você pode baixa-lo aqui).

Pode ser que outros bancos de dados implementem a funcionalidade de parâmetros de maneira diferente. Por exemplo, o Microsoft Access não suporta parâmetros nomeados (teríamos que colocar um ponto de interrogação no lugar do nome dos parâmetros e passa-los na mesma ordem que foram definidos na sentença SQL).

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Salvando arquivos no banco de dados com C# e ADO.NET appeared first on André Alves de Lima.


Calculando o hash de arquivos para verificação de integridade com C# e VB.NET

$
0
0

Uns meses atrás eu estava respondendo algumas dúvidas no fórum da MSDN e acabei me deparando com uma questão muito interessante. Imagine que você tem uma Web API que disponibiliza um arquivo para download e você quer, do lado do cliente, saber se o download foi realmente efetuado com sucesso ou não. Como podemos fazer isso? Simples: nós temos que calcular o hash do arquivo original e comparar com o hash do arquivo baixado.

Mas, como é que podemos calcular o hash de arquivos no .NET? Não se preocupe, essa tarefa é bem simples. O .NET traz nativamente consigo a implementação dos principais algoritmos de cálculo de hash. No artigo de hoje eu vou mostrar para você como utilizar esses algoritmos para calcular o hash de arquivos com o intuito de verificar a sua integridade.

Hashes e integridade de arquivos

O cálculo de hashes de arquivos tem sido utilizado desde há muito tempo para checar se um arquivo está íntegro ou não. O mais famoso deles é o MD5, que você provavelmente já encontrou pela internet quando você estava prestes a baixar o instalador de uma aplicação.

A ideia é muito simples. Juntamente com o arquivo que será baixado, você disponibiliza também a representação em texto do hash daquele arquivo. Então, depois do download ser concluído, o usuário que baixou o arquivo pode utilizar algum utilitário para calcular o hash do arquivo baixado e, se ele bater com o hash disponibilizado na hora do download, isso significa que o arquivo foi baixado de forma íntegra. Além disso, essa metodologia serve para detectarmos que o arquivo não foi modificado maliciosamente no meio do caminho.

Uma falha no algoritmo do MD5 descoberta em 2012 fez com que esse tipo de hash fosse descartado para fins de criptografia e integridade. Apesar disso, ele ainda continua sendo muito utilizado. Por exemplo, se você algum dia precisar baixar o Apache Server, verá que eles ainda disponibilizam o hash MD5 para verificação da integridade dos arquivos:

O algoritmo que substituiu o MD5 nesse tipo de verificação foi o SHA (SHA-1 e SHA-256). Se observarmos, por exemplo, a página de downloads do Audacity (popular editor de áudio open source), veremos que eles disponibilizam o hash SHA-256 dos arquivos:

Independente do algoritmo de hash utilizado, a ideia é sempre a mesma. Em algum lugar junto com o download do arquivo nós temos o seu hash. Uma vez concluído o download, podemos então calcular o hash do arquivo baixado para verificarmos se ele bate com o disponibilizado pela fonte do download. E assim sabemos se o arquivo foi baixado integralmente ou não. Para mais informações sobre verificação de integridade de arquivos através de cálculos de hash, confira esta entrada na Wikipedia.

Existem diversas ferramentas que fazem o cálculo do hash de arquivos. Em ambientes Unix, a ferramenta mais conhecida é a hashdeep. Já no Windows, se você quiser uma ferramenta confiável, eu recomendo o Microsoft File Checksum Integrity Verifier. E se você não quiser instalar ferramenta nenhuma, você pode utilizar também o website HTML5 File Hash Online Calculator, que faz o cálculo dos hashes via web.

No exemplo deste artigo, vamos utilizar três arquivos de exemplo: imagem.jpg, imagemCopia.jpg (que, como o próprio nome já diz, é uma cópia da imagem.jpg) e imagemDiferente.gif. Se calcularmos os hashes MD5 e SHA-256 desses três arquivos com o HTML5 File Hash Online Calculator, o resultado será este:

Agora vamos ver como podemos fazer o cálculo dos hashes na nossa aplicação?

Calculando hash de arquivos

Para entendermos como funciona o cálculo de hashes de arquivos no .NET, vamos criar um projeto do tipo “Console Application“. Uma vez criado o projeto, vá até o diretório bin/debug no Windows Explorer e copie as três imagens mencionadas na seção anterior.

O cálculo do hash é muito simples. Basta criarmos uma instância do algoritmo de hash desejado e, em seguida, chamamos o método ComputeHash passando a stream do arquivo que queremos calcular o hash. Todos os algoritmos de hash ficam dentro do namespace “System.Security.Cryptography“.

Vamos criar um método que receberá o caminho do arquivo, fará o cálculo do hash MD5 e retornará um array de bytes como resultado:

        // C#
        private static byte[] CalcularHash(string arquivo)
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                using (var stream = System.IO.File.OpenRead(arquivo))
                {
                    return md5.ComputeHash(stream);
                }
            }
        }
    ' VB.NET
    Private Function CalcularHash(Arquivo As String) As Byte()
        Using Md5 = System.Security.Cryptography.MD5.Create()
            Using Stream = System.IO.File.OpenRead(Arquivo)
                Return Md5.ComputeHash(Stream)
            End Using
        End Using
    End Function

Ao chamarmos esse método passando o caminho do nosso arquivo, teremos um array de bytes do hash calculado. Porém, como é que nós fazemos para converter esse array de bytes em uma string no mesmo formato utilizado pelas ferramentas de cálculo de hash? Para isso, utilizamos a classe BitConverter.

Veja como fica o código para calcular o hash do arquivo “imagem.jpg“, converter para string e imprimir o resultado no console:

            // C#
            var imagem = "imagem.jpg";
            var hashImagem = CalcularHash(imagem);
            var hashImagemString = BitConverter.ToString(hashImagem).Replace("-", "").ToLower();
            Console.WriteLine(hashImagemString);
        ' VB.NET
        Dim Imagem = "imagem.jpg"
        Dim HashImagem = CalcularHash(Imagem)
        Dim HashImagemString = BitConverter.ToString(HashImagem).Replace("-", "").ToLower()
        Console.WriteLine(HashImagemString)

E o resultado é este:

Note que o resultado é idêntico ao que foi calculado com a ferramenta web que utilizamos na seção anterior.

Comparando o hash de dois arquivos

Agora que já sabemos como calcular o hash de arquivos, vamos ver como podemos comparar o hash de diferentes arquivos para verificarmos se eles são iguais ou não. Essa comparação pode ser feita considerando tanto os bytes do hash quanto a sua representação string. Vamos começar com a comparação dos arrays de bytes dos hashes.

Primeiramente, vamos calcular os hashes dos três arquivos, armazenando-os em variáveis diferentes. Em seguida, fazemos a comparação dos hashes. Mas, como é que comparamos arrays de bytes no .NET? Não podemos utilizar o operador “==” nem o método “Equals“, pois isso fará uma comparação das instâncias, que sempre retornará falso, mesmo se o conteúdo dos hashes for idêntico. E agora?

Pois bem, para compararmos o conteúdo de dois arrays no .NET, nós utilizamos o método “SequenceEqual” da classe “Enumerable“, passando os dois arrays. Veja só como fica o código nesse caso:

            // C#
            var imagem = "imagem.jpg";
            var imagemCopia = "imagemCopia.jpg";
            var imagemDiferente = "imagemDiferente.gif";

            var hashImagem = CalcularHash(imagem);
            var hashImagemCopia = CalcularHash(imagemCopia);
            var hashImagemDiferente = CalcularHash(imagemDiferente);

            Console.WriteLine("Comparação com byte array");

            // imagem == imagemCopia?
            if (Enumerable.SequenceEqual(hashImagem, hashImagemCopia))
                Console.WriteLine("imagem.jpg = imagemCopia.jpg");
            else
                Console.WriteLine("imagem.jpg != imagemCopia.jpg");

            // imagem == imagemDiferente?
            if (Enumerable.SequenceEqual(hashImagem, hashImagemDiferente))
                Console.WriteLine("imagem.jpg = imagemDiferente.gif");
            else
                Console.WriteLine("imagem.jpg != imagemDiferente.gif");
        ' VB.NET
        Dim Imagem = "imagem.jpg"
        Dim ImagemCopia = "imagemCopia.jpg"
        Dim ImagemDiferente = "imagemDiferente.gif"

        Dim HashImagem = CalcularHash(Imagem)
        Dim HashImagemCopia = CalcularHash(ImagemCopia)
        Dim HashImagemDiferente = CalcularHash(ImagemDiferente)

        Console.WriteLine("Comparação com byte array")

        ' imagem == imagemCopia?
        If Enumerable.SequenceEqual(HashImagem, HashImagemCopia) Then
            Console.WriteLine("imagem.jpg = imagemCopia.jpg")
        Else
            Console.WriteLine("imagem.jpg != imagemCopia.jpg")
        End If

        ' imagem == imagemDiferente?
        If Enumerable.SequenceEqual(HashImagem, HashImagemDiferente) Then
            Console.WriteLine("imagem.jpg = imagemDiferente.gif")
        Else
            Console.WriteLine("imagem.jpg != imagemDiferente.gif")
        End If

Como mencionado anteriormente, uma outra maneira que podemos utilizar para compararmos os hashes é através da sua representação em string. Após termos convertido os hashes em string, fazemos a comparação através do método “string.Compare“:

            // C#
            Console.WriteLine("Comparação com string");

            var hashImagemString = BitConverter.ToString(hashImagem).Replace("-", "").ToLower();
            var hashImagemCopiaString = BitConverter.ToString(hashImagemCopia).Replace("-", "").ToLower();
            var hashImagemDiferenteString = BitConverter.ToString(hashImagemDiferente).Replace("-", "").ToLower();

            // imagem == imagemCopia?
            if (string.Compare(hashImagemString, hashImagemCopiaString, StringComparison.InvariantCulture) == 0)
                Console.WriteLine("imagem.jpg = imagemCopia.jpg");
            else
                Console.WriteLine("imagem.jpg != imagemCopia.jpg");

            // imagem == imagemDiferente?
            if (string.Compare(hashImagemString, hashImagemDiferenteString, StringComparison.InvariantCulture) == 0)
                Console.WriteLine("imagem.jpg = imagemDiferente.gif");
            else
                Console.WriteLine("imagem.jpg != imagemDiferente.gif");
        ' VB.NET
        Console.WriteLine("Comparação com string")

        Dim HashImagemString = BitConverter.ToString(HashImagem).Replace("-", "").ToLower()
        Dim HashImagemCopiaString = BitConverter.ToString(HashImagemCopia).Replace("-", "").ToLower()
        Dim HashImagemDiferenteString = BitConverter.ToString(HashImagemDiferente).Replace("-", "").ToLower()

        ' imagem == imagemCopia?
        If String.Compare(HashImagemString, HashImagemCopiaString, StringComparison.InvariantCulture) = 0 Then
            Console.WriteLine("imagem.jpg = imagemCopia.jpg")
        Else
            Console.WriteLine("imagem.jpg != imagemCopia.jpg")
        End If

        ' imagem == imagemDiferente?
        If String.Compare(HashImagemString, HashImagemDiferenteString, StringComparison.InvariantCulture) = 0 Then
            Console.WriteLine("imagem.jpg = imagemDiferente.gif")
        Else
            Console.WriteLine("imagem.jpg != imagemDiferente.gif")
        End If

Ao executarmos o projeto, teremos o resultado esperado (imagem.jpg é igual a imagemCopia.jpg e imagem.jpg é diferente de imagemDiferente.gif):

E com isso você conferiu como fazer o cálculo e comparação do hash de arquivos com C# e VB.NET. Essa metodologia pode ser utilizada para, por exemplo, validar o download de arquivos através de uma API na sua aplicação.

Trocando o algoritmo de hash

Uma última alteração que podemos fazer nesse exemplo é trocarmos o algoritmo de hash de MD5 para SHA-256. Como mencionado anteriormente, apesar de ainda ser amplamente utilizado, o algoritmo MD5 não é mais criptograficamente confiável e deve ser substituído pelo SHA.

Para trocarmos o algoritmo de hash, basta fazermos uma alteração na hora de criarmos o hash, trocando o algoritmo de MD5 para SHA-256. Por exemplo, para calcularmos o hash com o algoritmo SHA-256 ao invés do MD5, o código ficaria assim:

        // C#
        private static byte[] CalcularHash(string arquivo)
        {
            using (var algoritmoHash = System.Security.Cryptography.SHA256.Create())
            {
                using (var stream = System.IO.File.OpenRead(arquivo))
                {
                    return algoritmoHash.ComputeHash(stream);
                }
            }
        }
    ' VB.NET
    Private Function CalcularHash(Arquivo As String) As Byte()
        Using AlgoritmoHash = System.Security.Cryptography.SHA256.Create()
            Using Stream = System.IO.File.OpenRead(Arquivo)
                Return AlgoritmoHash.ComputeHash(Stream)
            End Using
        End Using
    End Function

Se quisermos ir um passo além, podemos transformar fazer uma alteração de forma que o método fique genérico:

        // C#
        private static byte[] CalcularHash<T>(string arquivo) where T: System.Security.Cryptography.HashAlgorithm
        {
            using (var algoritmoHash = System.Security.Cryptography.HashAlgorithm.Create(typeof(T).ToString()))
            {
                using (var stream = System.IO.File.OpenRead(arquivo))
                {
                    return algoritmoHash.ComputeHash(stream);
                }
            }
        }
    ' VB.NET
    Private Function CalcularHash(Of T As System.Security.Cryptography.HashAlgorithm)(Arquivo As String) As Byte()
        Using AlgoritmoHash = System.Security.Cryptography.HashAlgorithm.Create(GetType(T).ToString())
            Using Stream = System.IO.File.OpenRead(Arquivo)
                Return AlgoritmoHash.ComputeHash(Stream)
            End Using
        End Using
    End Function

Com essa alteração, a chamada para esse método genérico deve passar o algoritmo de hash desejado. Por exemplo, se quiséssemos utilizar o algoritmo SHA-256, a chamada ficaria assim:

            // C#
            var hashImagem = CalcularHash<System.Security.Cryptography.SHA256>(imagem);
            var hashImagemCopia = CalcularHash<System.Security.Cryptography.SHA256>(imagemCopia);
            var hashImagemDiferente = CalcularHash<System.Security.Cryptography.SHA256>(imagemDiferente);
        ' VB.NET
        Dim HashImagem = CalcularHash(Of System.Security.Cryptography.SHA256)(Imagem)
        Dim HashImagemCopia = CalcularHash(Of System.Security.Cryptography.SHA256)(ImagemCopia)
        Dim HashImagemDiferente = CalcularHash(Of System.Security.Cryptography.SHA256)(ImagemDiferente)

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Conclusão

Implementar o cálculo de hashes de arquivos no .NET é uma tarefa muito simples, uma vez que já temos nativamente os principais algoritmos de hash implementados diretamente no .NET Framework. No artigo de hoje você aprendeu a calcular o hash de um arquivo utilizando o algoritmo MD5 e SHA-256. Essa implementação pode ser utilizada para comparar o hash de dois arquivos diferentes, detectando se eles são exatamente o mesmo arquivo ou não.

Como você pode conferir no artigo, a comparação dos hashes pode ser feita byte a byte através do método Enumerable.SequenceEqual. Outra opção é fazermos a comparação da representação em string do hash, que pode ser feita através do método string.Compare.

Por fim, você conferiu como trocar o algoritmo de cálculo do hash, bem como a transformação do método em genérico, possibilitando a definição do algoritmo desejado no momento da sua chamada.

Essa implementação serve principalmente para verificarmos se o download de um arquivo foi feito com sucesso e para garantirmos que ninguém alterou o arquivo durante a sua transmissão. Você já precisou implementar algo parecido com isso? Como é que você acabou resolvendo? Deixe os seus comentários logo abaixo.

Até a próxima!

André Lima

Image by Pixabay used under Creative Commons
https://pixabay.com/en/binary-one-null-monitor-social-503590/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Calculando o hash de arquivos para verificação de integridade com C# e VB.NET appeared first on André Alves de Lima.

Trabalhando com o Report Viewer no MVC

$
0
0

O Report Viewer só tem suporte nativo ao Windows Forms e Web Forms, ou seja, quem trabalha com outras plataformas (como WPF ou MVC) precisa fazer alguns ajustes para poder utilizá-lo nos seus projetos. No caso do WPF, é só adicionar um WindowsFormsHost com o controle do Report Viewer dentro, como eu mostrei neste artigo. Já no ASP.NET MVC, ou adicionamos um WebForm (não recomendado) ou utilizamos uma biblioteca que possibilita a criação dos relatórios no “estilo MVC” de desenvolver.

Eu já escrevi um artigo mostrando como utilizar o Report Viewer no ASP.NET MVC, onde eu mostro as duas modalidades mencionadas acima. Devido à popularidade desse artigo, eu resolvi fazer uma versão em vídeo explorando um pouco mais a ideia da utilização da biblioteca “ReportViewerForMVC“. No vídeo eu mostro também um problema que temos ao utilizá-la com sub-relatórios. Confere aí:

Biblioteca ReportViewerForMVC

A biblioteca que podemos utilizar para trabalharmos com o Report Viewer no MVC (sem termos que adicionar um WebForm manualmente) é a “ReportViewerForMVC“. Como você pode reparar no site da biblioteca no CodePlex, ela não tem sido atualizada há um bom tempo. Porém, ela funciona bem com a versão 11 do Report Viewer, então ela acaba dando conta do recado.

Para adicionarmos uma referência a essa biblioteca no nosso projeto, nós utilizamos o NuGet. Basta procurarmos por “ReportViewerForMVC” na janela de gerenciamento de pacotes do NuGet ou digitarmos “Install-Package ReportViewerForMVC” no Package Manager Console:

Essa biblioteca adicionará a referência às dlls “Microsoft.ReportViewer.WebForms” e “ReportViewerForMvc“.

Outras referências necessárias

Em computadores que tenham a runtime do Report Viewer instalada, somente a referência à biblioteca já é o suficiente para que o nosso projeto funcione. Porém, para o caso em que a versão correta da runtime do Report Viewer não estiver instalada, algumas outras referências serão necessárias.

Por segurança, eu recomendo que você adicione pelo NuGet as referências às bibliotecas “MicrosoftReportViewerWebForms” e “Microsoft.SqlServer.Types” (versão 11.0.0):

O código do Controller

Uma vez adicionadas todas as referências necessárias, vamos partir para a criação do código do nosso Controller. Adicione um novo Controller vazio do MVC na pasta “Controllers” para que possamos fazer essa implementação.

A ideia é muito simples. Primeiramente nós temos que carregar os dados da nossa fonte de dados e armazená-los em um DataSet ou coleção. Em seguida, nós criamos uma instância da classe “Microsoft.Reporting.WebForms.ReportViewer” e configuramos algumas propriedades (como caminho do relatório e sua fonte de dados). Por fim, nós retornamos essa instância de “ReportViewer” através da ViewBag.

Veja só este exemplo:

        // C#
        public ActionResult Index()
        {
            var ds = ObterDados();

            var viewer = new Microsoft.Reporting.WebForms.ReportViewer();
            viewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;
            viewer.LocalReport.ReportPath = Request.MapPath(Request.ApplicationPath) + @"CaminhoDoSeuRelatorio.rdlc";
            viewer.LocalReport.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoRelatorio", (System.Data.DataTable)ds.NomeDaDataTable));

            ViewBag.ReportViewer = viewer;

            return View();
        }
            ' VB.NET
            Dim Ds = ObterDados()

            Dim Viewer = New Microsoft.Reporting.WebForms.ReportViewer()
            Viewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local
            Viewer.LocalReport.ReportPath = Request.MapPath(Request.ApplicationPath) + "CaminhoDoSeuRelatorio.rdlc"
            Viewer.LocalReport.DataSources.Add(New Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoRelatorio", DirectCast(Ds.NomeDaDataTable, System.Data.DataTable)))

            ViewBag.ReportViewer = Viewer

            Return View()

E como fica a View?

Com o Controller em mãos, podemos partir para a criação da nossa View. O código dela é muito simples. Primeiramente nós temos que adicionar uma referência à biblioteca “ReportViewerForMvc” (com uma cláusula “using” no início da View). Adicionada essa referência, nós teremos alguns métodos extras à nossa disposição, como o “Html.ReportViewer“. Ao chamarmos esse método passando a instância do ReportViewer da ViewBag, o relatório será renderizado naquele local:

<!-- C# -->
@using ReportViewerForMvc;

@{
    ViewBag.Title = "Index";
}

<h2>Relatório</h2>

@Html.ReportViewer(ViewBag.ReportViewer as Microsoft.Reporting.WebForms.ReportViewer)
<!-- VB.NET -->
@Imports ReportViewerForMvc

@Code
    ViewData("Title") = "Index"
End Code

<h2>Index</h2>

@Html.ReportViewer(DirectCast(ViewBag.ReportViewer, Microsoft.Reporting.WebForms.ReportViewer))

Execute o projeto e veja o resultado:

Ajuste automático do tamanho do controle

Se observarmos o resultado apresentado na imagem acima, veremos que o controle está com um tamanho fixo (o tamanho padrão). No controle do Report Viewer web, temos a possibilidade de configurarmos um tamanho dinâmico, ou seja, o controle será automaticamente redimensionado dependendo do tamanho do relatório.

Para atingirmos esse resultado, temos que alterar a propriedade “SizeToReportContent” para verdadeiro, além de configurarmos o Width e Height de forma que eles ocupem 100% da área disponível na View. Coloque este código antes de retornar o controle para a ViewBag e veja a diferença:

            // C#
            viewer.SizeToReportContent = true;
            viewer.Width = System.Web.UI.WebControls.Unit.Percentage(100);
            viewer.Height = System.Web.UI.WebControls.Unit.Percentage(100);
            ' VB.NET
            Viewer.SizeToReportContent = True
            Viewer.Width = System.Web.UI.WebControls.Unit.Percentage(100)
            Viewer.Height = System.Web.UI.WebControls.Unit.Percentage(100)

Problemas com sub-relatórios

A biblioteca Report Viewer for MVC funciona muito bem até o momento em que tivermos que trabalhar com sub-relatórios. Para exibirmos um relatório que tenha sub-relatório, o código do Controller ficaria assim:

        // C#
        DataSet ds;

        public ActionResult Index()
        {
            ds = ObterDados();

            var viewer = new Microsoft.Reporting.WebForms.ReportViewer();
            viewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;
            viewer.LocalReport.ReportPath = Request.MapPath(Request.ApplicationPath) + @"CaminhoDoSeuRelatorio.rdlc";
            viewer.LocalReport.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoRelatorio", (System.Data.DataTable)ds.NomeDaDataTable));
            viewer.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;

            ViewBag.ReportViewer = viewer;

            return View();
        }

        private void LocalReport_SubreportProcessing(object sender, Microsoft.Reporting.WebForms.SubreportProcessingEventArgs e)
        {
            e.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoSubRelatorio", (System.Data.DataTable)ds.NomeDaDataTableDetalhe));
        }
        ' VB.NET
        Private Ds As DataSet

        Public Function Index() As ActionResult
            Ds = ObterDados()

            Dim Viewer = New Microsoft.Reporting.WebForms.ReportViewer()
            Viewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local
            Viewer.LocalReport.ReportPath = Request.MapPath(Request.ApplicationPath) + "CaminhoDoSeuRelatorio.rdlc"
            Viewer.LocalReport.DataSources.Add(New Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoRelatorio", DirectCast(Ds.NomeDaDataTable, System.Data.DataTable)))
            AddHandler Viewer.LocalReport.SubreportProcessing, AddressOf LocalReport_SubreportProcessing

            Viewer.SizeToReportContent = True
            Viewer.Width = System.Web.UI.WebControls.Unit.Percentage(100)
            Viewer.Height = System.Web.UI.WebControls.Unit.Percentage(100)

            ViewBag.ReportViewer = Viewer

            Return View()
        End Function

        Private Sub LocalReport_SubreportProcessing(sender As Object, e As Microsoft.Reporting.WebForms.SubreportProcessingEventArgs)
            e.DataSources.Add(New Microsoft.Reporting.WebForms.ReportDataSource("NomeDoDataSetNoSubRelatorio", DirectCast(Ds.NomeDaDataTableDetalhe, System.Data.DataTable)))
        End Sub

Porém, se testarmos esse cenário com um breakpoint no evento “SubReportProcessing“, veremos que ele não será disparado. Isso acontece porque a biblioteca tem um bug que evita o disparo desse evento. Existe até uma “issue” criada no site da biblioteca falando sobre esse problema.

No link acima você pode notar que eu respondi aquela “issue” com a correção necessária para que os sub-relatórios funcionem. Eu fiz essa correção, recompilei a biblioteca e você pode baixar a versão corrigida aqui. Substitua essa dll na pasta “packages” e na pasta “Bin” do seu projeto para que os sub-relatórios funcionem corretamente. E acompanhe este repositório no meu GitHub, onde eu pretendo dar manutenção nessa biblioteca e disponibilizar atualizações.

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

The post Trabalhando com o Report Viewer no MVC appeared first on André Alves de Lima.

Utilizando a API do Google Drive no C# e VB.NET

$
0
0

Hoje em dia é muito difícil encontrar algum aplicativo que não salve algum tipo de informação na nuvem. Com serviços como Dropbox, Google Drive e OneDrive ficando cada vez mais baratos e acessíveis, a nossa vida acaba sendo muito facilitada caso precisemos subir um arquivo para a nuvem a partir da nossa aplicação.

Para facilitar ainda mais a nossa vida, esses serviços sempre contam com APIs que podem ser acessadas nas mais diversas plataformas de desenvolvimento. O Google Drive, por exemplo, conta com uma API bem extensa, possibilitando a manipulação completa dos seus serviços a partir de aplicações de terceiros.

No artigo de hoje eu vou mostrar para você como fazer todo tipo de interação com o Google Drive no C# e VB.NET. Primeiramente nós veremos como habilitar a API do Google Drive na nossa conta e como gerar o arquivo de credenciais que é necessário para o seu acesso. Em seguida, nós aprenderemos todas as operações que podemos executar com a API: listagem, criação, exclusão e download de arquivos.

A API do Google Drive

O Google Drive conta com uma extensa API que possibilita a manipulação do conteúdo das suas contas em projetos que utilizem qualquer linguagem de programação com suporte a serviços REST. No momento da escrita deste artigo, a API está na sua terceira versão.

Se procurarmos na Internet por exemplos da manipulação do Google Drive com .NET, encontraremos uma boa quantidade de artigos que utilizam a segunda versão da API (como essa excelente série que eu utilizei de inspiração para escrever este artigo). Porém, se quisermos utilizar a versão mais nova da API (que já foi lançada há um bom tempo, no final de 2015), acabamos ficando na mão. Existem diversas diferenças entre a versão 2 e a versão 3 da API, que estão todas documentadas aqui.

A própria página da API conta com alguns exemplos em C# (como, por exemplo, este quickstart). Porém, esses exemplos são muito simples e com pouca explicação do que está sendo feito em cada etapa. Além disso, eles estão todos quebrados em funcionalidades, ou seja, nós não conseguimos encontrar um exemplo que aborde todas as principais interações que podemos fazer com o Google Drive através da nossa aplicação. Neste artigo eu vou juntar todas essas informações em um só lugar e vou explicar para você o que está sendo feito em cada etapa.

Gerando o arquivo com as credenciais

Antes de tentarmos desenvolver qualquer funcionalidade na nossa aplicação utilizando a API do Google Drive, nós precisamos habilitar o suporte à API na nossa conta e temos que gerar também um arquivo com as nossas credenciais. Se você ainda não tiver seguido esses passos com a sua conta, este é o link para o assistente.

O primeiro passo é muito simples. Nós só temos que concordar com os termos do serviço (obviamente, nós não temos outra escolha):

Ao concordarmos com os termos, a API será ativada na nossa conta e nós conseguiremos continuar com o próximo passo, que é a criação do nosso arquivo de credenciais:

Na próxima etapa, escolha a API do Google Drive e a opção “Other UI“, uma vez que nós criaremos uma aplicação console Windows:

Em seguida, nós temos a possibilidade de fazer uma configuração muito interessante para o arquivo de credenciais, que basicamente responde à seguinte pergunta: esse arquivo de credenciais dará acesso a todos os arquivos e pastas do Google Drive ou somente a arquivos e pastas gerados por essa aplicação específica? Escolha a opção que melhor se enquadra no cenário da sua aplicação e prossiga para a próxima etapa. No meu caso, eu escolhi a opção “User data“, que dá acesso a todos os arquivos e pastas da conta:

Agora chegou a hora de escolher um nome para o Client ID e produto. Além disso, caso a sua conta esteja conectada com outras contas do Google, você terá que selecionar o e-mail da conta que você quer utilizar:

Pronto! Finalmente chegamos na última etapa do assistente, onde conseguimos fazer o download do arquivo com as credenciais. Clique no botão “Download” e salve o arquivo “client_id.json” em um local que seja acessível pela aplicação:

Adicionando a referência da API no projeto

Para a nossa sorte, a própria Google implementou um conjunto de classes “helpers” em .NET que fazem o encapsulamento das chamadas da API de maneira mais amigável. Se essas classes não existissem, nós teríamos que fazer as chamadas manualmente através de requisições HTTP.

Esse conjunto de classes está publicado no NuGet com o nome “Google.Apis.Drive.v3“, ou seja, para adicionar a referência no nosso projeto basta procurarmos por esse termo na janela “NuGet Package Manager” ou podemos executar também o comando “Install-Package Google.Apis.Drive.v3” no “Package Manager Console“:

Para facilitar a nossa vida, os exemplos desse artigo serão construídos em um projeto do tipo “Console Application“. Porém, nada impede que você aplique os mesmos conceitos em outros tipos de projeto, como Windows Forms, WPF ou ASP.NET.

Autenticando e conectando no serviço

A primeira coisa que temos que fazer antes de acessarmos a API do Google Drive é nos autenticarmos utilizando aquele arquivo que geramos nas etapas anteriores. Dito isso, vamos criar um método que retornará uma instância da classe “UserCredential” da API do Google Drive. Esse método fará toda a “mágica” da autenticação sempre que precisarmos nos conectar na API.

Dentro desse método nós precisaremos informar três parâmetros importantes: uma stream apontando para o arquivo com as credenciais (“client_id.json” que geramos anteriormente), o escopo das permissões que serão dadas para execução de comandos na API e o caminho para uma pasta onde será armazenado o token da autenticação. Esse token servirá como um “cache” das nossas credenciais, evitando que tenhamos que digitar o nosso usuário e senha novamente todas as vezes que o aplicativo for executado.

Veja como fica o código desse método:

        // C#
        private static Google.Apis.Auth.OAuth2.UserCredential Autenticar()
        {
            Google.Apis.Auth.OAuth2.UserCredential credenciais;

            using (var stream = new System.IO.FileStream("client_id.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                var diretorioAtual = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                var diretorioCredenciais = System.IO.Path.Combine(diretorioAtual, "credential");

                credenciais = Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.AuthorizeAsync(
                    Google.Apis.Auth.OAuth2.GoogleClientSecrets.Load(stream).Secrets,
                    new[] { Google.Apis.Drive.v3.DriveService.Scope.DriveReadonly },
                    "user",
                    System.Threading.CancellationToken.None,
                    new Google.Apis.Util.Store.FileDataStore(diretorioCredenciais, true)).Result;
            }

            return credenciais;
        }
    ' VB.NET
    Private Function Autenticar() As Google.Apis.Auth.OAuth2.UserCredential
        Dim Credenciais As Google.Apis.Auth.OAuth2.UserCredential

        Using Stream = New System.IO.FileStream("client_id.json", System.IO.FileMode.Open, System.IO.FileAccess.Read)
            Dim DiretorioAtual = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
            Dim DiretorioCredenciais = System.IO.Path.Combine(DiretorioAtual, "credential")

            Credenciais = Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker.AuthorizeAsync(
                Google.Apis.Auth.OAuth2.GoogleClientSecrets.Load(Stream).Secrets,
                New String() {Google.Apis.Drive.v3.DriveService.Scope.DriveReadonly},
                "user",
                System.Threading.CancellationToken.None,
                New Google.Apis.Util.Store.FileDataStore(DiretorioCredenciais, True)).Result
        End Using

        Return Credenciais
    End Function

À primeira vista esse código parece bem complicado. Porém, ele é mais simples do que parece. Primeiramente nós abrimos um FileStream somente leitura apontando para o arquivo “client_id.json“, que você terá que copiar para o diretório “bin/debug” do projeto. Em seguida, nós criamos uma variável que armazenará o caminho completo para um subdiretório da aplicação, chamado de “credential“. Esse é o diretório onde o token com as credenciais será armazenado. Por fim, nós fazemos a chamada ao método “GoogleWebAuthorizationBroker.AuthorizeAsync“, que deverá receber esses e outros parâmetros.

Note que a princípio estamos pedindo somente a permissão de leitura para a API (DriveReadonly). Ou seja, com essas permissões nós só conseguiremos listar as informações do nosso Google Drive. Mais para a frente nós veremos como essa permissão se comportará ao tentarmos escrever alguma alteração nos objetos da nossa conta.

Os parâmetros “user” e “CancellationToken.None” eu sinceramente não sei para que servem. Eu copiei essa chamada do exemplo oficial da própria Google e não consegui entender a utilidade desses parâmetros. Se alguém souber ou descobrir, avisa nos comentários.

Enfim, uma vez autenticado no serviço, nós temos que iniciar uma conexão. Para isso, nós temos que criar uma nova instância da classe “DriveService” passando um “Initializer” com as nossas credenciais. Entendeu? Pois é, eu sei que é complicado, então vamos ao código:

        // C#
        private static Google.Apis.Drive.v3.DriveService AbrirServico(Google.Apis.Auth.OAuth2.UserCredential credenciais)
        {
            return new Google.Apis.Drive.v3.DriveService(new Google.Apis.Services.BaseClientService.Initializer()
            {
                HttpClientInitializer = credenciais
            });
        }
    ' VB.NET
    Private Function AbrirServico(Credenciais As Google.Apis.Auth.OAuth2.UserCredential) As Google.Apis.Drive.v3.DriveService
        Return New Google.Apis.Drive.v3.DriveService(New Google.Apis.Services.BaseClientService.Initializer() With {
            .HttpClientInitializer = Credenciais
        })
    End Function

Bem mais simples do que quando eu tentei explicar, não é mesmo?

OK. Agora nós já temos o código que faz a autenticação e o código que abre uma conexão com a API do Google Drive. O próximo passo é implementar os métodos que farão a interação com o conteúdo do nosso repositório. Porém, qualquer interação que queiramos fazer com a API, nós precisaremos de uma instância da classe “DriveService” pronta para uso. Dessa forma, no local onde faremos as chamadas à API (por exemplo, o método “main” de um projeto console), nós criaremos um bloco “using” que fará a abertura do serviço:

            // C#
            var credenciais = Autenticar();

            using (var servico = AbrirServico(credenciais))
            {
            }
        ' VB.NET
        Dim Credenciais = Autenticar()

        Using Servico = AbrirServico(Credenciais)

        End Using

Como a classe “DriveService” implementa a interface “IDisposable“, ao colocarmos o código dentro de um bloco “using“, a conexão com a API será devidamente fechada e descartada assim que o bloco “using” for terminado, evitando que nós tenhamos que implementar código extra para fecharmos a conexão. Todo o código que fará a manipulação do nosso Google Drive deverá ser implementado dentro desse bloco “using“.

Listagem de arquivos

Com o serviço inicializado, vamos partir para a interação com os arquivos e pastas do nosso Google Drive. Primeiramente, vamos listar todo o conteúdo do nosso repositório?

Todos os métodos de interação do Google Drive estão implementados na propriedade “Files” da classe “DriveService“. Por exemplo, para criarmos um request que fará a listagem dos arquivos, nós teremos que chamar o método “servico.Files.List“. Essa mesma ideia servirá como base para os outros tipos de operações nas próximas seções deste artigo. Vamos ver como é que fica o código, dessa forma fica mais fácil de explicar:

        // C#
        private static void ListarArquivos(Google.Apis.Drive.v3.DriveService servico)
        {
            var request = servico.Files.List();
            request.Fields = "files(id, name)";
            var resultado = request.Execute();
            var arquivos = resultado.Files;

            if (arquivos != null && arquivos.Any())
            {
                foreach (var arquivo in arquivos)
                {
                    Console.WriteLine(arquivo.Name);
                }
            }
        }
    ' VB.NET
    Private Sub ListarArquivos(Servico As Google.Apis.Drive.v3.DriveService)
        Dim Request = Servico.Files.List()
        Request.Fields = "files(id, name)"
        Dim Resultado = Request.Execute()
        Dim Arquivos = Resultado.Files

        If Arquivos IsNot Nothing AndAlso Arquivos.Any() Then
            For Each Arquivo In Arquivos
                Console.WriteLine(Arquivo.Name)
            Next
        End If
    End Sub

Como você pode ver, primeiro nós criamos um request para o método de listagem de arquivos (“Files.List“). Em seguida, nós especificamos quais são os campos dos arquivos e pastas que devem ser retornados pelo request. Essa é uma grande diferença entre a segunda e a terceira versões da API. Pelo que eu entendi, na segunda versão, todos os campos eram retornados automaticamente. Já na terceira versão, nós precisamos especificar quais campos devem ser retornados, senão a API só retornará o ID dos arquivos e pastas.

Por fim, nós executamos o request e percorremos o resultado (que é uma lista de arquivos e pastas que estão armazenados no nosso Google Drive), imprimindo o nome dos itens. Vamos chamar esse método dentro do nosso bloco “using” para vermos o resultado:

                // C#
                Console.WriteLine("Listagem");
                ListarArquivos(servico);
                Console.WriteLine("Fim Listagem");
                Console.ReadLine();
            ' VB.NET
            Console.WriteLine("Listagem")
            ListarArquivos(Servico)
            Console.WriteLine("Fim Listagem")
            Console.ReadLine()

Aparentemente tudo funcionou perfeitamente, não é mesmo? Porém, essa metodologia só retornará os 100 primeiros itens que forem encontrados no nosso Google Drive. Se quisermos que mais itens sejam retornados, temos que alterar a propriedade “PageSize” do nosso request antes de executá-lo. Por exemplo:

            // C#
            request.PageSize = 1000;
            var resultado = request.Execute();
        ' VB.NET
        Request.PageSize = 1000
        Dim Resultado = Request.Execute()

O valor padrão dessa propriedade é “100“, e o máximo é “1000“. Se tentarmos utilizar um valor maior do que 1000, receberemos o seguinte erro:

Outro detalhe importante é que, por padrão, a listagem de arquivos também retorna todos os arquivos que estiverem na lixeira. Se esse não for o comportamento que você está querendo, basta configurar um filtro para o request, através da propriedade “Q“. Exemplo:

// C#
request.Q = "trashed=false";
' VB.NET
Request.Q = "trashed=false"

Listagem paginada

Como vimos na seção anterior, o máximo de arquivos e pastas que serão listados pela API do Google Drive é 1000. Mas, o que fazemos se quisermos listar mais do que 1000 itens? Nesse caso, a saída é fazer uma listagem paginada, ou seja, retornarmos “X” itens de cada vez.

Para fazermos isso, nós temos que retornar também o “nextPageToken” na hora de listarmos os itens. Com isso, sempre que executarmos o request, caso ainda existam itens para serem retornados, o resultado sempre contará com um token que possibilitará a listagem da próxima página. Veja como é que fica o código de um novo método para listagem de arquivos que receberá também a quantidade de itens por página:

        // C#
        private static void ListarArquivosPaginados(Google.Apis.Drive.v3.DriveService servico, int arquivosPorPagina)
        {
            var request = servico.Files.List();
            request.Fields = "nextPageToken, files(id, name)";
            //request.Q = "trashed=false";
            // Default 100, máximo 1000.
            request.PageSize = arquivosPorPagina;
            var resultado = request.Execute();
            var arquivos = resultado.Files;

            while (arquivos != null && arquivos.Any())
            {
                foreach (var arquivo in arquivos)
                {
                    Console.WriteLine(arquivo.Name);
                }

                if (resultado.NextPageToken != null)
                {
                    Console.WriteLine("Digite ENTER para ir para a próxima página");
                    Console.ReadLine();
                    request.PageToken = resultado.NextPageToken;
                    resultado = request.Execute();
                    arquivos = resultado.Files;
                }
                else
                {
                    arquivos = null;
                }
            }
        }
    ' VB.NET
    Private Sub ListarArquivosPaginados(Servico As Google.Apis.Drive.v3.DriveService, ArquivosPorPagina As Integer)
        Dim Request = Servico.Files.List()
        Request.Fields = "nextPageToken, files(id, name)"
        'Request.Q = "trashed=false"
        ' Default 100, máximo 1000.
        Request.PageSize = ArquivosPorPagina
        Dim Resultado = Request.Execute()
        Dim Arquivos = Resultado.Files

        While Arquivos IsNot Nothing AndAlso Arquivos.Any()
            For Each arquivo In Arquivos
                Console.WriteLine(arquivo.Name)
            Next

            If Resultado.NextPageToken IsNot Nothing Then
                Console.WriteLine("Digite ENTER para ir para a próxima página")
                Console.ReadLine()
                Request.PageToken = Resultado.NextPageToken
                Resultado = Request.Execute()
                Arquivos = Resultado.Files
            Else
                Arquivos = Nothing
            End If
        End While
    End Sub

No bloco “using“, nós podemos chamar esse novo método, passando a quantidade de itens por página. Por exemplo, o código para listarmos os itens de 10 em 10 ficaria assim:

                // C#
                Console.Clear();
                Console.WriteLine("Listagem paginada");
                ListarArquivosPaginados(servico, 10);
                Console.WriteLine("Fim Listagem paginada");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Listagem paginada")
            ListarArquivosPaginados(Servico, 10)
            Console.WriteLine("Fim Listagem paginada")
            Console.ReadLine()

Veja só o resultado:

Criação de diretórios

Agora que já conseguimos listar os itens do nosso Google Drive, vamos conferir como é que podemos fazer para criarmos um novo diretório. Estranhamente, tudo no Google Drive são “itens“. Ou seja, arquivos e diretórios são ambos tratados como “File” (arquivo). A única diferença é que diretórios possuem um “mime type” específico, que fará com que o Google Drive entenda que ele é um diretório, e não um arquivo.

A criação do diretório é bem simples. Basta criarmos uma nova instância da classe “File” e configurarmos um nome e o “mime type” indicando que ele é um diretório. Em seguida, criamos e executamos um request utilizando o método “Files.Create” passando a instância de “File” que instanciamos anteriormente:

        // C#
        private static void CriarDiretorio(Google.Apis.Drive.v3.DriveService servico, string nomeDiretorio)
        {
            var diretorio = new Google.Apis.Drive.v3.Data.File();
            diretorio.Name = nomeDiretorio;
            diretorio.MimeType = "application/vnd.google-apps.folder";
            var request = servico.Files.Create(diretorio);
            request.Execute();
        }
    ' VB.NET
    Private Sub CriarDiretorio(Servico As Google.Apis.Drive.v3.DriveService, NomeDiretorio As String)
        Dim Diretorio = New Google.Apis.Drive.v3.Data.File()
        Diretorio.Name = NomeDiretorio
        Diretorio.MimeType = "application/vnd.google-apps.folder"
        Dim Request = Servico.Files.Create(Diretorio)
        Request.Execute()
    End Sub

Para criarmos um diretório chamado “NovoDiretorio” temos que fazer a seguinte chamada dentro do nosso bloco “using“:

                // C#
                Console.Clear();
                Console.WriteLine("Criar diretório");
                CriarDiretorio(servico, "NovoDiretorio");
                Console.WriteLine("Fim Criar diretório");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Criar diretório")
            CriarDiretorio(Servico, "NovoDiretorio")
            Console.WriteLine("Fim Criar diretório")
            Console.ReadLine()

Porém, ao tentarmos executar esse código com o escopo de permissões que utilizamos anteriormente (“Scope.DriveReadonly“), nós receberemos um erro informando que não temos permissões suficientes:

Obviamente, o escopo “readonly” não permite a criação ou exclusão de itens. Se quisermos alterar itens no nosso Google Drive, teremos que alterar o escopo para “Scope.Drive” na hora de criarmos as credenciais (método “Autenticar“). Só não esqueça de deletar o subdiretório “credential” dentro da pasta “bin/debug“, uma vez que as credenciais com o escopo menor já estarão cacheadas nesse diretório e a autenticação continuará utilizando esse escopo “readonly” caso você não delete essa pasta para forçar uma nova autenticação.

Depois de seguir todos esses passos, ao executarmos novamente a aplicação, este será o resultado no nosso Google Drive:

Só tome cuidado, pois outra coisa estranha do Google Drive é que ele não liga para nomes repetidos. Ou seja, se você executar esse código duas vezes, ele criará duas vezes o mesmo diretório sem problema algum:

Deletando arquivos ou diretórios

A próxima operação que vamos conferir neste artigo é a exclusão de itens do nosso Google Drive. A exclusão em si é muito simples. Basta criarmos e executarmos um request para o método “Files.Delete” passando o “id” do item a ser deletado. A parte mais difícil está em descobrir o “id” do item que estamos querendo deletar.

Para descobrirmos o “id” de um item através do seu nome, nós temos que basicamente executar um comando de listagem filtrado, requisitando somente o “id” dos itens que tenham um nome específico. Vamos implementar primeiramente um método que retornará esses “ids“:

        // C#
        private static string[] ProcurarArquivoId(Google.Apis.Drive.v3.DriveService servico, string nome, bool procurarNaLixeira = false)
        {
            var retorno = new List<string>();

            var request = servico.Files.List();
            request.Q = string.Format("name = '{0}'", nome);
            if (!procurarNaLixeira)
            {
                request.Q += " and trashed = false";
            }
            request.Fields = "files(id)";
            var resultado = request.Execute();
            var arquivos = resultado.Files;

            if (arquivos != null && arquivos.Any())
            {
                foreach (var arquivo in arquivos)
                {
                    retorno.Add(arquivo.Id);
                }
            }

            return retorno.ToArray();
        }
    ' VB.NET
    Private Function ProcurarArquivoId(Servico As Google.Apis.Drive.v3.DriveService, Nome As String, Optional ProcurarNaLixeira As Boolean = False) As String()
        Dim Retorno = New List(Of String)()

        Dim Request = Servico.Files.List()
        Request.Q = String.Format("name = '{0}'", Nome)
        If Not ProcurarNaLixeira Then
            Request.Q += " and trashed = false"
        End If
        Request.Fields = "files(id)"
        Dim Resultado = Request.Execute()
        Dim Arquivos = Resultado.Files

        If Arquivos IsNot Nothing AndAlso Arquivos.Any() Then
            For Each Arquivo In Arquivos
                Retorno.Add(Arquivo.Id)
            Next
        End If

        Return Retorno.ToArray()
    End Function

Note que o método já está preparado para procurar itens na lixeira também. No nosso caso, nós não precisaremos dessa opção, mas já é bom implementá-la caso precisemos mais adiante.

Agora que já conseguimos uma lista com os “ids” dos itens, a exclusão fica muito simples:

        // C#
        private static void DeletarItem(Google.Apis.Drive.v3.DriveService servico, string nome)
        {
            var ids = ProcurarArquivoId(servico, nome);
            if (ids != null && ids.Any())
            {
                foreach (var id in ids)
                {
                    var request = servico.Files.Delete(id);
                    request.Execute();
                }
            }
        }
    ' VB.NET
    Private Sub DeletarItem(Servico As Google.Apis.Drive.v3.DriveService, Nome As String)
        Dim Ids = ProcurarArquivoId(Servico, Nome)
        If Ids IsNot Nothing AndAlso Ids.Any() Then
            For Each Id In Ids
                Dim Request = Servico.Files.Delete(Id)
                Request.Execute()
            Next
        End If
    End Sub

Por fim, a chamada para deletarmos o diretório “NovoDiretorio” que criamos anteriormente ficaria da seguinte maneira:

                // C#
                Console.Clear();
                Console.WriteLine("Deletar item");
                DeletarItem(servico, "NovoDiretorio");
                Console.WriteLine("Fim Deletar item");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Deletar item")
            DeletarItem(Servico, "NovoDiretorio")
            Console.WriteLine("Fim Deletar item")
            Console.ReadLine()

Fazendo o upload de arquivos

O upload de arquivos no Google Drive parece ser bem tranquilo à primeira vista. Temos à nossa disposição o método “Files.Create” que recebe uma instância de “File” (com as informações de nome e tipo do arquivo), a Stream com o conteúdo do arquivo e o seu “mime type“.

Se você não sabe o que é “mime type“, ele é um identificador que serve para definir o tipo de um arquivo. Por exemplo, “text/plain” é o “mime type” para arquivos texto, “image/jpeg” é o “mime type” para imagens jpg, e por aí vai. Esse tipo de identificador é muito utilizado em aplicações web na hora de fazer uma requisição.

Para nos ajudar com o cálculo do “mime type” no nosso projeto de exemplo, vamos utilizar a biblioteca MimeTypeMap, que é basicamente um dicionário gigante de “mime types” por extensão de arquivo. Adicione uma referência a essa biblioteca procurando por “MediaTypeMap” no NuGet:

Em seguida, vamos implementar o método que fará o upload do arquivo:

        // C#
        private static void Upload(Google.Apis.Drive.v3.DriveService servico, string caminhoArquivo)
        {
            var arquivo = new Google.Apis.Drive.v3.Data.File();
            arquivo.Name = System.IO.Path.GetFileName(caminhoArquivo);
            arquivo.MimeType = MimeTypes.MimeTypeMap.GetMimeType(System.IO.Path.GetExtension(caminhoArquivo));
            using (var stream = new System.IO.FileStream(caminhoArquivo, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                var request = servico.Files.Create(arquivo, stream, arquivo.MimeType);
                request.Upload();
            }
        }
    ' VB.NET
    Private Sub Upload(Servico As Google.Apis.Drive.v3.DriveService, CaminhoArquivo As String)
        Dim Arquivo = New Google.Apis.Drive.v3.Data.File()
        Arquivo.Name = System.IO.Path.GetFileName(CaminhoArquivo)
        Arquivo.MimeType = MimeTypes.MimeTypeMap.GetMimeType(System.IO.Path.GetExtension(CaminhoArquivo))
        Using Stream = New System.IO.FileStream(CaminhoArquivo, System.IO.FileMode.Open, System.IO.FileAccess.Read)
            Dim Request = Servico.Files.Create(Arquivo, Stream, Arquivo.MimeType)
            Request.Upload()
        End Using
    End Sub

Pronto! Para fazermos o upload de um arquivo chamado “arquivo.txt” que se encontra na pasta “bin/debug” da nossa aplicação, o código que teríamos que colocar no bloco “using” seria este:

                // C#
                Console.Clear();
                Console.WriteLine("Upload");
                Upload(servico, "arquivo.txt");
                Console.WriteLine("Fim Upload");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Upload")
            Upload(Servico, "arquivo.txt")
            Console.WriteLine("Fim Upload")
            Console.ReadLine()

E este seria o resultado no nosso Google Drive:

Tudo isso funciona muito bem da primeira vez que executarmos esse código. Porém, se executarmos uma segunda vez, ao invés do arquivo ser substituído, um novo “arquivo.txt” será criado no nosso Google Drive. É simplesmente incompreensível o fato do Google Drive trabalhar dessa maneira, possibilitando que existam dois arquivos com o mesmo nome no mesmo diretório. Mas, enfim, é assim que ele funciona.

Para corrigirmos esse problema (ou seja, substituirmos o arquivo caso ele já exista), teremos que detectar se o arquivo já existe e, caso positivo, temos que utilizar o método “Files.Update“, ao invés de “Files.Create“:

        // C#
        private static void Upload(Google.Apis.Drive.v3.DriveService servico, string caminhoArquivo)
        {
            var arquivo = new Google.Apis.Drive.v3.Data.File();
            arquivo.Name = System.IO.Path.GetFileName(caminhoArquivo);
            arquivo.MimeType = MimeTypes.MimeTypeMap.GetMimeType(System.IO.Path.GetExtension(caminhoArquivo));

            using (var stream = new System.IO.FileStream(caminhoArquivo, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                var ids = ProcurarArquivoId(servico, arquivo.Name);
                Google.Apis.Upload.ResumableUpload<Google.Apis.Drive.v3.Data.File, Google.Apis.Drive.v3.Data.File> request;

                if (ids == null || !ids.Any())
                {
                    request = servico.Files.Create(arquivo, stream, arquivo.MimeType);
                }
                else
                {
                    request = servico.Files.Update(arquivo, ids.First(), stream, arquivo.MimeType);
                }

                request.Upload();
            }
        }
    ' VB.NET
    Private Sub Upload(Servico As Google.Apis.Drive.v3.DriveService, CaminhoArquivo As String)
        Dim Arquivo = New Google.Apis.Drive.v3.Data.File()
        Arquivo.Name = System.IO.Path.GetFileName(CaminhoArquivo)
        Arquivo.MimeType = MimeTypes.MimeTypeMap.GetMimeType(System.IO.Path.GetExtension(CaminhoArquivo))

        Using Stream = New System.IO.FileStream(CaminhoArquivo, System.IO.FileMode.Open, System.IO.FileAccess.Read)
            Dim Ids = ProcurarArquivoId(Servico, Arquivo.Name)
            Dim Request As Google.Apis.Upload.ResumableUpload(Of Google.Apis.Drive.v3.Data.File, Google.Apis.Drive.v3.Data.File)

            If Ids Is Nothing OrElse Not Ids.Any() Then
                Request = Servico.Files.Create(Arquivo, Stream, Arquivo.MimeType)
            Else
                Request = Servico.Files.Update(Arquivo, Ids.First(), Stream, Arquivo.MimeType)
            End If

            Request.Upload()
        End Using
    End Sub

Agora sim. Se executarmos esse código mais de uma vez, ele simplesmente substituirá o “arquivo.txt” já existente no nosso Google Drive, utilizando o conteúdo da nova “Stream“.

Baixando um arquivo do Google Drive

Até agora nós já vimos como listar arquivos no Google Drive e como criar e deletar pastas e arquivos. O que é que está faltando? O download de arquivos! Esse é um dos procedimentos mais tranquilos de serem feitos com a API do Google Drive. Nós só temos que criar um request com o método “Files.Get” passando o “id” do arquivo a ser baixado e, em seguida, chamamos o método “Download” passando a “Stream” onde o arquivo deverá ser salvo. Veja só como é simples:

        // C#
        private static void Download(Google.Apis.Drive.v3.DriveService servico, string nome, string destino)
        {
            var ids = ProcurarArquivoId(servico, nome);
            if (ids != null && ids.Any())
            {
                var request = servico.Files.Get(ids.First());
                using (var stream = new System.IO.FileStream(destino, System.IO.FileMode.Create, System.IO.FileAccess.Write))
                {
                    request.Download(stream);
                }
            }
        }
    ' VB.NET
    Private Sub Download(Servico As Google.Apis.Drive.v3.DriveService, Nome As String, Destino As String)
        Dim Ids = ProcurarArquivoId(Servico, Nome)
        If Ids IsNot Nothing AndAlso Ids.Any() Then
            Dim Request = Servico.Files.[Get](Ids.First())
            Using Stream = New System.IO.FileStream(Destino, System.IO.FileMode.Create, System.IO.FileAccess.Write)
                Request.Download(Stream)
            End Using
        End If
    End Sub

Para baixarmos o “arquivo.txt” que fizemos o upload na seção anterior, a chamada ficaria assim:

                // C#
                Console.Clear();
                Console.WriteLine("Download");
                Download(servico, "arquivo.txt", "arquivoBaixado.txt");
                Console.WriteLine("Fim Download");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Download")
            Download(Servico, "arquivo.txt", "arquivoBaixado.txt")
            Console.WriteLine("Fim Download")
            Console.ReadLine()

Enviando um item para a lixeira

Por fim, a última coisa que quero mostrar para você neste artigo é: como enviamos um item para a lixeira? Na segunda versão da API do Google Drive, nós tínhamos à nossa disposição um método específico para enviar um item para a lixeira. Porém, na terceira versão da API a Google decidiu remover esse método.

Para enviarmos um item para a lixeira, nós temos que recuperar o seu “id“, criar uma instância da classe “File” e marcar a propriedade “Trashed” como verdadeiro. Em seguida, nós criamos um request para o método “Files.Update” enviando essa instância de “File“:

        // C#
        private static void MoverParaLixeira(Google.Apis.Drive.v3.DriveService servico, string nome)
        {
            var ids = ProcurarArquivoId(servico, nome);
            if (ids != null && ids.Any())
            {
                foreach (var id in ids)
                {
                    var arquivo = new Google.Apis.Drive.v3.Data.File();
                    arquivo.Trashed = true;
                    var request = servico.Files.Update(arquivo, id);
                    request.Execute();
                }
            }
        }
    ' VB.NET
    Private Sub MoverParaLixeira(Servico As Google.Apis.Drive.v3.DriveService, Nome As String)
        Dim Ids = ProcurarArquivoId(Servico, Nome)
        If Ids IsNot Nothing AndAlso Ids.Any() Then
            For Each Id In Ids
                Dim Arquivo = New Google.Apis.Drive.v3.Data.File()
                Arquivo.Trashed = True
                Dim Request = Servico.Files.Update(Arquivo, Id)
                Request.Execute()
            Next
        End If
    End Sub

A chamada desse método não tem segredo. Basta passarmos o nome do arquivo ou pasta que deverá ser mandado para a lixeira:

                // C#
                Console.Clear();
                Console.WriteLine("Lixeira");
                MoverParaLixeira(servico, "arquivo.txt");
                Console.WriteLine("Fim Lixeira");
                Console.ReadLine();
            ' VB.NET
            Console.Clear()
            Console.WriteLine("Lixeira")
            MoverParaLixeira(Servico, "arquivo.txt")
            Console.WriteLine("Fim Lixeira")
            Console.ReadLine()

Concluindo

O acesso à API do Google Drive em projetos .NET não é tão complicado de se implementar. Existem alguns detalhes que são chatos, mas, uma vez que entendemos o funcionamento da API, fica fácil de fazer qualquer tipo de manipulação do Google Drive a partir da nossa aplicação.

No artigo de hoje você aprendeu a habilitar a API do Google Drive na sua conta, bem como a geração do arquivo de credenciais que é necessário para o seu acesso. Em seguida, vimos como listar, fazer o upload, download e exclusão de arquivos do Google Drive no C# e VB.NET. Além disso, nós conferimos também como mandar itens para a lixeira.

E você, já precisou fazer alguma integração com o Google Drive nas suas aplicações? Já pensou na possibilidade de armazenar alguns dados da sua aplicação na sua conta do Google Drive? Quem sabe algum backup ou disponibilizar novas versões da aplicação? Conte pra gente nos comentários a sua experiência com o Google Drive ou talvez as ideias que você teve após ter lido este artigo.

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Utilizando a API do Google Drive no C# e VB.NET appeared first on André Alves de Lima.

Adicionando um ComboBox no DataGridView

$
0
0

Hoje eu vou mostrar para você um tema que pode parecer bem simples se você já tem alguma experiência com desenvolvimento de aplicações desktop na plataforma Microsoft, mas que acaba sendo uma grande dor de cabeça para quem está começando: como podemos adicionar um ComboBox no DataGridView?

Nesse caso, temos duas opções: ou criamos uma lista fixa direto no ComboBox, ou alimentamos o ComboBox com uma fonte de dados. Ambas as opções são bem tranquilas de serem implementadas, porém, existe uma pequena diferença caso precisemos ordenar os itens do nosso ComboBox.

Confira no vídeo um passo a passo mostrando como adicionar um ComboBox no DataGridView utilizando essas duas estratégias:

O projeto base

A construção do exemplo desse vídeo toma como base um projeto bem descomplicado, que na realidade é um simples formulário com um DataGridView e três colunas:

No code-behind do formulário nós carregamos manualmente o grid utilizando uma DataTable:

        // C#
        public Form1()
        {
            InitializeComponent();

            var dt = new DataTable();
            dt.Columns.Add("ID", typeof(int));
            dt.Columns.Add("Nome");
            dt.Columns.Add("Cidade", typeof(int));
            dt.Rows.Add(1, "André", 1);// "Limeira");
            dt.Rows.Add(2, "Fulano", 2);// "São Paulo");
            dt.Rows.Add(3, "Beltrano", 3);// "Rio de Janeiro");
            dt.Rows.Add(4, "Sicrano", 4);//"Brasília");
            dataGridView1.DataSource = dt;
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Dt = New DataTable()
        Dt.Columns.Add("ID", GetType(Integer))
        Dt.Columns.Add("Nome")
        Dt.Columns.Add("Cidade")
        Dt.Rows.Add(1, "André", "Limeira")
        Dt.Rows.Add(2, "Fulano", "São Paulo")
        Dt.Rows.Add(3, "Beltrano", "Rio de Janeiro")
        Dt.Rows.Add(4, "Sicrano", "Brasília")
        DataGridView1.DataSource = Dt
    End Sub

Uma outra opção para carregarmos os dados do DataGridView seria trabalharmos com uma colação de objetos:

            // C#
            var dados = new BindingList<object>(new object[]
            {
                new { ID = 1, Nome = "Andre", Cidade = "Limeira" },
                new { ID = 2, Nome = "Fulano", Cidade = "São Paulo" },
                new { ID = 3, Nome = "Beltrano", Cidade = "Rio de Janeiro" },
                new { ID = 4, Nome = "Sicrano", Cidade = "Brasília" }
            });
            dataGridView1.DataSource = dados;
        ' VB.NET
        Dim Dados = New System.ComponentModel.BindingList(Of Object)(New Object() _
        {New With {
            .ID = 1,
            .Nome = "Andre",
            .Cidade = "Limeira"
        }, New With {
            .ID = 2,
            .Nome = "Fulano",
            .Cidade = "São Paulo"
        }, New With {
            .ID = 3,
            .Nome = "Beltrano",
            .Cidade = "Rio de Janeiro"
        }, New With {
            .ID = 4,
            .Nome = "Sicrano",
            .Cidade = "Brasília"
        }})
        DataGridView1.DataSource = Dados

O resultado final seria exatamente o mesmo. Porém, eu preferi trabalhar com DataTable, pois é a maneira mais utilizada para carregarmos dados em projetos Windows Forms.

A ideia é transformarmos a coluna “Cidade” em um ComboBox onde poderemos selecionar uma opção dentro de uma lista de cidades disponíveis.

Criando um ComboBox fixo

A primeira opção seria criarmos um ComboBox fixo. Para isso, vamos até a lista de colunas do nosso grid, clicando na opção “Edit Columns” da smart tag do controle:

Em seguida, transformamos a coluna “Cidade” em uma “DataGridViewComboBoxColumn“:

Agora que a nossa coluna já é um ComboBox, nós teremos à nossa disposição a propriedade “Items“:

Para termos uma lista fixa, basta digitarmos os itens desejados, um em cada linha:

Execute o projeto e veja o resultado:

É importante notar que, ao transformarmos a coluna em ComboBox, todos os itens que estão sendo utilizados no grid devem estar disponíveis na lista do ComboBox. Caso contrário, receberemos um erro como este ao carregarmos o grid:

Ordenando o ComboBox

Ao trabalharmos com listas fixas no ComboBox, nós podemos facilmente ordenar os seus itens através da propriedade “Sorted“:

Criando um ComboBox dinâmico

Esse tipo de lista fixa pode até ser útil em alguns cenários (como quando queremos criar algum “Status” que tem uma quantidade bem delimitada de opções). Porém, em um cenário mais comum nós provavelmente precisaremos carregar a lista de opções de algum lugar (normalmente do banco de dados). Como é que podemos fazer isso?

Colunas do tipo ComboBox possuem uma propriedade chamada “DataSource“. Nós podemos carregar essa propriedade com uma DataTable ou coleção de objetos que deverão ser mostrados no ComboBox. Por exemplo, para carregarmos o ComboBox com uma DataTable, o código ficaria assim:

            // C#
            var dtCidades = new DataTable();
            dtCidades.Columns.Add("Cidade");
            dtCidades.Rows.Add("Limeira");
            dtCidades.Rows.Add("São Paulo");
            dtCidades.Rows.Add("Rio de Janeiro");
            dtCidades.Rows.Add("Brasília");
            dtCidades.Rows.Add("Fortaleza");
            ColumnCidade.ValueMember = "Cidade";
            ColumnCidade.DisplayMember = "Cidade";
            ColumnCidade.DataSource = dtCidades;
        ' VB.NET
        Dim DtCidades = New DataTable()
        DtCidades.Columns.Add("Cidade")
        DtCidades.Rows.Add("Limeira")
        DtCidades.Rows.Add("São Paulo")
        DtCidades.Rows.Add("Rio de Janeiro")
        DtCidades.Rows.Add("Brasília")
        DtCidades.Rows.Add("Fortaleza")
        ColumnCidade.ValueMember = "Cidade"
        ColumnCidade.DisplayMember = "Cidade"
        ColumnCidade.DataSource = DtCidades

Remova a lista de itens que criamos anteriormente na propriedade “Items” da coluna “Cidade” e execute o projeto. O resultado será idêntico ao que tivemos com a lista fixa.

Ordenando um ComboBox com DataSource

Ao utilizarmos uma fonte de dados para o nosso ComboBox, se tentarmos ordená-lo através da propriedade “Sorted“, receberemos um erro:

Nesse caso, nós teremos que ordenar os dados de uma outra maneira. A primeira opção é já trazer os dados ordenados do banco de dados (ou de onde quer que tivermos carregado os dados). Se isso não for possível, nós temos algumas outras opções.

A classe “DataTable” conta com uma propriedade chamada “DefaultView“. Essa propriedade é uma “DataView” onde podemos definir, entre outras coisas, a ordenação padrão da DataTable. Dessa forma, se configurarmos a propriedade “Sort” da “DefaultView“, o ComboBox virá ordenado da maneira que configuramos:

            // C#
            dtCidades.DefaultView.Sort = "Cidade ASC";
            ColumnCidade.DataSource = dtCidades;
        ' VB.NET
        DtCidades.DefaultView.Sort = "Cidade ASC"
        ColumnCidade.DataSource = DtCidades

Se por algum acaso essa opção não funcionar, nós podemos também criar manualmente uma outra DataView com base na nossa DataTable, configuramos o “Sort” dessa DataView e passamos essa DataView para o ComboBox (ao invés da DataTable):

            // C#
            var dtView = new DataView(dtCidades);
            dtView.Sort = "Cidade ASC";
            ColumnCidade.DataSource = dtView;
        ' VB.NET
        Dim DtView = New DataView(DtCidades)
        DtView.Sort = "Cidade ASC"
        ColumnCidade.DataSource = DtView

ValueMember e DisplayMember

Você deve ter percebido que utilizamos as propriedades “ValueMember” e “DisplayMember” na hora de configurarmos a fonte de dados do nosso ComboBox. Qual é o efeito dessas propriedades?

Essas propriedades servem para definirmos colunas específicas para fazermos a ligação entre o item selecionado no ComboBox e a linha do grid. No nosso exemplo, nós estamos armazenando o nome da cidade no grid e estamos alimentando o ComboBox com uma DataTable que só tem uma coluna: “Cidade“. Dessa forma, ambas as propriedades “DisplayMember” e “ValueMember” foram configuradas para “Cidade” (que é o nome da coluna na fonte de dados do ComboBox).

Mas, e se tivéssemos uma coluna “ID” para a cidade (tanto no grid quanto na fonte de dados do ComboBox)? Como é que faríamos para armazenar o “ID” mostrando os nomes das cidades no ComboBox? Simples! Nesse caso nós teríamos que configurar o “ValueMember” como sendo o “ID” e o “DisplayMember” como sendo a coluna onde temos os nomes das cidades (no nosso caso, a coluna se chama “Cidade“).

Veja só como é que ficaria o código:

            // C#
            var dtCidades = new DataTable();
            dtCidades.Columns.Add("ID", typeof(int));
            dtCidades.Columns.Add("Cidade");
            dtCidades.Rows.Add(1, "Limeira");
            dtCidades.Rows.Add(2, "São Paulo");
            dtCidades.Rows.Add(3, "Rio de Janeiro");
            dtCidades.Rows.Add(4, "Brasília");
            dtCidades.Rows.Add(5, "Fortaleza");
            ColumnCidade.ValueMember = "ID";
            ColumnCidade.DisplayMember = "Cidade";
            dtCidades.DefaultView.Sort = "Cidade ASC";
            ColumnCidade.DataSource = dtCidades;

            var dt = new DataTable();
            dt.Columns.Add("ID", typeof(int));
            dt.Columns.Add("Nome");
            dt.Columns.Add("Cidade", typeof(int));
            dt.Rows.Add(1, "André", 1);
            dt.Rows.Add(2, "Fulano", 2);
            dt.Rows.Add(3, "Beltrano", 3);
            dt.Rows.Add(4, "Sicrano", 4);
            dataGridView1.DataSource = dt;
        ' VB.NET
        Dim DtCidades = New DataTable()
        DtCidades.Columns.Add("ID", GetType(Integer))
        DtCidades.Columns.Add("Cidade")
        DtCidades.Rows.Add(1, "Limeira")
        DtCidades.Rows.Add(2, "São Paulo")
        DtCidades.Rows.Add(3, "Rio de Janeiro")
        DtCidades.Rows.Add(4, "Brasília")
        DtCidades.Rows.Add(5, "Fortaleza")
        ColumnCidade.ValueMember = "ID"
        ColumnCidade.DisplayMember = "Cidade"
        DtCidades.DefaultView.Sort = "Cidade ASC"
        ColumnCidade.DataSource = DtCidades

        Dim Dt = New DataTable()
        Dt.Columns.Add("ID", GetType(Integer))
        Dt.Columns.Add("Nome")
        Dt.Columns.Add("Cidade", GetType(Integer))
        Dt.Rows.Add(1, "André", 1)
        Dt.Rows.Add(2, "Fulano", 2)
        Dt.Rows.Add(3, "Beltrano", 3)
        Dt.Rows.Add(4, "Sicrano", 4)
        DataGridView1.DataSource = Dt

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

Photo by Peter Shanks used under Creative Commons
https://pixabay.com/en/startup-start-up-notebooks-creative-593327/

Song Rocket Power Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Adicionando um ComboBox no DataGridView appeared first on André Alves de Lima.

Impedindo que a aplicação seja executada mais de uma vez ao mesmo tempo

$
0
0

Existem cenários em que faz sentido bloquearmos a nossa aplicação de forma que o usuário não consiga executar mais de uma instância ao mesmo tempo. Até mesmo a Microsoft faz isso com algumas aplicações dela. Por exemplo, não faz sentido abrirmos o Outlook mais de uma vez ao mesmo tempo.

Como é que nós podemos fazer essa checagem na inicialização da nossa aplicação? Existem basicamente duas opções. A primeira delas é verificarmos se já existe algum processo sendo executado com o mesmo nome. Apesar de ser simples, essa opção não é lá muito eficiente. Uma outra alternativa seria bloquearmos algum recurso enquanto a nossa aplicação está sendo executada, dessa forma nós conseguimos detectar se a aplicação já está em execução ou não.

No artigo de hoje eu vou mostrar para você essas duas opções, implementadas em um projeto do tipo Console Application (mas que você pode adaptar para qualquer tipo de projeto desktop, como Windows Forms ou WPF).

Opção 1: checando o nome do processo

A maneira mais simples de detectarmos se a nossa aplicação já está sendo executada é procurarmos por algum outro processo que tenha o mesmo nome da nossa aplicação. Isso pode ser feito muito facilmente através da classe Process.

Primeiramente, temos que descobrir o nome do processo da nossa aplicação. Normalmente, o nome do processo será o nome do arquivo executável. Para descobrirmos essa informação em tempo de execução, nós utilizamos o método “GetCurrentProcess“, da classe Process. Tendo o nome do processo em mãos, nós podemos utilizar o método “GetProcessByName” para tentarmos encontrar outros processos que tenham o mesmo nome:

        // C#
        static void Main(string[] args)
        {
            var processo = System.Diagnostics.Process.GetCurrentProcess();
            var jaEstaRodando = System.Diagnostics.Process.GetProcessesByName(processo.ProcessName).Any(p => p.Id != processo.Id);
        }
    ' VB.NET
    Sub Main()
        Dim Processo = System.Diagnostics.Process.GetCurrentProcess()
        Dim JaEstaRodando = System.Diagnostics.Process.GetProcessesByName(Processo.ProcessName).Any(Function(P) P.Id <> Processo.Id)
    End Sub

Note que nós temos que filtrar o resultado para os processos que tenham um “ID” diferente do processo atual, uma vez que sempre existirá no mínimo um processo com o mesmo nome (que é justamente o processo que está sendo executado no momento da checagem).

Com isso, nós conseguimos simplesmente encerrar a nossa aplicação caso ela já esteja sendo executada. Caso contrário, nós continuamos com o fluxo normal da aplicação (que nesse exemplo imprime o texto “Instância única” no console):

            // C#
            if (jaEstaRodando)
            {
                Console.WriteLine("Já está rodando!");
                Console.ReadLine();
                return;
            }

            Console.WriteLine("Instância única");
            Console.ReadLine();
        ' VB.NET
        If (JaEstaRodando) Then
            Console.WriteLine("Já está rodando!")
            Console.ReadLine()
            Return
        End If

        Console.WriteLine("Instância única")
        Console.ReadLine()

Problemas ao checar o nome do processo

Essa opção parece muito simples e, à primeira vista, funciona muito bem. Porém, se tentarmos executar esse projeto pelo Visual Studio e, em seguida, navegarmos até a pasta “bin/debug” e rodarmos novamente o executável do projeto, qual será o resultado?

Ué, por que não funcionou? Vamos colocar um breakpoint na linha em que estamos calculando o valor para a variável “jaEstaRodando“:

Ahá! Veja só o problema. O processo que está sendo executado pelo Visual Studio na realidade não é o próprio executável, mas sim o executável com a terminação “vshost“! Esse é o comportamento padrão do Visual Studio quando executamos um projeto desktop em modo debug. Para verificarmos se esse código está realmente funcionando, teríamos que executá-lo duas vezes diretamente pelo Windows Explorer:

Porém, dessa forma nós perdemos a possibilidade de debugarmos a nossa aplicação pelo Visual Studio. Caso nós realmente precisemos debugar a aplicação enquanto testamos essa checagem de instâncias duplicadas, a alternativa seria iniciar a aplicação pelo Visual Studio sem o debugger:

E, logo em seguida, nós atachamos o debugger manualmente no nosso processo:

Como você pode perceber, essa maneira de verificarmos se a aplicação já está sendo executada tem uma forte dependência com o nome do processo. Ou seja, além das inconveniências que teremos para debugar o nosso projeto pelo Visual Studio, esse tipo de checagem pode ser facilmente burlado. Basta criarmos uma cópia do executável utilizando outro nome e pronto, está burlado o nosso algoritmo, uma vez que os executáveis teriam nomes diferentes e, por consequência, os processos não teriam o mesmo nome.

Qual seria, então, uma alternativa mais robusta para esse tipo de verificação? A classe Mutex!

Opção 2: Mutex

Se você não conhece a classe Mutex, não se preocupe, eu também não conhecia. Essa classe foi originalmente implementada para fazer a sincronização do fluxo de execução entre processos. Imagine que nós tenhamos dois processos (A e B), sendo que o processo A tem que esperar o processo B executar um cálculo antes de prosseguir com a sua execução. É esse tipo de controle de fluxo que nós conseguimos implementar utilizando a classe Mutex.

A ideia até que é bem simples. Nós criamos uma instância da classe Mutex utilizando um identificador único (pode ser uma string escolhida por você ou, melhor ainda, um GUID). Em seguida, na hora de iniciar a nossa aplicação, nós chamamos o método “WaitOne” do Mutex. Caso o retorno seja verdadeiro, isso significa que nenhum outro processo está bloqueando o Mutex, ou seja, não existe nenhum outro processo que esteja utilizando um Mutex com aquele identificador. Por fim, quando a execução da nossa aplicação for concluída, nós liberamos o Mutex através do método “ReleaseMutex“.

Veja só como é que fica o código:

            // C#
            using (var mutex = new System.Threading.Mutex(true, "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3"))
            {
                var jaEstaRodando = !mutex.WaitOne(0, true);
                if (jaEstaRodando)
                {
                    Console.WriteLine("Já está rodando!");
                    Console.ReadLine();
                    return;
                }

                Console.WriteLine("Instância única");
                Console.ReadLine();
                mutex.ReleaseMutex();
            }
        ' VB.NET
        Using Mutex = New System.Threading.Mutex(True, "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3")
            Dim JaEstaRodando = Not Mutex.WaitOne(0, True)

            If (JaEstaRodando) Then
                Console.WriteLine("Já está rodando!")
                Console.ReadLine()
                Return
            End If

            Console.WriteLine("Instância única")
            Console.ReadLine()
        End Using

E com isso nós conseguimos bloquear a execução múltipla do nosso aplicativo. Se algum usuário tentar executar a aplicação uma segunda vez, o resultado da chamada de “WaitOne” será falso, indicando que o aplicativo já está sendo executado. Essa metodologia de checagem funcionará mesmo se o usuário criar uma cópia do executável com outro nome, uma vez que o identificador do Mutex continuará idêntico independente do nome do processo ou executável.

Nota: no código acima eu utilizei um GUID como identificador do Mutex, que é um tipo de identificador com probabilidade baixíssima de ser duplicado (veja mais informações na Wikipedia). Você pode gerar GUIDs utilizando ferramentas online (como essa aqui) ou diretamente pelo Visual Studio, através da opção “Tools -> Create GUID”.

E no Terminal Services?

Tudo isso funciona perfeitamente caso a nossa aplicação rode diretamente no computador do cliente. Porém, em cenários onde a nossa aplicação estiver rodando através do Terminal Services (ou outros serviços desse tipo, como Citrix MetaFrame / XenApp), essa metodologia vai por água abaixo.

Uma vez que a nossa aplicação esteja sendo executada em um servidor de aplicações, é óbvio que múltiplas instâncias serão executadas ao mesmo tempo no mesmo servidor. Se implementarmos qualquer uma das duas soluções apresentadas até agora neste artigo, o primeiro usuário que executar a nossa aplicação bloqueará a execução para todos os outros usuários subsequentes.

Em cenários como esse, o ideal é que a checagem seja feita por usuário. Ou seja, cada usuário só poderá abrir uma única instância da nossa aplicação. Para fazermos isso, nós temos que levar em conta o nome do usuário na hora de verificarmos se a aplicação já está sendo executada.

Se optarmos por fazer a checagem pelo nome do processo, nós teríamos que descobrir o nome do usuário que iniciou cada processo. Isso pode ser feito através de uma consulta nos objetos de gerenciamento do Windows (Management Objects), conforme indicado nesta thread do StackOverflow:

        // C#
        public static string GetProcessOwner(int processId)
        {
            var query = "Select * From Win32_Process Where ProcessID = " + processId;
            var searcher = new System.Management.ManagementObjectSearcher(query);
            var processList = searcher.Get();

            foreach (System.Management.ManagementObject obj in processList)
            {
                string[] argList = new string[] { string.Empty, string.Empty };
                int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
                if (returnVal == 0)
                {
                    // return DOMAIN\user
                    return argList[1] + "\\" + argList[0];
                }
            }

            return "NO OWNER";
        }
    ' VB.NET
    Public Function GetProcessOwner(ProcessId As Integer) As String
        Dim Query = "Select * From Win32_Process Where ProcessID = " + ProcessId
        Dim Searcher = New System.Management.ManagementObjectSearcher(Query)
        Dim ProcessList = Searcher.[Get]()

        For Each Obj As System.Management.ManagementObject In ProcessList
            Dim ArgList As String() = New String() {String.Empty, String.Empty}
            Dim ReturnVal As Integer = Convert.ToInt32(Obj.InvokeMethod("GetOwner", ArgList))
            If ReturnVal = 0 Then
                ' return DOMAIN\user
                Return ArgList(1) + "\" + ArgList(0)
            End If
        Next

        Return "NO OWNER"
    End Function

Nota: algumas classes utilizadas no método acima dependem do assembly “System.Management”, que não é referenciado por padrão pelo Visual Studio em novos projetos. Não esqueça de adicionar uma referência para esse assembly no seu projeto, caso contrário você não conseguirá compilar a solução:

Com esse método copiado para o nosso projeto, nós podemos utilizá-lo para checarmos também o nome do usuário na hora de verificarmos se o processo já está sendo executado:

// C#
var jaEstaRodando = System.Diagnostics.Process.GetProcessesByName(processo.ProcessName).Any(p => p.Id != processo.Id && GetProcessOwner(p.Id) == GetProcessOwner(processo.Id));
' VB.NET
Dim JaEstaRodando = System.Diagnostics.Process.GetProcessesByName(Processo.ProcessName).Any(Function(P) P.Id <> Processo.Id And GetProcessOwner(P.Id) = GetProcessOwner(Processo.Id))

Pronto! Com essas alterações a nossa checagem pelo nome do processo também levará em conta o nome do usuário que iniciou o processo. Ou seja, nós só permitiremos que cada usuário rode somente uma instância da aplicação ao mesmo tempo.

Por fim, caso optemos pela alternativa do Mutex, a única coisa que temos que fazer é concatenarmos o nome do usuário no identificador do Mutex. Dessa forma, o identificador será único somente no contexto de cada usuário:

            // C#
            var identificadorMutex = string.Format("{0}_{1}", "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3", System.Security.Principal.WindowsIdentity.GetCurrent().User);
            using (var mutex = new System.Threading.Mutex(true, identificadorMutex))
            {
               // O resto do código fica o mesmo
            }
        ' VB.NET
        Dim IdentificadorMutex = String.Format("{0}_{1}", "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3", System.Security.Principal.WindowsIdentity.GetCurrent().User)
        Using Mutex = New System.Threading.Mutex(True, IdentificadorMutex)
            ' O resto do código fica o mesmo
        End Using

Concluindo

Em algumas situações não faz sentido o usuário executar a nossa aplicação mais de uma vez ao mesmo tempo. Nesses casos, a recomendação é implementarmos uma checagem na nossa aplicação, que não permita que um mesmo usuário execute a aplicação duas ou mais vezes ao mesmo tempo.

No artigo de hoje você aprendeu duas estratégias que podemos utilizar para implementarmos essa checagem. A primeira delas utiliza o como base o nome do processo. Já na segunda metodologia, nós utilizamos uma instância da classe Mutex construída com um identificador único.

Como você pode observar ao longo do artigo, a checagem através do nome do processo não é a mais confiável. Esse algoritmo pode ser facilmente burlado se o usuário simplesmente fizer uma cópia do executável da aplicação utilizando outro nome. Portanto, a opção mais recomendada para verificarmos se a nossa aplicação já está sendo executada é através da classe Mutex.

Por fim, você conferiu também os problemas que podemos enfrentar caso a nossa aplicação esteja sendo executada em um servidor de aplicações (como o Terminal Services). Nesse caso, temos que levar em consideração também o nome do usuário que está executando a aplicação.

E você, já precisou implementar uma checagem como essa? Qual das alternativas você utilizou na sua aplicação? Você enfrentou algum problema específico que não foi abordado neste artigo? Conte mais detalhes nos comentários logo abaixo!

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Icon by Pixabay used under Creative Commons
https://pixabay.com/en/menu-gui-interface-template-ui-303121/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Impedindo que a aplicação seja executada mais de uma vez ao mesmo tempo appeared first on André Alves de Lima.

Viewing all 210 articles
Browse latest View live