Coletânea de informações, códigos, referências sobre o exame de certificação 70-483 que é destinado a desenvolvedores com um ano de experiência ou mais em programação de lógica de aplicativo/negócio essencial para diversos tipos de aplicativos e plataformas de hardware/software usando C#
Resumo do treinamento para o exame.
IMPLEMENTAR ACESSO AOS DADOS (25–30%)
Realizar operações de E/S • Ler e escrever arquivos e fluxos; ler e escrever a partir da rede usando classes no namespace System.Net; implementar operações de E/S assíncronas
Operações E/S de Arquivo e Diretórios
Interagir com arquivos é uma tarefa comum ao desenvolver um aplicativo, as operações de E/S se referem à leitura e gravação de arquivos de e para armazenamento. Os arquivos são armazenados em diretórios e o .NET Framework fornece um conjunto de classes para copiar, mover, excluir ou verificar a existência de arquivos ou diretórios. Um arquivo é uma coleção de bytes ordenada e nomeada que foi salva no armazenamento. Ao trabalhar com arquivos, você usa um fluxo (Stream). Um fluxo é um objeto na memória usado para representar a sequência de bytes em um arquivo. As classes especiais de File Reader e File Writer permitem trabalhar com fluxos codificados que são úteis ao armazenar algum tipo de informação em arquivos, armazenar essas informações em um formato diferente (por exemplo, binário ou texto), ou quando você precisa enviar ou acessar algum tipo de dados pela rede.
Esta sessão abordará os principais conceitos usados para interagir com o sistema de arquivos e fornecerá um entendimento do funcionamento dos seguintes itens:
O .NET Framework fornece as classes para interagir com uma E/S de arquivo que pode ser encontrada no namespace System.IO. Este namespace é a coleção das classes base dedicadas a serviços de entrada e saída baseados em arquivos e memória.
Trabalhando com o Drive
DriveInfo é uma classe fornecida pelo .NET no System.IO, usada para interagir com uma mídia de armazenamento, que pode ser um disco rígido ou qualquer outro armazenamento (ou seja, disco removível). Esta classe fornece informações sobre as unidades, como nome, tamanho e espaço livre da unidade. Você também pode conhecer quais unidades estão disponíveis e que tipo elas são.
Dentro do construtor do DriveInfo, o nome do Drive é passado e você pode acessar suas informações. Você também pode obter todas as unidades e buscar seus detalhes usando o método GetDrives () (método estático do DriveInfo classe).
DriveInfo d_info = new DriveInfo(@”C:"); Console.WriteLine(“O nome é: “ + d_info.Name); Console.WriteLine(“Tipo de unidade é: “ + d_info.DriveType); Console.WriteLine(“********”);
// Obtenha toda a unidade DriveInfo[] driveInfo = DriveInfo.GetDrives(); foreach (DriveInfo info in driveInfo) { Console.WriteLine(“Drive {0}”, info.Name); Console.WriteLine(“File type: {0}”, info.DriveType); if (info.IsReady == true) { Console.WriteLine(“Volume label: {0}”, info.VolumeLabel); Console.WriteLine(“File system: {0}”, info.DriveFormat); Console.WriteLine(“Available space to current user:{0,15} bytes”, info.AvailableFreeSpace); Console.WriteLine(“Total available space: {0,15} bytes”, info.TotalFreeSpace); Console.WriteLine(“Total size of drive: {0,15} bytes”, info.TotalSize); } }
Trabalhando com Diretórios e Arquivos
O drive contém diretórios e arquivos. Para trabalhar com eles, DirectoryInfo ou Directory (Static Class) é usado. Ambas as classes podem ser usadas para acessar a estrutura de diretórios. Você pode acessar todos os arquivos das pastas (ou a subpasta), bem como o arquivo específico na pasta ou subpasta usando essas classes. Você também pode criar e executar outras operações relacionadas à pasta em uma nova pasta ou diretório usando essas classes.
O C# fornece as seguintes classes para trabalhar com o sistema de arquivos. Eles podem ser usados para acessar diretórios, acessar arquivos, abrir arquivos para leitura ou gravação, criar um novo arquivo ou mover arquivos existentes de um local para outro, etc. Classe Uso File É uma classe estática que fornece diferentes funcionalidades, como copiar, criar, mover, excluir, abrir para leitura ou gravação, criptografar ou descriptografar, verificar se existe um arquivo, anexar linhas ou texto ao conteúdo de um arquivo, obter a hora do último acesso, etc. FileInfo Fornece a mesma funcionalidade que uma classe File estática. Você tem mais controle sobre como executar operações de leitura/gravação em um arquivo escrevendo código manualmente para ler ou gravar bytes de um arquivo. Directory Euma classe estática que fornece funcionalidade para criar, mover, excluir e acessar um diretório ou subdiretórios. DirectoryInfo Fornece métodos de instância para criar, mover, excluir e acessar um diretório ou subdiretórios. Path É uma classe estática que fornece funcionalidades como recuperar a extensão de um arquivo, alterar a extensão de um arquivo, recuperar o caminho físico absoluto e outras funcionalidades relacionadas ao caminho usando uma variável string.
DIRECTORY E DIRECTORYINFO
A classe Directory é uma classe estática que executa uma única operação. Geralmente, é preferível usar em casos como ao executar uma única tarefa,como apenas criar uma pasta.
DirectoryInfo é uma classe não estática que executa várias operações. Geralmente é usado ao executar várias operações/tarefas, como criar uma pasta, criar subpastas ou movê-las ou obter arquivos dessa pasta. O código a seguir mostra como você pode verificar a existencia do diretorio e criar uma nova pasta usando essas duas classes.
A classe DirectoryInfo herda do objeto System.IO.FileSystemInfo e, assim como o objeto FileInfo, contém as mesmas propriedades adequadas para os atributos, hora da criação, extensão, nome completo, última hora de acesso e última. hora de gravação do diretório. As propriedades para a classe DirectoryInfo estão listadas na Tabela 9-22.
Propriedades e métodos importantes de DirectoryInfo: Propriedade Descrição Exists Retorna um booleano indicando se o diretório existe Name Obtém o nome da instância DirectoryInfo Parent Retorna um objeto DirectoryInfo do diretório pai Root Retorna um objeto DirectoryInfo do diretório raiz
O objeto Directory e DirectoryInfo possui métodos semelhantes que executam a mesma operação; a única diferença é que, como o objeto Directory é estático, os métodos levam parâmetros para os diretórios a serem manipulados, e o objeto DirectoryInfo manipula o diretório da instância. A tabela abaixo lista alguns dos métodos comuns entre o objeto Directory e DirectoryInfo. Método Descrição Create (DirectoryInfo) Cria o diretório CreateDirectory (Directory) Delete Exclui o diretório GetAccessControl Retorna um objeto DirectorySecurity que encapsula as entradas da lista de controle de acesso para o diretório atual GetDirectories Retorna uma matriz DirectoryInfo dos subdiretórios no diretório atual GetFiles Retorna uma matriz FileInfo dos arquivos no diretório atual GetFileSystemInfos Retorna uma matriz FileSystemInfo dos arquivos e diretórios no diretório atual MoveTo (DirectoryInfo) Move um diretório Move (Directory) SetAccessControl Aplica entradas da lista de controle de acesso descritas por um objeto DirectorySecurity ao diretório atual
var path_Directory = @”C:\Users\x_kat\Desktop\Code_Estudos\01_Certificado_70-483\Udemy_Exam-70-483\Chapter 11\path_Directory”;
var path_DirectoryInfo = @”C:\Users\x_kat\Desktop\Code_Estudos\01_Certificado_70-483\Udemy_Exam-70-483\Chapter 11\path_DirectoryInfo”;
// Verificar a existência do diretório / pasta criado usando a classe de diretório if (Directory.Exists(path_Directory)) { Console.WriteLine(“Pasta de Diretório Existe”); Console.WriteLine(“******”); } else { // Crie um novo diretório / pasta usando a classe Directory DirectoryInfo directory = Directory.CreateDirectory(path_Directory); Console.WriteLine(“O nome do directory é: “ + directory.Name); Console.WriteLine(“******”); }
// Crie um novo diretório / pasta usando a classe DirectoryInfo DirectoryInfo directoryInfo = new DirectoryInfo(path_DirectoryInfo);
// Verificar a existência do diretório / pasta criado usando a classe DirectoryInfo if (directoryInfo.Exists) { Console.WriteLine(“A pasta DirectoryInfo existe”); Console.WriteLine(“******”); } else { directoryInfo.Create(); Console.WriteLine(“O nome do directoryInfo é: “ + directoryInfo.Name); Console.WriteLine(“******”); }
Conforme mostrado no código, você só precisa fornecer um caminho completo, juntamente com o nome de uma pasta na qual deseja criar a pasta como um parâmetro no método CreateDirectory() da classe Directory. Um novo diretório ou pasta será criado. Quando o caminho não é fornecido, por padrão, a pasta será criada no diretório atual em que você está trabalhando.
O código também mostra a criação de uma pasta pela classe DirectoryInfo no qual você pode executar outras operações fornecidas pela classe DirectoryInfo em uma pasta recém-criada, como o método Exist() (para verificar a existência de uma pasta), Delete() (para excluir a pasta) ou o método CreateSubdirectory() (para criar um subdiretório). Você pode tentar criar um novo diretório em um local em que não tenha permissões suficientes. Nesse caso, será lançada a seguinte exceção: UnauthorizedAccessException Você também pode excluir a pasta usando o método Delete(), mas se o diretório não for encontrado, você encontrará a seguinte exceção: DirectoryNotFoundException.
Uma coisa importante a lembrar ao trabalhar com diretórios e arquivos é que o sistema operacional controla o acesso a todos os elementos no computador local ou em uma unidade de rede compartilhada. O acesso às pastas pode ser organizado usando a classe DirectorySecurity do sistema. Namespace Security.AccessControl. A Listagem 4-4 mostra como você pode usá-los para permitir que todos acessem uma pasta. Isso, é claro, requer que o programa em execução tenha os direitos para fazer essa modificação.
DirectoryInfo directoryInfo = new DirectoryInfo(path_DirectoryInfo);
directoryInfo.Create(); DirectorySecurity directorySecurity = directoryInfo.GetAccessControl(); directorySecurity.AddAccessRule(new FileSystemAccessRule(“everyone”, FileSystemRights.ReadAndExecute, AccessControlType.Allow));
directoryInfo.SetAccessControl(directorySecurity);
Além dos métodos para criar e remover diretórios, você também pode consultar um método para atributos, subdiretórios ou arquivos. As classes Directory e DirectoryInfo possuem um método para recuperar todos os subdiretórios de um determinado diretório. Isso retorna uma matriz de objetos DirectoryInfo.
Você pode especificar um padrão de pesquisa e uma enumeração do tipo SearchOption que permita pesquisar automaticamente apenas a pasta superior ou todas as subpastas. Ao percorrer todas as pastas, pode ser que você esteja tentando acessar uma pasta sem os direitos de acesso necessários. Isso lança um UnauthorizedAccessException. Ao usar o SearchOption.AllDirectories e uma exceção é lançada, você não obtém resultados.
O exmplo a seguir mostra um exemplo de percorrer a árvore de diretórios manualmente e manipular qualquer exceção que possa ocorrer. Também mostra como usar um padrão de pesquisa para limitar os resultados a todas as pastas que contêm o caractere a. Ele limita a profundidade recursiva da função para garantir que você possa controlar quanto tempo o método leva para executar.
private static void ListDirectories(DirectoryInfo directoryInfo, string searchPattern, int maxLevel, int currentLevel) { if (currentLevel >= maxLevel) { return; } string indent = new string(‘-‘, currentLevel); try { DirectoryInfo[] subDirectories = directoryInfo.GetDirectories(searchPattern); foreach (DirectoryInfo subDirectory in subDirectories) { Console.WriteLine(indent + subDirectory.Name); ListDirectories(subDirectory, searchPattern, maxLevel, currentLevel + 1); } } catch (UnauthorizedAccessException) { // You don’t have access to this folder. Console.WriteLine(indent + “Can’t access: “ + directoryInfo.Name); return; } catch (DirectoryNotFoundException) { // The folder is removed while iterating Console.WriteLine(indent + “Can’t find: “ + directoryInfo.Name); return; }
}
DirectoryInfo directoryInfo = new DirectoryInfo(@”C:\Program Files”); ListDirectories(directoryInfo, “a”, 5, 0);
Um padrão de pesquisa pode consistir em vários caracteres curinga que formam um padrão de pesquisa como você pode ver na tabela abaixo:
Caractere Descrição Exemplo
Ao trabalhar com a enorme estrutura de diretórios, use o método EnumerateDirectories()em vez do método GetDirectories() para buscar os diretórios, pois EnumerateDirectories() começa a enumerar diretórios antes que eles tenham sido completamente recuperados (execução tardia/deferred execution); enquanto que no caso GetDirectories(), o código não avançaria até que toda a lista de diretórios fosse recuperada (execução imediata/ immediate execution)
DriveInfo d_info = new DriveInfo(@”C:"); var diretorios_get = d_info.RootDirectory.GetDirectories(); var diretorios_info = d_info.RootDirectory.EnumerateDirectories();
foreach (var dirinfo in diretorios_info) { Console.WriteLine(“O nome é:” + dirinfo.Name); }
Outro método que pode ser útil é o método MoveTo em DirectoryInfo ou Move nas classes de diretório quando você deseja mover um diretório existente para um novo local.
//Using Directory Class Directory.Move(@”C:\source”, @”c:\destination”); //Using DirectoryInfo Class DirectoryInfo directorymove = new DirectoryInfo(@”C:\Source”); directoryInfo.MoveTo(@”C:\destination”);
O método Move() é usado com a classe Directory (classe estática), enquanto o método MoveTo() é usado com a classe DirectoryInfo. O método Move () requer que você conheça o caminho do diretório de origem e o caminho do diretório de destino, enquanto o método MoveTo() exige apenas o caminho do diretório de destino porque o objeto DirectoryInfo já restringe a referência do caminho do diretório de origem.
Função GetFiles
A classe Directory ou DirectoryInfo pode também ser usada para buscar todos os arquivos em uma pasta específica ou sua subpasta ou arquivos com tipos específicos (como imagens), já a classe File ou FileInfo é usada para acessar as informações desses arquivos ou para executar operações nesses arquivos. Estas classes permitem interagir com arquivos para, por exemplo, criar um arquivo ou excluir um arquivo ou verificar sua existência
path_Directory = @”C:\Windows”; path_DirectoryInfo = @”C:\Inetpub”;
// Obter arquivo de um diretório específico usando a classe de diretório string[] fileNames = Directory.GetFiles(path_Directory); foreach (var name in fileNames) { Console.WriteLine(“O nome é: {0}”, name); }
// Obter arquivos de um diretório específico usando a classe DirectoryInfo DirectoryInfo di = new DirectoryInfo(path_DirectoryInfo); FileInfo[] files = di.GetFiles(); foreach (var file in files) { Console.WriteLine(“O nome é: {0}”, file.Name); }
A classe Directory fornecerá apenas nomes de arquivos no diretório fornecido, enquanto DirectoryInfo retornará um objeto FileInfo(Class) no qual você pode executar operações relacionadas a arquivos.
FILE AND FILEINFO
A classe File e FileInfo é semelhante, exceto que a classe File é estática e contém apenas métodos nos quais a classe FileInfo permite criar uma instância que represente um arquivo, portanto ela possui propriedades e métodos. Observe que a classe File não possui propriedades porque é uma classe estática.
Os métodos para a classe File e FileInfo também são semelhantes. Esteja ciente de que os parâmetros são diferentes com base no uso de um objeto File ou FileInfo. Os métodos para a classe File usam parâmetros a um caminho de arquivo já os métodos da classe FileInfo usam uma instância em vez de parâmetros. Alguns dos métodos comuns entre a classe File e FileInfo são: AppendAllText, CopyTo/Copy, Create, Decrypt, Delete, Encrypt, MoveTo, Open, Replace, SetAccessControl.
File
O C# inclui a classe File estática para executar operações de E/S no sistema de arquivos físico. A classe estática File inclui vários métodos utilitários para interagir com arquivos físicos de qualquer tipo, por exemplo binário, texto etc. Use esta classe estática File para executar alguma operação rápida no arquivo físico. Não é recomendável usar a classe File para várias operações em vários arquivos ao mesmo tempo devido a razões de desempenho. Use a classe FileInfo nesse cenário.
Métodos importantes da classe de arquivo estático Método Descrição AppendAllLines Anexa linhas a um arquivo e, em seguida, fecha o arquivo. Se o arquivo especificado não existir, esse método cria um arquivo, grava as linhas especificadas no arquivo e fecha o arquivo. AppendAllText Abre um arquivo, anexa a sequência especificada ao arquivo e, em seguida, fecha o arquivo. Se o arquivo não existir, esse método cria um arquivo, grava a sequência especificada no arquivo e fecha o arquivo. AppendText Cria um StreamWriter que anexa o texto codificado em UTF-8 a um arquivo existente ou a um novo arquivo se o arquivo especificado não existir. Copy Copia um arquivo existente para um novo arquivo. Não é permitido substituir um arquivo com o mesmo nome. Create Cria ou substitui um arquivo no caminho especificado. CreateText Cria ou abre um arquivo para escrever texto codificado em UTF-8. Decrypt Descriptografa um arquivo que foi criptografado pela conta atual usando o método Criptografar. Delete Exclui o arquivo especificado. Encrypt Criptografa um arquivo para que somente a conta usada para criptografar o arquivo possa descriptografá-lo. Exists Determina se o arquivo especificado existe. GetAccessControl Obtém um objeto FileSecurity que encapsula as entradas da lista de controle de acesso (ACL) para um arquivo especificado. Move Move um arquivo especificado para um novo local, fornecendo a opção de especificar um novo nome de arquivo. Open Abre um FileStream no caminho especificado com acesso de leitura / gravação. ReadAllBytes Abre um arquivo binário, lê o conteúdo do arquivo em uma matriz de bytes e, em seguida, fecha o arquivo. ReadAllLines Abre um arquivo de texto, lê todas as linhas do arquivo e depois o fecha. ReadAllText Abre um arquivo de texto, lê todas as linhas do arquivo e depois o fecha. Replace Substitui o conteúdo de um arquivo especificado pelo conteúdo de outro arquivo, excluindo o arquivo original e criando um backup do arquivo substituído. WriteAllBytes Cria um novo arquivo, grava a matriz de bytes especificada no arquivo e fecha o arquivo. Se o arquivo de destino já existir, ele será substituído. WriteAllLines Cria um novo arquivo, grava uma coleção de seqüências de caracteres no arquivo e fecha o arquivo. WriteAllText Cria um novo arquivo, grava a sequência especificada no arquivo e, em seguida, fecha o arquivo. Se o arquivo de destino já existir, ele será substituído.
// Para criar um arquivo no local atual chamado “Arquivo” usando Arquivo (Classe estática) File.Create(“Arquivo.txt”).Close(); // Para escrever conteúdo em um arquivo chamado “Arquivo” File.WriteAllText(“Arquivo.txt”, “Este é um arquivo criado pela File Class”);
// Para ler o arquivo chamado “Arquivo” string fileContent = File.ReadAllText(“Arquivo.txt”); Console.WriteLine(fileContent);
//Se exixtir arquivo, apagar e criar.. if (File.Exists(“../Copied Arquivo.txt”)) { File.Delete(“../Copied Arquivo.txt”);
// Para copiar “Arquivo” do local atual para um novo (pasta anterior) File.Copy(“Arquivo.txt”, “../Copied Arquivo.txt”); } else { File.Copy(“Arquivo.txt”, “../Copied Arquivo.txt”); }
// Para criar um arquivo no local atual chamado “ArquivoInfo” usando a classe FileInfo FileInfo info = new FileInfo(“ArquivoInfo.txt”); info.Create().Close(); // Para mover “FileInfo” do local atual para um novo (Pasta anterior) info.MoveTo(“../Moved ArquivoInfo.txt”);
Como você notou na primeira linha de código, o método Create() é precedido pelo método Close(). Isso se deve ao arquivo(Classe estática) executando uma única operação que é, nesse caso, a criação de um arquivo e, após a criação de um arquivo, você deve fechá-lo antes de executar outra operação nele. É por isso que Close() é chamado após a criação: para que o arquivo possa ser gravado.
Essas operações executadas em um arquivo são muito semelhantes às realizadas no Directory.Copy/CopyTo (métodos de File/FileInfo) deve ser usado onde você deseja deixar uma cópia em um local anterior e outra/outras em um novo local; considerando que Move/MoveTo (métodos de File/FileInfo) deve ser usado onde você não deseja deixar um copie em um local anterior, mas copie-o para um novo local. É como um comportamento de recortar e colar. Quando você precisar atualizar o conteúdo de um arquivo ou alterar o conteúdo de um arquivo já criado, você pode usar o método AppendText () fornecido pelas classes File e FileInfo. Basicamente, existem dois métodos diferentes para criar um arquivo. Estes são dados abaixo com detalhes:
Repare que qunado a instrução File.Exists retorna false, você pode não pode assumir com segurança que o arquivo não existe, pois você pode não ser o único usuário acessando o sistema de arquivos. Enquanto você trabalha com o sistema de arquivos, outros usuários estão fazendo exatamente a mesma coisa. Talvez eles removem a pasta que você deseja usar para criar um novo arquivo. Ou eles alteram repentinamente as permissões em um arquivo para que você não possa mais acessá-lo. Normalmente, ao lidar com uma situação em que vários usuários acessam recursos compartilhados, começamos a usar um mecanismo de bloqueio para sincronizar o uso de recursos. O C# possui um mecanismo de bloqueio que você pode usar para sincronizar o acesso ao código quando vários segmentos estiverem envolvidos. Isso garante que um determinado pedaço de código não possa ser executado simultaneamente no mesmo momento. No entanto, o sistema de arquivos não possui esses mecanismos de bloqueio. É um sistema multithread, mas sem nenhum dos regulamentos de segurança que você deseja ver. Veja o código que determina se um arquivo existe e depois lê todo o texto nele.
string path = @”C:\temp\test.txt”; if (File.Exists(path)) File.ReadAllText(path);
Outro usuário pode remover o arquivo entre a chamada para Exists e ReadAllText, o que causaria uma exceção. No entanto, você pode antecipar as exceções que podem ser lançadas e garantir que seu aplicativo saiba como lidar com elas. Veja a Listagem 4-21.
string path = @”C:\temp\test.txt”; try { File.ReadAllText(path); } catch (DirectoryNotFoundException) { } catch (FileNotFoundException) { }
Ao trabalhar com o sistema de arquivos, lembre-se de que as exceções podem e ocorrerão. Um bom tratamento de exceções é importante para um aplicativo robusto que funciona com arquivos.
Anexar linhas de texto
Use o método AppendAllLines () para anexar várias linhas de texto ao arquivo especificado, como mostrado abaixo.
string dummyLines = “Esta é a primeira linha.” + Environment.NewLine + “Esta é a segunda linha.” + Environment.NewLine + “Esta é a terceira linha.”;
// Abre DummyFile.txt e acrescenta linhas. Se o arquivo não existir, crie e abra. File.AppendAllLines(@”Arquivo.txt”, dummyLines.Split(Environment.NewLine.ToCharArray()));
Anexar String
Use o método File.AppendAllText () para anexar uma string a um arquivo em uma única linha de código, como mostrado abaixo.
File.AppendAllText(@”Arquivo.txt”, Environment.NewLine + “Adicionar uma string ao arquivo”);
Substituir texto
Use o método File.WriteAllText () para escrever textos no arquivo. Observe que ele não acrescentará texto, mas substituirá os textos existentes.
File.WriteAllText(@”Arquivo.txt”, “Texto subtituido”);
O exemplo a seguir mostra como executar operações diferentes usando a classe estática File.
// Verifique se o arquivo existe ou não em um local específico bool isFileExists = File.Exists(@”C:\DummyFile.txt”); // retorna false
// Copia DummyFile.txt como novo arquivo DummyFileNew.txt File.Copy(@”C:\DummyFile.txt”, @”D:\NewDummyFile.txt”); // Obter quando o arquivo foi acessado pela última vez DateTime lastAccessTime = File.GetLastAccessTime(@”C:\DummyFile.txt”); // obtém quando o arquivo foi gravado pela última vez DateTime lastWriteTime = File.GetLastWriteTime(@”C:\DummyFile.txt”); // Mover arquivo para o novo local File.Move(@”C:\DummyFile.txt”, @”D:\DummyFile.txt”); // Abre o arquivo e retorna o FileStream para ler bytes do arquivo FileStream fs = File.Open(@”D:\DummyFile.txt”, FileMode.OpenOrCreate); // Abra o arquivo e retorne o StreamReader para ler a string do arquivo StreamReader sr = File.OpenText(@”D:\DummyFile.txt”); // Excluir arquivo File.Delete(@”C:\DummyFile.txt”);
Assim, é fácil trabalhar com arquivos físicos usando a classe estática File. No entanto, se você quiser mais flexibilidade, use a classe FileInfo. Da mesma maneira, use a classe Directory estática para trabalhar com diretórios físicos.
FileInfo
Você aprendeu a executar tarefas diferentes em arquivos físicos usando a classe estática File na seção anterior. Aqui, usaremos a classe FileInfo para executar operações de leitura/gravação em arquivos físicos. A classe FileInfo fornece a mesma funcionalidade que a classe estática File, mas você tem mais controle sobre operações de leitura/gravação em arquivos escrevendo código manualmente para ler ou gravar bytes de um arquivo.
O objeto FileInfo, diferentemente do objeto File, herda do objeto System.IO.FileSystemInfo, que contém propriedades para os atributos, hora da criação, extensão, nome completo, última hora de acesso e última hora de gravação do arquivo.
Propriedades e métodos importantes de FileInfo: Propriedade Descrição Directory Obtém uma instância do diretório pai. DirectoryName Obtém uma string representando o caminho completo do diretório. Exists Obtém um valor indicando se existe um arquivo. Extension Obtém a string que representa a parte da extensão do arquivo. FullName Obtém o caminho completo do diretório ou arquivo. IsReadOnly Obtém ou define um valor que determina se o arquivo atual é somente leitura. LastAccessTime Obtém ou define a hora em que o arquivo ou diretório atual foi acessado pela última vez LastWriteTime Obtém ou define o horário em que o arquivo ou diretório atual foi gravado pela última vez. Length Obtém o tamanho, em bytes, do arquivo atual. Name Obtém o nome do arquivo. … Método Descrição AppendText Cria um StreamWriter que acrescenta texto ao arquivo representado por esta instância do FileInfo. CopyTo Copia um arquivo existente para um novo arquivo, impedindo a substituição de um arquivo existente. Create Cria um arquivo. CreateText Cria um StreamWriter que grava um novo arquivo de texto. Decrypt Descriptografa um arquivo que foi criptografado pela conta atual usando o método Criptografar. Delete Exclui o arquivo especificado. Encrypt Criptografa um arquivo para que somente a conta usada para criptografar o arquivo possa descriptografá-lo. GetAccessControl Obtém um objeto FileSecurity que encapsula as entradas da lista de controle de acesso (ACL) para um arquivo especificado. MoveTo Move um arquivo especificado para um novo local, fornecendo a opção de especificar um novo nome de arquivo. Open Abre um no FileMode especificado. OpenRead Cria um FileStream somente leitura. OpenText Cria um StreamReader com codificação UTF8 que lê de um arquivo de texto existente. OpenWrite Cria um FileStream somente para gravação. Replace Substitui o conteúdo de um arquivo especificado pelo arquivo descrito pelo objeto FileInfo atual, excluindo o arquivo original e criando um backup do arquivo substituído. ToString Retorna um caminho como string.
O construtor da classe FileInfo usa um parâmetro de string que contém o caminho e o nome do arquivo. Este é o único construtor para o objeto FileInfo. O exemplo de código a seguir cria uma instância de um objeto FileInfo e grava o nome do arquivo na janela Saída:
// Cria o objeto FileInfo para o caminho especificado FileInfo fi = new FileInfo(@”DummyFile.txt”); Console.WriteLine(fi.Name);
Repare que o arquivo não foi "aberto"; a instância está simplesmente permitindo que você obtenha informações sobre o arquivo. Existem métodos que permitem abrir e alterar o conteúdo do arquivo, mas simplesmente criar uma instância de um objeto FileInfo não abre o arquivo. O exemplo a seguir mostra como ler bytes de um arquivo manualmente e depois convertê-los em uma string usando a codificação UTF8:
// Cria o objeto FileInfo para o caminho especificado FileInfo fi = new FileInfo(@”DummyFile.txt”);
// Abrir arquivo para leitura \ gravação FileStream fs = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
// cria uma matriz de bytes do mesmo tamanho que o comprimento do FileStream byte[] fileBytes = newbyte[fs.Length];
// define contador para verificar quantos bytes ler. Diminua o contador enquanto lê cada byte int numBytesToRead = (int)fileBytes.Length;
// Contador para indicar o número de bytes já lidos int numBytesRead = 0;
// itera até que todos os bytes sejam lidos no FileStream while (numBytesToRead > 0) { int n = fs.Read(fileBytes, numBytesRead, numBytesToRead);
if (n == 0) break;
numBytesRead += n;
numBytesToRead -= n; }
// Depois de ler todos os bytes do FileStream, você pode convertê-lo em string em codificação UTF8 string filestring = Encoding.UTF8.GetString(fileBytes); Console.WriteLine(filestring);
Como você viu no código acima, você deve escrever muito código para ler/escrever uma string de um FileSream. A mesma operação de leitura/gravação pode ser feita facilmente usando o StreamReader e o StreamWriter. O exemplo a seguir mostra como o StreamReader facilita a leitura de strings de um arquivo:
// Cria o objeto FileInfo para o caminho especificado FileInfo fi = new FileInfo(@”DummyFile.txt”);
Console.WriteLine(“======Com StreamReader e ===========”);
fs = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
// Crie o objeto StreamReader passando o objeto FileStream no qual ele precisa operar StreamReader sr = new StreamReader(fs);
// Use o método ReadToEnd para ler todo o conteúdo do arquivo string fileContent = sr.ReadToEnd(); Console.WriteLine(“StreamReader: “ + fileContent);
fs.Close();
Console.WriteLine(“======Com using StreamReader e ===========”);
using (StreamReader reader = new StreamReader(@”DummyFile.txt”)) { // Read entire text file with ReadToEnd. string contents = reader.ReadToEnd(); Console.WriteLine(“using StreamReader: “ + contents); }
Observe que fi.Open() possui três parâmetros: O primeiro parâmetro é FileMode para criar e abrir um arquivo, se ele não existir; o segundo parâmetro, FileAccess, é indicar uma operação de leitura; e o terceiro parâmetro é compartilhar o arquivo para leitura com outros usuários enquanto o arquivo estiver aberto. O exemplo a seguir mostra como o StreamWriter facilita a gravação de strings em um arquivo:
using (StreamWriter writer = new StreamWriter(fs)) { // Crie o objeto StreamWriter para gravar a string no FileSream writer.WriteLine(Environment.NewLine + “Outra linha do streamwriter”); }
Operações de leitura e gravação não são possíveis no mesmo objeto FileStream simultaneamente.
System.IO.IOException: ‘O processo não pode acessar o arquivo ‘DummyFile.txt’ porque ele está sendo usado por outro processo.’
Se você já estiver lendo um arquivo, crie um objeto FileStream separado para gravar no mesmo arquivo. Assim, você pode usar as classes FileInfo, StreamReader e StreamWriter para ler/gravar o conteúdo do arquivo físico.
FileInfo fi = new FileInfo(@”D:\DummyFile.txt”);
FileStream fsToRead = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); FileStream fsToWrite = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
Trabalhando com caminhos (path)
Ao acessar arquivos, você geralmente precisa combinar um diretório e um nome de arquivo. Concatená-los manualmente usando a adição simples de cadeias pode funcionar, mas é propenso a erros. Nunca tente adicionar manualmente seqüências de caracteres para formar um caminho. Sempre use a classe Path ao combinar várias seqüências para formar um caminho legal. Por exemplo, adicionar as seqüências de caracteres mostradas abaixo resulta em um caminho inválido.
string folder = @”C:\temp”; string fileName = “test.dat”; string fullPath = folder + fileName; // Results in C:\temptest.dat
A classe estática Path que pode ser encontrada no System.IO possui alguns métodos auxiliares para lidar com esses tipos de situações. Um desses métodos é o método estático Combine, que possui várias sobrecargas que aceitam vários parâmetros de seqüência de caracteres. O código abaixo mostra como o método Combine resulta em um caminho correto.
string fullPath = Path.Combine(folder, fileName); // Results in C:\temp\test.dat
A classe Path oferece alguns outros métodos úteis: GetDirectoryName, GetExtensions, GetFileName e GetPathRoot. A Listagem 4-13 mostra como você pode usar esses métodos em uma sequência que contém um caminho completo.
string path = @”C:\temp\subdir\file.txt”; Console.WriteLine(Path.GetDirectoryName(path)); // Displays C:\temp\subdir Console.WriteLine(Path.GetExtension(path)); // Displays .txt Console.WriteLine(Path.GetFileName(path)); // Displays file.txt Console.WriteLine(Path.GetPathRoot(path)); // Displays C:\
A classe Path também pode ajudá-lo quando você precisar armazenar temporariamente alguns dados. Você pode usar GetRandomFileName para criar um arquivo ou nome de diretório aleatório. GetTempPath retorna o local da pasta temporária do usuário atual e GetTempFileName cria um arquivo temporário que você pode usar para armazenar alguns dados.
Sumário
STREAM
Stream é uma classe abstrata usada para escrever e ler bytes. É para operações de E/S de arquivo. Ao trabalhar com arquivos, é importante saber sobre o Stream, porque um arquivo é armazenado no disco rígido ou no DVD do computador em uma sequência de bytes. Ao se comunicar pela rede, um arquivo é transferido na forma de uma sequência de bytes. Além disso, ele é armazenado na memória na forma de uma sequência de bytes.
O Stream tem três tarefas principais:
Stream: System.IO.Stream é uma classe abstrata que fornece métodos padrão para transferir bytes (leitura, gravação, etc.) para a fonte. É como uma classe de wrapper para transferir bytes. As classes que precisam ler/gravar bytes de uma fonte específica devem implementar a classe Stream.
As seguintes classes herdam a classe Stream para fornecer funcionalidade aos bytes de leitura / gravação de uma fonte específica:
Classe Descrição FileStream Lê ou grava bytes de / para um arquivo físico, seja um arquivo .txt, .exe, .jpg ou qualquer outro arquivo. IsolatedStorageFileStream Lê e grava arquivos em armazenamento isolado MemoryStream Lê ou grava bytes armazenados na memória. BufferedStream Lê ou grava bytes de outros Streams para melhorar o desempenho de certas operações de E / S. NetworkStream Lê ou grava bytes de um soquete de rede. PipeStream Lê ou grava bytes de diferentes processos. CryptoStream É para vincular fluxos de dados a transformações criptográficas.
Codificação e decodificação
O processo de conversão de caracteres em bytes (e vice-versa) é chamado de codificação e decodificação. O Unicode Consortium é responsável por manter um padrão que descreva como isso deve acontecer. Um char, que é o tipo de caractere mais básico, é equivalente a um único caractere Unicode que ocupa 2 bytes de memória. Uma string é simplesmente uma sequência de caracteres. System.Text.Encoding é a classe que ajuda a converter entre bytes e seqüências de caracteres.
O .NET Framework oferece vários padrões de codificação que você pode usar. UTF-8 é aquele que é suficiente para uso geral. Ele pode representar todos os caracteres Unicode e é usado como a codificação padrão em muitas classes do .NET Framework. Outras codificações são: Esquema Encoding Classe Utilização ASCII ASCIIEnconding GetEnconding(20127) ou propriedade ASCII Default Enconding GetEnconding(0) ou propriedade Default UTF-7 UTF7Enconding GetEnconding(65000) ou propriedade UTF7 UTF-8 UTF8Enconding GetEnconding(65001) ou propriedade UTF8 UTF-16 UnicodeEnconding GetEnconding(1201) ou propriedade BigEndianUnicode UTF-16 (little-endian) UnicodeEnconding GetEnconding(1200) ou propriedade Unicode UTF32 UTF32Enconding GetEnconding(12000) ou propriedade UTF32 Windows OS Enconding GetEnconding(1252)
Para obter o código de página para passer como parâmetro no método GetEncoding utilize o seguinte xódigo que lista todos os códigos de página:
// Print the header. Console.Write(“CodePage identifier and name “); Console.Write(“BrDisp BrSave “); Console.Write(“MNDisp MNSave “); Console.WriteLine(“1-Byte ReadOnly “);
// For every encoding, get the property values. foreach (EncodingInfo ei in Encoding.GetEncodings()) { Encoding e = ei.GetEncoding();
Console.Write("{0,-6} {1,-25} ", ei.CodePage, ei.Name);
Console.Write("{0,-8} {1,-8} ", e.IsBrowserDisplay, e.IsBrowserSave);
Console.Write("{0,-8} {1,-8} ", e.IsMailNewsDisplay, e.IsMailNewsSave);
Console.WriteLine("{0,-8} {1,-8} ", e.IsSingleByte, e.IsReadOnly); }
Para tornar isso um pouco mais fácil ao trabalhar com texto, a classe File também suporta um método CreateText que cria um arquivo com uma codificação UTF-8 para você. CreateText retorna um StreamWriter, uma classe que herda de TextWriter e permite que você escreva caracteres diretamente em um Stream com uma codificação específica, como mostra a Listagem 4-15.
string path = @”c:\temp\test.dat”; using (StreamWriter streamWriter = File.CreateText(path)) { string myValue = “MyValue”; streamWriter.Write(myValue); }
Obviamente, você também desejará ler dados de um arquivo. Você pode fazer isso usando diretamente um objeto FileStream, lendo os bytes e convertendo-os novamente em uma sequência com a codificação correta. A Listagem 4-16 mostra como ler os bytes de um arquivo e convertê-los em uma string com codificação UTF-8.
using (FileStream fileStream = File.OpenRead(path)) { byte[] data = new byte[fileStream.Length]; for (int index = 0; index < fileStream.Length; index++) { data[index] = (byte)fileStream.ReadByte(); } Console.WriteLine(Encoding.UTF8.GetString(data)); // Displays: MyValue
using (StreamReader streamWriter = File.OpenText(path))
{
Console.WriteLine(streamWriter.ReadLine()); // Displays: MyValue
} }
Se você souber que está analisando um arquivo de texto, também poderá usar um StreamReader (como o oposto do StreamWriter) para ler um arquivo de texto.
FileStream
FileStream provem da classe abstrata Stream, usada principalmente para escrever e ler bytes no arquivo. O próximo exemplo mostra como você pode usar o FileStream quando pode gravar conteúdo em um arquivo ao interagir com a classe File.
FileStream fileStream = File.Create(“File.txt”); string content = “This is file content”; byte[] contentInBytes = Encoding.UTF8.GetBytes(content); fileStream.Write(contentInBytes, 0, contentInBytes.Length); fileStream.Close();
Basicamente, quando você cria o arquivo usando as classes File ou FileInfo, ele retorna um objeto do tipo FileStream ou StreamWriter ou outro Stream(sequência de bytes), pois o arquivo é armazenado ou transferido na forma de bytes. Após obter o fluxo (stream) de um arquivo, você pode executar as respectivas operações em um arquivo, dependendo do tipo de fluxo. Como neste caso, File.Create() retorna um objeto FileStream para que você possa executar ainda mais as operações do FileStream no arquivo criado.
Como mencionado, o Stream funciona em bytes; portanto, para escrever algo no arquivo, você precisa converter o conteúdo na forma de bytes e depois gravar no arquivo usando o método Write() do FileStream. O método Write() utiliza três parâmetros que contêm os bytes de conteúdo a serem gravados, a posição inicial e final dos bytes a serem gravados.
Ao lidar com arquivos, é importante liberar o recurso, conforme mostrado no código para o métodoFile.Close() deve ser chamado para liberar os recursos do arquivo para que ele possa ser usado para operações posteriores a serem executadas em Files. Se você não liberar o recurso, receberá a exceção “Um arquivo está aberto/está sendo usado em outro processo” ou algo assim. Você também pode usar o bloco using para liberar o recurso. O FileStream tem alguns parâmetros para explicar, os seguintes detalhes ilustram parâmetros que o FileStream aceita: Parâmetro Descrição File_Name File_Name é o nome do arquivo no qual uma operação será executada. FileMode FileMode determina se você cria, abre ou trunca um arquivo.
Preste atenção a essas opções, pois é provável que no exame surja uma pergunta sobre as enumerações FileMode, FileAccess ou FileShare.
FileIOPermissionAccess (Veja mais sobre isso, na parte 4-Depuração e Seguranca, Criptografia)
Ao criar ou abrir um arquivo, o processo deve ter as permissões corretas para o arquivo ou diretório para executar a operação especificada. A enumeração System.Security.Permissions.FileIOPermissionAccess contém os tipos de permissões para um arquivo ou diretório. Valor Descrição NoAccess Sem acesso a um arquivo ou diretório Read Acesso de leitura a um arquivo ou diretório Write Acesso de gravação a um arquivo ou diretório Append Acesso para anexar dados a um arquivo ou diretório. O acesso anexado também inclui a capacidade de criar um novo arquivo ou diretório PathDiscovery Acesso às informações sobre o caminho AllAccess Append, Read, Write, e PathDiscovery fornecem acesso ao arquivo ou diretório
IPermission perm1 = new FileIOPermission(FileIOPermissionAccess.Read, @”C:\Windows”); IPermission perm2 = new FileIOPermission(FileIOPermissionAccess.Write, @”C:\Windows”); IPermission perm3 = new FileIOPermission(FileIOPermissionAccess.Write, @”C:\Windows”); IPermission perm4 = new FileIOPermission(FileIOPermissionAccess.AllAccess, @”C:\Windows”); IPermission all = new FileIOPermission(PermissionState.Unrestricted); IPermission none = new FileIOPermission(PermissionState.None);
Console.WriteLine(perm1.Union(perm2)); // IPermission Read=”C:\Windows” e Write=”C:\Windows” Console.WriteLine(perm2.Union(perm3)); // IPermission Write=”C:\Windows” Console.WriteLine(perm1.Intersect(perm4)); // IPermission Read=”C:\Windows” Console.WriteLine(perm1.Union(all)); // IPermission Unrestricted=”true” Console.WriteLine(perm1.Intersect(all)); // IPermission Read=”C:\Windows” Console.WriteLine(perm1.Union(none)); // IPermission Read=”C:\Windows”
IsolatedStorageFileStream
Para ler ou gravar de um arquivo em um armazenamento isolado, use um objeto IsolatedStorageFileStream com um leitor de fluxo (objeto StreamReader) ou o gravador do fluxo (objeto StreamWriter). V ocê pode usar IsolatedStorageFile e IsolatedStorageFileStream para gravar dados em um arquivo de armazenamento isolado. • IsolatedStorageFile é essencialmente um ponteiro para o arquivo de armazenamento isolado (área) no disco.
var isolated = IsolatedStorageFile.GetUserStoreForApplication(); using (var writer = new StreamWriter(isolated.CreateFile(“TestStore.txt”))) { writer.WriteLine(“Text”); }
• IsolatedStorageFileStream é uma representação na memória dos dados em um arquivo dentro da área de armazenamento isolada.
var isolated_stream = IsolatedStorageFile.GetUserStoreForApplication(); using (StreamWriter writer = new StreamWriter(new IsolatedStorageFileStream(“TestStore.txt”, FileMode.Create, FileAccess.Write, isolated_stream))) { writer.WriteLine(“Text”); }
O exemplo de código a seguir obtém um repositório isolado e verifica se existe um arquivo chamado TestStore.txt no repositório. Se não existir, ele cria o arquivo e grava “Armazenamento Isolado do Hello” no arquivo. Se TestStore.txt já existir, o código de exemplo será lido a partir do arquivo.
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null); |
if (isoStore.FileExists(“TestStore.txt”)) { Console.WriteLine(“The file already exists!”); using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(“TestStore.txt”, FileMode.Open, isoStore)) { using (StreamReader reader = new StreamReader(isoStream)) { Console.WriteLine(“Reading contents:”); Console.WriteLine(reader.ReadToEnd()); } } } else { using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(“TestStore.txt”, FileMode.CreateNew, isoStore)) { using (StreamWriter writer = new StreamWriter(isoStream)) { writer.WriteLine(“Hello Isolated Storage”); Console.WriteLine(“You have written to the file.”); } } }
MemoryStream
O MemoryStream provém da classe abstrata Stream; é usado principalmente para escrever e ler bytes da memória.Se seu trabalho estiver apenas na memória, é melhor usar o MemoryStream. Caso contrário, FileStream com BufferedStream.
Essencialmente, MemoryStream é um objeto que gerencia um buffer é uma matriz de bytes, enquanto os bytes gravados nesse fluxo serão automaticamente atribuídos à próxima posição a partir da posição atual do cursor na matriz. Quando o buffer estiver cheio, uma nova matriz com um tamanho maior a ser criado e copie os dados da matriz antiga.
// Crie um objeto MemoryStream com capacidade de 100 bytes. MemoryStream memoryStream = new MemoryStream(10);
byte[] javaBytes = Encoding.UTF8.GetBytes(“Java “); byte[] csharpBytes = Encoding.UTF8.GetBytes(“CSharp Comprimento”);
// Grava bytes no fluxo de memória. memoryStream.Write(javaBytes, 0, javaBytes.Length); memoryStream.Write(csharpBytes, 0, csharpBytes.Length);
// Escreva capacidade e comprimento. // ==> Capacidade: 10, Comprimento: 23. Aumenta automatico para Capacidade: 256 Console.WriteLine(“Capacidade: {0}, Comprimento: {1}”, memoryStream.Capacity.ToString(), memoryStream.Length.ToString());
// Neste momento, a posição do cursor está em pé após o caractere ‘0’. Console.WriteLine(“Position:” + memoryStream.Position); // ==> 23.
// Mova o cursor para trás 6 bytes, comparado à posição atual. memoryStream.Seek(-6, SeekOrigin.Current);
// Agora, posicione o cursor após o caractere ‘i’ e antes de ‘m’. Console.WriteLine(“Position:” + memoryStream.Position); // ==> 17
byte[] vsBytes = Encoding.UTF8.GetBytes(“vs”);
// Grava no fluxo de memória. Substituindo “im” por “vs” memoryStream.Write(vsBytes, 0, vsBytes.Length);
byte[] allBytes = memoryStream.GetBuffer();
string data = Encoding.UTF8.GetString(allBytes);
// ==> Java vs rp Console.WriteLine(“Conteudo lido por GetBuffer: “ + data);
// Define a posição para o início do fluxo memoryStream.Seek(0, SeekOrigin.Begin); // Ler do arquivo byte[] readContent = new byte[memoryStream.Length]; int count = memoryStream.Read(readContent, 0, readContent.Length); for (int i = count; i < memoryStream.Length; i++) { readContent[i] = Convert.ToByte(memoryStream.ReadByte()); } var resultado = Encoding.UTF8.GetString(readContent); Console.WriteLine(“Conteudo lido por Read: “ + resultado);
BufferedStream
Buffer é um bloco de bytes na memória usado para armazenar em cache os dados.O BufferedStream precisa que o fluxo (stream) seja armazenado em buffer e ajuda a melhorar a eficiência da leitura e gravação de dados.
BufferedStream apenas dois construtores, que envolvem fluxos diferentes. Constructor Description BufferedStream(Stream) Initializes a new instance of the BufferedStream class with a default buffer size of 4096 bytes. BufferedStream(Stream, Int32) Initializes a new instance of the BufferedStream class with the specified buffer size.
Os dados gravados no buffer de fluxo serão aramazenados temporariamente na memória e, quando o buffer estiver cheio, os dados serão automaticamente liberados no arquivo, você poderá liberar proativamente os dados no arquivo usando o método Flush(). O uso do BufferedStream nesse caso reduz o número de vezes ao gravar na unidade e, portanto, aumenta a eficiência do programa.
String fileName = @”File.txt”;
FileInfo file = new FileInfo(fileName);
// Verifique se o diretório existe. file.Directory.Create();
// Crie um novo arquivo, se existir, será sobrescrito. // Retorna o objeto FileStream. using (FileStream fileStream = file.Create()) { // Create BufferedStream quebra o FileStream. // (Especifique que o buffer é 10000 bytes). using (BufferedStream bs = new BufferedStream(fileStream, 10000)) { int índice = 0; for(índice = 1; índice < 2000; índice++) { String s = “Esta é a linha” + índice + “\n”;
byte[] bytes = Encoding.UTF8.GetBytes(s);
// Grava no buffer, quando o buffer estiver cheio, ele
// automaticamente pressiona o arquivo.
bs.Write(bytes, 0, bytes.Length);
}
//Set the position to the begninig of stream
bs.Seek(0, SeekOrigin.Begin);
//Read from file
byte[] readContent_Buff = new byte[bs.Length];
int count_Buff = bs.Read(readContent_Buff, 0, readContent_Buff.Length);
for (int i = count_Buff; i < bs.Length; i++)
{
readContent_Buff[i] = Convert.ToByte(bs.ReadByte());
}
string result = Encoding.UTF8.GetString(readContent_Buff);
Console.WriteLine(result);
// Liberando os dados restantes no buffer para o arquivo.
bs.Flush();
}
}
Os discos rígidos são otimizados para a leitura de blocos maiores de dados. Ler um arquivo byte a byte pode ser mais lento do que ler grandes pedaços de dados e processá-los byte a byte. Assim como no GZipStream, o BufferedStream usa outro Stream em seu construtor. O BufferedStream ajuda você a verificar se é possível ler ou gravar grandes pedaços de dados de uma só vez. O exemplo abaixo mostra como gravar alguns dados em um BufferedStream que agrupa um FileStream.
string path = @”c:\temp\bufferedStream.txt”; using (FileStream fileStream = File.Create(path)) { using (BufferedStream bufferedStream = new BufferedStream(fileStream)) { using (StreamWriter streamWriter = new StreamWriter(bufferedStream)) { streamWriter.WriteLine(“A line of text.”); } } }
NetworkStream
NetworkStream não está no exame 70-483. A classe NetworkStream fornece métodos para enviar e receber dados sobre Stream soquetes no modo de bloqueio. Você pode usar a classe NetworkStream para transferência de dados síncrona e assíncrona. O .NET fornece um conjunto de classes em System.Net.sockets que torna a nossa vida mais fácil, pois elas fornecem as classes TcpListener, TcpClient, NetworkStream que abstraem muitas tarefas que antes exigiam a criação de código extra.
O que é um socket ?
Um socket pode ser entendido como uma porta de um canal de comunicação que permite a um processo executando em um computador enviar/receber mensagens para/de outro processo que pode estar sendo executado no mesmo computador ou num computador remoto.
Os sockets permitem então a comunicação processo a processo da seguinte forma: • Comunicação local: processos locais usando sockets locais • Comunicação remota: processos remotos usando sockets em rede (TCP/IP)
Abaixo temos uma figura com que representa a comunicação de sockets e a pilha TCP/IP
Tipos de serviço de transporte: • Datagrama - transporte não orientado a conexão e sem controle de erros (protocolo UDP) • DataStream - transporte orientado a conexão com controle de erros (protocolo TCP)
NetworkStream.Write vs. Socket.Send
A vantagem de um NetworkStream deriva principalmente do fato de ser um Stream. A desvantagem de um soquete é que o código comum que lê e grava de fontes abstratas de E/S, como um Stream, não pode manipular um soquete. Digamos, por exemplo, que você tinha uma biblioteca de comunicações e que suportava serializar mensagens de arquivos, pipes nomeados e TCP/IP. A escolha ideal para a classe de E/S seria Stream, pois os métodos de serialização poderiam aceitar um FileStream, um PipeStream, um NetworkStream e até um MemoryStream. Esse é o benefício da abstração, porque depois que criamos o Stream, um método pode interagir com ele sem saber que tipo de Stream é.
A seguir a seqüência de ações realizadas no paradigma cliente/servidor (modo orientado a conexão) Servidor Cliente Cria um socket e atribui-lhe um endereço. Este endereço deve ser conhecido pelo cliente. Cria um socket e atribui-lhe um endereço Aguarda a conexão de um cliente Solicita a conexão do seu socket ao socket do servidor (conhece o endereço ) Aceita a conexão e cria um novo socket para comunicar com o cliente em causa Aguarda que a conexão seja estabelecida Recebe a mensagem no novo socket Envia uma mensagem ( request ) Envia mensagem de resposta (reply) Recebe a mensage de resposta (reply) fecha a conexão com o cliente Fecha a conexão com o servidor
Para colocar em prática a teoria vou criar dois projetos: TCPServidor e TCPCliente. Como não vou usar threads vamos precisar executar cada projeto separadamente. O projeto TCPServidor deverá ser executado primeiro e a seguir o projeto TCPCliente.
Para criar um NetworkStream, você deve fornecer um Socketconectado. Você também pode especificar o que FileAccess permissão que o NetworkStream tem sobre o Socketfornecido. Por padrão, fechar o NetworkStream não fecha o Socketfornecido. Se você quiser que o NetworkStream tenha permissão para fechar o Socketfornecido, deverá especificar true para o valor do parâmetro ownsSocket.
Código do servidor:
IPAddress[] ips = Dns.GetHostAddresses(“localhost”); var adress = ips.Length == 0 ? System.Net.IPAddress.Any : ips[1]; const int numeroPorta = 8000; TcpListener tcpListener = new TcpListener(adress, numeroPorta);
tcpListener.Start();
try { Console.WriteLine(“Aguardando uma conexão…”);
// aceita a conexao do cliente e retorna uma inicializacao TcpClient
using (TcpClient tcpClient = tcpListener.AcceptTcpClient())
{
Console.WriteLine("Conexão aceita.");
// obtem o stream
NetworkStream networkStream_read = tcpClient.GetStream();
NetworkStream networkStream_write = tcpClient.GetStream();
// qualquer comunicacao com o cliente remoto usando o TcpClient pode comecar aqui
string responseString = "Conectado ao servidor";
int contagem = 0;
bool terminado = false;
while (!terminado)
{
Byte[] sendBytes = Encoding.ASCII.GetBytes(responseString + " - " + contagem);
networkStream_write.Write(sendBytes, 0, sendBytes.Length);
// le o stream em um array de bytes
byte[] bytes = new byte[tcpClient.ReceiveBufferSize + 1];
networkStream_read.Read(bytes, 0, System.Convert.ToInt32(tcpClient.ReceiveBufferSize));
// Retorna os dados recebidos do cliente para o console
string clientdata = Encoding.ASCII.GetString(bytes);
clientdata = clientdata.Replace("\0", "");
Console.WriteLine(clientdata);
responseString = "";
contagem++;
if (clientdata == "tchau" || clientdata == "quit"
|| clientdata == "fim" || clientdata == "exit") terminado = true;
}
}
tcpListener.Stop(); } catch (Exception e) {
Console.WriteLine(e.ToString());
Console.ReadLine(); }
Código do cliente:
TcpClient tcpClient = new TcpClient();
Console.WriteLine(“Estabelecendo conexão.”);
tcpClient.Connect(“127.0.0.1”, 8000);
NetworkStream networkStream_read = tcpClient.GetStream(); NetworkStream networkStream_write = tcpClient.GetStream();
if (networkStream_write.CanWrite & networkStream_read.CanRead) { bool completed = false; while (!completed) { // Le o NetworkStream em um buffer byte[] bytes = new byte[tcpClient.ReceiveBufferSize + 1];
string input = Console.ReadLine();
if (input == "tchau" || input == "quit"
|| input == "fim" || input == "exit") completed = true;
networkStream_read.Read(bytes, 0, System.Convert.ToInt32(tcpClient.ReceiveBufferSize));
// exibe os dados recebidos do host no console
string returndata = Encoding.ASCII.GetString(bytes);
returndata = returndata.Replace("\0", "");
Console.WriteLine(("Host retornou : " + returndata));
byte[] sendBytes = Encoding.UTF8.GetBytes(input);
networkStream_write.Write(sendBytes, 0, sendBytes.Length);
} } else if (!networkStream_read.CanRead) {
Console.WriteLine("Não é possível enviar dados para este stream");
tcpClient.Close(); } else if (!networkStream_write.CanWrite) {
Console.WriteLine("Não é possivel ler dados deste stream");
tcpClient.Close(); }
Use os métodos Write e Read para E/S de bloqueio síncrono de thread único simples. Se você quiser processar a E/S usando threads separados, considere o uso dos métodos BeginWrite e EndWrite ou os métodos BeginRead e EndRead para comunicação. O NetworkStream não dá suporte ao acesso aleatório ao fluxo de dados de rede. O valor da propriedade CanSeek, que indica se o fluxo dá suporte à busca, é sempre false; ler a propriedade Position, ler a propriedade Length ou chamar o método Seek gerará um NotSupportedException.
As operações de leitura e gravação podem ser executadas simultaneamente em uma instância da classe NetworkStream sem a necessidade de sincronização. Desde que haja um thread exclusivo para as operações de gravação e um thread exclusivo para as operações de leitura, não haverá nenhuma interferência cruzada entre threads de leitura e gravação e nenhuma sincronização será necessária.
NetworkStream networkStream_read = tcpClient.GetStream(); NetworkStream networkStream_write = tcpClient.GetStream();
PipeStream
PipeStream não está no exame 70-483. A classe PipeStream fornece a classe base para operações de pipes nomeados e anônimos no .NET Framework.
Os Pipes, ou canais, são usados para comunicação entre processos e threads. Normalmente, há um servidor de pipe único ao qual um ou mais clientes podem se conectar e trocar mensagens.
Na plataforma .NET os Pipes são implementados como streams e assim você tem uma opção para não somente enviar bytes em um pipe, mas você pode usar todos os recursos dos streams como os leitores(readers) e os escritores (writers).
Pipes são representados pelo namespace System.IO.Pipes na plataforma .NET. Estes são os principais objetos que você precisará, dependendo do tipo de Pipe com o qual deseja trabalhar:
• Nomeados: NamedPipeServerStream e NamedPipeClientStream; • Anônimos: AnonymousPipeServerStream e AnonymousPipeClientStream;
Os pipes anônimos vêm com algumas limitações em comparação com os pipes nomeados: • Eles são unidirecionais, ou seja, o servidor e o cliente não podem trocar mensagens. A comunicação em que o servidor e o cliente têm permissão para enviar mensagens é chamada de duplex; • Só permitem a comunicação com um único cliente. Pipes nomeados fornecem um cenário de vários clientes; • Não podem funcionar pela rede, são limitados à mesma máquina. Pipes nomeados não têm tal limitação;
Abaixo implementação do servidor Pipe, nomeado e anônimo:
class Program { //Variaveis globais para Conexão Anonima private string _pipeHandle; private ManualResetEventSlim _pipeHandleSet;
static void Main(string[] args)
{
var p = new Program();
p.Run(args);
}
public void Run(string[] args)
{
_pipeHandleSet = new ManualResetEventSlim(initialState: false);
string nomePipe = args.Length == 1 ? args[0] : "PipeReaderDemo";
PipesLeitor(nomePipe);
}
private void PipesLeitor(string nomePipe)
{
Console.WriteLine($"### Servidor - {nomePipe} ####");
try
{
using (var pipeReader = new NamedPipeServerStream(nomePipe, PipeDirection.In))
{
Console.WriteLine("Aguardando conexão de clientes....");
pipeReader.WaitForConnection();
Console.WriteLine("Cliente(writer) conectado !!");
const int BUFFERSIZE = 256;
bool terminado = false;
while (!terminado)
{
byte[] buffer = new byte[BUFFERSIZE];
int nRead = pipeReader.Read(buffer, 0, BUFFERSIZE);
string input = Encoding.UTF8.GetString(buffer, 0, nRead);
Console.WriteLine(input);
if (input == "tchau" || input == "quit"
|| input == "fim" || input == "exit") terminado = true;
}
}
Console.WriteLine("Agora... pipe anônimo - reader");
var pipeReader_Anon = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.None);
using (var reader = new StreamReader(pipeReader_Anon))
{
//Seta valor do canal
_pipeHandle = pipeReader_Anon.GetClientHandleAsString();
Console.WriteLine($"pipe handle: {_pipeHandle}");
//Controle que avisa que servidor esta pronto
_pipeHandleSet.Set();
//iniciando cliente anonimo
Anonimo_Writer();
bool fim = false;
while (!fim)
{
string line = reader.ReadLine();
Console.WriteLine(line);
if (line == "fim") fim = true;
}
Console.WriteLine("concluindo a leitura...");
}
Console.WriteLine("Leitura completa.");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void Anonimo_Writer()
{
Console.WriteLine("pipe anônimo - writer");
//Controle que aguarda o servidor estar pronto
_pipeHandleSet.Wait();
//Aqui é inidicado qual canal será utilizado _pipeHandle
var pipeWriter = new AnonymousPipeClientStream(PipeDirection.Out, _pipeHandle);
using (var writer = new StreamWriter(pipeWriter))
{
writer.AutoFlush = true;
Console.WriteLine("iniciando o writer");
for (int i = 0; i < 5; i++)
{
writer.WriteLine($"Mensagem {i}");
Task.Delay(500).Wait();
}
writer.WriteLine("fim");
}
} }
Abaixo implementação do cliente(writer) Pipe nomeado:
private static void PipesWriter(string nomeServidor, string nomePipe) { Console.WriteLine($”### Servidor - {nomePipe} ####”); try { // Caso o nomeServidor estiver incorreto: Erro “O caminho da rede não foi encontrado.\r\n” // Caso o nomePipe estiver incorreto: Não há conexão e fica aguandando…. using (var pipeWriter = new NamedPipeClientStream(nomeServidor, nomePipe, PipeDirection.Out)) { Console.WriteLine(“Tentando conexão com servidor “ + nomePipe + “….”); pipeWriter.Connect(); Console.WriteLine(“Servidor(reader) conectado”); Console.WriteLine(“Iniciando o writer, comece a digitar…”);
bool completed = false;
while (!completed)
{
string input = Console.ReadLine();
if (input == "tchau" || input == "quit"
|| input == "fim" || input == "exit") completed = true;
byte[] buffer = Encoding.UTF8.GetBytes(input);
pipeWriter.Write(buffer, 0, buffer.Length);
}
}
Console.WriteLine("Escrita terminada...");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} }
string nomePipe = args.Length >= 1 ? args[0] : “PipeReaderDemo”; string nomeServidor = args.Length >= 2 ? args[1] : “localhost”; PipesWriter(nomeServidor, nomePipe);
CryptoStream
CryptoStream não está no exame 70-483. CryptoStream é uma classe usada para criptografia de fluxo de dados.Quando você escreve dados de bytes no CryptoStream, os bytes são criptografados em outros bytes antes de serem liberados no fluxo que está gravado no arquivo. A imagem abaixo ilustra o CryptoStream encapsular outro fluxo (como gravar arquivo de fluxo).Observe que você pode escolher um algoritmo de criptografia ao criar o objeto CryptoStream.
Em uma situação inversa, o CryptoStream quebra um arquivo de fluxo de leitura (arquivo cujo conteúdo foi criptografado acima), o byte no FileStream foi criptografado e será descriptografado pelo CryptoStream. Outra coisa importante a ser lembrada é que nem todos os algoritmos de criptografia têm soluções de criptografia e descriptografia bidirecional.
A criptografia dos dados que passam por streams para privacidade e integridade também é importante. O C # fornece uma classe, CryptoStream, para a criptografia de dados que trafega por streams. A implementação é similar a da criptografia simétrica, porém, como se trata de streams, se não for usar using é necessário finalizar o objeto para não carregar a memória.
class Criptografia_Stream { public byte[] EncryptStream(SymmetricAlgorithm symmetricAlgo) { // especifique os dados string plainData = “Mensagem Secreta STREAM”;
byte[] cipherDataInBytes;
ICryptoTransform encryptor = symmetricAlgo.CreateEncryptor(symmetricAlgo.Key, symmetricAlgo.IV);
// Create the streams used for encryption.
using (MemoryStream memoryStream = new MemoryStream())
{
// crptoStream conhece o criptografador e o stream no qual os dados serão gravados
using (CryptoStream crptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
// writer tem referência de cryptoStream (o que criptografar e onde)
using (StreamWriter streamWriter = new StreamWriter(crptoStream))
{
//Write all data to the stream.
streamWriter.Write(plainData);
}
cipherDataInBytes = memoryStream.ToArray();
// coloca os bytes dos dados criptografados na string
string cipherData = Encoding.UTF8.GetString(cipherDataInBytes);
// Simétrica criptografia:o???w????r?__7v?l?Zf?zzi?_J?c
Console.WriteLine("Stream criptografado:" + cipherData);
}
}
return cipherDataInBytes;
}
public void DecryptStream(SymmetricAlgorithm symmetricAlgo, byte[] cipherDataInBytes)
{
ICryptoTransform decryptor = symmetricAlgo.CreateDecryptor(symmetricAlgo.Key, symmetricAlgo.IV);
using (MemoryStream msDecrypt = new MemoryStream(cipherDataInBytes))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
var plaintext = srDecrypt.ReadToEnd();
Console.WriteLine("Stream Descriptografado..." + plaintext);
}
}
}
} }
using (SymmetricAlgorithm symmetricAlgo = SymmetricAlgorithm.Create()) { var stream_cripto = new Criptografia_Stream();
var cipherDataInBytes = stream_cripto.EncryptStream(symmetricAlgo);
stream_cripto.DecryptStream(symmetricAlgo, cipherDataInBytes); }
GZipStream - Compactar dados
Devido à maneira como os Streams são projetados, você pode acoplar vários objetos de Stream para executar uma operação mais complexa. Este princípio é chamado de padrão decorador. Por exemplo, quando você deseja compactar alguns dados, pode usar um GZipStream., Que utiliza outro objeto Stream em seu construtor. O segundo fluxo é usado como entrada ou saída do algoritmo de compactação. Ao atribuir a ele um objeto FileStream, você pode compactar facilmente alguns dados (como outro arquivo) e armazená-los em disco. Você também pode usar um MemoryStream como entrada ou saída de um GZipStream. Você pode encontrar o GZipStream no espaço para nome System.IO.Compression. A Listagem 4-18 cria alguns dados, grava-os em um arquivo e compacta o arquivo. Como você pode ver, o arquivo compactado é muito menor que o arquivo original.
string folder = @”c:\temp”; string uncompressedFilePath = Path.Combine(folder, “uncompressed.dat”); string compressedFilePath = Path.Combine(folder, “compressed.gz”); byte[] dataToCompress = Enumerable.Repeat((byte)’a’, 1024 * 1024).ToArray(); using (FileStream uncompressedFileStream = File.Create(uncompressedFilePath)) { uncompressedFileStream.Write(dataToCompress, 0, dataToCompress.Length); } using (FileStream compressedFileStream = File.Create(compressedFilePath)) { using (GZipStream compressionStream = new GZipStream( compressedFileStream, CompressionMode.Compress)) { compressionStream.Write(dataToCompress, 0, dataToCompress.Length); } } FileInfo uncompressedFile = new FileInfo(uncompressedFilePath); FileInfo compressedFile = new FileInfo(compressedFilePath); Console.WriteLine(uncompressedFile.Length); // Displays 1048576 Console.WriteLine(compressedFile.Length); // Displays 1052
Como mostra a Lista 4-18, você pode passar outro Stream para o construtor de um GZipStream. Ao gravar dados no GZipStream, ele os comprime e os encaminha imediatamente para o FileStream.
WebRequest e WebResponse
WebRequest e WebResponse formam um par de classes que você pode usar em conjunto para enviar uma solicitação de informações e, em seguida, receber a resposta com os dados solicitados. Um WebRequest é criado usando um método estático Create na classe WebRequest. O método Create inspeciona o endereço que você passa para ele e, em seguida, seleciona a implementação correta do protocolo. Se você passasse o endereço http://www.microsoft.com para ele, veria que você está trabalhando com o protocolo HTTP e retornaria um HttpWebRequest. Depois de criar o WebRequest correto, você pode definir outras propriedades, como instruções de autenticação ou cache. Ao terminar de compor sua solicitação, chame o método GetResponse para executar a solicitação e recuperar a resposta.
WebRequest request = WebRequest.Create(@”http://www.microsoft.com”); using (WebResponse resp = request.GetResponse()) { using (StreamReader responseStream = new StreamReader(resp.GetResponseStream())) { string responseText = responseStream.ReadToEnd(); Console.WriteLine(responseText); // Displays the HTML of the website } }
FILE READER AND WRITER
Para converter bytes em formato legível ou gravar ou ler valores como bytes ou como sequência, o .NET oferece o seguinteclasses nesse caso. Reader e Writer são classes no namespace System.IO que lêem ou gravam caracteres codificados de e para fluxos. A tabela abaixo lista os tipos comuns de leitores e gravadores no namespace System.IO. Classe Descrição BinaryReader, BinaryWriter Usado para ler e escrever valores binários StreamReader, StreamWriter Usado para ler e escrever caracteres usando um valor codificado para converter os caracteres de e para bytes StringReader, StringWriter Usado para ler e escrever caracteres de e para strings TextReader, TextWriter Classes abstratas para outros leitores e escritores que lêem e escrevem caracteres ou seqüências
BinaryReader and BinaryWriter
Essas classes são usadas para ler e escrever valores como Valores Binários. • BinaryReader é uma classe auxiliar para ler o tipo de dados primitivo a partir de bytes. • BinaryWriter grava tipos primitivos em binário.
O BinaryReader possui métodos para ler o valor de um tipo de dados específico. Por exemplo, se houver um valor de sequência em forma de binário, use o método ReadString () e assim por diante, mas se não houver valor escrito como binário e você desejar ler, a exceção será lançada. Além disso, é importante ler ordinalmente à medida que os valores são gravados.
//Write Data Types values as Binary Values in Sample.dat file FileStream file = File.Create(“Sample.dat”); BinaryWriter binaryWriter = new BinaryWriter(file); binaryWriter.Write(“String Value”); binaryWriter.Write(‘A’); binaryWriter.Write(true); binaryWriter.Close(); //Read Binary values as respective data type’s values from Sample.dat FileStream fileToOpen = File.Open(“Sample.dat”, FileMode.Open); BinaryReader binaryReader = new BinaryReader(fileToOpen); Console.WriteLine(binaryReader.ReadString()); Console.WriteLine(binaryReader.ReadChar()); Console.WriteLine(binaryReader.ReadBoolean());
Console.ReadKey();
StreamReader and StreamWriter
O StreamReader e o StringReader herdam da classe abstrata TextReader, é uma classe auxiliar para ler caracteres de um fluxo convertendo bytes em caracteres usando um valor codificado. Pode ser usado para ler strings (caracteres) de diferentes fluxos, como FileStream, MemoryStream, etc. A classe StreamReader é usada para ler a entrada de caracteres em uma codificação específica. A codificação padrão é UTF-8. Você pode usar um StreamReader para ler um arquivo de texto padrão. A tabela abaixo lista alguns dos métodos para a classe StreamReader. Método Descrição Close Fecha o leitor de fluxo e o fluxo subjacente Peek Retorna o próximo caractere no fluxo, mas não move a posição do caractere Read() Retorna o próximo caractere no fluxo e move a posição do caractere em um Read(Char[], Int32, Int32) Lê o número especificado de caracteres na matriz de bytes ReadBlock(Char[], Int32, Int32) ReadLine Lê uma linha de caracteres e retorna uma string ReadToEnd Lê todos os caracteres da posição atual até o final do arquivo e retorna uma string
O StreamReader fornece métodos para leitura de caractere por caractere, linha por linha ou um arquivo inteiro em uma chamada. Os métodos Read e ReadBlock retornam caracteres e matrizes de caracteres; os métodos ReadLine e ReadToEnd retornam seqüências de caracteres. O código a seguir abre um arquivo e primeiro lê o conteúdo caractere por caractere, depois linha por linha e, em seguida, lê todo o conteúdo.
string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName;
using (StreamReader streamReader = new StreamReader(projectDirectory + “\Numbers.txt”)) { Console.WriteLine(“Char by Char”); while (!streamReader.EndOfStream) Console.WriteLine((char)streamReader.Read()); }
using (StreamReader streamReader = new StreamReader(projectDirectory + “\Numbers.txt”)) { Console.WriteLine(“Line by line”); while (!streamReader.EndOfStream) Console.WriteLine(streamReader.ReadLine()); }
using (StreamReader streamReader = new StreamReader(projectDirectory + “\Numbers.txt”)) { Console.WriteLine(“Entire file”); Console.WriteLine(streamReader.ReadToEnd()); }
O StreamWriter e o StringWriter herdam da classe TextWriter, é uma classe auxiliar para escrever uma string em um Stream convertendo caracteres em bytes. Ele pode ser usado para gravar strings em diferentes Streams, como FileStream, MemoryStream, etc.
A imagem acima mostra que o FileStream lê bytes de um arquivo físico e, em seguida, o StreamReader lê as strings convertendo esses bytes em strings. Da mesma forma, o StreamWriter pega uma string e a converte em bytes e grava no FileStream e, em seguida, o FileStream grava os bytes em um arquivo físico. Portanto, o FileStream lida com bytes, como StreamReader e StreamWriter lida com seqüências de caracteres.
using (StreamWriter streamWriter = new StreamWriter(projectDirectory + “\Numbers.txt”)) { streamWriter.Write(‘A’); }
using (StreamReader streamReader = new StreamReader(projectDirectory + “\Numbers.txt”)) { Console.WriteLine(streamReader.ReadLine()); }
StringReader e StringWriter
Essas classes são usadas para ler e escrever caracteres de e para a string. Lembre-se que estas duas classes aceitam em seus construtures a classe StringBuilder como parâmetro, o que, geralmente é feito para que se conecte a entrada e leitura da string em partes diferentes do código.
// Grava string ou caracteres using (StringWriter stringWriter = new StringWriter()) { stringWriter.Write(“Exemplo do String Writer”); stringWriter.Write(“Anexar texto”); Console.WriteLine(stringWriter.ToString()); }
using (StringReader stringReader = new StringReader(“Exemplo de leitor de string”)) { Console.WriteLine(stringReader.ReadLine()); }
Comunicação através da rede
O namespace System.Net fornece suporte para seus aplicativos se comunicarem em uma rede. Geralmente, os membros desse namespace que você usa são as classes WebRequest e WebResponse. Ambas classes são abstratas e usadas para se comunicar pela rede. O namespace System.Net também fornece classes implementadas específicas que dependem de qual protocolo será usado para comunicação. Por exemplo, a classe HttpWebRequest e a classe HttpWebResponse são usadas quando você está usando o protocolo Http. Em geral, usamos a classe WebRequest para enviar a solicitação de informações e a classe WebResponse para receber a resposta das informações solicitadas.
WebRequest request = WebRequest.Create(“http://www.apress.com”);
using (WebResponse response = request.GetResponse()) { StreamReader reader = new StreamReader(response.GetResponseStream()); string result = reader.ReadToEnd(); Console.WriteLine(result); }
O WebRequest é criado usando o método Create() (método estático da classe WebRequest), que pega o endereço da solicitação em uma string ou no formato Uri. O WebResponse está vinculado ao WebRequest, então ele obtem a resposta das informações ou dados solicitados usando seu método GetResponse().
O método Create() do WebRequest inspeciona o endereço e escolhe a implementação correta do protocolo. No código, passamos o http://www.apress.com, para que ele escolhesse o protocolo Http e retornasse o HttpWebRequest. Você também pode usar o método ou as propriedades do WebRequest para executar outras operações nele. Após obter a resposta, o StreamReader é usado para obter a resposta no fluxo para que possa ser lida.
ARQUIVO ASSÍNCRONO: ASYNC E AWAIT IN FILE I/O
A leitura e gravação do arquivo pode ser uma tarefa demorada e é necessário aguardar muito tempo para concluir a operação. No código síncrono é executado linha por linha e frequentemente aguarda até que a tarefa termine. Essa espera pode ser longa o suficiente para incomodar o usuário e causar um grande impacto na experiência do usuário. Por exemplo, em aplicativos de desktop, você tem um thread que é o thread principal e que é responsável por todas as tarefas, ou seja, atualizar a interface do usuário e processar outras tarefas também. Se você tem uma tarefa longa a ser processada (ou seja, aguardar a resposta do fluxo da rede ou ler um arquivo da Internet), o thread principal ficará ocupado no processamento dessa tarefa e, enquanto isso, a interface do usuário do aplicativo ficará bloqueada e não sendo possível responder, o que será uma experiência ruim para o usuário.
Quando você cria um aplicativo da área de trabalho, como um aplicativo Windows Presentation Foundation (WPF), WinForm ou Windows Store, seu aplicativo possui um thread principal responsável pela atualização da interface do usuário. Esse encadeamento é responsável por processar todas as atividades do usuário. Se esse segmento estiver ocupado com outra coisa, o aplicativo parece não responder. A maioria dos usuários tentará fechar esse aplicativo porque eles acham que algo está errado. É importante executar operações de execução longa em outro encadeamento, o que garante que seu aplicativo permaneça responsivo enquanto executa a operação de execução longa em segundo plano.
Com um aplicativo de servidor, as coisas são um pouco diferentes. Quando o usuário acessa o servidor Web do ASP.NET, ele precisa aguardar antes que a solicitação seja concluída. Se a página demorar muito para carregar, o usuário perceberá isso como um desempenho ruim. O que está acontecendo no servidor é que um encadeamento atende a solicitação e começa a criar a resposta para o usuário. O servidor começa a coletar dados, produzir HTML e executar todos os tipos de outras ações. Um servidor possui um número limitado de threads para lidar com solicitações recebidas. Quando um encadeamento aguarda a E/S, não pode funcionar com outras solicitações. Quando todos os encadeamentos estão ocupados e uma nova solicitação é recebida, a nova solicitação é colocada na fila. Esse problema é chamado de “falta de pool de threads”. Todos os encadeamentos aguardam que algumas E/S ocorram com uma carga de CPU de 0%, e os tempos de resposta para novas solicitações passarão do teto.
Em tais cenários, as classes Stream, Reader e Writer oferecem a capacidade de ler ou gravar arquivos de forma assíncrona. Isso pode ser útil quando você deseja retornar o processamento de volta à interface do usuário enquanto realiza uma operação demorada em um arquivo grande. No C#, existem duas novas palavras-chave usadas ao lidar com o processamento assíncrono: async e await. Async e Await são as palavras-chave fornecidas pelo .NET Framework no C# 5.0. Eles dizem ao compilador para executar o código de maneira assíncrona. A palavra-chave async é um modificador de método que permite que o método use a palavra-chave wait e também habilita um método de chamada para essa função usando a palavra-chave wait. A palavra-chave wait é usada ao chamar um método para suspender a execução nesse método até que a tarefa esperada seja concluída.
Um método assíncrono contém async em seu nome, como ReadAsync, WriteAsync, ReadLineAsync e ReadToEndAsync, etc. Esses métodos assíncronos são implementados em classes de fluxo, como Stream, FileStream, MemoryStream e em classes usadas para ler ou gravar em fluxos como TextReader e TextWriter.
Por exemplo, suponha que você tenha um aplicativo de formulários do Windows que tenha um botão que execute uma tarefa de longa execução. Você pode modificar a assinatura do evento de clique no botão com o modificador assíncrono e, em seguida, chamar o método de longa duração usando a palavra-chave wait. Isso permite que o processo de longa execução seja executado, mas também permite que o usuário navegue para diferentes partes do seu aplicativo, para que o sistema não pareça estar bloqueado.
private async void button1_Click(object sender, EventArgs e) { button1.Text = “Searching…”;
// This will get the current WORKING directory (i.e. \bin\Debug)
string workingDirectory = Environment.CurrentDirectory;
// This will get the current PROJECT directory
string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName;
string outputFileName = projectDirectory + "\\FoundFiles.txt";
await SearchDirectory(workingDirectory, "A", outputFileName);
button1.Text = "Finished";
Process.Start(outputFileName); }
No exemplo, a legenda do formulário é alterada para “Searching” quando o botão é clicado. Quando o método MyLongRunningProcess é chamado, o método é executado, mas o processamento é retornado ao thread principal até que o método SearchDirectory termine a execução. Depois disso, a legenda do formulário é alterada para “Concluído”. Você pode usar os métodos assíncronos para o arquivo de E/S de maneira semelhante. O exemplo de código a seguir implementa o método SearchDirectory que pesquisa todos os arquivos em uma determinada pasta e procura uma sequência específica no arquivo. Se o arquivo contiver a string, seu nome será gravado em um arquivo de saída. Quando o processo é concluído, o arquivo de saída é mostrado no visualizador de texto padrão.
private static async Task SearchDirectory(string searchPath, string searchString, string outputFileName)
{
StreamWriter streamWriter = File.CreateText(outputFileName);
string[] fileNames = Directory.GetFiles(searchPath);
//para Testar funcionalidade do async
await Task.Run(() =>
{
for (int i = 0; i < 8; i++)
{
Thread.Sleep(4000);
Debug.Write("Processando... ");
}
});
//await FindTextInFilesAsync(fileNames, searchString, streamWriter);
streamWriter.Close(); }
private static async Task FindTextInFilesAsync(string[] fileNames, string searchString, StreamWriter outputFile)
{
foreach (string fileName in fileNames)
{
if (fileName.ToLower().EndsWith(“.txt”))
{
StreamReader streamReader = new StreamReader(fileName);
string textOfFile = await streamReader.ReadToEndAsync();
streamReader.Close();
if (textOfFile.Contains(searchString))
{
await outputFile.WriteLineAsync(fileName);
}
}
} }
É importante saber que a classe File estática não suporta E/S assíncrona real. Se você chamar métodos assíncronos, isso será falsificado usando outro thread do pool de threads para trabalhar com o arquivo. Para E / S assíncrona real, você precisa usar o objeto FileStream e transmitir um valor verdadeiro para o parâmetro useAsync.
O exemplo abaixo mostra um exemplo de como gravar assincronamente em um arquivo. O método Write é substituído por WriteAsync, o método é marcado com o modificador assíncrono para sinalizar ao compilador que você precisa de ajuda para transformar seu código e, finalmente, você usa a palavra-chave await na tarefa retornada.
public async Task CreateAndWriteAsyncToFile() { using (FileStream stream = new FileStream(“test.dat”, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) { byte[] data = new byte[100000]; new Random().NextBytes(data); await stream.WriteAsync(data, 0, data.Length); } }
O objeto Task retornado representa algum trabalho em andamento, encapsulando o estado da operação assíncrona. Eventualmente, o objeto Task retorna o resultado da operação ou exceções que foram geradas de forma assíncrona. No caso acima, o método não possui nenhum valor retornado ao chamador. É por isso que o WriteAsync retorna um objeto Task comum. Quando um método tem um valor de retorno, ele retorna a Tarefa
public async Task ReadAsyncHttpRequest() { HttpClient client = new HttpClient(); string result = await client.GetStringAsync(“http://www.microsoft.com”);
Debug.WriteLine("Fim ReadAsyncHttpRequest... "); }
Sempre que o .NET Framework oferece um equivalente assíncrono de um método síncrono, é melhor usá-lo. Cria uma melhor experiência do usuário e um aplicativo mais escalável. No entanto, quando você está trabalhando em um aplicativo crítico para o desempenho, vale a pena saber o que o compilador faz por você. Se não tiver certeza, use um criador de perfil para realmente medir a diferença, para que você possa tomar uma decisão informada.
Executando operações de E/S em paralelo
Operações E/S assíncrona reais garantem que seu thread possa executar outro trabalho até que o sistema operacional notifique seu aplicativo de que a E/S esteje pronta. Várias operações de E/S assíncronas ainda podem ser executadas uma após a outra. Quando você olha para o código abaixo, pode ver que várias esperas são usadas em um método. Com o código atual, cada solicitação da web inicia quando a anterior é concluída. O thread não será bloqueado durante a execução dessas solicitações, mas o tempo que o método leva é a soma das três solicitações da Web.
public async Task ExecuteMultipleRequests() { HttpClient client = new HttpClient(); string microsoft = await client.GetStringAsync(“http://www.microsoft.com”); string msdn = await client.GetStringAsync(“http://msdn.microsoft.com”); string blogs = await client.GetStringAsync(“http://blogs.msdn.com/”);
Debug.WriteLine("Fim ExecuteMultipleRequests... "); }
Você também pode escrever um código que executará essas operações em paralelo. Se você executasse essas solicitações em paralelo, teria apenas que esperar o tempo que a solicitação mais longa demorar (as outras duas já estarão concluídas). Você pode fazer isso usando o método estático Task.WhenAll. Assim que você chama GetStringAsync, a operação assíncrona é iniciada. No entanto, você não espera imediatamente o resultado. Em vez disso, você deixa todos os três pedidos iniciarem e depois espera que eles terminem. Agora, todas as três operações são executadas em paralelo, o que pode economizar muito tempo.
public async Task ExecuteMultipleRequestsInParallel() { HttpClient client = new HttpClient(); Task microsoft = client.GetStringAsync(“http://www.microsoft.com”); Task msdn = client.GetStringAsync(“http://msdn.microsoft.com”); Task blogs = client.GetStringAsync(“http://blogs.msdn.com/”); await Task.WhenAll(microsoft, msdn, blogs);
Debug.WriteLine("Fim ExecuteMultipleRequestsInParallel... "); }
Sumário
Dados de consumo • Recuperar dados de um banco de dados; atualizar dados em um banco de dados; consumir dados JSON e XML; recuperar dados usando serviços Web
O gerenciamento de dados é um dos aspectos mais importantes de um aplicativo. Imagine que você pode criar apenas aplicativos que armazenam seus dados na memória. Assim que o usuário sai, todos os dados são perdidos e os lançamentos subsequentes exigem a reinserção de todos os dados necessários. Obviamente, seria uma situação impossível de se trabalhar, e é por isso que o .NET Framework ajuda a armazenar seus dados de maneira persistente. Isso pode ser feito em um banco de dados usando diretamente o ADO.NET ou o Entity Framework. Você também pode armazenar e solicitar dados de um Web service externo e recuperar a resposta em JavaScript Object Notation (JSON) ou Extensible Markup Language (XML). O .NET ajuda a executar essas solicitações e analisar os dados retornados.
INSTALAR SQL SERVER E O SQL SERVER MANAGEMENT STUDIO
Antes de começarmos a explorar os tópicos dessa seção, como muitos exercícios serão realizados utilizando como base o provedor SQL, será necessário instalar duas ferramentas: SQL Server e o SQL Server Management Studio. Para instalar o SQL Server, você precisa baixá-lo no site Microsoft.com através do seguinte link:
Após a conclusão do download, clique duas vezes no arquivo SQL2019-SSEI-Dev para iniciar o instalador.
O instalador solicita que você selecione o tipo de instalação, escolha o tipo de instalação personalizada, que permite percorrer o assistente de instalação do SQL Server e selecionar os recursos que você deseja instalar.
Especifique a pasta para armazenar os arquivos de instalação do download e clique no botão Instalar.
O instalador começa a baixar o pacote de instalação por um tempo.
Após o download, abra a pasta que armazena o pacote de instalação e clique duas vezes no arquivo SETUP.exe.
A seguinte janela é exibida; selecione a opção de instalação à esquerda.
Clique no primeiro link para iniciar um assistente para instalar o SQL Server 2017.
Especifique a edição que deseja instalar, selecione Developer edition e clique no botão Next.
Selecione “Aceito os termos da licença” e clique no botão Avançar.
Desmarque a opção “Use o Microsoft Update para verificar se há atualizações (recomendadas)” para não receber informações de segurança, atualizações, etc do SQL Server e clique no botão Avançar.
A instalação verifica os pré-requisitos antes da instalação. Se nenhum erro for encontrado, clique no botão Avançar.
Selecione os recursos que você deseja instalar. Por enquanto, você só precisa dos Serviços do Mecanismo de Banco de Dados, basta marcar a caixa de seleção e clicar no botão Avançar para continuar
Especifique o nome e o ID de instalação da instância do SQL Server e clique no botão Avançar.
Especifique a conta de serviço e a configuração do agrupamento. Basta usar a configuração padrão e clique no botão Avançar.
Especifique o modo de segurança do mecanismo de banco de dados, para simplificar coloque Autenticação Windows. Senão, primeiro, escolha Modo Misto. Em seguida, insira a senha da conta de administrador do sistema do SQL Server (sa). Em seguida, digite novamente a mesma senha para confirmá-la. Depois disso, clique no botão Adicionar usuário atual. Por fim, clique no botão Avançar.
Verifique os recursos do SQL Server 2017 a serem instalados:
O instalador inicia o processo de instalação
Depois de concluída, a seguinte janela é exibida. Clique no botão OK.
Para interagir com servidores SQL, você precisa instalar o SQL Server Management Studio (SSMS). O SQL Server Management Studio é um software para consultar, projetar e gerenciar o SQL Server no computador local ou na nuvem. Ele fornece ferramentas para configurar, monitorar e administrar instâncias do SQL Server. Primeiro, baixe o SSMS no site da Microsoft através do seguinte link:
Segundo, clique duas vezes no arquivo de instalação SSMS-Setup-ENU.exe para iniciar a instalação. O processo de instalação do SMSS é direto, basta seguir a sequência da tela.
Clique no botão Instalar
Aguarde alguns minutos enquanto o instalador configura o software.
Quando a instalação estiver concluída, clique no botão Fechar
Agora, você deve ter o SQL Server 2017 e o SQL Server Management Studio instalados no seu computador. Em seguida, para conectar-se ao SQL Server usando o Microsoft SQL Server Management Studio, siga estas etapas: Primeiro, inicie o Microsoft SQL Server Management Studio no menu Iniciar:
Em seguida, no menu Conectar no Pesquisador de Objetos, escolha o Mecanismo de Banco de Dados…
Em seguida, se você não optou por Autenticação Windows, insira as informações para o nome do servidor (host local), autenticação (autenticação do SQL Server) e senha do usuário sa e clique no botão Conectar para conectar-se ao SQL Server. Observe que você deve usar o usuário sa e a senha inseridos durante a instalação.
Se a conexão for estabelecida com sucesso, você verá o seguinte painel do Pesquisador de Objetos:
TRABALHANDO COM UM BANCO DE DADOS
No .NET Framework, você pode encontrar vários tipos de classes relacionadas a dados no System.Data.dll. Existem inúmeras classes base e interfaces definidas neste namespace que um provedor de dados deve implementar para permitir que o ADO.NET acesse um banco de dados. Quase todos os aplicativos usam bancos de dados, portanto você deve entender os conceitos que envolvem o ADO.NET, na qual existem três partes conceituais:
CAMADA CONECTADA
Em uma camada conectada, você se conecta a um banco de dados como fonte de dados e executa consultas escrevendo SQL. Essas consultas são usadas pelo ADO.NET e encaminhadas para o banco de dados de sua escolha. Para interagir com um banco de dados, você normalmente usa objetos Connection, Command e DataReader.
Provedoresde dados
Um provedor de dados do .NET Framework é usado para conectar-se a um banco de dados, executar comandos e trabalhar os dados resultantes. Os provedores de dados do .NET Framework fornecem uma camada fina que se integra a um banco de dados específico, para que você possa criar programas que possam trabalhar com diferentes tipos de bancos de dados sem precisar alterar nenhum código.
Existem muitos tipos diferentes de bancos de dados como o Microsoft SQL Server, Oracle e MySQL, entre outros. O .NET Framework oferece recursos para trabalhar com dados em todos esses tipos de bancos de dados de maneira padrão. Oracle, MySQL e outros principais sistemas de banco de dados têm seu próprio namespace e classes que implementam as classes e interfaces básicas do ADO.NET. Isso permite que você use sintaxe consistente em todas as bases de dados. Por exemplo, o namespace System.Data.SqlClient e o namespace System.Data.OleDb que contém os tipos que implementam as classes base e interfaces do ADO.NET para conectar-se a um banco de dados do SQL Server.
Conectando-se a um Banco de Dados
Depois de decidir o provedor de dados, é importante trabalhar com a conexão com um banco de dados específico. Você deve estabelecer uma conexão com seu banco de dados para interação adicional. O ADO.NET fornece classes para estabelecer conexão com um banco de dados específico.
Um objeto de conexão é usado para abrir uma linha de comunicação com um banco de dados. O objeto SqlConnection é usado para conectar-se a um banco de dados do SQL Server. Essa classe, juntamente com a classe de conexão de qualquer provedor, herda da classe System.Data.Common.DBConnection. A Tabela a seguir lista as propriedades mais comuns para a classe DBConnection. Propriedade Descrição ConnectionString Obtém ou define a sequência usada para abrir uma conexão com um banco de dados ConnectionTimeout Obtém o tempo em segundos que o sistema deve esperar ao estabelecer uma conexão com o banco de dados antes de gerar um erro Database Obtém o nome do banco de dados DataSource Obtém o nome do servidor de banco de dados ServerVersion Obtém a versão do servidor para o banco de dados State Obtém uma sequência que representa o estado da conexão, como Aberta ou Fechada
A propriedade mais importante a ser observada é a propriedade ConnectionStringque contém todas as informações que o .NET Framework precisa saber:
• Local dos dados • Nome do banco de dados • Provedor/tipo de banco de dados • Autenticação no banco de dados
Cada provedor tem pequenas variações nas configurações na cadeia de conexão, um ótimo recurso para consultar a sintaxe das cadeias de conexão para diferentes de bancos de dados é: http://www.ConnectionStrings.com
Para o SQL Server, uma cadeia de conexão possui a seguinte sintaxe.
connectionString = “Server=myServerAddress;Database=myDataBase; User Id=myUsername;Password=myPassword;”;
Essa cadeia de conexão descreve onde o banco de dados está localizado, como é nomeado e alguns detalhes sobre como se autenticar no banco de dados. O formato básico de uma cadeia de conexão é uma série de pares de chave/valor conectados por um sinal de igual (=), todos separados por ponto e vírgula (;).
A classe DbConnection é uma classe base que fornece a funcionalidade relacionada à conexão. SqlConnection é derivado da classe DbConnection e pode ser usado para conexão com um banco de dados do SQL Server. A seguir, uma lista os métodos mais importantes para a classe DBConnection. Método Descrição BeginTransaction Inicia uma transação de banco de dados Close Fecha a conexão com o banco de dados GetSchema Retorna um DataTable que contém as informações do esquema para a fonte de dados Open Abre a conexão com o banco de dados usando a cadeia de conexão
O método Open é usado para estabelecer uma conexão com o banco de dados. Depois de ter uma conexão, você pode usar esse objeto junto com os outros objetos do ADO.NET para executar comandos no banco de dados. O código a seguir cria uma instância do objeto SqlConnection, define a propriedade ConnectionString e abre uma conexão com o banco de dados:
var connectionString = @”Persist Security Info = False; Integrated Security = true; Initial Catalog = Northwind; server = (local)”; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); // Execute operations against the database } // Connection is automatically closed.
Como a classe DbConnection usa uma conexão de banco de dados real e não gerenciada, é importante garantir que você feche corretamente a conexão quando terminar. Ter conexões abertas por muito tempo é um problema, pois outros usuários não podem se conectar, pois o servidor permite um número específico de conexões e, se o limite for excedido, não permitirá que nenhum outro usuário se conecte devido à das conexões já atribuídas (que ainda estão abertas).Por esse motivo, a classe DbConnection implementa IDisposable, para que você possa determinar deterministicamente a conexão e liberar qualquer objeto não gerenciado associado.
Quando você precisar criar uma cadeia de conexão dinamicamente, poderá usar uma das várias classes DbConnectionStringBuilder. Por exemplo, você pode usar OracleConnectionStringBuilder para criar uma cadeia de conexão para um banco de dados Oracle e SqlConnectionStringBuilder para um banco de dados Microsoft SQL Server. O código abaixo mostra como usar o SqlConnectionStringBuilder para criar uma nova cadeia de conexão programaticamente.
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(); sqlConnectionStringBuilder.DataSource = @”localhost”; sqlConnectionStringBuilder.InitialCatalog = “School”; sqlConnectionStringBuilder.IntegratedSecurity = true; string connectionString = sqlConnectionStringBuilder.ToString();
A propriedade IntegratedSecurity obtém ou define um valor booliano que indica se a ID de Usuário e a Senha são especificadas na conexão(quando false) ou se as atuais credenciais da conta do Windows são usadas para autenticação(quando true).Codificar uma string de conexão geralmente não é a melhor maneira de configurar um aplicativo. Quando você implanta um aplicativo em um ambiente de teste, preparo ou produção, a cadeia de conexão geralmente precisa ser alterada e, na maioria das vezes, isso é feito por um profissional de TI que não possui (e não quer ter!) qualquer experiência de programação.
Por esse motivo, você pode armazenar facilmente cadeias de conexão em um arquivo de configuração externo. Dependendo do tipo de aplicativo, você usa o arquivo app.config ou web.config. Esses arquivos podem ser usados para armazenar todos os tipos de definições de configuração para um aplicativo. Você também pode usar outros arquivos de configuração, que você faz referência a partir desses arquivos.Um exemplo de uma cadeia de conexão no arquivo app.config de um aplicativo é mostrado abaixo.
<?xmlversion=”1.0”encoding=”utf-8” ?>
Se você deseja usar a cadeia de conexão no seu aplicativo, pode usar a propriedade ConfigurationManager.ConnectionStrings do System.Configuration.dll. Você pode acessar cadeias de conexão por índice e por nome. A exemplo a seguir mostra como acessar uma cadeia de conexão por nome e usá-la para abrir uma conexão com o banco de dados.
using System.Configuration;
string connectionString = ConfigurationManager.ConnectionStrings[“ProgConnection”].ConnectionString;
Conectar-se a um banco de dados é uma operação demorada. Ter uma conexão aberta por muito tempo também é um problema, pois pode levar outros usuários a não conseguirem se conectar. Para minimizar os custos de abrir e fechar repetidamente conexões, o ADO.NET aplica uma otimização chamada de pool de conexões. Ao usar o SQL Server, um pool de conexões é mantido pelo seu aplicativo. Quando uma nova conexão é solicitada, o .NET Framework verifica se há uma conexão aberta no pool. Se houver, não será necessário abrir uma nova conexão e executar todas as etapas iniciais da configuração. Por padrão, o pool de conexões está ativado, o que pode proporcionar uma enorme melhoria de desempenho.
Comandos SqlCommand
ADO.NET fornece a classe SqlCommand, usado para executar instruções (comandos/consultas) no banco de dados. Ao usar esta classe, você pode executar comandos de inserção, exclusão, atualização ou procedimento armazenado.
SqlCommand command = new SqlCommand(“SELECT * FROM Student”, connection);
SqlCommand requer um comando (instrução/consulta) para executar e uma conexão (cadeia de conexão) na qual um comando escrito será executado. Esse código sabe qual comando executar em qual conexão (em outras palavras, o que fazer usando qual caminho).
Método ExecuteReader - Lendo dados
Depois que a conexão for estabelecida, você poderá começar a enviar consultas ao banco de dados. As consultas são construídas usando SQL, que é uma sintaxe especial otimizada para trabalhar com bancos de dados. Digamos que você esteja trabalhando com uma tabela do banco de dados chamada People com a estrutura abaixo: Column name Data type Nullable id int, Primary Key False FirstName varchar(30) False MiddleName varchar(30) True LastName varchar(30) False
Se você deseja selecionar algumas linhas nesta tabela, primeiro precisa definir uma instrução SQL. Uma consulta SQL pode ser executada usando um objeto SqlCommand. Use o método ExecuteReader para recuperar resultados do banco de dados, como quando você usa uma instrução Select. Esse método retorna um objeto SqlDataReader que permanece conectado a um banco de dados o tempo todo em que o leitor está aberto. SqlDataReader é um conjunto de resultados somente para frente, o que significa que você não pode mover para nenhum registro anterior e pode ler somente um registro por vez. Você pode ler a coluna específica de uma tabela pelo número do índice ou nome da coluna. O exemplo abaixo mostra o uso do método ExecuteReader:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = connection; cmd.CommandType = CommandType.Text; cmd.CommandText = “SELECT * FROM People”;
using (SqlDataReader dr = cmd.ExecuteReader()) { if (dr.HasRows) { while (dr.Read()) { Console.WriteLine(string.Format(“Id: {0}”, dr[0])); Console.WriteLine(string.Format(“First Name: {0}, Last Name: {1}”, dr[“FirstName”], dr[“LastName”])); } } } }
O método abaixo mostra como você pode usá-lo para percorrer as linhas na tabela People e exibir os resultados. O ADO.NET também suporta as palavras-chave async/wait, para que você possa executar consultas de forma assíncrona.
publicstaticasync Task SelectDataFromTable() { string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command = new SqlCommand(“SELECT * FROM People”, connection); await connection.OpenAsync(); SqlDataReader dataReader = await command.ExecuteReaderAsync(); while (await dataReader.ReadAsync()) { string formatStringWithMiddleName = “Person({0}) is named {1}{2}{3}”; if (!(dataReader[“middlename”] == null)) { Console.WriteLine(formatStringWithMiddleName, dataReader[“id”], dataReader[“firstname”], dataReader[“middlename”], dataReader[“lastname”]); } } dataReader.Close(); } }
O SqlDataReader fornece algumas propriedades listadas abaixo, as duas últimas propriedades são indexadores que usamos nos exemplos acima. Eles são usados para buscar um valor de coluna específico com base em seu nome (string) ou número de índice (int). Propriedade Descrição FieldCount Retorna o número de colunas na linha atual. HasRows Retorna um booleano indicando se o leitor possui alguma linha. IsClosed Retorna um booleano indicando se o leitor está fechado. Item[Int32] Este é um indexador que retorna a coluna com base no índice. Item[String] Este é um indexador que retorna a coluna com base no nome da coluna.
Não há uma propriedade Count para o número de linhas no conjunto de resultados. A única maneira de obter a contagem é atravessar o datareader. Os indexadores, que são as propriedades do item, retornam um objeto. Eles permitem que você obtenha o valor de uma coluna pelo índice da coluna ou pelo nome. Cabe a você converter o objeto para o tipo certo ao usar os indexadores
O método Read lê o registro de um banco de dados e está pronto para ser lido para o próximo, se existir. Enquanto o loop itera e a execução ocorre para o próximo registro, Read retorna true, e assim por diante até que haja o último registro, Read retorna false, e o loop termina. Você deve fechar o objeto leitor e, em seguida, feche o objeto de conexão. Esquecer de fechar a conexão pode prejudicar o desempenho. Você pode usar o bloco “using” para evitar essas coisas.
O SqlDataReader oferece alguns métodos que podem mapear o valor de uma coluna para o tipo CLR correspondente. Por exemplo, você pode chamar métodos como GetInt32 (int index), GetGuid (int index), GetString (int index) e assim por diante. A seguir, uma lista métodos de comumente usado para a classe System.Data.Common.DBDataReader:
Método Descrição
Close Fecha o objeto
GetBoolean Retorna o valor da coluna especificada como um valor booleano
GetByte Retorna o valor da coluna especificada como um byte
GetChar Retorna o valor da coluna especificada como um caractere
GetDateTime Retorna o valor da coluna especificada como um objeto DateTime
GetDecimal Retorna o valor da coluna especificada como um objeto Decimal
GetDouble Retorna o valor da coluna especificada como um objetoDouble
GetFieldType Retorna o tipo de dados da coluna especificada
GetFieldValue
Vários métodos GetTYPE permitem que você use um índice de coluna para obter o valor do leitor de dados e converter o valor no tipo especificado. Isso funciona apenas com índices de coluna e não com nomes. Se a ordem na sua cláusula SELECT mudar, seus índices também deverão mudar.
Ao executar uma consulta, você também pode agrupar várias instruções SQL juntas. O Sql-DataReader retornado contém vários conjuntos de resultados. Você pode avançar para o próximo conjunto de resultados chamando NextResult ou NextResultAsync no seu objeto SqlDataReader. O código abaixo mostra como executar uma consulta que executa dois conjuntos de resultados.
publicstaticasync Task SelectMultipleResultSets() { string connectionString = ConfigurationManager. ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command = new SqlCommand(@”SELECT * FROM People; SELECT TOP 1 * FROM People ORDER BY LastName”, connection);
await connection.OpenAsync(); SqlDataReader dataReader = await command.ExecuteReaderAsync(); await ReadQueryResults(dataReader); await dataReader.NextResultAsync(); // Move to the next result set await ReadQueryResults(dataReader); dataReader.Close(); } }
privatestaticasync Task ReadQueryResults(SqlDataReader dataReader) { while (await dataReader.ReadAsync()) { string formatStringWithMiddleName = @”Person({0}) is named {1}{2}{3}”; if (!(dataReader[“middlename”] == null)) { Console.WriteLine(formatStringWithMiddleName, dataReader[“id”], dataReader[“firstname”], dataReader[“middlename”], dataReader[“lastname”]); } } }
Você pode usar a classe OleDbDataReader no lugar da classe SqlDataReader para recuperar dados do Microsoft Access (arquivo.accdb apresentado no Access 2007, formato de arquivo.mdb versões anteriores).
DBNull vs null
Esteja ciente de que se uma coluna contiver um valor nulo, o objeto retornado do indexador não será nulo; é DBNull.Value. Isso pode ser a causa de muitos erros, se você não entender a diferença entre os dois.DBNull é uma classe singleton, o que significa que somente essa instância dessa classe pode existir.
Se um campo de banco de dados estiver faltando, você poderá usar a propriedade DBNull.Value para atribuir explicitamente um valor de objeto de DBNull ao campo. No entanto, a maioria dos provedores de dados faz isso automaticamente.
Para avaliar os campos de banco de dados para determinar se seus valores são DBNull, você pode passar o valor do campo para o método DBNull.Value.Equals. No entanto, esse método raramente é usado porque há várias outras maneiras de avaliar um campo de banco de dados para os que estão faltando. Isso inclui a função Visual Basic IsDBNull, o método Convert.IsDBNull, o método DataTableReader.IsDBNull, o método IDataRecord.IsDBNull e vários outros métodos.
var middlename = AddFieldValue(dr, “MiddleName”); Console.WriteLine(string.Format(“MiddleName: {0}”, middlename));
privatestaticstring AddFieldValue(SqlDataReader row, string fieldName) { if (!DBNull.Value.Equals(row[fieldName])) return (string)row[fieldName] + “ “; elseif (Convert.IsDBNull(row[fieldName])) return”NA”; else return String.Empty; }
Método ExecuteScalar - Lendo uma linha
O método ExecuteScalar é usado quando você sabe que seu conjunto de resultados contém apenas uma única coluna com uma única linha. Isso é ótimo quando sua consulta retorna o resultado de uma função agregada, como SUM ou AVG. O código a seguir chama o método ExecuteScalar e retorna o Count of records na tabela People:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = connection; cmd.CommandType = CommandType.Text; cmd.CommandText = “SELECT COUNT(*) FROM People”; object obj = cmd.ExecuteScalar(); Console.WriteLine(string.Format(“Count: {0}”, obj.ToString())); }
O método ExecuteScalar sempre retorna um objeto; portanto, cabe a você converter esse valor para o tipo certo quando desejar usá-lo.
Método ExecuteXmlReader
O método ExecuteXmlReader retorna um XMLReader, que permite representar os dados como XML. O código a seguir retorna os dados da tabela People em um objeto XmlReader:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = connection; cmd.CommandType = CommandType.Text; cmd.CommandText = “SELECT * FROM People FOR XML AUTO, XMLDATA”; System.Xml.XmlReader xmlreader = cmd.ExecuteXmlReader();
while (xmlreader.Read()) { var xml = xmlreader.ReadOuterXml(); xml = xml.Replace(@”/>”, @”/>” + Environment.NewLine);
Console.WriteLine(xml);
} }
A instrução SQL foi alterada e incluiu a cláusula FOR XML AUTO, XMLDATA. Observe que o esquema é retornado junto com os dados neste exemplo. O resultado XML desta consulta é
Método ExecuteNonQuery
O método ExecuteNonQuery é usado para executar instruções no banco de dados que não retornam conjuntos de resultados. O método ExecuteNonQuery() retorna somente o número de linhas afetadas.Por exemplo, uma instrução de inserção, atualização ou exclusão não retorna nenhum registro. Eles simplesmente executam a instrução em uma tabela. O código a seguir demonstra como executar uma instrução de inserção no banco de dados:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = connection; cmd.CommandType = CommandType.Text; cmd.CommandText = “INSERT INTO People (FirstName, LastName) “ + “VALUES (‘Joe’, ‘Smith’)”; int result = cmd.ExecuteNonQuery();
if (result > 0) Console.WriteLine(“Data is Inserted”); else Console.WriteLine(“Error while inserting”); }
Observe as três propriedades do objeto de comando que precisavam ser definidas antes de chamar o método ExecuteNonQuery. A primeira é a propriedade Connection. Isso deve ser definido como uma conexão aberta. Isso informa ao objeto de comando qual banco de dados usar ao executar o texto contido na propriedade CommandText. Neste exemplo, você usa o SQL embutido, e é por isso que a propriedade CommandType está definida como CommandType.Text. Se você usar um procedimento armazenado para inserir um registro People, seria necessário alterar o CommandType para CommandType.StoredProcedure e defina CommandText como nome do procedimento armazenado. Por exemplo, suponha que você tenha o seguinte procedimento armazenado que insere um registro na tabela People:
CREATEPROCEDURE [dbo].[PersonInsert] @FirstName varchar(50), @LastName varchar(50) AS BEGIN INSERTINTO PEOPLE(FirstName, LastName)VALUES (@FirstName, @LastName) END
O código a seguir executa o procedimento armazenado e passa nos parâmetros @FirstName e @LastName:
SqlCommand cmd_sp = new SqlCommand(); cmd_sp.Connection = connection; cmd_sp.CommandType = CommandType.StoredProcedure; cmd_sp.CommandText = “PersonInsert”; cmd_sp.Parameters.Add(new SqlParameter(“@FirstName”, “Sasha”)); cmd_sp.Parameters.Add(new SqlParameter(“@LastName”, “Gray”)); int result_sp = cmd_sp.ExecuteNonQuery();
O objeto Command possui uma propriedade Parameters que você usa para passar parâmetros para o procedimento armazenado. Observe também que o método ExecuteNonQuery retorna 1 como número de linhas afetadas pelo comando quando é executado uma Store Procedure.
Atualizando dados
Além de selecionar dados, você também pode atualizar os dados no banco de dados. Você pode criar novos registros, atualizar os existentes ou remover completamente um registro. Você pode fazer tudo isso usando uma conexão e um objeto de comando. Mas, em vez de retornar um leitor com as linhas resultantes, você recebe um valor inteiro de volta que mostra quantas linhas são afetadas pela sua última consulta. Isso significa que, se você executar um procedimento armazenado ou encadear várias instruções SQL juntas, obterá apenas o número de registros afetados pela última consulta, não em todas as consultas no seu procedimento. Ao verificar o resultado, você pode determinar se a quantidade correta de linhas foi afetada. Você usa o método ExecuteNonQuery ou ExecuteNonQueryAsync para fazer isso, conforme mostrado abaixo.
publicstaticasync Task UpdateRows() { string connectionString = ConfigurationManager. ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command = new SqlCommand(“UPDATE People SET FirstName =’John’ WHERE id = 3”, connection); await connection.OpenAsync(); int numberOfUpdatedRows = await command.ExecuteNonQueryAsync(); Console.WriteLine(“Updated {0} rows”, numberOfUpdatedRows); } }
Usando parâmetros
Até agora, você viu apenas consultas que já estavam completamente construídas em tempo de compilação. No mundo real, no entanto, muitas vezes você precisa usar valores dinâmicos ao executar uma consulta. Como uma consulta SQL nada mais é do que uma sequência simples, você pode ser tentado a concatenar várias seqüências para criar sua consulta. No entanto, esteja ciente de que este é um enorme risco de segurança. Por exemplo, digamos que você tenha um formulário no qual as pessoas possam preencher os nomes que você inserirá na tabela People. Sua consulta será mais ou menos assim:
SqlCommand command1 = new SqlCommand(“INSERT INTO People VALUES(‘Diane’, ‘Birch’, null)”, connection);
Agora, você pode ler os valores que um usuário digita e depois construir manualmente essa sequência.Mas o que aconteceria se um usuário digitasse ‘); DELETE FROM People; – invés de passar null para o útimo parâmetro, como no exemplo abaixo:
SqlCommand commanddel = new SqlCommand(“INSERT INTO People VALUES(‘John’, ‘Birch’, ‘’); DELETE FROM People; –’”, connection);
Depois de executar esta consulta, todos os dados na sua tabela Peopleserão excluídos. Essa falha de segurança é conhecida como injeção SQL.
Para se proteger contra a injeção de SQL, você nunca deve usar diretamente a entrada do usuário em suas seqüências de caracteres SQL. Em vez de criar manualmente a consulta SQL correta, você pode usar instruções SQL com parâmetros.
publicstaticasync Task InsertRowWithParameterizedQuery() { string connectionString = ConfigurationManager. ConnectionStrings[“PeopleConnection”].ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command = new SqlCommand(@”INSERT INTO People([FirstName], [LastName], [MiddleName]) VALUES(@firstName, @lastName, @middleName)”, connection); await connection.OpenAsync(); command.Parameters.AddWithValue(“@firstName”, “John”); command.Parameters.AddWithValue(“@lastName”, “Doe”); command.Parameters.AddWithValue(“@middleName”, “Little”); int numberOfInsertedRows = await command.ExecuteNonQueryAsync(); Console.WriteLine(“Inserted {0} rows”, numberOfInsertedRows); } }
Essas consultas parametrizadas podem ser usadas ao executar consultas de seleção, atualização, inserção e exclusão. Além de serem muito mais seguros, eles também oferecem melhor desempenho. Como o banco de dados obtém uma consulta mais genérica, ele pode encontrar mais facilmente um plano de execução pré-compilado para executar sua consulta.
Usando transações
Ao trabalhar com um banco de dados, as coisas podem dar errado. Talvez você esteja executando várias consultas que devem ser agrupadas. Se o último falhar, os anteriores já serão executados e, de repente, seus dados poderão ficar corrompidos.Por isso, o .NET Framework ajuda você nas transações. Uma transação possui quatro propriedades principais que são chamadas de ACID: • Atomicidade: Todas as operações são agrupadas. Se alguma falha, todas falham. • Consistência: As transações levam o banco de dados de um estado válido para outro. • Isolamento: As transações podem operar independentemente uma da outra. Várias transações simultâneas não se influenciam. Será como se eles fossem executados em série. • Durabilidade: O resultado de uma transação confirmada é sempre armazenado permanentemente, mesmo se o banco de dados travar imediatamente depois.
Ao trabalhar com transações, é mais fácil usar a classe TransactionScope. O escopo da transação oferece uma maneira fácil de trabalhar com transações sem exigir que você interaja com a própria transação.
using System.Transactions;
publicasync Task InsertRowTransactionScope() { string connectionString = ConfigurationManager. ConnectionStrings[“ProgrammingInCSharpConnection”].ConnectionString;
using (TransactionScope transactionScope = new TransactionScope()) { using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command1 = new SqlCommand(@”INSERT INTO People([FirstName], [LastName], [MiddleInitial]) VALUES(‘John’, ‘Doe’, null)”, connection); SqlCommand command2 = new SqlCommand(@”INSERT INTO People([FirstName], [LastName], [MiddleInitial]) VALUES(‘Jane’, ‘Doe’, null)”, connection); await connection.OpenAsync(); command1.ExecuteNonQuery(); command2.ExecuteNonQuery(); } transactionScope.Complete(); } }
Parece inofensivo, mas gera um System.InvalidOperationException: um TransactionScope deve ser descartado no mesmo thread que foi criado.O motivo é que o TransactionScope não flui de um thread para outro por padrão. Para corrigir isso, precisamos usar TransactionScopeAsyncFlowOption.Enabled:
using (TransactionScope transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { using (SqlConnection connection = new SqlConnection(connectionString)) { //…. } //…. }
Se ocorrer uma exceção dentro do TransactionScope, a transação inteira será revertida.Se nada der errado, use TransactionScope.Complete para concluir a transação. É importante usar o TransactionScope dentro de uma instrução using para que seja automaticamente descartado quando não for mais necessário.
Um TransactionScope também pode ser construído passando uma enumeração TransactionScopeOption para ele. Usando essa enumeração, você pode definir o comportamento do TransactionScope.Um TransactionScope pode ser construído com três opções:
Ao usar o TransactionScope, o .NET Framework gerencia automaticamente a transação para você. Se sua transação usar conexões aninhadas, vários bancos de dados ou vários tipos de recursos, sua transação será promovida para uma transação distribuída. A promoção para uma transação distribuída pode ter um enorme impacto no desempenho, se não for necessário. Se possível, tente evitar transações distribuídas. Se você precisar deles, o .NET Framework gerencia isso para você.
CAMADA DESCONECTADA
Em uma camada desconectada, você normalmente usa DataSets e DataTables que copiam a estrutura de um banco de dados relacional na memória. Um DataSet é criado no resultado de uma execução de uma consulta em um banco de dados conectado. Ele pode ser manipulado na memória e as alterações em um banco de dados ocorrem usando o DataAdapter. DataTable e DataSets são outra maneira de recuperar resultados de um banco de dados.
DataTable
DataTable é o mesmo que DataReader, exceto pelo fato de estar desconectado do banco de dados; você pode mover o cursor para frente e para trás; e você pode atualizar dados no DataTable, reconectar-se ao banco de dados e confirmar as alterações.
DataSet
Um DataSet é um contêiner para um ou mais DataTables. Você pode executar uma declaração SQL que retorna vários conjuntos de resultados e cada um pode estar contido no DataSet. Você pode filtrar, classificar ou atualizar os dados na memória.
Os DataSets podem ser tipados ou não. Um DataSet tipado é um dataset que é derivado de uma classe DataSet e que usa a informação contida em um arquivo de esquema XML (.xsd) para gerar uma nova classe. Toda a informação do esquema (tabelas, colunas, linhas, etc.) é gerada e compilada neste nova classe DataSet. Como esta nova classe é derivada (herdar) da classe DataSet ela assuem toda a funcionalidade da classe DataSet.
Um DataSet não tipado não possui um corresponde arquivo de esquema, ele também possui tabelas, colunas, linhas, etc mas que são expostas somente como uma coleção. Você é quem deverá informar os nomes dos itens presentes nas coleções. Isto pode levar a que os erros sejam detectados somente na hora da compilação. Por exemplo, a linha de código abaixo tem um erro de digitação no nome da tabela que será detectado somente quando você for compilar o código: Ex: dim dt As Datatable = ds.Tables(“Produtos”). Vejamos a seguir a comparação entre dois códigos que acessam dados. Um usa DataSets tipados e outro não tipado: DataSet Código Tipados string s = dsCustomersOrders1.Customers(0).CustomerID; Não Tipados string s = (string)dsCustomersOrders1.Tables(“Customers”).Rows(0).Item(“CustomerID”);
Além de ser mais fácil de usar um DataSet tipado permite que você use o recurso da intellisense no seu código ao usar o editor do Visual Studio. É claro que haverá ocasiões que você vai ter que usar um DataSet não tipado. Vamos criar e usar um DataSet tipado para exibir o resultado de uma consulta SQL no banco de dados, mas poderia ser uma consulta a um banco de dados Northwind.mdf:
Etapa 1: Clique com o botão direito do mouse no seu projeto no Visual Studio e clique em Adicionar >> Novo Item. Selecione DataSet e nomeie-o como quiser (Sample.xsd neste exemplo).
Depois disso, a janela Sample.xsd aparece na sua frente.
Etapa 2: adicione a conexão de dados acessando o Server Explorer. No Server Explorer, clique com o botão direito em Data Connections » Add Connection; um assistente será aberto onde você precisará especificar o nome do servidor e selecionar um banco de dados criado no servidor SQL. Neste exemplo, foi adicionado o banco de dados School, com duas tabelas que irão ser usadas: Class e Student.
Etapa 3: arraste as tabelas da conexão adicionada (no Server Explorer) que for utilizar em Sample.xsd (a janela é aberta à sua frente) e salve o Sample.xsd. Para obter uma cadeia de conexão, clique com o botão direito do mouse em seu Connection (do server explorer) e clique em propriedades. Procure a propriedade String de conexão, onde você encontra sua string de conexão. Basta copiar e colar no seu código.
Passo 4: À medida que os componentes são adicionados, agora você pode interagir com seu banco de dados.
var connectionString = @”Data Source=localhost;Initial Catalog=School;Integrated Security=True”; Sample tds = new Sample(); using (SqlConnection connection = new SqlConnection(connectionString)) { SqlDataAdapter da = new SqlDataAdapter(“SELECT * FROM Student”, connection); da.Fill(tds, tds.Student.TableName); } // Connection is automatically closed.
foreach (DataRow item in tds.Student.Rows) { Console.WriteLine(string.Format(“ID: {0} - Name: {1}”, item[0], item[1])); }
Use o método ReadXmlSchema para criar o esquema de um DataTable. O esquema inclui tabela, relação e definições de restrição. Para gravar um esquema em um documento XML, use o método WriteXmlSchema.
string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName; var path = projectDirectory + “\Sample.xsd”; DataSet dataSet = new DataSet(); dataSet.ReadXmlSchema(path); foreach (DataTable table in dataSet.Tables) { Console.WriteLine(table.TableName); foreach (DataColumn column in table.Columns) { Console.WriteLine(“\t{0}: {1}”, column.ColumnName, column.DataType.Name); } }
É possível passar como parâmetro para o método ReadXmlSchema um StreamReader:
using (var xmlStream = new StreamReader(path)) { dataSet.ReadXmlSchema(xmlStream); foreach (DataTable table in dataSet.Tables) { // ….. } }; Class ClassID: Int32 ClassName: String StudentID: Int32 Student StudentID: Int32 StudentName: String
DataAdapter
DataAdapter é o objeto importante quando você trabalha com uma camada desconectada. Ele atua como uma ponte entre os dados na memória e um banco de dados. O DataAdapter preenche um DataTable ou DataSets e reconecta os dados na memória a um banco de dados. Você pode executar consultas de inserção, atualização, exclusão ou leitura enquanto os dados estiverem na memória e reconectar-se a um banco de dados para confirmar as alterações. O código abaixo mostra como executar operações orientadas a banco de dados usando uma camada desconectada:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString; DataTable tbl = new DataTable();
using (SqlConnection connection = new SqlConnection(connectionString)) { string command = “select * from Student”; SqlDataAdapter ad = new SqlDataAdapter(command, connection); ad.Fill(tbl);//Now the data in DataTable (memory) }
foreach (DataRow item in tbl.Rows) { Console.WriteLine(string.Format(“ID: {0} - Name: {1}”, item[0], item[1])); }
Quando o método de preenchimento do DataAdapter é chamado, uma consulta é executada e o método Fill() preenche o DataTable (obtém os dados e o mapa no DataTable). O DataTable não precisa manter aberta a conexão para preencher os dados, o que não é o caso do DataReader (em uma camada conectada). Essa é a beleza de uma camada desconectada e tem melhor desempenho do que uma camada conectada, pois lida com os dados presentes na memória, que são rapidamente acessíveis. Você também pode usar DataSet em vez de DataTable ao esperar vários conjuntos de resultados. O trabalho é o mesmo, exceto que ele pode retornar várias tabelas. DataSet possui a propriedade Table, pela qual você pode iterar sobre dados específicos da tabela.
Conforme indicado, você pode executar outras operações no DataTable ou no DataSet, como inserir, excluir etc. (Dados na memória), e essas operações têm desempenho rápido em comparação às operações executadas em uma camada Conectada. Como em uma camada conectada, o banco de dados é conectado e a arquitetura do ADO.NET leva a consulta e o mapa para um banco de dados (que consome tempo comparado ao mapa) ou executa alguma função nos dados presentes na memória, mas não em algum local externo (o caso de uma camada desconectada). O exemplo a seguir mostra a inserção de dados em uma DataTable e confirma as alterações em um banco de dados:
string connectionString = ConfigurationManager.ConnectionStrings[“PeopleConnection”].ConnectionString; DataTable tbl = new DataTable();
//New Record to add in DataTable DataRow newRow = tbl.NewRow(); newRow[“StudentName”] = “Mary Moon”; tbl.Rows.Add(newRow); DataRow newRow2 = tbl.NewRow(); newRow2[“StudentName”] = “Bob Cuspe”; tbl.Rows.Add(newRow2);
using (SqlConnection connection = new SqlConnection(connectionString)) { //Now newRow has to add in Database(Pass newRow Parameters to this insert query) string newCommand = @”Insert into Student(StudentName) Values(@StudentName)”; SqlDataAdapter ad = new SqlDataAdapter(newCommand, connection); SqlCommand insertCommand = new SqlCommand(newCommand, connection); //Create the parameters insertCommand.Parameters.Add(new SqlParameter(“@StudentName”,SqlDbType.VarChar, 50, “StudentName”)); //Associate Insert Command to DataAdapter so that it could add into Database ad.InsertCommand = insertCommand; ad.Update(tbl); }
Neste exemplo, é usada uma propriedade Parameter de um objeto Command, que recebe novos dados relacionados a parâmetros, como nome da coluna, tamanho da coluna e nome do parâmetro. “NewRow” adicionado no DataTable (novo registro na memória), mas não no banco de dados, mas mais tarde usou o método Update() do DataAdpater, que se reconecta a um banco de dados para fazer alterações (ou seja, DataTable atualizado mapeado para um banco de dados).
Este código abaixo demonstrará como usar a propriedade UpdateCommand de um objeto DbDataAdapter para atualizar um registro e também como usar a propriedade DeleteCommand de um DbDataAdapter para excluir um registro.
using (SqlConnection connection = new SqlConnection(connectionString)) { SqlDataAdapter da = new SqlDataAdapter(“SELECT * FROM Student”, connection); //Create the update command SqlCommand update = new SqlCommand(); update.Connection = connection; update.CommandType = CommandType.Text; update.CommandText = “UPDATE Student SET StudentName = @StudentName WHERE StudentID = @StudentID”; //Create the parameters update.Parameters.Add(new SqlParameter(“@StudentName”, SqlDbType.VarChar, 50, “StudentName”)); update.Parameters.Add(new SqlParameter(“@StudentID”, SqlDbType.Int, 0, “StudentID”));
//Create the delete command SqlCommand delete = new SqlCommand(); delete.Connection = connection; delete.CommandType = CommandType.Text; delete.CommandText = “DELETE FROM Student WHERE StudentID = @StudentID”; //Create the parameters SqlParameter deleteParameter = new SqlParameter(“@StudentID”, SqlDbType.Int, 0, “StudentID”); deleteParameter.SourceVersion = DataRowVersion.Original; delete.Parameters.Add(deleteParameter); //Associate the update and delete commands with the DataAdapter. da.UpdateCommand = update; da.DeleteCommand = delete; //Get the data. DataSet ds = new DataSet(); da.Fill(ds, “Student”); //Update the first row ds.Tables[0].Rows[0][“StudentName”] = “Ricky Martin”;
//Delete the second row. ds.Tables[0].Rows[5].Delete();
//Update the database. da.Update(ds.Tables[0]); }
Observe que as propriedades UpdateCommand e DeleteCommand estão definidas para diferentes objetos de comando que contêm a lógica para atualizar e excluir registros. A lógica DbDataAdapter executará automaticamente o objeto de comando de acordo com a função que ele deve executar. Certifique-se de colocar a lógica correta em seus objetos de comando, porque o DbDataAdapter apenas executará o comando; ele não verifica se realmente faz o trabalho correto.
System.Data.SqlClient.SqlException: ‘A instrução DELETE conflitou com a restrição do REFERENCE “FK_Class_Student”. O conflito ocorreu no banco de dados “School”, tabela “dbo.Class”, column ‘StudentID’. A instrução foi finalizada.’
ENTITY FRAMEWORK
Usando um ORM (Object Relational Mapper)
As camadas conectadas e desconectadas forçam você a tratar dados da maneira de um esquema físico de um banco de dados. Essas camadas são fortemente acopladas ao banco de dados relacional, pois o usuário precisa usar o SQL para executar consultas e ter em mente a conexão, o comando, o DataReader, o DataSet e o DataAdapter, etc.
Um dos problemas com um banco de dados relacional é que ele é inerentemente diferente da estrutura orientada a objetos que você deseja usar em seus aplicativos. Você usa objetos que têm relação com outros objetos ou que herdam deles. O banco de dados usa tabelas com colunas e chaves estrangeiras para outras tabelas. Isso é chamado de incompatibilidade de impedância objeto-relacional.
Você pode criar aplicativos complexos escrevendo manualmente suas consultas SQL e executando-as no banco de dados. Mas quando seu aplicativo cresce e você deseja usar um design orientado a objetos, começa a ter problemas. Obviamente, você pode mapear os resultados da sua consulta para objetos e criar consultas a partir das alterações nos seus objetos, mas essa é uma tarefa difícil.É aqui que entra o software Object Relational Mapping (ORM). Um desses ORM é o Entity Framework de código aberto da Microsoft, que diminui o fardo do trabalho tedioso de mapear seus objetos, construir consultas e acompanhar todas as alterações.
O uso de um ORM é um compromisso entre desempenho e velocidade de desenvolvimento. Diferentemente dessas camadas, o Entity Framework fornece a maneira orientada a objetos para interagir com um banco de dados. Usando essa camada conceitual do ADO.NET, você não precisa se preocupar com objetos de conexão ou de comando. Esse tipo de coisa é tratado automaticamente pelo Entity Framework. Você pode imaginar como o Entity Framework usa a reflexão para mapear dos resultados da consulta SQL para os objetos, o que é sempre mais lento do que fazê-lo manualmente. No entanto, o uso do Entity Framework aumentará imensamente sua velocidade de desenvolvimento. Ele permite que você crie seu aplicativo rapidamente, priorizando as partes que precisam de melhor desempenho. O Entity Framework possui uma visualização gráfica na qual é possível arrastar e soltar objetos do banco de dados e atualizar essa visualização sempre que houver uma alteração no base de dados. Ele geralmente atende a maioria das situações, caso contrário, você pode adicionar uma Store Procedure simples para as consultas problemáticas.
O LINQ é usado em vez do SQL e você pode usar um dos tipos de LINQ com sua fonte de dados fornecida pelo ADO.NET (banco de dados). Quando você executa consultas LInQ na fonte de dados ADO.Net, o tempo de execução da estrutura da entidade gera uma instrução SQL adequada em seu nome. Mas isso diminui o desempenho em comparação com a camada conectada e a desconectada.
O Entity Framework (EF) normalmente tem quatro abordagens para usar, a primeira é mais utilizada que é quando há um banco de dados existente, as outras são variações de abordagens para quando não houver um banco de dados definido e deseja iniciar o projeto e depois criar a base: • Database First (EF Designer from database):Você deseja mapear um banco de dados existente para sua estrutura de objeto, com interface visual de mapeamento edmx. • Model First (Empty EF Designer model): permite modelar nossa base de dados de forma visual através dos recursos do próprio Visual Studio, através da criação de um arquivo .edmx. A vantagens é de não haver necessidade de conhecimentos prévios do banco de dados ou do Entity Framework. Por outro lado, causa problemas em relação a manutenções e alterações nas regras de negócio da aplicação. • Empty Code First model:O Entity Framework introduziu a abordagem Code-First com o Entity Framework 4.1. O Code-First é principalmente útil no Design Orientado a Domínios (Domain Driven Design). Na abordagem Code-First, você se concentra no domínio do seu aplicativo e começa a criar classes para a entidade do domínio, em vez de criar o banco de dados primeiro e depois criar as classes que correspondem ao design do banco de dados. A figura a seguir ilustra a abordagem do primeiro código.
Como você pode ver na figura acima, a EF API criará o banco de dados com base nas suas classes e configuração de domínio. Isso significa que você precisa começar a codificar primeiro em C# ou VB.NET e, em seguida, a EF criará o banco de dados a partir do seu código. • Code First(Code first from database):Mesmo você tendo o banco pronto, pode adotar a abordagem Code First que vai escrever as classes para mapear o banco existente e a partir daí começa a trabalhar a partir das classes, sem interface visual de mapeamento edmx.
Essas abordagens podem ser usadas usando o Assistente de modelo de dados de entidade, mas a abordagem “Code first from database” também pode ser usada sem o Assistente de modelo de dados da entidade. Toda abordagem tem sua própria adequação. Temos duas principais frentes no que diz respeito à ordem e gestão dos processos durante o desenvolvimento de um software: • Modelo waterfall ou em cascata: é a mais utilizada, onde o software passa por diversas etapas até sua entrega, e em teoria, o sistema possui todos os seus requisitos levantados no início e o projeto de software possui uma arquitetura, design, vários diagramas e estrutura da base de dados feita logo no início. • Modelo ágil: onde o desenvolvimento de software é incremental, as necessidades são separadas em pequenas partes e o sistema não é imutável ocorrendo entregas periódicas com novas partes do sistema. Como deve imaginar, em um modelo ágil, como há releases incrementais, a base de dados e os dados necessários podem mudar muitas vezes antes de chegar a seu estado final. Nesses momentos a utilização do Entity Framework Migrations é fundamental, fornecendo uma forma prática para lidar com essas mudanças.
Database First (EF Designer from database)
Etapa 1: clique com o botão direito do mouse no seu projeto e clique em Adicionar» Novo item. Selecione “ADO.NET Entity Data Model”, nomeie o que quiser (neste exemplo, eu o nomeei Sample) e clique no botão “Add”.
Passo 2: Depois de clicar no botão “Adicionar”, o próximo passo é escolher o conteúdo do modelo. Escolha o primeiro, EF Designer do banco de dados, e clique no botão “Avançar”.
Etapa 3: Depois de clicar no botão “Avançar”, o próximo passo é escolher Conexão de dados. Escolha na caixa suspensa (a Conexão de dados será escolhida e uma string de conexão será criada. Observe o nome abaixo no TextBox que seria o objeto do seu banco de dados. Você pode alterar esse nome. Eu o chamei de SchoolDB) e clique no botão “ Avançar”, (vá para a Etapa 5); caso contrário, clique no botão “Nova conexão” na janela à sua frente (vá para a Etapa 4).
Passo 4: Se você clicou no botão “Nova conexão”, especifique o nome do servidor e o banco de dados e clique no botão “OK”. O painel “Conectar-se a um banco de dados” será ativado quando você especificar o nome do servidor. Você pode usar a autenticação do Windows ou uma autenticação do SQL Server para se conectar ao banco de dados. Se você selecionar Autenticação do SQL Server, precisará inserir um Logon e uma Senha válidos do SQL. A Conexão de Dados será selecionada e uma cadeia de conexão será estabelecida. Clique no botão “Next”.
Etapa 5: Depois de clicar no botão “Avançar”, escolha seus objetos e configurações de banco de dados. Existem caixas de seleção de “Tabelas”, “View” e “Stored Procedures and Functions”. Marque as caixas de seleção nos Objetos de Banco de Dados que você deseja adicionar.
Eu seleciono as “Tabelas” neste exemplo. Após a seleção, clique no botão “Concluir”. O assistente está concluído. Referências necessárias e um arquivo chamado “Sample.edmx” serão adicionados aos seus projetos.
Clique na seta ao lado do arquivo NorthwindsModel.edmx no Solution Explore para exibir todos os arquivos criados para você. Observe o arquivo chamado EntityModel.tt. Este é um arquivo do Text Transformation Template Toolkit, também conhecido como modelo T4. Um arquivo de modelo T4 é usado para gerar automaticamente o código no Visual Studio. Os modelos T4 são uma mistura de blocos de texto e instruções de controle que permitem gerar um arquivo de código. Clique na seta ao lado do arquivo NorthwindsModel.tt para expandir a lista de arquivos gerados por este modelo. Um arquivo foi criado para cada tabela, exibição e procedimento armazenado que retorna um conjunto de resultados.
Agora clique no arquivo EntityModel.Context.tt no Solution Explorer. Este é o modelo T4 para o objeto de contexto. Pense no objeto Context como a classe que representa todo o banco de dados. Se você clicar na seta ao lado do arquivo EntityModel.Context.tt, poderá ver um arquivo, EntityModel.Context.cs. Esta é a classe que foi criada pelo modelo T4. Abra o arquivo EntityModel.Context.cs clicando nele. O nome da classe é SchoolDB e possui propriedades para cada tabela contida no banco de dados. As propriedades são tipos DbSet genéricos, que são uma coleção de cada tipo que representa uma tabela ou exibição. Algumas das propriedades que foram criadas estão listadas aqui:
publicvirtual DbSet
Agora você está pronto para interagir com seu banco de dados. Caso as referências as dll do EntityFramework não forem incluídas ao projeto, ou for adicionar o Entity Framework a qualquer outro projeto é possível utilizar o Gerenciador de Pacotes Nuget para essa tarefa:
O exemplo a seguir mostra como você pode obter todos os dados dos alunos usando o Entity Framework:
using (SchoolDB db = new SchoolDB()) { var students = (from p in db.Student select p).ToList(); foreach (var student in students) { Console.WriteLine(“ID is: “ + student.StudentID); Console.WriteLine(“Name is: “ + student.StudentName); } }
Como você pode ver, foram necessárias apenas algumas linhas de código para obter os registros e você não precisou escrever nenhuma consulta SQL. Tudo é tratado por você pelo Entity Framework. SchoolDB é um nome que representa seu banco de dados. Ele contém todas as coisas dentro do seu base de dados. Você pode usar seu objeto para acessar ou usar itens de banco de dados, como acessar tabelas, etc. Uma consulta LINQ é usada para recuperar os dados do banco de dados e recuperar os resultados. Observe que você escreve a consulta usando a sintaxe C#. As classes do Entity Framework sabem como converter isso em uma consulta SQL para você nos bastidores. Quando os dados são recuperados, você pode enumerar as categorias para gravar cada registro na janela Saída.
Anteriormente, foi apontado que a tabela Class tinha uma chave estrangeira para a tabela Student. A classe Class que é gerada pelo Assistente de Modelo de Dados da Entidade criou uma propriedade para representar esse relacionamento.
publicvirtual Student Student { get; set; }
O exemplo de código a seguir mostra como gravar uma consulta LINQ para unir as duas tabelas e gravar o nome do estudante e o nome da classe na janela Saída:
using (SchoolDB db = new SchoolDB()) { var classes = from c in db.Student join p in db.Class on c.StudentID equals p.StudentID select p; foreach (var clas in classes) { Console.WriteLine(string.Format(“StudentName: {0}, ClassName: {1}”, clas.Student.StudentName, clas.ClassName)); } } Neste exemplo, em vez de selecionar o objeto Student, ele seleciona o objeto Class. O Entity Framework recupera as colunas corretas e preenche as propriedades da classe Studente, como você pode ver, também preenche a propriedade Class.O código abaixo mostra como você pode adicionar um registro (novo aluno) a uma tabela de alunos usando EF:
using (SchoolDB db = new SchoolDB()) { Student st = new Student(); st.StudentName = “Mubashar Rafique”; db.Student.Add(st); db.SaveChanges(); }
É tão simples quanto adicionar um item em uma lista. “Db” é o objeto do banco de dados e para adicionar um novo objeto de aluno, chame o método Add () no db.Students; isso adicionará o aluno aos Student e, após o método SaveChanges, um novo aluno será adicionado ao seu banco de dados. Você pode executar outras operações, como atualizar, excluir e localizar usando o LINQ.
using (SchoolDB db = new SchoolDB()) { //Find specific Studnet var std = (from p in db.Student where p.StudentName == “Mubashar Rafique” select p).FirstOrDefault();
if (std != null)//if student is found { std.StudentName = “Updated Name”; db.SaveChanges(); }
if (std != null)//if student is found { db.Student.Remove(std); db.SaveChanges(); } }
Chamar uma Stored Procedure
Como não selecionamos para incluir as Stored Procedure ao criar o modelo do Entity Framework, iremos fazer isso agora. Para isso, basta clicar com o botão direito do mouse na interface visual do arquivo .edmx e selecionar “Update Model from Database” conforme mostrado na figura abaixo.
Para chamar um procedimento armazenado, basta chamar o método O exemplo de código a seguir chama o procedimento armazenado PersonInsert:
var retornosp = db.PersonInsert(“Alceu”, “Valenca”); Console.WriteLine(retornosp); //-1
Model First (Empty EF Designer model):
Vamos construir agora nosso próprio modelo usando um novo designer de Context e EntityFramework, usando uma metodologia chamada Model-First, o que significa apenas que iremos construir o modelo do Entity Framework e depois usá-lo para criar o banco de dados.Nosso modelo representará um catálogo de cursos universitários muito simples. Criaremos entidades para alunos e cursos e mostraremos as relações entre eles.
O Visual Studio adicionará um novo arquivo EDMX ao local escolhido e exibirá uma tela de designer vazia mostrando o seguinte texto:
Vamos começar a configurar o nosso modelo! Primeiro, vamos criar uma entidade para representar um aluno. Clique com o botão direito do mouse no designer e selecione Adicionar novo -> Entidade: • Entity Name: exatamente o que parece. Estamos chamando o nosso de “Aluno”. De um modo geral, essa será uma forma singular. • Base Type: se a nova entidade herdar de outra entidade, você poderá usar este menu suspenso para selecionar a entidade de base. • Entity Set: o nome da coleção desse tipo de entidade que o Contexto produzirá. Geralmente, essa será a forma plural do Nome da entidade. • Key Property: os detalhes sobre uma coluna de chave que você deseja criar.
Nesse caso, estamos criando uma entidade Estudante com ID da chave. A entidade resultante é assim:
Agora temos um estudante, com um único ID de propriedade. Vamos adicionar mais algumas propriedades à entidade Student. Clique com o botão direito do mouse na entidade e selecione Adicionar novo -> Propriedade escalar
Propriedades escalares são propriedades da entidade que contêm um valor. No nosso caso, vamos adicionar uma propriedade para Name. Confira a janela Propriedades ao selecionar Nome para ver os detalhes da propriedade:
Algumas propriedades que você deve conhecer: Nullable: se esta propriedade pode ser nula. Type: O tipo .NET desta propriedade. Default Value: se uma instância desta entidade for criada, o valor padrão usado para esta propriedade. Max Length: o comprimento máximo dos dados. No caso de seqüências de caracteres, corresponde ao nvarchar (X) no SQL Server, onde X é o comprimento máximo.
No nosso caso, as propriedades já são o que queremos. Vamos adicionar mais duas propriedades, uma string LastName e um datetime DateOfBirth. Para DateOfBirth, lembre-se de alterar o Tipo na janela Propriedades.Agora vamos criar uma nova entidade para o Curso.
Agora que definimos todas as nossas entidades, podemos começar a criar os relacionamentos entre Estudante e Curso. Construímos esse relacionamento clicando com o botão direito do mouse em Estudante e selecionando Adicionar novo -> Associação. Isso abre o diálogo Associação. Um único aluno pode fazer muitos cursos, e um único curso (espero) terá muitos alunos. Portanto, esse é um relacionamento de muitos para muitos. Vamos criar esse relacionamento no designer:
Agora que o modelo foi totalmente elaborado, podemos mostrar como o Entity Framework nos permite usar um modelo para criar um banco de dados.Primeiro, clique com o botão direito do mouse na tela do modelo e selecione Gerar banco de dados do modelo:
Em seguida, escolha a conexão que você deseja usar (estou usando um banco de dados local):
Clique em Avançar e você verá o script SQL necessário para criar as tabelas, colunas e relacionamentos para este banco de dados:
Clique em Concluir para criar o script necessário para criar o banco de dados. Para executar o script ainda é necessário fazer mais uma validação de conexão. Depois de executar o scriptseu banco de dados estará pronto. Aqui está meu banco de dados completo:
Empty Code First model
A figura a seguir ilustra o fluxo de trabalho de desenvolvimento com o Code-First.
O fluxo de trabalho de desenvolvimento na abordagem de primeiro código seria: Criar ou modificar classes de domínio -> configurar essas classes de domínio usando a API do Fluent ou atributos de anotação de dados -> Criar ou atualizar o esquema do banco de dados usando migração automatizada ou migração baseada em código.
O Entity Framework 4.3 incluiu um novo recurso Code First Migrations que permite evoluir de forma incremental o esquema do banco de dados conforme seu modelo muda com o tempo. Para a maioria dos desenvolvedores, essa é uma grande melhoria em relação às opções do inicializador de banco de dados das versões 4.1 e 4.2 que exigiam que você atualize manualmente o banco de dados ou descarte e recriá-lo quando o modelo foi alterado. • Antes do Entity Framework 4.3, se você já possui dados (exceto dados de propagação) ou procedimentos armazenados, gatilhos etc. no seu banco de dados, essas estratégias eram usadas para descartar o banco de dados inteiro e recriá-lo, para que você perdesse os dados e outros bancos de dados objetos. • Com a migração, ele atualizará automaticamente o esquema do banco de dados quando o modelo for alterado sem perder dados ou outros objetos de banco de dados existentes. • Ele usa um novo inicializador de banco de dados chamado MigrateDatabaseToLatestVersion.
Existem dois tipos de migração:
Migração automatizada
A migração automatizada foi introduzida pela primeira vez na estrutura 4.3 da entidade. Na migração automatizada, você não precisa processar a migração do banco de dados manualmente no arquivo de código. Por exemplo, para cada alteração, você também precisará alterar nas classes de domínio. Mas com a migração automatizada, basta executar um comando no Package Manager Console para fazer isso. Neste exemplo, iniciaremos nossas 3 aulas básicas, como Aluno, Curso e Inscrição, conforme mostrado no código a seguir.
publicclassAluno
{
publicint ID { get; set; }
publicstring LastName { get; set; }
publicstring FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
publicvirtual ICollection
publicclassCurso
{
publicint CourseID { get; set; }
publicstring Title { get; set; }
publicvirtual ICollection
publicclassInscricao { publicint EnrollmentID { get; set; } publicint CourseID { get; set; } publicint StudentID { get; set; } publicvirtual Curso Course { get; set; } publicvirtual Aluno Student { get; set; } }
Logo após criar as classes, vamos adicionar o Entity Framework CodeFirst usando o NuGet. Para isto abra o gerenciador do NuGet em Tools/Library Package Manager/Packager Manager Console e digite:
Ou instale pelo Gerenciador de Pacotes Nuget:
Após isto teremos o EF CodeFirst instalado em nosso projeto. Vamos agora criar um Contexto e uma classe para podermos trabalhar com o Migrations. A seguir está a classe de contexto.
publicclassMyContext : DbContext
{
public MyContext() : base(“MyContextDB”) { }
publicvirtual DbSet
O DbContext é um invólucro para ObjectContext e além disso esta classe contém: • Um conjunto de APIs que são mais fáceis de usar do que a exposta pelo ObjectContext; • As APIs que permitem utilizar o recurso do Code-First e as convenções;
Você pode definir no arquivo de configuração App.Config a string de conexão e indicar nome banco de dados, senão fizer isso, o padrão que o Entity Framework usa é o seguinte: A API DbContext por convenção cria o banco de dados para você em localhost\SQLEXPRESS (ou (localdb)\mssqllocaldb ou outras variações dependendo da instalação de seu serviço SQL) e o nome do banco de dados é derivado a partir do nome do projeto e do contexto, no nosso caso seria: EF_Auto_CodeFirst.MyContext. Porém, o nome pode ser customizado na classe contexto, como segue abaixo:
publicclassMyContext : DbContext { public MyContext() : base(“SchoolDB”)
Vamos tentar adicionando um arquivo app.config para identificarmos nosso servidor SQL e o nome do banco de dados que já temos chamado db_code_first. A tag configSections deve ser única e ser a primeira do arquivo de configurações, antes do connectionStrings:
<?xmlversion=”1.0”encoding=”utf-8”?>
Vamos inicialmente adicionar o Migrations ao nosso projeto. Independente do método: manual ou automático, precisamos adicioná-lo ao nosso projeto. Faremos isto usando novamente a janela do Nuget através do comando Enable-Migrations:
Etapa 1 - Abra o Console do Gerenciador de Pacotes em Ferramentas → Gerenciador de Pacotes NuGet → Console do Gerenciador de Pacotes.
Etapa 2 - Para habilitar a migração automatizada, execute o seguinte comando no Package Manager Console. enable-migrations -EnableAutomaticMigrations:$true
Repare que se os nomes das tabelas não forem iguais aos nomes dos campos chaves pode haver problemas de criação das chaves primárias.
Etapa 3 - Depois que o comando é executado com êxito, ele cria uma classe de Configuração selada interna derivada de DbMigrationConfigurationna pasta Migração do seu projeto, conforme mostrado no código a seguir.
Como você pode ver no construtor da classe Configuration, AutomaticMigrationsEnabled está definido true.
Etapa 4 - Defina o inicializador do banco de dados na classe de contexto com a nova estratégia de inicialização do banco de dados MigrateDatabaseToLatestVersion. Repare que o nome MyContextDB é o mesmo definido como o nome da configuração em App.config, conforme mostrado abaixo. Apesar de estar definindo aqui o nome do banco também como MyContextDB em base(“MyContextDB”), esta configuração será ignorada uma vez que prevalence o que está definido no App.config que é initial catalog=db_code_first.
publicclassMyContext : DbContext
{
public MyContext() : base(“MyContextDB”)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext,
EF_Auto_CodeFirst.Migrations.Configuration>(“MyContextDB”));
}
publicvirtual DbSet
O que fizemos foi adicionar a chamada do DatabaseSetInitializer() com a opção MigrateDatabaseToLastVersion, o que faz com que nosso banco de dados seja sempre atualizado de acordo com as nossas classes.
Etapa 5 - Agora que temos o nosso modelo de entidades pronto e a migração automatizada configurada. Vamos definir o código no Modulo da solução para vermos o Code-First em ação, digite o código abaixo na aplicação console:
using (MyContext db = new MyContext()) { Aluno st = new Aluno(); st.EnrollmentDate = DateTime.Now; st.FirstMidName = “Mubashar Rafique”; st.LastName = “Silva”; db.Alunos.Add(st); db.SaveChanges(); Console.WriteLine(“Aluno Added!”);
var alunos = db.Alunos; foreach (var item in alunos) { Console.WriteLine(item.LastName); } }
Console.WriteLine(“ Banco de dados e tabelas cridas com sucesso”);
Quando você executa seu aplicativo, ele cuida automaticamente da migração quando você altera o modelo.A tabela foi definida com o nome Alunoes, Cursoes porque o Entity Framework pluraliza o nome da tabela usando a regra do idioma inglês, ou seja, acrescenta es ao final do nome.
Etapa 6 - Como você pode ver, uma tabela do sistema __MigrationHistory também é criada no seu banco de dados com outras tabelas. No __MigrationHistory, a migração automatizada mantém o histórico de alterações no banco de dados.
Etapa 7 - Quando você adiciona outra classe de entidade como sua classe de domínio e executa seu aplicativo, ele cria a tabela no seu banco de dados. Vamos adicionar a seguinte classe StudentLogIn.
publicclassAlunoLogin { [Key, ForeignKey(“Aluno”)] publicint ID { get; set; } publicstring EmailID { get; set; } publicstring Password { get; set; }
publicvirtual Aluno Aluno { get; set; } }
Etapa 8 - não se esqueça de adicionar o DBSet para a classe mencionada acima na sua classe de contexto, conforme mostrado no código a seguir.
publicvirtual DbSet
Etapa 9 - Execute seu aplicativo novamente e você verá que a tabela StudentsLogIn foi adicionada ao seu banco de dados.
As etapas acima mencionadas para migrações automatizadas funcionarão apenas para sua entidade. Por exemplo, para adicionar outra classe de entidade ou remover a classe existente, ela será migrada com êxito. Mas se você adicionar ou remover qualquer propriedade à sua classe de entidade, ela lançará uma exceção.
Etapa 10 - Para lidar com a migração de propriedades, você precisa definir AutomaticMigrationDataLossAllowed = true no construtor da classe de configuração.
internalsealedclassConfiguration : DbMigrationsConfiguration
Migração Baseada em Código
A migração baseada em código é útil quando você deseja ter mais controle sobre a migração. Essa abordagem permite que o Code First atualize o esquema do banco de dados em vez de eliminar e recriar o banco de dados. Aqui está a regra básica para migrar alterações no banco de dados – • Enable Migrations:Habilita a migração no seu projeto, criando uma classe de Configuração. • Add-Migrations “nome_migrations” – cria um alteração no banco de dados, onde o “nome_migrations” é o nome que você irá dar para a atualização; Cria uma nova classe de migração conforme o nome especificado com os métodos Up() e Down(). • Update Database: aplica as alterações no banco de dados; Executa o último arquivo de migração criado pelo comando Add-Migration e aplica mudanças no esquema do banco de dados. • Update-DataBase – script : gera um script com os comandos SQL para serem executados no banco de dados.
Neste exemplo, começaremos novamente com nossas 3 classes básicas, como Aluno, Curso e Inscrição.
A seguir está a classe de contexto. Vamos fazer algumas pequenas modificações do exemplo anterior para verificarmos os resultados. Primeiro, não utilizaremos o arquivo App.config para indicarmos a nossa conexão com o banco de dados. Segundo, os nomes atribuído aos DbSet abaixo estão no singular, anteriormente haviam sido colocados no plural e mesmo assim gerou as tabelas como Cursoes e Alunoes.
publicclassMyContext : DbContext { public MyContext() : base(“MyContextDB”) { //Database.SetInitializer(new MigrateDatabaseToLatestVersion< // MyContext, EF_CodeBasedFirst.Migrations.Configuration>(“MyContextDB”)); }
publicvirtual DbSet
}
Etapa 1 - Antes de executar o aplicativo, você precisa habilitar a migração, execute o comando enable-migrations no Console do Gerenciador de Pacotes.
O comando Enable-Migrations criará a classe Configuration derivada de DbMigrationsConfiguration com AutomaticMigrationsEnabled = false.
Etapa 2 - Agora, você precisa definir o inicializador do banco de dados MigrateDatabaseToLatestVersion na sua classe de contexto, conforme mostrado abaixo. publicclassMyContext : DbContext { public MyContext() : base(“SchoolDB”) { Database.SetInitializer(new MigrateDatabaseToLatestVersion< MyContext, EF_CodeBasedFirst.Migrations.Configuration>()); }
publicvirtual DbSet
Repare que o Migrations.Configuration não carrega mais a indicação do nome da configuração no App.config. Um dos principais problemas dessa abordagem, que ocorreu no outro exemplo também é que, se você não souber aonde exatamente esta mapeado o seu serviço local do SQL, a migração gera o banco mas você não acaba sabendo exatamente onde. Em exemplos, indicavam que o banco estaria sendo gerado em localhost\SQLEXPRESS, porém após a geração do banco neste exemplo, descobrimos finalmente aonde estava sendo gerado nossas migrações locais (em (localdb)\mssqllocaldb ) apesar de estarmos sempre fazendo as conexões em banco com o servidor “localhost”….
Etapa 3 - A migração já está ativada, agora adicione a migração ao seu aplicativo executando o seguinte comando.
add-migration “UniDB Schema”
Etapa 4 - Quando o comando for executado com êxito, você verá que um novo arquivo foi criado na pasta Migração com o nome do parâmetro que você passou para o comando com um prefixo de carimbo de data/hora, conforme mostrado na imagem a seguir.
O comando acima criará um arquivo
Para saber mais sobre os parâmetros do comando add-migration, execute os comandos get-help add-migration ou get-help add-migration -detailed no PMC, conforme mostrado abaixo. get-help add-migration
Etapa 5 - Após criar um arquivo de migração usando o comando add-migration, é necessário atualizar o banco de dados. Execute o comando Update-Database para criar ou modificar um esquema de banco de dados. Use a opção –verbose para visualizar as instruções SQL que estão sendo aplicadas ao banco de dados de destino.
update-database -Verbose
Execute o comando get-help update-database ou get-help update-database -detailed no PMC para saber mais sobre o comando.Nesse ponto, o banco de dados será criado ou atualizado. Agora, sempre que você alterar as classes de domínio, execute Add-Migration com o parâmetro name para criar um novo arquivo de migração e execute o comando Update-Database para aplicar as alterações no esquema do banco de dados.
Observe que aqui nota-se a alteração dos nomes da classe contexto do plural para o singular, pois as chamadas que antes eram db.Alunos agora passaram a ser db.Aluno. Inserindo alguns dados.
Aluno st = new Aluno(); st.EnrollmentDate = DateTime.Now; st.Name = “Carla”; st.LastName = “Perez”; db.Aluno.Add(st); db.SaveChanges(); Console.WriteLine(“Aluno Added!”);
Etapa 6 - adicione mais uma propriedade ‘Idade’ na turma do aluno. Se tentarmos apenas editar a propriedade e executarmos o comando update-database –Verbose dá o seguinte erro:
Mas se executaras instruções de adição e atualização.
add-migration “Update Idade” update-database -Verbose
Quando você executa PM → Atualizar banco de dados - detalhado, quando o comando é executado com êxito, você verá que a nova coluna Idade é adicionada ao seu banco de dados.
E se tentarmos editar uma propriedade que já haja dados gravados como, por exemple, o nome do aluno:
add-migration “Update Name para FirstName”
Repare nos métodos Up e Down gerados, indicando que irá adicionar a coluna FirstName e excluir a coluna Name.Mas antes de enviar isto para o banco, vamos imaginar que você queira setar um valor para o campo FirstName, por exemplo, todos os campos Ativos deverão estar true. Você pode fazer isto através do comando Sql() dentro do arquivo do migrations:
publicoverridevoid Up() { AddColumn(“dbo.Alunoes”, “FirstName”, c => c.String()); Sql(“update Alunoes set FirstName = ‘Bob’”); DropColumn(“dbo.Alunoes”, “Name”); }
Agora vamos enviar o comando para o banco, mas de uma maneira um pouco diferente, vamos gerar um script SQL. Para isto execute o comando da seguinte maneira:
Update-DataBase –script
E o resultado será um arquivo de script SQL:
Basta executar este arquivo dentro do SQL que o campo será criado e atualizado, ou se preferir, execute novamente o Update-DataBase que isto será feito automaticamente.Este recurso do script pode ser util se você precisar atualizar também um ambiente e produção da sua aplicação.
O mesmo problema da abordagem automática ocorreu, ou seja, excluiu a coluna com dados e anexou a nova coluna. Um recurso bem interessante do Migrations é que você pode voltar o seu banco de dados a qualquer ponto em que tenha executado um Add-Migrations. Imagine então que nós criamos o campo ativo no banco e agora você queira voltar ao ponto onde este campo ainda não existia, para isto vamos executar o seguinte comando do Migrations:
Update-DataBase –target “CriacaoBanco” ou update-database -TargetMigration:SchoolDB-v1
Isto volta nosso banco de dados a este ponto, que nosso caso foi a criação do banco, mas poderia ser qualquer outro ponto. Lembrando que este comando afeta apenas o banco de dados e não a nossa classe.
Code First (Code first from database)
Se você deseja usar o Code First para mapear essa entidade para um banco de dados, siga o mesmo procedimento para criação do .edmx em Database First (EF Designer from database)
Isso irá gerar as classes de entidade para suas tabelas e visualizações de banco de dados, como mostrado abaixo.
A parte mais interessante de tudo isso é que essas classes que a EF gerou para nós são as mesmas classes geradas pelas ferramentas de geração de código do EDMX. A única coisa que o Code-First realmente está fazendo de maneira diferente é permitir acesso direto a esses arquivos gerados, removendo o designer.
using (EntityModel ctx = new EntityModel()) { var person = new People { FirstName = “John”, LastName = “Doe” }; ctx.People.Add(person); ctx.SaveChanges();
// Display all Blogs from the database var query = from b in ctx.People orderby b.FirstName select b;
Console.WriteLine("All People in the database:"); foreach (var item in query)
{
Console.WriteLine(item.FirstName);
} }
O assistente de Code First para banco de dados foi criado para gerar um conjunto de classes de ponto de partida que você pode ajustar e modificar. Se o esquema do banco de dados for alterado, você poderá editar manualmente as classes ou executar outra engenharia reversa para substituir as classes.
CONSUMINDO DADOS XML E JSON
XML e JSON são usados principalmente para comunicação na rede entre aplicativos diferentes ou plataformas diferentes. Discutiremos esses dois formatos de transmissão de mensagens / dados pela Internet com uma breve descrição.
Consumindo XML
XML (Extensible Markup Language) é uma linguagem de marcação que consiste em um conjunto de regras que definem como um documento deve ser formatado. O .NET Framework fornece classes presentes no System.Xml.dll para trabalhar com XML que foi basicamente projetado para armazenar e transportar dados. A vantagem do XML é que ele pode ser lido por humanos e programas de computador. Foi originalmente criado para ser a única solução de comunicação pela Internet entre diferentes aplicativos.
<?xmlversion=”1.0”encoding=”utf-8” ?>
Como você pode ver, um documento XML consiste em vários elementos. Primeiro, há uma linha opcional que especifica que você está olhando para XML. Essa linha é chamada de prólogo. Diz algo sobre a codificação usada e pode conter outros metadados. Após o prólogo, vem o conteúdo. Deve haver um único elemento raiz que contém o restante das informações. Dessa forma, você cria uma árvore hierárquica que define o relacionamento entre todos os elementos.
Abaixo do elemento raiz, há outros elementos filhos. Um elemento filho pode ser um único elemento que descreve alguma característica ou vários elementos que agrupam outros elementos. Os elementos podem conter atributos, que são pares nome-valor associados a um elemento. Um documento XML também pode conter comentários e instruções especiais de processamento. As instruções especiais de processamento geralmente estão contidas em um arquivo DTD (Document Type Definition) que é armazenado externamente no XML e referenciado no prólogo.
XML no .NET Framework
Analisar um arquivo XML como se fosse um arquivo de texto comum é muito trabalhoso. O .NET Framework ajuda você, fornecendo classes que podem ser usadas para analisar, criar e editar arquivos XML - na memória e no disco. Essas classes são encontradas no namespace System.Xml. As classes importantes estão listadas na Tabela 4-3.
Name Descrição XmlReader Uma maneira rápida de ler um arquivo XML. Podesomente avançar através do arquivo e nada é armazenado em cache, por isso que é rápido e consome menos memória. XmlWriter Uma maneira rápida de criar um arquivo XML. Assim como no XmlReader, ele podesomente avançar e apenas e não armazenado em cache. XmlDocument Esta classe lê todo o XML na memória e permite navegar e editar o XML. XPathNavigator Ajuda na navegação por um documento XML para encontrar informações específicas.
XmlReader
A classe XmlReader oferece a opção mais rápida de trabalhar com dados XML. É uma classe base abstrata que é herdada por classes como XmlTextReader, XmlNodeReader e XmlValidatingReader. Você cria uma nova instância do XmlReader usando o método estático Create. Você pode transmitir a esse método uma instância de XmlReaderSettings para configurar como o XML deve ser analisado. Dessa forma, você pode optar por ignorar dados do seu arquivo XML, como espaço em branco e comentários, ou iniciar em uma posição específica. O exemplo de como usar um XmlReader para analisar uma sequência que contém os dados XML em um arquivo xml ou de uma string.
staticvoid Parsing_XmlReader(StringBuilder sb) { // This will get the current WORKING directory (i.e. \bin\Debug) string workingDirectory = Environment.CurrentDirectory; // or: Directory.GetCurrentDirectory() gives the same result
// This will get the current PROJECT directory string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName; string xmlString = System.IO.File.ReadAllText(projectDirectory + “\XMLFile1.xml”);
//Se for de um StringBuilder/StringWriter var xml = sb.ToString();
using (StringReader stringReader = new StringReader(xmlString)) { using (XmlReader xmlReader = XmlReader.Create(stringReader, new XmlReaderSettings() { IgnoreWhitespace = true })) { xmlReader.MoveToContent(); xmlReader.ReadStartElement(“people”); string firstName = xmlReader.GetAttribute(“firstName”); string lastName = xmlReader.GetAttribute(“lastName”); Console.WriteLine(“Person: {0}{1}”, firstName, lastName); xmlReader.ReadStartElement(“person”); Console.WriteLine(“ContactDetails”); xmlReader.ReadStartElement(“contactdetails”); string emailAddress = xmlReader.ReadString(); Console.WriteLine(“Email address: {0}”, emailAddress);
Console.WriteLine("=======xmlReader.Read()======="); while (xmlReader.Read())// read the entire xml
{
Console.WriteLine(xmlReader.Value);
}
}
} }
Como você pode ver, o XmlReader trata o XML como uma hierarquia de nós. Isso é mais fácil do que trabalhar com XML como se fosse um arquivo simples. Ao passar para nós, nós filhos e atributos, você pode analisar o documento para o conteúdo necessário.
XmlWriter
Quando você deseja criar um arquivo XML, pode usar a classe XmlWriter. Essa classe é criada usando o método estático Create e pode ser configurada usando uma instância da classe XmlWriterSettings. O código abaixo mostra como criar um novo arquivo XML. Como você pode ver, você tem métodos para escrever os elementos e atributos individuais. static StringBuilder CreatingXM_XmlWriter(StringBuilder sb) { using (StringWriter stream = new StringWriter(sb)) { using (XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings() { Indent = true })) { writer.WriteStartDocument(); writer.WriteStartElement(“people”); writer.WriteStartElement(“person”); writer.WriteAttributeString(“firstName”, “John”); writer.WriteAttributeString(“lastName”, “Doe”); writer.WriteStartElement(“contactdetails”); writer.WriteElementString(“EmailAddress”, “john@unknown.com”); writer.WriteEndElement(); writer.WriteEndElement(); writer.Flush(); } }
return sb; }
Assim como na classe XmlReader, a classe XmlWriter está ciente da estrutura hierárquica do XML. No entanto, você precisa certificar-se de escrever a tag inicial e a final de cada elemento. Você pode optar por adicionar atributos ou adicionar elementos que tenham um valor de sequência como conteúdo.
XmlDocument
Embora XmlReader e XmlWriter sejam as opções mais rápidas, elas definitivamente não são as mais fáceis de usar. Quando você trabalha com documentos relativamente pequenos e o desempenho não é tão importante, você pode usar a classe XmlDocument. Sua função principal permite editar arquivos XML e representa o XML de forma hierárquica na memória e permite navegar facilmente no documento e editar elementos no local. Depois de terminar de editar o documento, você pode salvá-lo de volta a um arquivo ou fluxo.
Um XmlDocument usa um conjunto de objetos XmlNode para representar os vários elementos que compõem o documento. XmlDocument herda de XmlNode e adiciona recursos específicos para carregar e salvar documentos. O XmlNode ajuda na leitura de conteúdo e atributos e fornece métodos para adicionar nós filhos, para que você possa estruturar facilmente seu documento. O código a seguir mostra um exemplo no qual os recursos de leitura e edição do XmlDocument são usados no XML.
XmlDocument doc = new XmlDocument();
// This will get the current WORKING directory (i.e. \bin\Debug) string workingDirectory = Environment.CurrentDirectory; // or: Directory.GetCurrentDirectory() gives the same result
// This will get the current PROJECT directory string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName; string xmlString = System.IO.File.ReadAllText(projectDirectory + “\XMLFile1.xml”);
Console.WriteLine(“=====sReader====”);
//to read xml string as a stream StringReader sReader = new StringReader(xmlString); doc.Load(sReader); foreach (XmlNode item in doc.DocumentElement) { Console.WriteLine(item.InnerText); }
doc.LoadXml(xmlString); XmlNodeList nodes = doc.GetElementsByTagName(“person”); // Output the names of the people in the document foreach (XmlNode node in nodes) { string firstName = node.Attributes[“firstName”].Value; string lastName = node.Attributes[“lastName”].Value; Console.WriteLine(“Name: {0}{1}”, firstName, lastName); } // Start creating a new node XmlNode newNode = doc.CreateNode(XmlNodeType.Element, “person”, “”); XmlAttribute firstNameAttribute = doc.CreateAttribute(“firstName”); firstNameAttribute.Value = “Foo”; XmlAttribute lastNameAttribute = doc.CreateAttribute(“lastName”); lastNameAttribute.Value = “Bar”; newNode.Attributes.Append(firstNameAttribute); newNode.Attributes.Append(lastNameAttribute); doc.DocumentElement.AppendChild(newNode); Console.WriteLine(“Modified xml…”); doc.Save(Console.Out);
XPathNavigator
Um recurso bacana para navegar por um documento é o XPath, um tipo de linguagem de consulta para documentos XML. XmlDocument implementa IXPathNavigable para que você possa recuperar um objeto XPathNavigator a partir dele. O XPathNavigator oferece uma maneira fácil de navegar por um documento XML. Você pode usar métodos semelhantes aos de um XmlDocument para mover de um nó para outro ou pode usar uma consulta XPath. Isso permite selecionar elementos ou atributos com determinados valores, semelhante à maneira como o SQL seleciona dados em um banco de dados. A Listagem 4-46 mostra um exemplo de como usar uma consulta XPath para selecionar uma People pelo nome.
XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); // Can be found in Listing 4-43 XPathNavigator nav = doc.CreateNavigator(); string query = “//people/person[@firstName=’Jane’]”;
XPathNodeIterator nodes_xpath = nav.Select(“/people/person”); nodes_xpath.MoveNext(); XPathNavigator nodesNavigator = nodes_xpath.Current; XPathNodeIterator nodesText = nodesNavigator.SelectDescendants(XPathNodeType.Text, false);
while (nodesText.MoveNext()) Console.WriteLine(nodesText.Current.Value);
XPathNodeIterator iterator = nav.Select(query); Console.WriteLine(iterator.Count); // Displays 1 while (iterator.MoveNext()) { string firstName = iterator.Current.GetAttribute(“firstName”,””); string lastName = iterator.Current.GetAttribute(“lastName”,””); Console.WriteLine(“Name: {0}{1}”, firstName, lastName); }
Consumindo JSON
Outro formato popular usado por muitos serviços da web é o JavaScript Object Notation (JSON). Esses tipos de formatos (XML e JSON) são usados pelos Serviços da Web ou APIs da Web. Embora o XML seja útil, é detalhado e possui muitas regras em relação à estrutura de um documento. JSON é o que chamamos de alternativa “sem gordura” ao XML. Tem uma gramática mais fácil e geralmente carrega significativamente menos peso. O exemplo abaixo mostra como o XML People dos exemplos anteriores pode ser representado usando JSON.
{ “People”: { “Person”: [ { “firstName”: “John”, “lastName”: “Doe”, “ContactDetails”: { “EmailAddress”: “john@unknown.com” } }, { “firstName”: “Jane”, “lastName”: “Doe”, “ContactDetails”: { “EmailAddress”: “jane@unknown.com”, “PhoneNumber”: “001122334455” } } ]} }
Ao trabalhar com XML, você usa classes como XmlWriter, XmlReader e XmlDocument; JSON não tem classes como elas. Normalmente, ao trabalhar com JSON, você usa uma biblioteca de serialização que ajuda a converter objetos em JSON e vice-versa. O .NET fornece uma classe JavaScriptSerializer para análise de dados JSON. Além disso, usamos a biblioteca Newtonsoft.Json para analisar dados JSON.
O JSON é usado com mais frequência em cenários assíncronos de JavaScript e XML (AJAX). Na realidade, o AJAX deve ser chamado de AJAJ porque o XML é trocado por JSON agora. AJAX é uma tecnologia que permite que um site execute chamadas em segundo plano para um servidor web. Essa é a técnica que permite que um site se atualize sem fazer uma atualização completa da página. JSON é particularmente útil para isso, porque todos os mecanismos JavaScript podem analisar uma cadeia JSON em um objeto por um comando relativamente simples.
USANDO WEB SERVICES
Outra opção além de armazenar dados em um banco de dados relacional ou em um arquivo é usar um serviço externo para armazenar dados. Pode ser um serviço da web que você criou ou pode ser de terceiros. Com esses serviços, você pode trocar dados entre aplicativos de uma maneira pouco acoplada. Você só precisa saber o endereço do serviço e como fazer uma solicitação para esse serviço. A implementação por trás da chamada está completamente oculta de seus consumidores, e você geralmente não precisa se preocupar com isso. O .NET Framework possui um suporte sólido para a criação de serviços da web. Você pode criar serviços da Web altamente flexíveis usando tecnologia .NET: • Serviço Web ASMX: Esse recurso está incluído no .NET Framework 3.5 e abaixo, é uma abordagem antiga para criar serviços da Web que usa a classe “Visually Designed Class for web service”. Mas a implementação continua válida até para Visual Studio 2017, .NET Framework 4.6.1. • WCF (Windows Communication Foundation): pode enviar dados como mensagens assíncronas de um terminal em serviço para outro. Um terminal em serviço pode fazer parte de um serviço continuamente disponível hospedado pelo IIS ou pode ser um serviço hospedado em um aplicativo.
SERVIÇO WEB ASMX
Há duas formas principais para criar um serviço da Web usando a “Visually Designed Class for web service”:
Criando o serviço da Web
As etapas a seguir mostram o caminho para criar o serviço da web:
Etapa 1: Abra o VS, crie um novo projeto “Web”»“ASP.NET Empty Web Application”. Dê o nome que desejar (neste exemplo, eu o chamei de Web_ASMX) e clique no botão “OK”. O Framework selecionado foi “.NET Framework 3.5”, mas testado com o Framework 4.6.1 sem nenhuma diferença.
Passo 2:Após a criação do projeto, clique com o botão direito do mouse em projeto » Adicionar » Novo Item. Selecione Serviço Web (ASMX) e nomeie o que quiser (neste exemplo, eu o nomeei SampleService) e clique no botão “Adicionar”.
Etapa 3:Depois de adicionar o SampleService, o SampleService.asmx será adicionado ao seu projeto, que é uma classe Visually Designed, ou seja, seu serviço da web. Nesta classeos métodos são implementados pelo “WebMethod”, você pode adicionar suas lógicas e métodos para expor para consumo esse serviço da Web no lado do cliente. Esses atributos são necessários para expor o serviço, caso contrário, se o atributo de serviço da Web estiver ausente, o serviço não será exposto ao cliente. Adicione outras duas funções no arquivo SampleService.asmx (na classe SampleService no método HelloWorld), como:
[WebMethod] publicint Add(int a, int b) { return a + b; } [WebMethod] publicint Subtract(int a, int b) { return a - b; }
Etapa 4: Para testar este serviço, clique com o botão direito do mouse em SampleService.asmx » Ver no navegador. A lista de métodos escritos em seu serviço está na sua frente. A visualização pode fornecer acesso para usá-lo como cliente (basicamente expõe os métodos do serviço da Web).
Passo 2:Clique em qualquer um para testar o método. (Clique em Adicionar.)
Etapa 3:Após executar o método, o resultado é exibido na forma de XML, conforme descrito, o formatos XML e JSON são usados para transportar dados pela rede e usados por um Serviço da Web ou APIs da Web.
Caso os dados forem inválidos, como for maior do que um Int32 ou uma string, ocorre uma exceção:
Para próxima etapa, lembre-se da Url utilizada:
Criar proxy e consumir o serviço
A criação do Proxy é importante, pois é registrada no aplicativo cliente e permite que os métodos dos serviços da Web sejam usados como métodos locais no lado do cliente.Para isso, você deve seguir as seguintes etapas:
Passo 1: Crie outro projeto (Projeto C# Console) em sua solução e nomeie o que quiser. Clique com o botão direito do mouse em Referências » Adicionar referência de serviço. Clique no botão “Discover”, pois ele buscará Web Serviceem sua solução. Você pode fornecer qualquer endereço externo para o Web Service que deseja consumir.
Você pode alterar o namespace da web descoberto. (Eu o chamei de MyService, que será usado posteriormente no código.)
Passo 2: Depois de clicar no botão “OK”, uma pasta de referências de serviço é adicionada com as DLLs necessárias. Agora, no seu arquivo de código (arquivo de programa), basta escrever o seguinte trecho de código:
//Create the proxy for your service to use its methods MyService.SampleServiceSoapClient proxy = new MyService.SampleServiceSoapClient();
int addResult = proxy.Add(5, 10); int subtractResult = proxy.Subtract(100, 40); Console.WriteLine(“Addition Result is: “ + addResult); // Addition Result is: 15 Console.WriteLine(“Subtraction Result is: “ + subtractResult); // Subtraction Result is: 60
MyService é o namespace que você adicionou ao adicionar SampleService neste projeto. Você pode criar um proxy com a classe escrita (acima no código).Quando houver uma alteração no Serviço, você só precisará atualizar sua referência adicionada a esse serviço. Isso é feito apenas expandindo a pasta “Referência de Serviço” no projeto do Cliente. Clique com o botão direito do mouse no seu serviço e clique em “Atualizar Referência de Serviço”.
A classe do seu serviço é SampleService mas, no momento da criação de um proxy, você deve escrever SoapClient (sufixo) com o nome da classe SampleServiceSoapClient. Se o nome da classe de serviço for o MyService, a classe de proxy será MyServiceSoapClient.
SERVIÇO WEB WCF
No passado, você tinha muitas opções ao criar um serviço. Você poderia usar o .NET Remoting, XML Web Services (incluindo serviços baseados em SOAP) ou o Microsoft Message Queues, todos com suas próprias opções e recursos. O Windows Communication Foundation (WCF) substitui todas essas tecnologias por um modelo de programação único, unificado e extensível, que engloba as tecnologias do passado.
O WCF é uma estrutura para a criação de aplicativos orientados a serviços, permite acessar um banco de dados pela Web ou uma intranet usando um URI. Usando o WCF, você pode enviar dados como mensagens assíncronas de um terminal em serviço para outro. Um terminal em serviço pode fazer parte de um serviço continuamente disponível hospedado pelo IIS ou pode ser um serviço hospedado em um aplicativo.No serviço da Web WCF, a classe de serviço é implementada pelo atributo ServiceContract (como o serviço da Web no ASMX) e seus métodos são implementados pelo OperationContract (como o método da Web no ASMX). Serviço WCF Serviço ASMX O serviço WCF pode ser hospedado no IIS, WAS, Console, Host fornecido pelo WCF O serviço ASMX pode apenas ser hospedado no IIS. Ele suporta vários protocolos de comunicação, ou seja, HTTP, TCP, MSMQ e NamedPipes. Ele suporta apenas HTTP. Ele usa DataContractSerializer. Ele usa XmlSerializer
Nas versões anteriores do .NET, isso era chamado ADO.NET Data Services. Você pode selecionar, filtrar, adicionar, atualizar e até excluir dados usando um URI e parâmetros de string de consulta. O WCF Data Services usa o Open Data Protocol, OData, que é um protocolo da web que usa HTTP. Por exemplo, a solicitação a seguir pode ser feita para um Serviço de Dados WCF que expõe a tabela Categorias do banco de dados Northwinds.
http://localhost/WcfDataService1.svc/Categories? $ filter = CategoryName eq ‘Beverages’
Neste exemplo, Categorias especifica a entidade a ser retornada, e o parâmetro filter (eq) na string de consulta é usado para encontrar a categoria com o nome ‘Beverages’. Os espaços são permitidos na string de consulta. Você pode optar por retornar os dados como XML, caso em que segue o formato OData ATOM (a representação XML dos dados retornados de uma consulta OData) ou a JavaScript Object Notation, JSON (um formato leve de intercâmbio de dados). Por padrão, os dados são retornados como XML. O XML a seguir mostra a resposta para a chamada anterior ao Serviço de Dados do WCF:
O WCF é superior às tecnologias anteriores, pois a comunicação com um serviço WCF ocorre através de endpoints do serviço,não contendo nenhuma informação sobre como o serviço pode ser chamado - não especifica um endereço ou protocolo. Os endpoints fornecem aos clientes o acesso às funcionalidades oferecidas por um serviço WCF. O EndPoint do WCF é chamado também de modelo ABC: • Address: define onde o serviço reside • Binding: define como se comunicar com o serviço. • Contract: define o que o serviço vai fazer
Endpoint = Address + Binding + Contract
Ao criar um serviço WCF, você geralmente começa com o contrato, que define quais operações seu serviço expõe. O contrato é o que o mundo exterior espera do seu serviço. Depois de especificar seu contrato, você especifica os bindings. Um bindingconfigura os protocolos e transportes que podem ser usados para chamar seu serviço. Talvez seu serviço possa ser usado por HTTP, HTTPS e uma conexão de pipe nomeado. Em seguida, você precisa especificar o endereço, que é o ponto de extremidade que seu serviço expõe. Isso garante que haja um endereço de rede físico que possa ser usado para chamar seu serviço com um bindingespecífico.
Tipos de contratosWCF
A seguir temos um resumo dos 3 tipos de contratos WCF existentes:
Service Contracts
Contratos de serviços (Service Contracts) - Descrevem as operações que um serviço pode realizar. (Mapeia os tipos CLR para WSDL).
Data Contracts
Contratos de Dados (Data Contracts) - Descreve a estrutura de dados usada no serviço. (Mapeia tipos CLR para XSD). Um Data Contract é um acordo formal entre um serviço e um cliente que descreve os dados que serão trocados entre ambos. Para estabelecer a comunicação, o cliente e o serviço não necessitam trocar necessariamente os mesmos tipos de dados, devem trocar apenas os mesmos data contracts.
Um Data Contract especifica para cada parâmetro ou tipo de retorno qual informação será serializada (convertida em XML) para ser trocada. Os DataContracts são definidos através de classes e uma classe DataContract deverá ser decorada com o atributo DataContract e os campos e propriedades que o tipo possui devem ser decorados com o atributo DataMember. (A partir do service pack versão 3.5 da plataforma .NET isso não é mais obrigatório)
Message Contracts
Contratos de Mensagens (Message Contracts) - Define a estrutura de mensagens usadas no serviço. (Mapeia os tipos CLR para mensagens SOAP). Os contratos de mensagens permitem ao serviço interagir diretamente com mensagens e podem ser tipados ou não tipados. Eles são úteis em casos de interoperabilidade e quanto existe um formato de mensagem com o qual devemos dar conformidade e/ou customizar.
Por meio dos Contratos de Mensagens podemos ter um controle maior sobre a estrutura de mensagem SOAP usada pelo WCF efetuando uma personalização dos componentes da mensagem. Os Contratos de Mensagens são definidos em classes e as mesmas devem ser decoradas com o atributo MessageContract. Além disso uma classe definida com um contrato de Mensagem deve possuir um construtor sem parâmetros. Nas classes para especificar se um campo fará parte do header ou do body da mensagem devemos usar os atributos MessageHeader ou MessageBodyMember.
Aplicativo cliente que consome o serviço WCF
A seguir iremos criar um aplicativo cliente que consome o serviço WCF. Isso é semelhante à seção que descreveu como criar um modelo do ADO.NET Entity Framework e mostrou como selecionar, adicionar, atualizar e excluir registros. Esta seção cria um aplicativo de console que faz referência a um serviço WCF na mesma solução e executa todas as operações CRUD nos dados.
Criar um Serviço de Dados WCF envolve a criação de um aplicativo Web, a criação de um modelo ADO.NET Entity Framework para o banco de dados e, em seguida, a exposição do modelo, adicionando um arquivo WCF Data Service ao seu aplicativo Web. Para implementar um serviço WCF iniciamos pela definição do contrato para o serviço e então implementamos o contrato em um tipo de serviço. Um contrato para um serviço geralmente envolve a aplicação do atributo ServiceContract para uma interface e então a aplicação do atributo OperationContract para cada método exposto como parte do contrato do serviço. A seguir veja as regras para a definição de um contrato: • O contrato deve ser uma interface contendo as assinaturas dos métodos do serviço; • A interface deverá ser decorada com o atributo: ServiceContract de outra forma será lançada a exceção InvalidOperationException; • Todas as operações que serão disponibilizadas devem ser decoradas com o atributo OperationContract; • É obrigatória a definição de pelo menos uma operação com o atributo OperationContract;
Passo 1: Selecione Aplicativo da Web vazio do ASP.NET na lista de modelos C# instalados.
Passo 2: (Opcional) Especifique um número de porta específica para seu aplicativo Web. No Gerenciador de soluções, clique com o botão direito do mouse no projeto ASP.NET que você acabou de criar e escolha Propriedades. Selecione a guia Web e defina o valor da caixa de texto porta específica como 12345.
Passo 3: Clique com o botão direito do mouse no projeto no Gerenciador de Soluções, clique em Adicionar e selecione Novo Item no menu pop-up. Escolha WCF Data Service na lista de modelos da Web C# instalados. Caso não encontrar os templates WCF, você poderá adiciona-los através do instalador do Visual Studio na parte de intalação de componentes individuais. Nomeie o arquivo SchoolService.svc e clique no botão Adicionar.
Depois de criar o SchoolService, você pode ver dois arquivos criados, • ISchoolService.cs: interface para declarar seus contratos de serviço e contratos de dados. • SchoolService.scv.cs: é uma classe normal herdada pelo ISchoolService onde é possível definir todos os métodos e outros processos.
O SchoolService.svc contém instruções sobre como hospedar seu serviço no Internet Information Services (IIS), para que você possa colocar o arquivo.svc com o arquivo de código associado em um site hospedado pelo IIS para disponibilizá-lo aos usuários. O arquivo de código SchoolService.cs não contém nenhuma informação sobre como o serviço pode ser chamado - não especifica um endereço ou protocolo.
Passo 4: Você pode ver os dois arquivos tendo um tipo de coisa DoWork(). Você pode comentar ou remover ou deixar como está. Implementaremos um serviço da web Hello World. Altere DoWork() para retornar uma string “Hello World” na interface ISchoolService.cs:
[ServiceContract] publicinterfaceISchoolService { [OperationContract] string DoWork(); }
E na classe SchoolService.cs:
publicclassSchoolService : ISchoolService { publicstring DoWork() { return”Hell World”; } }
Passo 5: No Visual Studio, pressione a tecla F5 para iniciar a depuração do aplicativo. Se iniciar a depuração no arquivo SchoolService.scv.cs, o Visual Studio abre uma interface WCF Test Client de testes do serviço WFC.
Inclusive é possível colocar breakpoints nos métodos e acompanhar passo-a-passo a execução dos mesmos.
Passo 6: Abra um navegador da Web no computador local. Na barra de endereços, digite a seguinte URL: http://localhost:52042/SchoolService.svc
Isso retorna o documento padrão de serviço, que contém uma lista de conjuntos de entidades que são expostos por esse serviço de dados.
Passo 7: Para chamar métodos WCF a partir de um navegador, você precisa fazer duas coisas: • Use os atributos [WebGet] e [WebInvoke] em seus métodos. Caso não colocar é emitido a seguinte mensagem:
• Configure o Web.config para usar um webHttpBinding para o terminal do seu serviço e ative o comportamento do webHttp.
<?xmlversion=”1.0”encoding=”utf-8”?>