Quantcast
Channel: Visual Studio – André Alves de Lima
Viewing all 89 articles
Browse latest View live

Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll

$
0
0

Um erro clássico que acontece ao tentarmos exibir um relatório do Crystal Reports em aplicações que utilizam o .NET Framework 4 ou superior é o “FileNotFoundException” relacionado ao arquivo “crdb_adoplus.dll“. Como é que esse erro pode ser resolvido? A maneira mais simples e mais utilizada é fazermos um pequeno ajuste no arquivo app.config da nossa aplicação. Porém, essa não é a única maneira. Existe uma outra alternativa que faz muito sentido em alguns casos. No artigo de hoje você verá essas duas maneiras de resolver o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll.

Entendendo o problema

Para entendermos esse problema, vamos criar um novo projeto do tipo “Windows Forms Application“. Dentro desse projeto, vamos adicionar um relatório do Crystal Reports em branco (vamos dar o nome de “Relatorio” para esse novo relatório que está sendo criado). Em seguida, no formulário, temos que arrastar um controle do Crystal Reports e selecionar o relatório que foi criado no passo anterior.

Ao executarmos essa aplicação, não recebemos erro algum:

Porém, vamos combinar que uma aplicação com um relatório em branco sem fonte de dados não tem nenhuma utilidade. Dessa forma, vamos criar uma classe muito simples que servirá de fonte de dados para o relatório. Essa classe se chamará “QualquerClasse” e terá somente uma propriedade (chamada “Propriedade“):

    // C#
    public class QualquerClasse
    {
        public int Propriedade { get; set; }
    }
' VB.NET
Public Class QualquerClasse
    Public Property Propriedade As Integer
End Class

Feito isso, no relatório, vamos até o Database Expert para arrastarmos essa classe para dentro do relatório:

A partir desse momento, ao executarmos novamente a nossa aplicação, o Crystal Reports mostrará a típica caixa de login que aparece quando não passamos a fonte de dados para o nosso relatório:

Isso faz todo o sentido, uma vez que temos uma tabela definida no nosso relatório e nós não estamos alimentando essa tabela ao exibirmos o relatório. OK, então vamos alimentar essa tabela com uma coleção de “QualquerClasse“. Essa coleção conterá somente uma instância dessa classe:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();
            Relatorio1.SetDataSource(new List<QualquerClasse>(new QualquerClasse[] { new QualquerClasse() { Propriedade = 1 } }));
        }
    ' VB.NET
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

E é aí que surge o problema. Nesse ponto, ao executarmos novamente a nossa aplicação, receberemos esse belo erro:

An unhandled exception of type ‘System.IO.FileNotFoundException’ occurred in mscorlib.dll
Additional information: Could not load file or assembly ‘file:///C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\dotnet1\crdb_adoplus.dll’ or one of its dependencies. The system cannot find the file specified.

Como mencionei no início do artigo, esse erro pode ser consertado de duas maneiras. Vamos conferir as duas opções?

Opção 1: useLegacyV2RuntimeActivationPolicy no app.config

A primeira opção é bem simples. Nós só temos que fazer um pequeno ajuste no arquivo app.config da nossa aplicação. Na tag “startup“, temos que definir o elemento “useLegacyV2RuntimeActivationPolicy” como “true“:

Se você quiser copiar e colar, segue o código da tag “startup” já com o ajuste desse elemento:

    <startup useLegacyV2RuntimeActivationPolicy="true"> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>

Execute novamente a aplicação e veja que o erro foi resolvido.

Opção 2: RuntimePolicyHelper

Em algumas situações, adicionarmos essa tag no arquivo app.config acaba sendo muito complicado. Isso acontece principalmente quando desmembramos a exibição dos relatórios em uma biblioteca (dll) separada. Nesse caso, seria o app.config da aplicação consumidora que deveria ser alterado, e isso é definitivamente algo que deve ser evitado ao disponibilizarmos uma biblioteca que pode ser consumida por várias aplicações consumidoras.

Na empresa onde eu trabalho nós tivemos exatamente esse problema. A lógica “genérica” de exibição de relatórios do Crystal Reports fica em uma dll separada, que é consumida por “N” aplicações. Nós queríamos evitar que o app.config de cada aplicação consumidora tivesse que ser alterado ao utilizar o Crystal Reports. Ao invés disso, nós queríamos fazer essa alteração do “legacy runtime” automaticamente quando a aplicação fosse executada.

Depois de pesquisar um pouquinho, encontramos uma classe “mágica que faz com que esse elemento seja adicionado em tempo de execução, evitando que o app.config tenha que ser alterado nas aplicações consumidoras. Vamos ver como essa classe funciona?

Primeiramente, vamos adicionar uma nova classe ao nosso projeto, dando o nome de “RuntimePolicyHelper“. Aqui vai o código dessa classe:

    // C#
    public static class RuntimePolicyHelper
    {
        public static bool LegacyV2RuntimeEnabledSuccessfully { get; private set; }

        static RuntimePolicyHelper()
        {
            ICLRRuntimeInfo clrRuntimeInfo =
                (ICLRRuntimeInfo)System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(
                    Guid.Empty,
                    typeof(ICLRRuntimeInfo).GUID);
            try
            {
                clrRuntimeInfo.BindAsLegacyV2Runtime();
                LegacyV2RuntimeEnabledSuccessfully = true;
            }
            catch (System.Runtime.InteropServices.COMException)
            {
                // This occurs with an HRESULT meaning 
                // "A different runtime was already bound to the legacy CLR version 2 activation policy."
                LegacyV2RuntimeEnabledSuccessfully = false;
            }
        }

        [System.Runtime.InteropServices.ComImport]
        [System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
        [System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")]
        private interface ICLRRuntimeInfo
        {
            void xGetVersionString();
            void xGetRuntimeDirectory();
            void xIsLoaded();
            void xIsLoadable();
            void xLoadErrorString();
            void xLoadLibrary();
            void xGetProcAddress();
            void xGetInterface();
            void xSetDefaultStartupFlags();
            void xGetDefaultStartupFlags();

            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType = System.Runtime.CompilerServices.MethodCodeType.Runtime)]
            void BindAsLegacyV2Runtime();
        }
    }
' VB.NET
Public NotInheritable Class RuntimePolicyHelper
    Public Shared Property LegacyV2RuntimeEnabledSuccessfully() As Boolean
        Get
            Return m_LegacyV2RuntimeEnabledSuccessfully
        End Get
        Private Set
            m_LegacyV2RuntimeEnabledSuccessfully = Value
        End Set
    End Property
    Private Shared m_LegacyV2RuntimeEnabledSuccessfully As Boolean

    Shared Sub New()
        Dim clrRuntimeInfo As ICLRRuntimeInfo = DirectCast(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty, GetType(ICLRRuntimeInfo).GUID), ICLRRuntimeInfo)
        Try
            clrRuntimeInfo.BindAsLegacyV2Runtime()
            LegacyV2RuntimeEnabledSuccessfully = True
        Catch generatedExceptionName As System.Runtime.InteropServices.COMException
            ' This occurs with an HRESULT meaning 
            ' "A different runtime was already bound to the legacy CLR version 2 activation policy."
            LegacyV2RuntimeEnabledSuccessfully = False
        End Try
    End Sub

    <System.Runtime.InteropServices.ComImport>
    <System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)>
    <System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")>
    Private Interface ICLRRuntimeInfo
        Sub xGetVersionString()
        Sub xGetRuntimeDirectory()
        Sub xIsLoaded()
        Sub xIsLoadable()
        Sub xLoadErrorString()
        Sub xLoadLibrary()
        Sub xGetProcAddress()
        Sub xGetInterface()
        Sub xSetDefaultStartupFlags()
        Sub xGetDefaultStartupFlags()

        <System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType:=System.Runtime.CompilerServices.MethodCodeType.Runtime)>
        Sub BindAsLegacyV2Runtime()
    End Interface
End Class

A utilização dessa classe é muito simples. Nós só temos que acessar a propriedade “LegacyV2RuntimeEnabledSuccessfully“, que retornará “true” caso o elemento tenha sido adicionado no startup com sucesso e “false” caso contrário. No nosso exemplo, como a exibição dos relatórios está sendo feita diretamente no projeto da aplicação, o lugar ideal para adicionarmos essa chamada seria no método “Main“:

        // C#
        [STAThread]
        static void Main()
        {
            if (RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new FormRelatorio());
            }
            else
            {
                MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

No VB.NET, por padrão, você não encontrará um método “Main” em projetos do tipo “Windows Forms Application“. Nesse caso, ou você altera as propriedades do projeto de forma que ele tenha um método “Main” (como descrito nesta thread do StackOverflow) ou você coloca a chamada dessa propriedade no construtor do formulário onde o relatório está sendo exibido:

    ' VB.NET
    Public Sub New()
        If (Not RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully) Then
            MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

Pronto! Com essas alterações, você não precisa mais definir o elemento no arquivo app.config. Ele será automaticamente adicionado em tempo de execução.

Concluindo

O disparo de uma “FileNotFoundException” é algo comum ao utilizarmos o controle do Crystal Reports em aplicações desenvolvidas com o .NET Framework 4 ou superior. Esse erro pode ser facilmente corrigido de duas maneiras: fazendo um ajuste no arquivo app.config ou adicionando dinamicamente um elemento nas políticas de runtime em tempo de execução. Neste artigo você conferiu as duas maneiras de resolver esse problema.

E você, já passou por esse problema com o Crystal Reports? Conhecia a segunda metodologia de resolução? Qual das duas opções você achou melhor? 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll appeared first on André Alves de Lima.


Trabalhando com SQLite no C# e VB.NET

$
0
0

Você sabe as diferenças entre os bancos de dados locais SQLite, SQL Compact (CE) e LocalDb? E você sabe como utilizar cada um desses bancos de dados na sua aplicação? Um dos leitores desse site me fez justamente essa pergunta, que eu não sabia responder de “bate-pronto“, então, fui pesquisar. Depois de finalizar a minha pesquisa, cheguei à conclusão que ficaria impraticável escrever somente um artigo mostrando as diferenças de cada um desses bancos locais, além de dar exemplos de como utilizá-los. É um assunto muito extenso. Portanto, resolvi escrever uma série, primeiramente mostrando como utilizá-los e, por fim, um artigo mostrando as diferenças entre eles. Nesse primeiro artigo você vai ver como utilizar o SQLite no C# e VB.NET.

Criando um banco de dados SQLite

Uma das grandes vantagens dos bancos de dados locais é que eles são facilmente transportados e distribuídos com a aplicação. Normalmente, com esse tipo de banco de dados, tudo fica armazenado em um arquivo único, sem a necessidade de um servidor rodando por trás dele. Com o SQLite, toda a estrutura das tabelas, bem como os seus dados, ficam armazenados em um arquivo de extensão “.db“. Existem duas opções para criarmos os nossos bancos de dados do SQLite: via código ou utilizando ferramentas de administração.

Uma vez criado um novo arquivo com a extensão “.db“, a inicialização das tabelas via código pode ser feita via ADO.NET puro (com comandos do tipo “CREATE TABLE“) ou através do Entity Framework Code First (onde você tem as classes de domínio e o Entity Framework cria as tabelas automaticamente para você). No final desse artigo veremos como podemos utilizar o Code First com o SQLite (que não funciona “por padrão“). Não mostrarei a criação das tabelas via comandos SQL porque esse não é um cenário muito comum. Além disso, uma vez que você saiba utilizar o SQLite com o ADO.NET (que eu vou mostrar no artigo), você pode simplesmente adaptar o código para executar comandos “CREATE TABLE“.

Ao invés de mostrar a criação do banco via comandos SQL, resolvi mostrar uma ferramenta de administração de bancos SQLite que é bastante famosa: o DB Browser for SQLite. Essa ferramenta é open source e sua interface é bem simples e eficiente.

Uma vez instalado o DB Browser, ao abrir a aplicação, temos duas opções: criar um novo banco de dados ou abrir um banco de dados já existente. No nosso caso, como nós ainda não temos nenhum banco, vamos criar um novo arquivo, clicando na opção “New Database“:

Por padrão, ao criarmos um novo banco com o DB Browser, ele mostrará uma janela para criarmos a nossa primeira tabela. Poderíamos cancelar essa tela, mas, como realmente precisamos de uma tabela para fazer os nossos testes, vamos aproveitar para cria-la agora mesmo. Dê o nome de “Cliente” para a tabela sendo criada e adicione duas colunas: “Id” (chave primária, auto incremento) e “Nome” (texto, não nulo), conforme apresentado na imagem abaixo:

Note que, mesmo depois de termos criado a tabela, ela ainda está armazenada somente em memória. Para realmente salvarmos as alterações no arquivo, temos que clicar no botão “Write Changes“:

Como qualquer outro banco de dados, no SQLite podemos criar diversas tabelas e relacionamentos (através de “foreign keys“). O SQLite suporta até mesmo triggers (apesar de eu não gostar delas). Para não complicar muito o nosso exemplo, vamos trabalhar somente com essa única tabela.

Você pode conferir o conteúdo de cada tabela dentro da aba “Browse Data” do DB Browser. Obviamente, como acabamos de criar a tabela, ela estará vazia:

Adicionando as bibliotecas do SQLite via NuGet

Agora que já temos o nosso banco de dados criado, vamos conferir como podemos utilizá-lo nas nossas aplicações .NET. Para simplificar, vamos criar um novo projeto do tipo “Console Application“. Poderíamos baixar as dlls do SQLite diretamente no site oficial, porém, existe uma maneira muito mais simples de adicionarmos o SQLite no nosso projeto: através do NuGet!

Para isso, abra o Package Manager Console ou a janela de administração dos pacotes do NuGet, faça uma busca por SQLite e instale o pacote “System.Data.SQLite“:

Nota: caso você não saiba como o NuGet funciona, confira este artigo que eu escrevi sobre ele.

No meu caso, devido a algum problema na minha instalação do Visual Studio, eu não estava conseguindo instalar esse pacote. O Package Manager Console sempre me apresentava esse erro esquisito:

Depois de pesquisar bastante, eu encontrei esta thread no StackOverflow, sugerindo que eu apagasse o arquivo Nuget.config da pasta AppData. Entretanto, essa solução não funcionou. Por sorte, acabei encontrando essa issue no GitHub do NuGet, onde uma pessoa sugeriu limpar o cache através da ferramenta de linha de comando do NuGet. E foi isso que acabou resolvendo o meu problema:

Numa outra oportunidade eu escrevo um artigo contando em detalhes esse problema que eu tive com o NuGet. Se você estiver passando pelo mesmo problema e não conseguir resolver, entre em contato comigo que eu tento te ajudar.

Acessando um banco SQLite com ADO.NET puro

Após instalarmos a biblioteca do SQLite no nosso projeto, acessar um banco de dados com ADO.NET puro é muito simples. Dentro do namespace System.Data.SQLite temos as classes usuais do ADO.NET, que são as implementações de DbConnection, DbCommand, DbDataReader, DbDataAdapter, etc. Com essas classes, conseguimos criar uma conexão com o banco para executarmos comandos SQL que, ou farão alguma alteração nos dados do banco, ou retornarão um conjunto de dados baseado em uma consulta.

Para exemplificarmos a utilização das classes ADO.NET do SQLite, vamos fazer as operações CRUD básicas (Create, Read, Update, Delete). Primeiramente, temos que abrir uma conexão passando a connection string. Você encontra os mais diversos padrões de connection strings no site www.connectionstrings.com. A string de conexão padrão do SQLite é muito simples: “Data Source=CAMINHO_DO_ARQUIVO;“. No nosso caso, vamos supor que o arquivo que criamos anteriormente tenha o nome de “banco.db” e que ele esteja presente no diretório “bin/debug” do projeto:

            // C#
            using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=banco.db;"))
            {
                conn.Open();
            }
        ' VB.NET
        Using Conn As New System.Data.SQLite.SQLiteConnection("Data Source=banco.db;")
            Conn.Open()
        End Using

Em seguida, vamos executar um comando para deletar todos os registros da tabela “Cliente“:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using

Agora que a tabela está vazia, vamos criar um novo registro contendo o nome “Novo Cliente“:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using

Até aqui, tudo bem tranquilo, não é mesmo? Só utilizamos uma instância de SQLiteCommand e chamamos o método ExecuteNonQuery. Agora, como é que fazemos para atualizar o nome desse cliente que acabamos de cadastrar? Primeiro temos que pegar o seu “Id” utilizando o método ExecuteScalar e depois efetuamos um comando de UPDATE, só que agora utilizando a funcionalidade de parâmetros:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id";
                        comm.Parameters.AddWithValue("@Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id"
                    Comm.Parameters.AddWithValue("@Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using

Nota: se você não utiliza parâmetros nos seus comandos ADO.NET, pare agora mesmo com isso. Você corre vários riscos ao concatenar valores nas suas consultas. Veja este artigo onde eu expliquei as implicações disso, além de uma demonstração de como utilizar parâmetros com o ADO.NET.

Por fim, só ficou faltando a parte “R” do CRUD, que significa “Read“, ou seja, leitura. Como é que fazemos para retornar todos os registros da tabela de clientes para listarmos os seus nomes? Fácil: é só utilizar um comando SELECT. Porém, para iterarmos nos resultados, temos duas opções: a primeira delas é utilizarmos um DataReader e a segunda opção é preenchermos uma DataTable utilizando um DataAdapter. Veja a diferença no trecho de código abaixo:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new System.Data.SQLite.SQLiteDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New System.Data.SQLite.SQLiteDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using

Pronto! Com isso nós vimos todas as operações de criação, leitura, atualização e remoção de registros com o SQLite utilizando ADO.NET puro. Veja só como ficou o código completo:

            // C#
            using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=banco.db;"))
            {
                conn.Open();

                // DELETE
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
                // INSERT
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
                // UPDATE
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id";
                        comm.Parameters.AddWithValue("@Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
                // SELECT
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new System.Data.SQLite.SQLiteDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            }
        ' VB.NET
        Using Conn As New System.Data.SQLite.SQLiteConnection("Data Source=banco.db;")
            Conn.Open()

            ' DELETE
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using
            ' INSERT
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using
            ' UPDATE
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id"
                    Comm.Parameters.AddWithValue("@Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using
            ' SELECT
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New System.Data.SQLite.SQLiteDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using
        End Using

Execute a aplicação e abra o banco no DB Browser para ver o novo registro criado na tabela de Clientes.

Acessando um banco SQLite com Entity Framework

Na seção anterior nós vimos como é simples acessarmos o SQLite através do ADO.NET puro. Mas, e se utilizamos o Entity Framework nos nossos projetos? Como é que fica a questão do SQLite? Sem problema algum! Quando adicionamos a biblioteca do SQLite pelo NuGet, ele já adiciona o suporte ao Entity Framework. Tem alguns truquezinhos para que tudo funcione corretamente, mas, não se preocupe. É isso que eu vou mostrar para você agora.

Antes de tudo, temos que criar uma nova classe que representará a nossa entidade (Cliente). Essa classe será bem simples, conforme você pode conferir abaixo:

    // C#
    public class Cliente
    {
        public long Id { get; set; }
        public string Nome { get; set; }
    }
' VB.NET
Public Class Cliente
    Public Property Id As Long
    Public Property Nome As String
End Class

Atenção! Note que o tipo do campo “Id” deve ser “long”, e não “int”. Isso se deve ao fato que todas as colunas INTEGER no SQLite são consideradas como Int64 pelo Entity Framework. Portanto, precisamos sempre utilizar o tipo “long” para evitarmos problemas.

A segunda coisa que precisamos adicionar ao trabalharmos com o Entity Framework é o contexto. Adicione uma nova classe no projeto, dando o nome de “EfContext“. Essa classe tem que herdar do DbContext do Entity Framework e, dentro dela, temos que criar um DbSet de Clientes:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)
End Class

Não sei se você está acostumado a utilizar o Entity Framework, mas, existem algumas maneiras de passarmos a string de conexão para o contexto. Para simplificar as coisas, a metodologia que eu vou utilizar neste artigo é a especificação da string de conexão diretamente no arquivo app.config.

Como o banco estará armazenado no mesmo diretório da aplicação, temos que adicionar o seguinte item dentro da tag “configuration” do arquivo app.config:

  <connectionStrings>
    <add name="EfContext" connectionString="Data Source=.\banco.db;" providerName="System.Data.SQLite.EF6" />
  </connectionStrings>

Note que o Entity Framework só conseguirá encontrar a nossa string de conexão se ela tiver exatamente o mesmo nome da nossa classe de contexto (“EfContext“).

Uma vez criada a classe de contexto, vamos utilizá-la para fazermos as operações CRUD na tabela de Clientes. A primeira coisa que temos que fazer é criarmos uma instância do contexto:

            // C#
            using (var contexto = new EfContext())
            {

            }
        ' VB.NET
        Using Contexto = New EfContext()

        End Using

Em seguida, como fizemos anteriormente com o ADO.NET puro, vamos deletar todos os clientes da tabela de clientes:

                // C#
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();
            ' VB.NET
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

E aí começam os problemas do SQLite com o Entity Framework. Tente executar a aplicação com o trecho de código acima e receba esse belo erro:

Unable to determine the provider name for provider factory of type ‘System.Data.SQLite.SQLiteFactory’. Make sure that the ADO.NET provider is installed or registered in the application config.

Esse erro acontece porque o arquivo app.config não é devidamente alterado para que consigamos utilizar o SQLite com o Entity Framework. Ao adicionarmos o SQLite ao nosso projeto, ele até faz algumas alterações referentes à utilização do SQLite com o Entity Framework, mas, infelizmente, algumas configurações ficam faltando. Para que consigamos fazer o SQLite funcionar com o Entity Framework, temos que adicionar três chaves no arquivo app.config:

E tome cuidado porque essas chaves precisam estar exatamente nessa ordem. Dependendo da ordem que você as colocar, o projeto continuará não funcionando.

Veja como deve ficar a chave “entityframework/providers” do seu app.config:

    <providers>
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
    </providers>

E aqui temos a chave “system.data/DbProviderFactories“:

      <DbProviderFactories>
        <remove invariant="System.Data.SQLite" />
        <remove invariant="System.Data.SQLite.EF6" />
        <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
        <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />    
      </DbProviderFactories>

Pronto. Com essas alterações nós conseguimos resolver o primeiro problema, ou seja, agora o Entity Framework conseguirá encontrar as informações necessárias para utilizarmos o SQLite. Porém, ao tentarmos executar o projeto novamente, receberemos um segundo erro:

Ao abrirmos os detalhes da Inner Exception, fica claro o motivo de estarmos recebendo esse erro:

O Entity Framework não está conseguindo encontrar a tabela chamada “Clientes“. Mas, a nossa tabela se chama “Cliente“, por que é que o Entity Framework está considerando “Clientes“? Simples: por padrão, o Entity Framework pluraliza os nomes das tabelas. Dessa forma, mesmo tendo dado o nome de “Cliente” para o DbSet, o Entity Framework pluralizará e considerará “Clientes” no seu lugar.

Para desativarmos a pluralização do Entity Framework, temos que fazer um “override” no método “OnModelCreating” do nosso contexto. Dentro desse método, nós encontramos e removemos a convenção da pluralização do modelo:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
        }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)

    Protected Overrides Sub OnModelCreating(modelBuilder As Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.Conventions.Remove(Of System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention)()
    End Sub
End Class

Agora sim, finalmente, ao executarmos a aplicação, não teremos nenhum erro e a tabela “Cliente” ficará vazia, pois estamos deletando todos os registros dela. Com isso, podemos continuar com a próxima etapa das nossas operações CRUD: a inserção de um novo cliente. O código nesse caso é muito simples. Basta adicionarmos uma nova instância da classe “Cliente” no nosso DbSet de Clientes e, logo em seguida, ao chamarmos o método “SaveChanges” um novo cliente será adicionado no banco de dados:

                // C#
                contexto.Cliente.Add(new Cliente() { Nome = "Novo Cliente EF" });
                contexto.SaveChanges();
            ' VB.NET
            Contexto.Cliente.Add(New Cliente() With {.Nome = "Novo Cliente EF"})
            Contexto.SaveChanges()

Em seguida, temos o código para alterarmos um cliente. Nesse caso, temos que recuperar o cliente do nosso DbSet, fazer as alterações desejadas e, por fim, temos que chamar o método “SaveChanges” para persistir as alterações (estou utilizando o método “First” porque só temos um cliente cadastrado – em um cenário real, teríamos que fazer uma pesquisa no DbSet através do “Id” do cliente):

                // C#
                var cliente = contexto.Cliente.First();
                cliente.Nome = "Novo Cliente EF Alterado";
                contexto.SaveChanges();
            ' VB.NET
            Dim Cliente = Contexto.Cliente.First()
            Cliente.Nome = "Novo Cliente EF Alterado"
            Contexto.SaveChanges()

Por fim, para listarmos as informações dos nossos clientes, basta percorrermos o DbSet com um laço “foreach“:

                // C#
                foreach (var c in contexto.Cliente)
                {
                    Console.WriteLine("Nome do Cliente: {0}", c.Nome);
                }
            ' VB.NET
            For Each C In Contexto.Cliente
                Console.WriteLine("Nome do Cliente: {0}", C.Nome)
            Next

Veja só como fica o código completo:

            // C#
            using (var contexto = new EfContext())
            {
                // DELETE
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();

                // INSERT
                contexto.Cliente.Add(new Cliente() { Nome = "Novo Cliente EF" });
                contexto.SaveChanges();

                // UPDATE
                var cliente = contexto.Cliente.First();
                cliente.Nome = "Novo Cliente EF Alterado";
                contexto.SaveChanges();

                // SELECT
                foreach (var c in contexto.Cliente)
                {
                    Console.WriteLine("Nome do Cliente: {0}", c.Nome);
                }
            }
        ' VB.NET
        Using Contexto = New EfContext()
            ' DELETE
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

            ' INSERT
            Contexto.Cliente.Add(New Cliente() With {.Nome = "Novo Cliente EF"})
            Contexto.SaveChanges()

            ' UPDATE
            Dim Cliente = Contexto.Cliente.First()
            Cliente.Nome = "Novo Cliente EF Alterado"
            Contexto.SaveChanges()

            ' SELECT
            For Each C In Contexto.Cliente
                Console.WriteLine("Nome do Cliente: {0}", C.Nome)
            Next
        End Using

E o code first?

Uma das grandes maravilhas do Entity Framework é a funcionalidade chamada “Code First“. Com ela, nós podemos criar o banco de dados através das nossas classes de domínio. Com o SQL Server nós temos essa funcionalidade nativamente, mas, e com o SQLite? Nesse caso, o “Code First” não está disponível por padrão, mas, existe uma biblioteca que adiciona essa funcionalidade: a SQLite.CodeFirst.

Após adicionarmos essa biblioteca através do NuGet, podemos ativar o “Code First” no nosso contexto. Isso é muito simples de ser feito, basta adicionarmos uma linha no método “OnModelCreating” do nosso contexto:

// C#
Database.SetInitializer(new SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists<EfContext>(modelBuilder));
' VB.NET
System.Data.Entity.Database.SetInitializer(New Sqlite.CodeFirst.SqliteCreateDatabaseIfNotExists(Of EfContext)(modelBuilder))

Pronto! Só com a adição dessa linha de código o Entity Framework criará o banco de dados automaticamente baseado nas entidades do seu contexto (caso o banco ainda não exista).

Concluindo

O SQLite é um dos tipos de bancos de dados “portáteis” disponíveis atualmente (e, a propósito, um dos mais utilizados). Este artigo foi um guia definitivo da utilização do SQLite com C# e VB.NET, onde eu mostrei uma ferramenta de administração de bancos SQLite, a utilização do SQLite com ADO.NET puro, a utilização do SQLite com Entity Framework e, por fim, mostrei como podemos habilitar o “Code First” do Entity Framework em projetos que utilizam o SQLite.

Você já conhecia o esse banco de dados? Como é que você faz para acessar os dados desse banco na sua aplicação? Você uiliza ADO.NET puro ou Entity Framework? Conhecia todas essas bibliotecas que eu mostrei no artigo? Conte-nos mais sobre as suas experiências 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com SQLite no C# e VB.NET appeared first on André Alves de Lima.

Report Viewer foi descontinuado pela Microsoft? Não!

$
0
0

É sempre a mesma coisa. A cada nova versão do Visual Studio surge aquele frio na barriga com relação à ferramenta de relatórios da Microsoft: será que o Report Viewer foi descontinuado? Isso aconteceu com bastante força no lançamento do Visual Studio 2015, quando a Microsoft decidiu remover o Report Viewer da instalação “típica” do Visual Studio, fazendo com que ele só aparecesse se você selecionasse o item “SQL Server Data Tools” durante a instalação do Visual Studio (inclusive eu mostro como fazer isso neste artigo).

Com a nova versão do Visual Studio (até o momento denominada de Visual Studio “15“), isso não poderia ser diferente. Nessa versão, a sensação é ainda pior. Mesmo instalando o “SQL Server Data Tools“, o Report Viewer ainda não aparece! Por que será? Teria a Microsoft realmente descontinuado o Report Viewer?

Não se assuste, até agora o Report Viewer não foi descontinuado pela Microsoft. Porém, a partir da próxima versão do Visual Studio, o modelo de distribuição dos controles e designers passarão por grandes mudanças. Confira o restante do artigo para ver os detalhes dessa mudança.

O novo modelo de distribuição do Report Viewer

Por ser o produto que eu escolhi focar nos últimos dois anos, eu tenho acompanhado de perto o que está acontecendo no mundo do Report Viewer / Reporting Services 2016. Até configurei uns alertas no Google para alguns termos relacionados a essas tecnologias. Dessa forma, sempre que algum novo artigo é indexado pelo Google, eu fico sabendo e vou lá dar uma olhada.

Foi através desse tipo de alerta que eu fiquei sabendo desta thread nós fóruns da MSDN americana. Nessa thread descobri qual será o novo modelo de distribuição do controle e do designer do Report Viewer / Reporting Services integrado ao Visual Studio.

Vejam só este trecho da thread, publicado pelo Brad Syrupta, engenheiro de software que trabalha no time de SQL Server BI na Microsoft:

Ou seja, o controle do Report Viewer não será mais distribuído em conjunto com o Visual Studio. A partir da próxima versão, ele será distribuído através de um pacote do NuGet. Essa é uma estratégia que a Microsoft tem utilizado com diversas outras tecnologias (como Entity Framework, por exemplo). Já a parte de designer dos relatórios será disponibilizada através de uma extensão do Visual Studio (VSIX), que será publicada na Visual Studio Gallery.

Qual o motivo da mudança?

Não podemos nos esquecer que a Microsoft é uma empresa gigantesca, formada por inúmeros times. Agora, vamos pensar somente na divisão de ferramentas para desenvolvimento de software (Visual Studio, .NET, etc). Já pensou a dificuldade que deve ser para sincronizar os diversos times envolvidos nesse processo?

Desde o Visual Studio 2005 até o Visual Studio 2015, o controle e o designer de relatórios do Report Viewer foram distribuídos juntamente com o Visual Studio. Isso traz um grande desafio para os times, uma vez que nem sempre as releases do Visual Studio estão sincronizadas com as releases do SQL Server (time por trás da experiência de relatórios RDL e RDLC).

Para tentar diminuir esse stress (entre outras coisas, como custos com recursos, manutenção, estratégias do produto, etc), a nova metodologia de distribuição fará com que as releases do controle do Report Viewer não estejam mais atreladas às releases do Visual Studio. Ou seja, nós não vamos mais precisar esperar que uma nova versão do Visual Studio seja lançada para podermos utilizar as novas versões do controle e do designer do Report Viewer.

Veja só este outro trecho do post do Brad Syrupta:

Testando a versão preview do controle do Report Viewer 2016

A primeira versão de testes do controle do Report Viewer 2016 foi lançada no dia 20 de setembro. Tanto o controle Windows Forms quanto o controle Web Forms estão disponíveis através de um pacote do NuGet. Para instalar o pacote, abra a janela de gerenciamento de pacotes do NuGet e procure por “reportviewercontrol“:

Outra opção é fazer a instalação do pacote através do Package Manager Console, utilizando estes nomes de pacotes (WinForms ou WebForms, dependendo do tipo do seu projeto):

– Microsoft.ReportingServices.ReportViewerControl.WinForms.Preview

– Microsoft.ReportingServices.ReportViewerControl.WebForms.Preview

Para mais informações sobre o gerenciamento de pacotes do NuGet através do Visual Studio, confira este artigo.

Para projetos Windows Forms, você não precisa fazer nenhum ajuste e o novo controle deve funcionar normalmente. Obviamente, se você já utilizava uma versão anterior do controle do Report Viewer, você precisa remover as referências antigas antes de adicionar o pacote da versão preview.

Para projetos web, se você já utilizava uma versão anterior do controle do Report Viewer, você terá que alterar algumas referências, apontando para a versão mais nova do controle (13.0.0.0). O primeiro lugar que você tem que alterar é a tag “Register” na página onde você utiliza o controle do Report Viewer:

O novo conteúdo dessa tag deve ser o seguinte:

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

Depois, temos que ajustar alguns pontos do arquivo web.config. O primeiro lugar é a referência para os assemblies (tag “assemblies“, dentro de “compilation“). Você deve assegurar-se que essas referências estejam apontando para a versão mais nova do controle:

      <assemblies>
        <add assembly="Microsoft.ReportViewer.Common, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" />
        <add assembly="Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" />
      </assemblies>

Em seguida, temos que alterar o conteúdo da tag “httpHandlers“:

    <httpHandlers>
      <add path="Reserved.ReportViewerWebControl.axd" verb="*" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" validate="false" />
    </httpHandlers>

Por fim, temos que fazer a mesma alteração na tag “handlers“, dentro de “system.webserver“:

    <handlers>
      <add name="ReportViewerWebControlHandler" verb="*" path="Reserved.ReportViewerWebControl.axd" preCondition="integratedMode"
        type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91"/>
    </handlers>

Com essas alterações, o seu projeto deve rodar sem nenhum problema de compilação.

Resultados Windows Forms

Depois de testar o controle do Windows Forms, eu não consegui detectar nenhuma diferença visual no controle. Também fiz um teste de performance com um relatório contendo um milhão de registros e não consegui notar nenhuma diferença na velocidade no processamento. Veja só a “cara” do controle, que é idêntica à da versão anterior:

Resultados Web Forms

Por outro lado, o controle Web Forms foi completamente reformulado. Pelo que andei lendo, a Microsoft decidiu abandonar de vez a utilização de ActiveX para a impressão dos relatórios e está renderizando todo o conteúdo em HTML5. Porém, no relatório que eu testei as coisas não funcionaram muito bem:

Veja só o mesmo relatório sendo exibido sem problema algum na versão anterior do Report Viewer:

Testei o mesmo relatório tanto no Google Chrome quanto no Internet Explorer e Edge. O resultado foi o mesmo. Tentei também adicionar a tag “meta” forçando que sites intranet não sejam renderizados em modo compatibilidade (como indicado na seção “Common issuesdeste link), mas, também não tive sucesso.

Consertando possíveis problemas com o controle web

Depois de finalizar a escrita deste artigo, eu estava dando uma olhada no fórum de Reporting Services da MSDN americana e acabei encontrando esta thread. Seguindo as instruções apresentadas nela, a versão nova do controle web do Report Viewer funcionou corretamente:

O problema acontece basicamente porque a última versão do SQL Server Data Tools (instalada com o Visual Studio 2015 e seus updates) adiciona no GAC uma versão do novo controle que ainda não estava finalizada. E como as dlls do GAC têm mais prioridade do que as dlls presentes na pasta da aplicação, o IIS acaba carregando essa versão incorreta do GAC.

Para corrigirmos esse problema, precisamos remover as dlls problemáticas do GAC. Conseguimos remover dlls do GAC utilizando a ferramenta “gacutil“. Essa ferramenta fica armazenada na pasta “C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin“. Abra um prompt de comando elevado (como administrador), navegue até esse diretório e tente executar os seguintes comandos para remover todas as dlls problemáticas do Report Viewer do GAC:

gacutil /u "Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.WinForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.WebDesign, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.Common, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f

Se esses comandos forem executados sem nenhum erro, o seu ambiente já estará pronto. Porém, no meu caso eu recebi um erro ao tentar remover as dlls do GAC:

Unable to uninstall: assembly is required by one or more applications

Pending references:

SCHEME: <WINDOWS_INSTALLER> ID: <MSI> DESCRIPTION:<Windows Installer>

Depois de pesquisar um pouco sobre esse erro, acabei encontrando este artigo, que explica claramente que o problema é que essas dlls estão sendo referenciadas pelo Windows Installer no registro do Windows. Se isso também estiver acontecendo no seu ambiente, você terá que primeiramente remover as suas referências do Windows Installer no registro do Windows.

No meu computador, eu encontrei as referências na pasta “HKLM\SOFTWARE\Classes\Installer\Assemblies\Global“. Porém, pode ser que em algumas instalações as referências estejam em “HKCU\Software\Microsoft\Installer\Assemblies\Global“, portanto, é importante procurar nessas duas pastas do registro. Uma vez encontradas as chaves do Report Viewer, delete tudo o que tiver versão “13” (não esqueça de fazer um backup do registro antes de deletar as chaves!):

Em seguida, execute novamente os comandos para remover as dlls do GAC e veja que, dessa vez, os comandos funcionam com sucesso:

Concluindo

Sempre que uma nova versão do Visual Studio é lançada (ou está prestes a ser lançada), surgem os rumores que o Report Viewer esteja sendo descontinuado pela Microsoft. Até hoje, isso nunca se concretizou e espero que não se concretize num futuro próximo.

Como você conferiu neste artigo, por razões estratégicas, a Microsoft está alterando o modelo de distribuição do Report Viewer a partir da próxima versão do Visual Studio. Esse novo modelo não será mais dependente das releases do Visual Studio, ou seja, poderemos ter novas versões do controle e das ferramentas de design independentemente das datas de release do Visual Studio.

O que você achou dessa nova estratégia? E o que você achou do novo visual do controle web do Report Viewer, desenvolvido completamente em HTML5? Fico aguardando as suas opiniões 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Report Viewer foi descontinuado pela Microsoft? Não! appeared first on André Alves de Lima.

Conheça o que vem aí na próxima versão do Visual Studio

$
0
0

A próxima versão do Visual Studio, até agora denominada Visual Studio “15” e que provavelmente se chamará Visual Studio 2017 (se a Microsoft continuar com a mesma estratégia de nomenclatura), está atualmente em Preview 5. Indiscutivelmente, o foco dessa versão tem sido a melhoria de performance nos mais diversos aspectos do Visual Studio, indo desde a experiência de instalação até a responsividade do editor, passando pelo tempo de carregamento do Visual Studio e tempo de carregamento das soluções. Tudo isso está sendo otimizado para que nós, desenvolvedores, não percamos tempo desnecessário e foquemos no que realmente nos interessa: programar.

Instalação mais rápida e limpa

A primeira experiência que temos com qualquer aplicação é com o seu instalador. Se o processo de instalação de um aplicativo apresenta problemas ou demora demais, nós já ficamos com um pé atrás. Esse é um comportamento natural dos seres humanos. Com certeza você já deve ter ouvido aquela expressão que “a primeira impressão é a que fica“, não é mesmo?

Pois bem. Se você já instalou qualquer edição do Visual Studio até a 2015, você sabe que o processo é demorado e toma um espaço considerável em disco. Não é à toa que a maior reclamação sobre o Visual Studio no Twitter diz respeito ao tempo de instalação e/ou o espaço em disco necessário.

Se para nós já era claro que a experiência de instalação do Visual Studio era demorada, imagina só para a Microsoft, que provavelmente ouve essa reclamação diariamente?

Para melhorar o tempo de download e instalação do Visual Studio, a Microsoft reconstruiu do zero o instalador para a próxima versão do Visual Studio:

Como você pode perceber, o novo instalador está dividido em “bundles“, que você deverá escolher dependendo das suas necessidades. Até o instalador do Visual Studio 2015, você tinha que escolher exatamente os itens que você gostaria de instalar, tendo que tomar cuidado para não esquecer um item que seja imprescindível nos seus projetos. Eu vejo muita gente escolhendo fazer a instalação completa por não saber exatamente o que escolher, justamente porque uma lista com 100 opções para selecionar deixa qualquer pessoa confusa.

Os “bundles” foram introduzidos para acabar com essa confusão. Seus nomes são mais claros e objetivos. Você desenvolve aplicativos desktop? Então é uma boa ideia marcar o item “.NET desktop development“. Você só trabalha com desenvolvimento web? Então provavelmente você não precisará desse “bundle“, mas sim, do “Web Development“.

Além disso, a Microsoft está criando agora a opção de instalação “Visual Studio Core Editor“, que instalará somente o editor, compiladores, ferramentas de debugging e suporte a controle de versão (TFS/Git). Essa instalação mínima só consumirá aproximadamente 325 Mb em disco, ao invés dos 6 Gb necessários para a instalação mínima do Visual Studio 2015.

Por fim, toda essa experiência de instalação ficará ainda melhor, porque a Microsoft otimizou o processo e, teoricamente, a instalação deverá demorar muito menos tempo do que a instalação do Visual Studio 2015. O Giovanni Bassi cronometrou o tempo de instalação do Visual Studio “15” e o resultado foi surpreendente: 2 minutos e meio para concluir a instalação do “Visual Studio Core Editor” e 13 minutos para concluir a instalação do Visual Studio com as ferramentas para desenvolvimento web. Muito melhor do que os tempos de instalação do Visual Studio 2015.

Mais para a frente eu também quero fazer um teste parecido, utilizando duas máquinas virtuais idênticas no Microsoft Azure, uma instalando o Visual Studio 2015 completo e outra instalando o Visual Studio “15” completo, ambas a partir da ISO (que ainda não está disponível para as versões de Preview do Visual Studio “15” – por isso que eu ainda não fiz o teste).

Se você quiser saber mais sobre o novo instalador da próxima versão do Visual Studio, confira estes links:

Faster, Leaner, Focused on Your Development Needs: The New Visual Studio Installer

Visual Studio “15”: Installing Just What You Need

Anatomy of a Low Impact Visual Studio Install

Testamos o tempo de instalação do Visual Studio “15”

Domingo Surpresa – Visual Studio “15” Preview 5

Melhorias de performance: inicialização e carregamento de soluções

Não é só a experiência de instalação que está sendo melhorada na próxima versão do Visual Studio. Como eu mencionei no início do post, a Microsoft está focando muito na melhoria de performance dos mais diversos aspectos do Visual Studio “15“.

Como você deve saber, a inicialização do Visual Studio pode ser um tanto quanto demorada em alguns computadores. Em especial, a primeira execução do Visual Studio (first launch) tende a ser bem demorada. Isso acontece porque vários caches são criados nessa primeira execução do Visual Studio (como MEF, extensões, caixa de ferramentas, fontes, etc).

No Visual Studio “15“, a Microsoft alterou a criação de várias dessas caches para um modelo assíncrono. O que não deu para ser feito assíncrono, ela alterou para um modelo “deferido“, ou seja, alguns componentes só serão carregados quando o Visual Studio perceber que você precisará deles (como a caixa de ferramentas e lista de fontes, por exemplo). Todas essas melhorias resultaram em uma primeira execução do Visual Studio “15” que é 3 vezes mais rápida do que o Visual Studio 2015.

Já quanto às outras inicializações, a Microsoft implementou a possibilidade do carregamento assíncrono e “on-demand” de extensões. Porém, eu posso estar errado, mas, pelo que eu entendi, isso não será automático. Cada autor de extensão deverá implementar esse suporte em cada uma das suas extensões. Dessa forma, nós ainda vamos demorar um pouco para sentirmos o efeito dessa melhoria. Até o momento, a Microsoft implementou o esquema de carregamento sob demanda para as extensões da Xamarin e do Python.

Outra melhoria que a Microsoft está implementando nesse aspecto é uma janela que mostrará quais extensões e janelas de ferramentas que estão afetando negativamente o carregamento do seu Visual Studio. Nessa janela você pode decidir o que fazer com esses itens (desativar a extensão/ferramenta ou mantê-la ativada mesmo assim):

Além disso, a Microsoft adicionou uma outra funcionalidade que melhorará o tempo de carregamento de soluções grandes. Essa nova funcionalidade está sendo chamada de “Lightweight Solution Load“. Essa opção não virá habilitada por padrão e você só deve ativá-la caso você trabalhe com soluções médias ou grandes (Tools -> Options -> Projects and Solutions -> General -> Lightweight Solution Load). Com essa funcionalidade ativada, ao carregar uma solução, você ainda conseguirá navegar pelo código (com “Navigate To“, “Go to definition“, “Find all references“, etc), aplicar refactorings e compilar a solução. Porém, algumas operações serão feitas somente sob demanda.

Veja só este vídeo que a Microsoft gravou comparando o tempo de abertura da solução do Roslyn (que é composta por centenas de projetos) no Visual Studio 2015 e Visual Studio “15“:

Para saber mais sobre essas melhorias do Visual Studio, confira estes links:

Faster Visual Studio “15” Startup

Shorter Solution Load Time in Visual Studio “15”

Redução nos estouros de memória

Não sei se você sabe, mas, o Visual Studio ainda é um executável 32 bits. Isso quer dizer que o seu processo é limitado a 4 Gb de memória RAM. Uma vez que o processo do Visual Studio ultrapassa esse limite, duas coisas podem acontecer: ou ele começa a não funcionar direito (começa a agir de modo “estranho“) ou ele trava por completo, dando um erro de “out of memory“. Você provavelmente já passou por essas situações. Todo desenvolvedor Microsoft sabe que, quando o Visual Studio começa a não funcionar direito, é hora de fechar e abrir de novo. Isso provavelmente acontece porque o processo estava chegando perto do limite de 4 Gb de memória.

Para melhorar essa situação na nova versão do Visual Studio, a Microsoft está separando várias ferramentas em sub-processos. Dessa forma, ela diminui a carga do processo principal (devenv.exe), reduzindo, consequentemente, a chance de o Visual Studio travar por falta de memória.

Um dos serviços que foram migrados para um processo separado foi o serviço que implementa a compatibilidade com o Javascript no Visual Studio (IntelliSense para o Javascript, navegação dentro de arquivos Javascript, etc), que é necessário em aproximadamente um terço das soluções.

Outro serviço substituído nessa otimização foi o suporte ao Git. A versão 2015 do Visual Studio utiliza internamente uma biblioteca chamada “libgit2” para implementar as funcionalidades do Git. Na nova versão do Visual Studio a Microsoft está substituindo essa biblioteca pelo uso direto do executável “git.exe“, o que obviamente reduziu a utilização de memória do processo principal, além de ter trazido também diversas melhorias de responsividade.

Por fim, a Microsoft está otimizando o carregamento de símbolos (arquivos pdb) durante o processo de debugging. Em algumas situações o Visual Studio estava carregando mais arquivos de símbolos do que era necessário, e isso foi corrigido.

Para mais informações sobre esse tema, confira este link:

Reduced Out of Memory Crashes in Visual Studio “15”

Responsividade melhorada

Sabe quando tentamos digitar algum código no editor do Visual Studio e o editor parece que trabalha em “slow motion“? Sabe aquela impressão que enquanto estamos digitando a terceira palavra o editor ainda nem acabou de digitar a primeira palavra? Ou quando clicamos em alguma opção e o Visual Studio dá aquela travada até fazer o que deveria ser feito? Ou até mesmo quando clicamos no botão para iniciar e a aplicação e o modo de debugging demora a iniciar? Obviamente a Microsoft procura melhorar a responsividade dessas operações a cada nova versão do Visual Studio.

No Visual Studio “15” a Microsoft está removendo o processo host para aplicações WPF, Windows Forms e Console em tempo de debugging. Não sei se você já percebeu, mas, quando debugamos uma aplicação desse tipo, o Visual Studio inicia um processo com extensão “.vshost.exe“. Esse processo servia para deixar o próximo processo de debugging mais rápido. Porém, ele estava causando uma certa lerdeza quando clicávamos no botão para parar a execução da aplicação (inclusive para outros tipos de projetos que nem utilizavam esse host, como projetos ASP.NET, UWP, etc). Por isso a Microsoft decidiu removê-lo e otimizou o processo de debugging de forma que ele está mais rápido do que no Visual Studio 2015, e sem o problema da lerdeza ao clicarmos no botão para parar a execução da aplicação.

Além disso, a Microsoft melhorou também a performance do editor XAML, principalmente no que diz respeito à mudança de uma tab para outra. Muito do trabalho que era feito diretamente no editor será feito agora em uma thread em segundo plano, o que melhorou consideravelmente o tempo de resposta do editor.

Por fim, na janela “Manage Visual Studio Performance” nós encontraremos agora uma lista de extensões que estão possivelmente causando aquela sensação de lerdeza do editor (será que o ReSharper vai aparecer nessa lista?):

Para saber mais sobre as melhorias de responsividade no Visual Studio “15”, confira este link:

Improved overall Visual Studio “15” Responsiveness

Melhorias de produtividade

Não são só melhorias de performance que a Microsoft está implementando na próxima versão do Visual Studio. Além de tudo funcionar mais rápido, ela também está trazendo diversas novas funcionalidades muito interessantes. Dessas novas funcionalidades, duas me chamaram a atenção. A primeira delas é o fato que o IntelliSense agora está ficando mais inteligente, filtrando e selecionando automaticamente as opções disponíveis de acordo com o contexto. Ou seja, se você está chamando um método que recebe um tipo específico de objeto, o IntelliSense automaticamente selecionará e/ou filtrará as opções de forma que somente objetos do tipo esperado sejam exibidos na lista.

A segunda funcionalidade que me chamou a atenção foi o “Run to Click“. Sabe quando estamos debugando um código e queremos que a execução avance até uma certa linha? Nesse caso nós normalmente colocamos um breakpoint e apertamos “F5” para o programa rodar até aquele ponto. Com essa nova funcionalidade, nós não precisamos mais disso. Um novo botão será exibido no começo de cada linha de código, cuja função será fazer com que a execução corra até a linha escolhida. Veja só que legal o novo botãozinho verde com símbolo de “avançar” no editor de texto:

Para dar uma olhada em outras funcionalidades de produtividade que estão sendo adicionadas no Visual Studio “15“, acesse este link:

Announcing Visual Studio “15” Preview 5

Onde baixar?

Gostou do que viu até aqui? Quer baixar a versão de preview para testar você mesmo? É só acessar este link:

http://aka.ms/vs/15/preview/vs_enterprise

Só tome cuidado porque existem alguns problemas de compatibilidade com versões anteriores do Visual Studio “15“, que podem até mesmo fazer com que funcionalidades do Visual Studio 2015 parem de funcionar. Sugiro que você dê uma olhada neste post do Renato Groffe para saber exatamente quais são esses problemas de compatibilidade.

Concluindo

A nova versão do Visual Studio está trazendo diversas melhorias de performance, responsividade, redução na utilização de memória, além de várias novas funcionalidades que aumentarão a sua produtividade no desenvolvimento dos seus aplicativos. Neste post você conferiu uma lista completa de todas as novas funcionalidades e melhorias divulgadas até agora e já disponíveis no Preview 5. Não perca tempo, baixe agora mesmo e fique por dentro das novidades você também!

O que você achou das melhorias que estão por vir na próxima versão do Visual Studio? Deixe a sua opinião 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

Images by MSDN
https://blogs.msdn.microsoft.com/visualstudio/2016/10/05/announcing-visual-studio-15-preview-5/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Conheça o que vem aí na próxima versão do Visual Studio appeared first on André Alves de Lima.

Utilizando o Oracle com C# e VB.NET

$
0
0

De acordo com o ranking do site DB-Engines, o Oracle é atualmente o banco de dados relacional mais utilizado no mundo. Quando eu comecei a trabalhar na Savcor em 2008, eu lembro que a instalação e configuração do Oracle, bem como a instalação do provider .NET, eram desafiadores. Eram muitos detalhes que tínhamos que prestar atenção para que tudo funcionasse corretamente. E o PL/SQL Developer? Como eu tinha ódio daquela ferramenta.

Desde 2008 eu venho trabalhando somente com SQL Server nas aplicações que eu tenho desenvolvido. Mas, como sempre é bom ficar por dentro das mais diversas tecnologias (principalmente quando estamos falando do banco de dados relacional mais utilizado do mundo), eu resolvi dar uma olhada no processo da instalação, configuração, gerenciamento e utilização do Oracle com C# e VB.NET. Veja só o que eu aprendi.

Instalando o Oracle Database Express

Como eu não trabalho normalmente com o Oracle, eu não entendo muito do modelo de licenciamento dele, ou seja, não sei quanto custa a licença da versão Standard ou Enterprise e não consegui encontrar a informação no site (aliás, que site complicado, não?). Inclusive, se alguém tiver mais experiência nessa área, fala para gente como é que funciona lá na caixa de comentários (ou me manda um e-mail com os detalhes aí eu coloco as informações no artigo dando crédito para você).

Dito isso, ao invés de me aventurar com alguma edição mais profissional do Oracle, eu decidi utilizar a edição Oracle Database Express, que é gratuita. No site da Oracle, procure pelo item “Oracle Database 11g Express“:

Aceite os termos de licença e baixe a versão que fizer mais sentido para você:

A instalação é muito simples, no estilo “next, next, finish” das instalações de aplicativos Windows. Basicamente a única coisa que você precisa escolher é a senha do usuário administrador.

Uma vez que você for apresentado com esta tela, a instalação foi concluída com sucesso:

Logo de cara, vi que a instalação criou um ícone no desktop, além de algumas entradas no menu iniciar:

Porém, ao tentar executar a opção “Get Started with Oracle Database“, recebi essa bela mensagem de erro:

Que bela experiência pós-instalação, hein Oracle? Enfim, pesquisei rapidamente sobre esse erro e encontrei esta thread no StackOverflow. Seguindo as instruções, chamei a URL “http://localhost:8080/apex/f?p=4950” no browser e, aí sim, consegui abrir as opções de configuração do Oracle:

Muito bem, com isso eu tive a certeza que o banco de dados foi realmente instalado com sucesso e que ele estava rodando.

Limitações da edição Express

Antes de continuarmos com o artigo, vamos dar uma olhada nas limitações do Oracle Database Express. Como toda versão gratuita, é natural esperarmos que ela seja limitada de alguma forma.

Como podemos observar na Wikipedia, a primeira versão do Oracle Database Express (10g, lançada em 2005) tinha as seguintes limitações:

– Utiliza somente um CPU do computador onde estiver instalado
– Somente um máximo de 1GB de memória RAM é utilizado pelo processo
– Banco de dados limitado a 4GB de armazenamento

Depois de um tempo, a partir da versão 11g (lançada em 2011), a Oracle decidiu aumentar o limite de armazenamento do banco de dados para 11GB. Essas limitações são quase idênticas às limitações do SQL Server Express (com a única diferença que o SQL Server Express tem um limite de 10GB por database, ao invés de 11GB do Oracle). É claro que você não vai colocar o ERP da sua empresa rodando em uma versão gratuita do Oracle ou SQL Server (pelo menos não deveria), mas, mesmo com essas limitações, dá para rodar uns sistemas bem consideráveis com essa edição gratuita.

Ferramentas de administração

OK. Até agora nós já conseguimos instalar o banco de dados e entendemos as limitações da edição Express. E agora, cadê o correspondente ao “SQL Server Management Studio” do Oracle? Como eu falei anteriormente, um dos meus maiores pesadelos quando trabalhei com Oracle no passado foi a ferramenta PL/SQL Developer que, comparada ao SQL Server Management Studio, não chega nem perto (na minha opinião), além de ser paga. Então, resolvi pesquisar sobre ferramentas gratuitas de administração do Oracle e encontrei esta outra thread no StackOverflow, onde acabei descobrindo que as principais ferramentas são o Oracle SQL Developer (da própria Oracle) e o Database.NET.

Eu comecei dando uma olhada no Oracle SQL Developer e ele acabou servindo para o que eu estava precisando neste artigo (administração básica do banco de dados, criação de usuários, tabelas, etc). Dessa forma, vou utilizá-lo nesse artigo para criarmos o banco de dados. Se você estiver mais familiarizado com o Dababase.NET ou alguma outra ferramenta de administração, fique à vontade para utilizar a ferramenta que você preferir.

Criando uma tabela com o Oracle SQL Developer

O Oracle SQL Developer pode ser baixado no próprio site da Oracle:

Atente-se para baixar a versão que já vem com o JDK, senão você precisará ter o JDK instalado no seu computador:

Uma coisa interessante é que nós não precisamos, de fato, instalar o Oracle SQL Developer. Ele é um arquivo zip que você pode descompactar no seu computador e executar o arquivo “sqldeveloper.exe” para abrir a aplicação.

Com a aplicação aberta, tentei criar uma nova conexão utilizando o usuário admin (SYS), mas, recebi um erro falando que só consigo conectar como “SYS” utilizando a role SYSDBA ou SYSOPER. Muito bem, o problema é que eu deixei a role “Default” na hora de conectar:

Basta mudar o valor no ComboBox “Role” para SYSDBA que a conexão funcionará normalmente (não sei porque o banco de dados já vem com umas 500 tabelas já criadas, mas, tudo bem – OK, acabei aprendendo depois que essas tabelas apareceram porque eu loguei com o usuário de sistema):

Para analisarmos o funcionamento do Oracle em uma aplicação de exemplo, vamos seguir a mesma estratégia que eu utilizei no artigo sobre SQLite e vamos criar uma nova tabela extremamente simples, chamada “Cliente“, contendo somente uma coluna “Id” (chave primária, auto-incremento) e “Nome“:

Onde é que nós definimos uma coluna como auto-incremento no Oracle? Pois bem. Se você não sabe, o Oracle não tem uma coluna do tipo “auto-incremento” como o SQL Server ou MySQL. No Oracle isso é feito através de uma funcionalidade chamada “sequence” em conjunto com uma trigger de inserção de registros na tabela. Felizmente, com o Oracle SQL Developer, nós não temos que criar tudo isso manualmente. Ao ativarmos o modo avançado, nós conseguimos encontrar a opção “Identity Column” na tela de criação de tabelas. Aí é só alterar o tipo para “Column Sequence” e a coluna terá o comportamento de auto-incremento:

Será que se a gente clicar em “OK” a criação dessa simples tabela vai funcionar? É claro que não! Para não perder o costume, receberemos esse belo erro:

Como você pode perceber pela mensagem de erro, o Oracle não permite a criação de triggers em objetos que são do usuário de sistema. E, como eu mencionei anteriormente, a funcionalidade de auto-incremento no Oracle é implementada através de uma “sequence” em conjunto com uma trigger. E agora? O que fazemos? Pesquisando sobre esse erro, eu descobri nessa thread do StackOverflow que temos que criar um outro usuário para podermos fazer a criação dos objetos no nosso banco de dados (o que faz sentido – a criação de objetos no banco de dados com o usuário de sistema não é recomendada em nenhuma ferramenta de banco de dados).

Primeiramente, vamos deletar a tabela “CLIENTE” que foi criada pela ferramenta. Pois é. Mesmo tendo recebido o erro da trigger, a tabela foi criada:

Em seguida, criamos um novo usuário, clicando com o botão direito em “Other users” e escolhendo a opção “Create User“:

Eu dei o nome de “dbuser” para o novo usuário e adicionei todas as permissões para ele:

Obviamente, em um servidor de verdade você deveria criar o usuário somente com as permissões que ele deveria ter, mas, para fins didáticos neste artigo, eu configurei permissão total para o usuário.

Uma vez criado o usuário, vamos alterar a conexão para que ela não utilize mais o usuário de sistema, mas sim, esse usuário que acabamos de criar. Para isso, nós precisamos primeiramente desconectar do servidor:

Depois, nas propriedades da conexão, nós alteramos o usuário e senha:

Pronto! Agora se você seguir os mesmos passos que seguimos anteriormente para criarmos a tabela de Cliente, tudo vai funcionar sem nenhum erro:

Instalando o provider e suporte ao Visual Studio do Oracle

Ufa! Agora que já conseguimos finalmente criar a nossa singela tabela de Clientes, vamos partir para o desenvolvimento. Porém, antes disso, temos que instalar o provider e as ferramentas de suporte ao Visual Studio. Essas ferramentas podem ser baixadas diretamente no site da Oracle:

Eu recomendo a opção “ODAC + Developer Tools“. Essa é a opção mais completa, que instalará também o provider do Oracle para .NET, além do suporte ao Oracle no Visual Studio:

A instalação é bem tranquila. A única etapa que você tem que prestar atenção é o “DB Connection Configuration“. Nessa etapa o instalador perguntará os dados do seu servidor para que ele possa criar automaticamente as entradas no arquivo “tnsnames.ora” (se você não sabe, uma das formas para se conectar a um banco Oracle é utilizar o nome da entrada nesse arquivo na string de conexão – veremos mais informações sobre isso nas próximas seções deste artigo):

Acessando o Oracle com ADO.NET puro

Uma vez instalado o provider e ferramentas de suporte ao Visual Studio do Oracle, podemos partir para o desenvolvimento do nosso código de exemplo. Vamos ver como fazemos para implementarmos um CRUD (create, read, update, delete) bem básico em cima da tabela de Clientes?

Para simplificar as coisas, vou criar um novo projeto do tipo “Console Application“. Com o projeto criado, vamos adicionar a referência para a dll que implementa o suporte ao ADO.NET para o Oracle, que está localizada dentro da categoria “Extensions“:

Adicionada essa referência, nós já conseguimos trabalhar com as famosas classes do ADO.NET para o Oracle (OracleConnection, OracleCommand, OracleDataAdapter, etc). Todas essas classes estão localizadas no namespace “Oracle.DataAccess.Client“. A primeira coisa que vamos fazer é instanciar uma conexão e abri-la:

            // C#
            using (var conn = new Oracle.DataAccess.Client.OracleConnection("Data Source=dbserver;User Id=dbuser;Password=dbuser;"))
            {
                conn.Open();
            }
        ' VB.NET
        Using Conn As New Oracle.DataAccess.Client.OracleConnection("Data Source=dbserver;User Id=dbuser;Password=dbuser;")
            Conn.Open()
        End Using

Note que eu utilizei as informações criadas no arquivo “tnsnames.ora“, ou seja, o nome da entrada TNS escolhida durante a instalação das ferramentas de suporte ao Visual Studio do Oracle. Até uns tempos atrás, essa era a única opção para nos conectarmos a um banco de dados Oracle. Porém, felizmente, a partir de uma certa versão do Oracle (que eu não sei ao certo), nós podemos especificar todas as informações do banco de dados diretamente na string de conexão, sem termos que nos preocupar com o arquivo “tnsnames.ora“. Você encontra mais informações sobre essas strings de conexão aqui e aqui. No meu caso, a string de conexão sem utilizar o TNS ficou assim:

            // C#
            using (var conn = new Oracle.DataAccess.Client.OracleConnection("Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;"))
            {
                conn.Open();
            }
        ' VB.NET
        Using Conn As New Oracle.DataAccess.Client.OracleConnection("Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;")
            Conn.Open()
        End Using

Agora que já temos a nossa conexão instanciada e aberta, podemos começar a criar comandos para efetuarmos as nossas operações CRUD. A primeira operação que faremos é a exclusão de todos os dados da tabela de Clientes:

                // C#
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using

Note que essa operação é extremamente simples. Ela segue exatamente o mesmo padrão de execução de comandos ADO.NET com outros bancos de dados (SQL Server, Access, MySQL, SQLite, etc).

A próxima operação que analisaremos é a inserção de dados na tabela de Clientes. Faremos a inserção de uma nova entrada utilizando um valor fixo para a coluna “Nome“:

                // C#
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using

Em seguida, faremos a atualização do registro que acabamos de criar. Para isso, precisamos primeiramente descobrir qual foi o “Id” utilizado no registro que acabamos de criar. Descobriremos isso fazendo uma consulta pelo maior “Id” da tabela Cliente através de um “ExecuteScalar“. Com o “Id” em mãos, fazemos então a alteração no registro:

                // C#
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = :Id";
                        comm.Parameters.Add("Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
            ' VB.NET
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = :Id"
                    Comm.Parameters.Add("Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using

Note que, na parte da atualização, nós utilizamos a funcionalidade de parâmetros do ADO.NET. Diferente do SQL Server (onde os nomes dos parâmetros devem começar com arroba), os parâmetros nomeados no Oracle devem começar com “dois pontos” seguido do nome do parâmetro (por exemplo, “:Id“). Depois, na hora de adicionar os parâmetros no comando, basta utilizarmos o mesmo nome que definimos no meio da query (sem os dois pontos) e tudo deve funcionar corretamente. Confira o meu artigo sobre parâmetros do ADO.NET para saber porque você deve utilizar essa funcionalidade ao invés de concatenar valores nas suas sentenças.

Por fim, vamos conferir como podemos fazer para listarmos os registros de uma tabela. Com o ADO.NET puro, temos duas opções: DataReader ou DataAdapter. Veja só como fica o código:

                // C#
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new Oracle.DataAccess.Client.OracleDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            ' VB.NET
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New Oracle.DataAccess.Client.OracleDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using

E, com isso, completamos todas as operações de CRUD no Oracle utilizando ADO.NET puro. Veja como ficou o código completo:

            // C#
            using (var conn = new Oracle.DataAccess.Client.OracleConnection("Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;"))
            {
                conn.Open();

                // DELETE
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
                // INSERT
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
                // UPDATE
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = :Id";
                        comm.Parameters.Add("Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
                // SELECT
                using (var comm = new Oracle.DataAccess.Client.OracleCommand())
                {
                    comm.Connection = conn;
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new Oracle.DataAccess.Client.OracleDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            }
        ' VB.NET
        Using Conn As New Oracle.DataAccess.Client.OracleConnection("Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;")
            Conn.Open()

            ' DELETE
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using
            ' INSERT
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using
            ' UPDATE
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = :Id"
                    Comm.Parameters.Add("Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using
            ' SELECT
            Using Comm As New Oracle.DataAccess.Client.OracleCommand()
                Comm.Connection = Conn
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New Oracle.DataAccess.Client.OracleDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using
        End Using

E como fica com o Entity Framework?

Agora que já vimos como fazer as operações CRUD no Oracle utilizando ADO.NET puro, vamos ver como conseguimos obter o mesmo resultado utilizando Entity Framework?

Primeiramente, temos que adicionar a referência para a biblioteca que implementa o suporte ao Entity Framework no Oracle. Poderíamos adicionar a referência diretamente para a dll que foi instalada no nosso computador (ao instalarmos o provider e as ferramentas de suporte ao Visual Studio do Oracle, a dll do Entity Framework para o Oracle também é instalada). Porém, se fizermos isso, teremos que ajustar o nosso arquivo app.config por conta própria.

Para evitarmos esse ajuste manual, vamos adicionar a referência para essa biblioteca utilizando o NuGet. Ao fazermos isso, o arquivo app.config será ajustado automaticamente com algumas configurações padrão, poupando um pouco do nosso trabalho:

Nesse exemplo, vamos trabalhar com Code First. Ou seja, temos que criar as classes de domínio no nosso projeto e o Entity Framework criará automaticamente as tabelas necessárias. No nosso caso, já temos a tabela de Clientes criada no banco, então, o Entity Framework deverá simplesmente detectar a sua existência e considerá-la nas consultas. A nossa única classe de domínio será a classe “Cliente“:

// C#
public class Cliente
{
    public long Id { get; set; }
    public string Nome { get; set; }
}
' VB.NET
Public Class Cliente
    Public Property Id As Long
    Public Property Nome As String
End Class

Em seguida, temos que criar a nossa classe de contexto do Entity Framework:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)
End Class

Com isso, nós já temos a base que precisamos para criar uma instância do contexto na nossa aplicação. Não esqueça de instanciar o contexto dentro de um bloco “using“. Dessa forma, ele será automaticamente descartado após a finalização do bloco:

            // C#
            using (var contexto = new EfContext())
            {

            }
        ' VB.NET
        Using Contexto = New EfContext()

        End Using

Se executarmos o nosso projeto após adicionarmos o código acima, nós receberemos o seguinte erro:

Nós recebemos esse erro porque nós não configuramos as informações do nosso banco de dados no arquivo app.config. Para que o nosso exemplo funcione sem dar erro, nós temos que alterar dois lugares do nosso arquivo app.config:

Primeiro, temos que ajustar a tag “dataSource“, onde o elemento “alias” deve conter o nome do nosso contexto (“EfContext“) e o elemento “descriptor” deve conter a nossa string de conexão. Além disso, temos que alterar a tag “connectionStrings“, onde o elemento “name” deve conter o nome do nosso contexto e o elemento “connectionString” deve conter a nossa string de conexão.

Veja só como ficou a minha tag “dataSource” após as alterações:

  <oracle.manageddataaccess.client>
    <version number="*">
      <dataSources>
        <dataSource alias="EfContext" descriptor="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;"/>
      </dataSources>
    </version>
  </oracle.manageddataaccess.client>

E aqui vai a minha tag “connectionStrings” após as alterações:

  <connectionStrings>
    <add name="EfContext" providerName="Oracle.ManagedDataAccess.Client"
      connectionString="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User ID=dbuser;Password=dbuser;"/>
  </connectionStrings>

Feito isso, se executarmos novamente a nossa aplicação, não receberemos mais nenhum erro. Isso quer dizer que o Entity Framework conseguiu localizar o nosso banco de dados e o contexto foi inicializado com sucesso.

Agora, da mesma forma que fizemos com o ADO.NET puro, vamos implementar o primeiro passo do nosso CRUD, que será a exclusão de todos os dados da tabela de Clientes. Para isso, nós fazemos um “foreach” na tabela de Clientes, removendo todos os registros e salvando as alterações:

                // C#
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();
            ' VB.NET
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

Porém, ao executarmos o nosso projeto, receberemos o seguinte erro:

Por padrão, o Entity Framework usa o “schema” chamado “dbo“. Ao trabalhar com o Oracle no Entity Framework, descobri nesta thread do StackOverflow que temos que alterar o schema padrão no contexto de forma que ele fique com o mesmo nome do usuário (no meu caso, “DBUSER“). Para isso, temos que fazer um override no método “OnModelCreating” do nosso contexto e, dentro desse método, temos que fazer uma chamada ao método “HasDefaultSchema“, passando o nome do usuário:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.HasDefaultSchema("DBUSER");
        }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)

    Protected Overrides Sub OnModelCreating(modelBuilder As Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.HasDefaultSchema("DBUSER")
    End Sub
End Class

Será que depois desse ajuste a gente consegue rodar o projeto sem erros? Infelizmente não. Vamos analisar o próximo erro:

Hmm. O nome já está sendo usado? Que estranho. Vamos dar uma olhada no banco de dados novamente, dando um “refresh” nas tabelas para ver o que o Entity Framework aprontou:

Veja só. Além da nossa tabela “CLIENTE“, o Entity Framework tentou criar a tabela “Clientes” (note o “s” no final) e provavelmente deu algum conflito com a tabela “CLIENTE” quanto ao nome da trigger que faz o auto-incremento do “Id“. Isso acontece porque a pluralização de nomes de tabelas é habilitada por padrão no Entity Framework. Para desabilitá-la, temos que fazer um ajuste no nosso método “OnModelCreating“, removendo a convenção de pluralização do nosso contexto:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.HasDefaultSchema("DBUSER");
            modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
        }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)

    Protected Overrides Sub OnModelCreating(modelBuilder As Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.HasDefaultSchema("DBUSER")
        modelBuilder.Conventions.Remove(Of System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention)()
    End Sub
End Class

Agora vamos deletar a tabela “Clientes” que o Entity Framework criou erroneamente no nosso banco de dados e vamos executar a nossa aplicação novamente. Ao fazermos isso, receberemos o mesmo erro (dizendo que o nome já está sendo utilizado). O que será que aconteceu agora? Após um “refresh” nas tabelas, temos a seguinte situação:

Mas o que é isso? O Entity Framework não viu que a tabela “CLIENTE” já existia e acabou tentando criar uma outra tabela chamada “Cliente” (sem ser todas as letras maiúsculas)? Pois é. O Entity Framework trabalha com aspas simples ao redor de nomes de tabelas e campos. Isso faz com que o Oracle funcione em modo sensível a letras maiúsculas e minúsculas! Ou seja, para consertar isso, ou nós descartamos a tabela “CLIENTE” e passamos a trabalhar com a nova tabela “Cliente“, ou nós temos que fazer o mapeamento do nome da tabela e propriedades no método “OnModelCreating“:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.HasDefaultSchema("DBUSER");
            modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
            modelBuilder.Entity<Cliente>()
                .Property(c => c.Id)
                .HasColumnName("ID");
            modelBuilder.Entity<Cliente>()
                .Property(c => c.Nome)
                .HasColumnName("NOME");
            modelBuilder.Entity<Cliente>().ToTable("CLIENTE");
        }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)

    Protected Overrides Sub OnModelCreating(modelBuilder As Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.HasDefaultSchema("DBUSER")
        modelBuilder.Conventions.Remove(Of System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention)()
        modelBuilder.Entity(Of Cliente)() _
            .Property(Function(C) C.Id) _
            .HasColumnName("ID")
        modelBuilder.Entity(Of Cliente)() _
            .Property(Function(C) C.Nome) _
            .HasColumnName("NOME")
        modelBuilder.Entity(Of Cliente)().ToTable("CLIENTE")
    End Sub
End Class

Pronto! Agora sim o nosso código funcionará corretamente, considerando a tabela “CLIENTE“:

Veja só como fica o código completo para fazer todas as operações CRUD com o Entity Framework:

            // C#
            using (var contexto = new EfContext())
            {
                // DELETE
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();

                // INSERT
                contexto.Cliente.Add(new Cliente() { Nome = "Novo Cliente EF" });
                contexto.SaveChanges();

                // UPDATE
                var cliente = contexto.Cliente.First();
                cliente.Nome = "Novo Cliente EF Alterado";
                contexto.SaveChanges();

                // SELECT
                foreach (var c in contexto.Cliente)
                {
                    Console.WriteLine("Nome do Cliente: {0}", c.Nome);
                }
            }
        ' VB.NET
        Using Contexto = New EfContext()
            ' DELETE
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

            ' INSERT
            Contexto.Cliente.Add(New Cliente() With {.Nome = "Novo Cliente EF"})
            Contexto.SaveChanges()

            ' UPDATE
            Dim Cliente = Contexto.Cliente.First()
            Cliente.Nome = "Novo Cliente EF Alterado"
            Contexto.SaveChanges()

            ' SELECT
            For Each C In Contexto.Cliente
                Console.WriteLine("Nome do Cliente: {0}", C.Nome)
            Next
        End Using

Concluindo

Muitas vezes nós ficamos mal-acostumados quando trabalhamos com .NET e SQL Server. Nesse tipo de ambiente tudo normalmente funciona perfeitamente de primeira. Quando partimos para alguma outra tecnologia que não seja da Microsoft, aí começam a surgir os problemas. E foi isso que experimentamos na pele ao tentarmos utilizar o Oracle com C# e VB.NET no artigo dessa semana.

Não há dúvidas que o Oracle é um banco de dados importantíssimo, afinal de contas, como eu mencionei no começo do artigo, ele é atualmente o banco de dados relacional mais utilizado no mundo. Entretanto, podemos concordar que a sua configuração e utilização através do .NET não é lá tão simples assim, principalmente quando adicionamos um ORM como o Entity Framework no meio do caminho (cenário muito comum hoje em dia).

No artigo de hoje, você aprendeu a instalar o Oracle Database Express (versão gratuita do Oracle) e conheceu as suas limitações. Em seguida, eu te apresentei a ferramenta de administração da própria Oracle (o Oracle SQL Developer) que, apesar de não chegar nem perto do SQL Server Management Studio (na minha opinião), até que dá conta do recado. Por fim, você viu como instalar o provider e ferramentas de suporte ao Visual Studio, além de uma implementação bem simples de CRUD com ADO.NET puro e Entity Framework.

E aí, qual a sua opinião? Você acha que o SQL Server é mais simples de instalar e configurar? Já teve que trabalhar com o Oracle nos seus projetos? Passou pelos mesmos problemas que eu passei? 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Utilizando o Oracle com C# e VB.NET appeared first on André Alves de Lima.

Como incrementar o número da versão automaticamente no Visual Studio

$
0
0

Imagine a seguinte situação: você está de boa implementando uma nova funcionalidade no seu projeto quando, do nada, o telefone toca. Um dos clientes do seu sistema está reclamando de um bug que você já corrigiu faz um tempão. E agora? Será que o bug está de volta? Ou será que o cliente está com uma versão antiga da aplicação, na qual o bug ainda não tinha sido corrigido?

É por essas e outras que é importantíssimo controlarmos o número da versão dos nossos assemblies. Dessa forma, conseguimos saber se o usuário já está utilizando a última versão da aplicação. Porém, se você já tentou controlar o número da versão manualmente, você sabe que essa é uma tarefa um tanto quanto chata. Não é à toa que o Visual Studio tem uma funcionalidade de cálculo automático do número de build e revisão. Entretanto, essa funcionalidade do Visual Studio não é nada flexível, uma vez que não conseguimos incrementar o número da versão começando em zero (ele usa uma data e hora fixas como base).

Mas, não se preocupe. Como de costume, existe uma extensão do Visual Studio que implementa essa possibilidade para os nossos projetos. Quer conferir tudo sobre às versões de assemblies no mundo .NET? Então, vem comigo!

O arquivo AssemblyInfo

Sempre que criamos um novo projeto no Visual Studio, ele cria automaticamente um arquivo chamado AssemblyInfo.cs (ou AssemblyInfo.vb no caso de projetos VB.NET). Em projetos C#, conseguimos abrir esse arquivo facilmente pelo Solution Explorer, expandindo o nó “Properties“:

Como falei no parágrafo anterior, no VB.NET nós temos também o arquivo AssemblyInfo. Porém, nós não conseguimos acessá-lo através do Solution Explorer. Para abrirmos o arquivo AssemblyInfo.vb, temos que navegar até o subdiretório “My Project” do nosso projeto através do Windows Explorer:

Já é de se imaginar que esse arquivo armazena diversas informações sobre o assembly – afinal, ele se chama “AssemblyInfo“. Uma das informações que encontramos nesse arquivo é a versão do assembly. Mas, não pense que nós só temos um número de versão para cada assembly. Para complicar as coisas, nós temos a possibilidade de definir 3 (!!) números de versões diferentes.

Primeiramente, temos a versão do assembly propriamente dita, definida com o atributo “AssemblyVersion“:

Em seguida, temos o número da versão do “arquivo do assembly“, que pode ser definida com o atributo “AssemblyFileVersion“:

Por fim, temos um outro número de versão que pouca gente conhece: o número da versão do produto! Essa versão pode ser definida com o atributo “AssemblyInformationalVersion“:

As versões do assembly e do “arquivo do assembly” também podem ser definidas nas propriedades do projeto, clicando no botão “Assembly Information“:

A versão mais importante é a versão do assembly. Se a versão do “arquivo do assembly” estiver em branco, o número da versão do assembly será considerado no seu lugar. O mesmo acontece com a versão do produto – se ela estiver em branco, a versão do “arquivo do assembly” será utilizada. Ou seja, a hierarquia dos números de versões é: versão do assembly => versão do arquivo do assembly => versão do produto.

Além disso, como a descrição do arquivo AssemblyInfo já explica, os números de versão são compostos por quatro seções:

– Primeira seção = versão principal
– Segunda seção = versão secundária
– Terceira seção = número da build
– Quarta seção = número da revisão

Descobrindo o número da versão em tempo de execução

Não é nada difícil descobrirmos o número da versão em tempo de execução. A versão do assembly pode ser acessada através da propriedade Version do assembly. Já as outras versões podem ser acessadas através da classe FileVersionInfo. Por exemplo, em um projeto do tipo “Console Application“, o código para pegarmos os três tipos de versão e imprimirmos no console ficaria assim:

            // C#
            string assemblyVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
            string fileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion;
            string productVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).ProductVersion;

            Console.WriteLine(string.Format("Assembly Version: {0}", assemblyVersion));
            Console.WriteLine(string.Format("File Version: {0}", fileVersion));
            Console.WriteLine(string.Format("Product Version: {0}", productVersion));
        ' VB.NET
        Dim AssemblyVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()
        Dim FileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion
        Dim ProductVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).ProductVersion

        Console.WriteLine(String.Format("Assembly Version: {0}", AssemblyVersion))
        Console.WriteLine(String.Format("File Version: {0}", FileVersion))
        Console.WriteLine(String.Format("Product Version: {0}", ProductVersion))

Por padrão, a versão do assembly vem configurada como “1.0.0.0” quando criamos um projeto novo no Visual Studio. Dessa forma, o resultado da execução desse código para um projeto feito “do zero” deve ser:

Atenção! Se o seu projeto tiver mais de um assembly (por exemplo, um projeto com um “exe” e várias “dlls“), esse código retornará a versão do assembly onde o código está sendo executado. Ou seja, se você colocar esse código em um assembly que não seja o “exe“, pode ser que o número retornado não seja o que você está esperando. Se você quiser recuperar explicitamente a versão do executável principal da aplicação, substitua a chamada de “GetExecutingAssembly” por “GetEntryAssembly“.

Incrementando o número da build e revisão utilizando asterisco (*)

O próprio Visual Studio possui um mecanismo de atualização automática do número de versão a cada build. Porém, como veremos a seguir, ele não é nada flexível.

A opção que temos no Visual Studio possibilita que incrementemos automaticamente o número da build ou o número da revisão (ou ambos). Para fazer isso, utilizamos um asterisco (*) na terceira ou quarta seção do número da versão. Por exemplo, se utilizarmos o asterisco na última seção, este será o resultado:

Já se utilizarmos o asterisco no número da build, este será o resultado:

Se utilizamos o asterisco no número da build, nós não podemos colocar um número fixo no número da revisão. Ou seja, ao escolhermos fazer o incremento automático do número da build, o número da revisão também terá que ser obrigatoriamente incrementado de forma automática.

Porém, você viu que o Visual Studio utilizou uns valores bastante esquisitos para o número da build e revisão? Pois é, os números não serão incrementados de um em um, mas sim, utilizando uma lógica pré-estabelecida.

Ao utilizarmos asterisco no número da build, ela será incrementada diariamente. O valor atribuído será a diferença de dias entre hoje e o dia 1 de janeiro de 2000. Você pode confirmar isso fazendo a conta no Excel:

Para o número da revisão, o Visual Studio considerará a quantidade de segundos desde a meia noite do dia atual. Ou seja, para cada compilação, o assembly receberá um número de revisão diferente.

Esse sistema de numeração automática do Visual Studio até que é legalzinho e muito fácil de utilizar, mas, como falei anteriormente, ele não é nada flexível. Se quisermos criar uma metodologia de incremento que não considere a data/hora, mas sim, um incremento começando com o número “1“, nós teríamos que fazer o controle manualmente.

Extensão Automatic Versions

Para nossa sorte, o Visual Studio é extremamente extensível. Não sei se você já deu uma olhada no Visual Studio Gallery, mas, tem extensão para tudo quanto é coisa! Uma extensão muito útil no que diz respeito ao incremento de versões é a Automatic Versions.

Ao instalarmos essa extensão, uma nova opção será adicionada no menu “Tools” do Visual Studio:

Ao clicarmos nesse item, veja só que legal:

Note que podemos configurar números de versões globalmente, para a solução ou para projetos específicos dentro da solução. Por exemplo, para configurarmos o nosso projeto de forma que o número da revisão seja incrementado em cada compilação (começando em “1“), fazemos o seguinte:

Feito isso, temos que voltar nas propriedades do projeto para removermos o asterisco que colocamos anteriormente no número da build:

Em seguida, se recompilarmos o nosso projeto, o número da revisão será incrementado automaticamente:

Para cada vez que recompilarmos o projeto, o número da revisão será incrementado em “1“. Legal, não? Pois saiba que a extensão Automatic Versions disponibiliza diversas outras opções para numerarmos os nossos assemblies:

Por exemplo, se quisermos que o número da versão tome como base a data e hora da compilação, basta configurá-lo da seguinte maneira:

E esse seria o resultado:

Concluindo

Uma vez que começamos a distribuir os nossos aplicativos para os nossos clientes, é importante fazermos o controle do número da versão. Dessa forma, fica fácil saber qual versão o usuário está executando, sem termos que ficar olhando a data dos arquivos.

O controle do número da versão dos nossos assemblies pode obviamente ser feito de maneira manual, porém, isso não é nada prático. Por isso, o Visual Studio traz consigo a possibilidade de incrementarmos automaticamente o número da build e o número da revisão para cada compilação. Entretanto, essa funcionalidade do Visual Studio tem como base uma data e hora fixas, fazendo com que a versão dos nossos assemblies fique um tanto quanto esquisitas.

Para fazermos com que o número da versão seja incrementado em cada processo de compilação, podemos utilizar a extensão chamada Automatic Versions. Essa extensão possibilita definirmos incrementos automáticos da versão ou até mesmo lógicas mais complexas (como a utilização da data/hora da compilação como versão do assembly).

No artigo de hoje você conferiu todas essas opções de numeração de versões dos nossos assemblies com o Visual Studio. E você? Como você faz a numeração dos assemblies do seu projeto? Manualmente? Utilizando o incremento do Visual Studio? Ou quem sabe utilizando uma extensão para fazer isso de forma automática? Compartilhe os detalhes das suas estratégias de numeração de versão nos 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/data-configure-organize-27652/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como incrementar o número da versão automaticamente no Visual Studio appeared first on André Alves de Lima.

Trabalhando com múltiplas tabelas no Report Viewer através da expressão Lookup

$
0
0

Quem trabalha com o Report Viewer provavelmente já sabe que ele não permite a exibição de dados vindos de múltiplas tabelas em um único controle. Isso quer dizer que, se você quiser exibir dados de mais de uma tabela em um controle Tablix, você terá que partir para alguma alternativa.

A alternativa mais utilizada é a consolidação dos dados em uma única DataTable (ou classe) que servirá como fonte de dados para os nossos relatórios. Porém, essa não é a melhor das alternativas, uma vez que teremos que potencialmente construir DataSets e DataTables customizados para cada relatório do nosso sistema.

O que pouca gente conhece é que existe a expressão “Lookup” no Report Viewer, que nos permite justamente pegar informações de outras tabelas. No artigo de hoje eu vou mostrar para você como utilizar essa expressão. Você quer deixar os seus relatórios do Report Viewer mais simples? Então continue lendo e veja como a expressão “Lookup” pode simplificar consideravelmente os seus relatórios.

Entendendo o problema

Antes de apresentar a solução, vamos entender o problema? Para isso, vamos criar um exemplo onde temos que desenvolver um relatório que exibirá dados vindos de múltiplas tabelas. Temos inúmeros cenários onde isso se faz necessário, mas, para não complicarmos muito, preparei um exemplo bem simples para que vocês possam entender mais facilmente.

Imagine que a nossa tarefa seja gerar um relatório sobre uma entidade chamada “Pessoa” em um sistema de Recursos Humanos. Essa entidade, além das propriedades habituais, tem também uma ligação com a entidade “Empresa” (a pessoa trabalha em algum lugar) e outra ligação com a entidade “Cargo” (ela ocupa uma determinada posição nessa empresa).

Para simularmos essa estrutura, vamos começar criando um novo projeto do tipo “Windows Forms Application“. Dentro desse projeto, adicione um novo DataSet tipado (dando o nome de “DataSetPessoa“) com as seguintes tabelas, colunas e relacionamentos:

Aproveitando que temos um DataSet tipado, vamos dar um duplo clique para implementarmos um método que alimentará esse DataSet com alguns dados de exemplo. Obviamente, em um sistema “de verdade” você teria que ler esses dados do banco:

    // C#
    partial class DataSetPessoa
    {
        public void PreencherComDadosDeExemplo()
        {
            var abcdev = this.Empresa.AddEmpresaRow("abcdev", "AA Lima Dev EPP", "99.999.999/9999-99");
            var microsoft = this.Empresa.AddEmpresaRow("Microsoft", "Microsoft BR Ltda", "99.999.999/9999-99");
            var google = this.Empresa.AddEmpresaRow("Google", "Google Brasil S/A", "99.999.999/9999-99");

            var analista = this.Cargo.AddCargoRow("Analista de Sistemas", "XXX");
            var arquiteto = this.Cargo.AddCargoRow("Arquiteto de Sistemas", "YYY");
            var diretor = this.Cargo.AddCargoRow("Diretor Administrativo", "ZZZ");

            this.Pessoa.AddPessoaRow("Andre", "Lima", new System.DateTime(1984, 1, 1), abcdev, analista, "999.999.99-99");
            this.Pessoa.AddPessoaRow("Larissa", "Lima", new System.DateTime(1987, 2, 2), abcdev, diretor, "999.999.99-99");
            this.Pessoa.AddPessoaRow("Fulano", "de Tal", new System.DateTime(1978, 3, 3), microsoft, arquiteto, "999.999.99-99");
            this.Pessoa.AddPessoaRow("Paula", "Bellizia", new System.DateTime(1970, 4, 4), microsoft, diretor, "999.999.99-99");
            this.Pessoa.AddPessoaRow("Fabio", "Coelho", new System.DateTime(1968, 5, 5), google, diretor, "999.999.99-99");
        }
    }
' VB.NET
Partial Class DataSetPessoa
    Public Sub PreencherComDadosDeExemplo()
        Dim Abcdev = Me.Empresa.AddEmpresaRow("abcdev", "AA Lima Dev EPP", "99.999.999/9999-99")
        Dim Microsoft = Me.Empresa.AddEmpresaRow("Microsoft", "Microsoft BR Ltda", "99.999.999/9999-99")
        Dim Google = Me.Empresa.AddEmpresaRow("Google", "Google Brasil S/A", "99.999.999/9999-99")

        Dim Analista = Me.Cargo.AddCargoRow("Analista de Sistemas", "XXX")
        Dim Arquiteto = Me.Cargo.AddCargoRow("Arquiteto de Sistemas", "YYY")
        Dim Diretor = Me.Cargo.AddCargoRow("Diretor Administrativo", "ZZZ")

        Me.Pessoa.AddPessoaRow("Andre", "Lima", New System.DateTime(1984, 1, 1), Abcdev, Analista, "999.999.99-99")
        Me.Pessoa.AddPessoaRow("Larissa", "Lima", New System.DateTime(1987, 2, 2), Abcdev, Diretor, "999.999.99-99")
        Me.Pessoa.AddPessoaRow("Fulano", "de Tal", New System.DateTime(1978, 3, 3), Microsoft, Arquiteto, "999.999.99-99")
        Me.Pessoa.AddPessoaRow("Paula", "Bellizia", New System.DateTime(1970, 4, 4), Microsoft, Diretor, "999.999.99-99")
        Me.Pessoa.AddPessoaRow("Fabio", "Coelho", New System.DateTime(1968, 5, 5), Google, Diretor, "999.999.99-99")
    End Sub
End Class

E agora? Como poderíamos fazer para montarmos um relatório que lista as pessoas desse DataSet, porém, incluindo as informações da empresa onde ela trabalha e as informações do cargo que ela ocupa?

Primeira alternativa: juntando tudo em uma única tabela

A primeira alternativa para resolvermos essa situação (e a mais utilizada) é criarmos um novo DataSet (ou classe) juntando todas as colunas que queremos exibir no relatório em uma única tabela. Ou seja, no nosso caso, teríamos somente uma tabela com todas as informações da Pessoa, Empresa e Cargo.

Para implementarmos essa alternativa, vamos adicionar um segundo DataSet tipado no nosso projeto (dando o nome de “DataSetRelatorio“), que deverá conter a seguinte tabela:

Vamos também dar um duplo clique no DataSet para criarmos um método que receberá uma instância de “DataSetPessoa” e juntará tudo na tabela unificada:

    // C#
    partial class DataSetRelatorio
    {
        public DataSetRelatorio(DataSetPessoa dsPessoa)
        {
            foreach (var pessoa in dsPessoa.Pessoa)
            {
                this.Pessoa.AddPessoaRow(
                    pessoa.PessoaID,
                    pessoa.Nome,
                    pessoa.Sobrenome,
                    pessoa.DataNascimento,
                    pessoa.EmpresaID,
                    pessoa.CargoID,
                    pessoa.CPF,
                    pessoa.EmpresaRow.NomeFantasia,
                    pessoa.EmpresaRow.RazaoSocial,
                    pessoa.EmpresaRow.CNPJ,
                    pessoa.CargoRow.Nome,
                    pessoa.CargoRow.Descricao);
            }
        }
    }
' VB.NET
Partial Class DataSetRelatorio
    Public Sub DataSetRelatorio(DsPessoa As DataSetPessoa)
        For Each PessoaRow In DsPessoa.Pessoa
            Me.Pessoa.AddPessoaRow(
                    PessoaRow.PessoaID,
                    PessoaRow.Nome,
                    PessoaRow.Sobrenome,
                    PessoaRow.DataNascimento,
                    PessoaRow.EmpresaID,
                    PessoaRow.CargoID,
                    PessoaRow.CPF,
                    PessoaRow.EmpresaRow.NomeFantasia,
                    PessoaRow.EmpresaRow.RazaoSocial,
                    PessoaRow.EmpresaRow.CNPJ,
                    PessoaRow.CargoRow.Nome,
                    PessoaRow.CargoRow.Descricao)
        Next
    End Sub
End Class

Com esse DataSet em mãos, podemos simplesmente criar um novo relatório baseado nessa DataTable unificada. Vamos dar o nome de “RelatorioPessoa” para esse novo relatório:

Dentro desse relatório, vamos adicionar um componente do tipo “Table“. Quando fazemos isso, o Visual Studio automaticamente nos perguntará as informações sobre o DataSet que deverá alimentar essa tabela. Nessa janela, vamos dar o nome de “DataSetPessoa” para o DataSet que será criado e vamos escolher a tabela “Pessoa” do nosso “DataSetRelatorio“:

Em seguida, vamos arrastar cada uma das colunas do DataSet para dentro da tabela:

O resultado final deverá ficar parecido com a imagem abaixo:

Por fim, vamos até o nosso formulário, onde arrastaremos um controle visualizador do Report Viewer para dentro do formulário e escolheremos o relatório que acabamos de criar:

No code-behind do formulário, vamos criar os DataSets que serão passados para o relatório:

        // C#
        private void Form1_Load(object sender, EventArgs e)
        {
            var dsPessoa = new DataSetPessoa();
            dsPessoa.PreencherComDadosDeExemplo();
            var dsRelatorio = new DataSetRelatorio(dsPessoa);

            this.reportViewer1.LocalReport.DataSources.Clear();
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", (DataTable)dsRelatorio.Pessoa));
            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim DsPessoa As New DataSetPessoa()
        DsPessoa.PreencherComDadosDeExemplo()
        Dim DsRelatorio As New DataSetRelatorio(DsPessoa)

        Me.ReportViewer1.LocalReport.DataSources.Clear()
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DirectCast(DsRelatorio.Pessoa, DataTable)))
        Me.ReportViewer1.RefreshReport()
    End Sub

Pronto! Execute a aplicação e confira o resultado:

Obviamente o relatório poderia ser melhor formatado, mas, como esse não é o foco desse artigo, resolvi deixa-lo com a formatação padrão.

Alternativa mais simples: múltiplas tabelas + expressão Lookup

Como vimos na seção anterior, para exibirmos dados de múltiplas tabelas no Report Viewer, nós podemos criar um novo DataSet (ou classe) juntando todos os dados em uma só estrutura. Apesar de essa alternativa resolver o problema, muitas vezes ela acaba sendo um tanto quanto custosa. Imagine se o nosso sistema tiver diversos relatórios e todos eles tiverem que exibir dados de múltiplas tabelas? Teríamos que criar um DataSet novo para cada relatório, o que seria possível, mas, impraticável.

Uma alternativa mais simples seria trabalharmos com vários DataSets no mesmo relatório, sendo que um DataSet apontaria para a tabela “principal” (no nosso caso a tabela “Pessoa“) e os outros DataSets seriam as tabelas de “Lookup” (no nosso caso as tabelas “Empresa” e “Cargo“). Então, através da função “Lookup“, nós conseguimos pegar dados das tabelas de “Empresa” e “Cargo” no nosso relatório (seria a mesma ideia do PROCV ou VLOOKUP do Excel).

Para vermos como ficaria essa alternativa, vamos criar uma cópia do “RelatorioPessoa“, dando o nome de “RelatorioPessoaMultiplasTabelas“. Dentro desse relatório, vamos excluir o DataSet que tínhamos criado no relatório original (o “DataSetPessoa“) e vamos adicionar três novos DataSets, cada um apontando para uma tabela do nosso DataSetPessoa:

Em seguida, vamos alterar o DataSource da nossa tabela, de forma que ele aponte para o DataSet “Pessoa“:

E agora é que vem o segredo. Como é que conseguimos pegar os dados da Empresa, uma vez que o DataSet da tabela não tem essas informações? Simples, através da expressão Lookup! Por exemplo, para pegarmos o nome fantasia da empresa, a expressão ficaria assim:

=Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!NomeFantasia.Value, "Empresa")

O primeiro parâmetro dessa expressão deve ser o nome do campo na tabela de origem (campo “EmpresaID” da tabela “Pessoa“). Já o segundo parâmetro corresponde ao nome do campo na tabela destino (no nosso caso, “EmpresaID“, que é o nome do campo chave na tabela “Empresa“). Ou seja, esses dois primeiros parâmetros indicam os campos de relacionamento entre a tabela origem e a tabela destino.

Em seguida, o terceiro parâmetro deve conter o nome que você quer pegar na tabela de destino. Nesse caso, queremos pegar o campo “NomeFantasia“. Por fim, no último parâmetro nós temos que indicar o nome da tabela de lookup (nesse caso, “Empresa“).

Fácil, não? Veja só como fica a expressão para os outros campos:

' Razão social:
=Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!RazaoSocial.Value, "Empresa")
' CNPJ
=Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!CNPJ.Value, "Empresa")
' Nome do Cargo
=Lookup(Fields!CargoID.Value, Fields!CargoID.Value, Fields!Nome.Value, "Cargo")
' Descrição do Cargo
=Lookup(Fields!CargoID.Value, Fields!CargoID.Value, Fields!Descricao.Value, "Cargo")

Agora que já temos o nosso novo relatório, vamos criar um novo formulário para exibi-lo (Form2). Como fizemos no primeiro formulário, nós temos que adicionar um controle exibidor do Report Viewer, só que dessa vez nós selecionaremos o nosso novo relatório (“RelatorioPessoaMultiplasTabelas“). No code-behind, nós não temos mais que criar um DataSetRelatorio, mas sim, temos que passar cada uma das tabelas para o relatório em DataSources diferentes:

        // C#
        private void Form2_Load(object sender, EventArgs e)
        {
            var dsPessoa = new DataSetPessoa();
            dsPessoa.PreencherComDadosDeExemplo();

            this.reportViewer1.LocalReport.DataSources.Clear();
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Pessoa", (DataTable)dsPessoa.Pessoa));
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Empresa", (DataTable)dsPessoa.Empresa));
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Cargo", (DataTable)dsPessoa.Cargo));
            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim DsPessoa = New DataSetPessoa()
        DsPessoa.PreencherComDadosDeExemplo()

        Me.ReportViewer1.LocalReport.DataSources.Clear()
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Pessoa", DirectCast(DsPessoa.Pessoa, DataTable)))
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Empresa", DirectCast(DsPessoa.Empresa, DataTable)))
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Cargo", DirectCast(DsPessoa.Cargo, DataTable)))
        Me.ReportViewer1.RefreshReport()
    End Sub

Por fim, vamos alterar o objeto de inicialização para que o nosso Form2 seja exibido (ao invés do Form1). Em projetos C#, fazemos isso no arquivo Program.cs:

Application.Run(new Form2());

Já no VB.NET, temos que alterar o “Startup Form” nas propriedades do projeto:

Pronto! Execute o projeto e veja que o resultado é idêntico ao primeiro relatório:

Concluindo

Quando estamos desenvolvendo relatórios para as nossas aplicações, não é tão difícil nos depararmos com uma situação em que precisamos de dados vindos de múltiplas tabelas. Como o Report Viewer não suporta a exibição de dados de mais de uma tabela no mesmo controle e também não tem o conceito de relacionamentos entre tabelas (como temos no Crystal Reports), a saída mais utilizada é a criação de um segundo DataSet (ou classe) onde todos os dados são consolidados em uma única DataTable.

Apesar dessa alternativa ser a mais utilizada, ela não é a mais ideal. O que pouca gente sabe é que nós podemos utilizar a expressão Lookup para pegarmos dados de outras tabelas no Report Viewer. Esse procedimento é muito mais simples do que termos que criar um DataSet consolidado para cada relatório.

No artigo de hoje você conferiu essas duas alternativas para a exibição de dados vindos de múltiplas tabelas no Report Viewer. Como você pode perceber, o resultado é idêntico, então, não pense duas vezes se você puder utilizar a expressão Lookup nos seus relatórios, que é muito mais prática.

E você, já passou por essa situação onde você tinha que exibir dados de múltiplas tabelas no Report Viewer? Se sim, como é que você resolveu? Você já conhecia a expressão Lookup? 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 Jake Przespo used under Creative Commons
https://www.flickr.com/photos/jakeprzespo/4566115233/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com múltiplas tabelas no Report Viewer através da expressão Lookup appeared first on André Alves de Lima.

Juntando executável e dlls em um arquivo só com o ILMerge

$
0
0

Você já desenvolveu um aplicativo que possui referências a diversas bibliotecas? Caso positivo, você sabe a dor de cabeça que enfrentamos na hora de distribuirmos esse tipo de aplicação. Basta esquecer uma simples dll e pronto: nossa aplicação não funcionará corretamente.

O que talvez você não saiba é que nós conseguimos mesclar executável e dlls em um arquivo só através da ferramenta chamada ILMerge. O resultado será um único executável (ou dll), tornando o processo de deployment muito mais fácil.

No artigo de hoje, confira como utilizar o ILMerge via linha de comando e conheça também o ILMergeGui, uma interface visual que interage com o ILMerge e facilita muito a nossa vida quando precisamos mesclar assemblies.

Criando um projeto de exemplo

Para entendermos como juntar vários assemblies em um único executável ou dll, vamos criar um exemplo extremamente simples. Esse exemplo será formado por um projeto “Console Application” (.exe) e dois projetos “Class Library” (.dll). Darei o nome de “ExemploILMerge” para a solução que será criada com a “Console Application“. Dentro dessa solution, vamos adicionar outros dois projetos do tipo “Class Library“, dando o nome de “Biblioteca1” e “Biblioteca2” respectivamente. A estrutura da solução deve ficar parecida com a imagem abaixo:

Dentro da “Class1” da “Biblioteca1“, vamos adicionar um método estático (chamado “Metodo“) que retornará uma string contendo “Biblioteca1.Class1.Metodo“:

// C#
namespace Biblioteca1
{
    public class Class1
    {
        public static string Metodo()
        {
            return "Biblioteca1.Class1.Metodo";
        }
    }
}
' VB.NET
Public Class Class1
    Public Shared Function Metodo()
        Return "Biblioteca1.Class1.Metodo"
    End Function
End Class

Faremos exatamente a mesma coisa na “Class1” da “Biblioteca2“, porém, nesse caso o método deverá retornar a string contendo “Biblioteca2.Class1.Metodo“:

// C#
namespace Biblioteca2
{
    public class Class1
    {
        public static string Metodo()
        {
            return "Biblioteca2.Class1.Metodo";
        }
    }
}
' VB.NET
Public Class Class1
    Public Shared Function Metodo()
        Return "Biblioteca2.Class1.Metodo"
    End Function
End Class

Em seguida, vamos adicionar as referências para “Biblioteca1” e “Biblioteca2” no nosso projeto “Console Application“. Para isso, temos que clicar com o botão direito em “References” e escolher os projetos que estarão listados dentro da categoria “Projects“:

Por fim, vamos até a classe “Program” (ou “Module1” no VB.NET), onde imprimiremos o resultado das chamadas para os métodos das bibliotecas:

        // C#
        static void Main(string[] args)
        {
            Console.WriteLine(Biblioteca1.Class1.Metodo());
            Console.WriteLine(Biblioteca2.Class1.Metodo());
            Console.ReadLine();
        }
    ' VB.NET
    Sub Main()
        Console.WriteLine(Biblioteca1.Class1.Metodo())
        Console.WriteLine(Biblioteca2.Class1.Metodo())
        Console.ReadLine()
    End Sub

Execute a solução e veja o resultado na janela de console:

Nada sofisticado, não é mesmo? Porém, se dermos uma olhada no diretório de saída da solução (onde os projetos são compilados), veremos que temos 3 assemblies (um executável e duas dlls):

Isso quer dizer que a aplicação só funcionará no computador do cliente caso tivermos todos esses arquivos distribuídos na pasta da aplicação. Lembre-se que neste artigo estamos falando de uma solução extremamente simples. Imagine em um cenário real onde diversas bibliotecas são utilizadas nos nossos projetos? A quantidade de assemblies que devem ser distribuídos pode ser bem grande e isso pode se tornar um problema. É por isso que o ILMerge foi desenvolvido: para juntar todos os assemblies em um único executável ou dll!

Baixando e utilizando o ILMerge via linha de comando

O ILMerge pode ser baixado no Download Center da Microsoft ou no formato de um pacote NuGet. Nesta seção do artigo eu vou utilizar a opção do pacote NuGet. Para instalá-lo no seu projeto, abra a janela “Manage NuGet Packages” e procure por “ILMerge“:

Outra opção é digitar o comando “Install-Package ilmerge” diretamente no NuGet Package Manager. Se você tiver alguma dúvida sobre esse processo, confira este artigo onde eu mostro como gerenciar pacotes do NuGet no Visual Studio.

Uma vez instalado, o executável do NuGet estará disponível dentro do subdiretório “packages\ilmerge.2.14.1208\tools” da nossa solução:

Para facilitar a nossa vida, vamos copiar esse executável para a pasta de saída da nossa solução (bin\debug) e, em seguida, vamos abrir um prompt de comando nesse diretório. A propósito, você sabia que dá para abrir um prompt de comando dentro de um diretório específico através do Windows Explorer? Saca só:

Com o prompt de comando aberto, vamos entender a sintaxe básica do ILMerge. Primeiramente, temos que indicar o tipo de saída através do argumento “/target“. Existem três opções para esse argumento: “library“, “exe” ou “winexe“. A opção “library” gerará uma dll, já as opções “exe” e “winexe” gerarão um executável. Mas, qual é a diferença entre essas duas opções que geram executáveis? Simples: a opção “exe” gera um executável do tipo “Console Application” e a opção “winexe” gera um executável do tipo “aplicação Windows” (Windows Forms / WPF). Se você confundir esses dois tipos de executável, a aplicação não funcionará.

Em seguida, com o argumento “/out” nós temos que definir o nome do arquivo de destino. No nosso exemplo, nós utilizaremos o nome “Executavel.exe“. Por fim, temos que passar a lista dos arquivos que deverão ser mesclados (no nosso caso, “ExemploILMerge.exe“, “Biblioteca1.dll” e “Biblioteca2.dll“).

Veja como fica a linha de comando para mesclarmos os arquivos da nossa solução:

ILMerge.exe /target:exe /out:Executavel.exe ExemploILMerge.exe Biblioteca1.dll Biblioteca2.dll

Ao executarmos essa linha de comando, teremos o novo executável gerado na pasta bin\debug:

Teste a execução dessa nova aplicação e veja que o comportamento é exatamente o mesmo da aplicação original. Entretanto, agora nós não precisamos mais distribuir três assemblies separados. Basta distribuirmos o arquivo “Executavel.exe” nos computadores clientes e tudo deve funcionar perfeitamente.

Utilizando a ferramenta visual ILMergeGui

Muito legal esse esquema do ILMerge, mas, trabalhar com linha de comando muitas vezes acaba enchendo o saco, não é mesmo? Imagine uma solução onde tenhamos que mesclar 30 assemblies. Vamos ter que digitar o nome dos 30 assemblies na linha de comando? Nada prático.

Foi pensando nisso que uma galera da comunidade resolveu desenvolver uma interface gráfica para o ILMerge. Por trás dos panos o “ILMerge.exe” será chamado passando os argumentos corretos de acordo com o que foi escolhido na interface visual. Essa interface gráfica é chamada ILMergeGui.

Uma vez instalada e executada, pode ser que você receba este erro:

Esse erro acontece quando utilizamos o ILMerge através do pacote NuGet (ao invés de instalarmos pelo Download Center da Microsoft). Nesse caso, o ILMergeGui não consegue encontrar o “ILMerge.exe” e acaba dando esse erro. Para resolvermos esse contratempo, temos três opções:

1) Instalar o ILMerge utilizando o instalador baixado no Download Center
2) Copiar o ILMerge.exe para o diretório C:\Windows\System32
3) Copiar o ILMerge.exe para dentro do diretório de instalação do ILMergeGui

Eu recomendo que você utilize a primeira ou segunda opções. O ILMergeGui é instalado através do ClickOnce, portanto, o seu diretório de instalação fica nas entranhas do sistema de arquivos, o que dificulta um pouco a localização do diretório para utilizarmos a terceira alternativa. No meu computador, por exemplo, o ILMergeGui foi instalado dentro deste diretório: “C:\Users\andrealveslima\AppData\Local\Apps\2.0\TKL4XB63.2CN\TLE58ZPL.D5G\ilme..tion_f70b3bef76080fc4_0002.0000_182dad39646c61d9“:

Enfim, com esse contratempo resolvido, ao abrirmos o ILMergeGui novamente, nós não receberemos mais nenhuma mensagem de erro. A interface do ILMergeGui é muito simples. Na parte de cima (“Assemblies to merge“) nós temos que arrastar os arquivos que devem ser mesclados (no nosso caso ExemploILMerge.exe, Biblioteca1.dll e Biblioteca2.dll) e na parte inferior (“Output assembly“) nós escolhemos o caminho e o nome do executável ou dll onde o resultado deverá ser gerado:

Aí é só clicar em “Merge” e pronto! Bem mais fácil do que trabalhar com o ILMerge via linha de comando, não é mesmo?

Concluindo

A ferramenta ILMerge serve para mesclarmos vários assemblies (.exe e .dll) em um único assembly. Isso pode ser muito útil nas situações em que as nossas aplicações possuam muitas referências a dlls externas.

No artigo de hoje você conferiu como juntar executável e dlls em um arquivo só utilizando o ILMerge. Você viu também que o ILMerge só trabalha com linha de comando, porém, com a ferramenta ILMergeGui, nós temos à nossa disposição uma interface visual que facilita bastante a nossa vida.

E você, já precisou juntar vários assemblies em um só arquivo? Você utilizou o ILMerge ou alguma outra ferramenta? Tudo funcionou certinho ou você enfrentou algum desafio na distribuição dos seus aplicativos? Espero os seus comentários logo abaixo!

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/connect-connection-cooperation-20333/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Juntando executável e dlls em um arquivo só com o ILMerge appeared first on André Alves de Lima.


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.

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.


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.

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.

Viewing all 89 articles
Browse latest View live