Records e Structs em C#: diferenças, objetivos e cenários de uso

O C# oferece uma variedade de tipos de dados, entre eles as classes, structs e records. Cada um desses tipos tem um comportamento distinto, com objetivos específicos e cenários ideais de uso. Neste artigo, exploraremos as diferenças entre eles, como usá-los corretamente e como escolher o tipo adequado para diferentes situações. Diferença entre tipos por valor e por referência No C#, a diferença entre tipos por valor e tipos por referência está na forma como eles são armazenados e manipulados na memória. Tipos por valor Os tipos por valor, como structs, são armazenados diretamente na stack. Quando você copia um tipo por valor, uma nova instância é criada, e alterações na cópia não afetam o original. Isso torna os tipos por valor eficientes em termos de memória e desempenho, principalmente para dados pequenos e temporários. Para entender melhor como a stack impacta o desempenho e a alocação de memória, consulte o artigo sobre memória stack e heap. Tipos por referência Os tipos por referência, como classes e records, armazenam uma referência para os dados na heap. Ao copiar uma variável de tipo por referência, estamos copiando apenas o endereço onde os dados estão armazenados, não os dados em si. Isso significa que alterações feitas em uma cópia afetam o objeto original. A alocação na heap é mais flexível, mas também mais lenta, sendo gerenciada pelo garbage collector. Struct em C# Uma struct é um tipo por valor que armazena seus dados diretamente na memória stack. Os Structs são ideais para representar pequenas unidades de dados, como coordenadas, pontos em um gráfico ou valores simples que não precisam de herança. Vantagens das Structs Desempenho: como são salvos na stack a sua alocação e desalocação é mais rápida em comparação com objetos alocados na memória heap. Imutabilidade: Structs geralmente são imutáveis, o que as torna seguras para uso em ambientes concorrentes. Ideal para pequenos valores: como são mais eficientes para armazenar pequenos valores, structs são úteis quando o desempenho e a economia de memória são importantes. Limitações das Structs Imutabilidade: uma vez que structs são imutáveis por design, elas não oferecem suporte a herança, o que limita sua flexibilidade. Tamanho ideal: Structs devem ser usados para armazenar dados pequenos e simples, como números ou pares de valores. Usá-las para armazenar grandes quantidades de dados pode prejudicar a performance. Podemos criar uma struct da seguinte forma: public struct Ponto { public int X { get; } public int Y { get; } public Ponto(int x, int y) { X = x; Y = y; } } Record Introduzidos no C# 9, os records são tipos imutáveis e focados em dados. Eles são perfeitos para representar informações que não mudam, como objetos que precisam ser transferidos entre diferentes partes de um sistema, como DTOs (Data Transfer Objects). Diferente das classes, os records têm recursos que facilitam o trabalho com dados, como comparação por valor, imutabilidade e a possibilidade de criar deconstructors. Características dos Records Imutabilidade: Por padrão, records são imutáveis. Isso significa que seus dados não podem ser modificados após a criação. Comparação por valor: Ao contrário das classes, que comparam objetos por referência, os records comparam suas propriedades por valor. Facilidade de transferência de dados: Eles são perfeitos para serem usados como objetos que são transferidos entre sistemas, como APIs REST. Comparação por valor em Records Uma das principais características dos records é a comparação por valor. Isso significa que, quando você compara duas instâncias de um record, o C# compara os valores de suas propriedades, e não a referência do objeto na memória. Isso é diferente das classes, que por padrão, fazem comparação por referência, ou seja, elas só são iguais se apontarem para o mesmo objeto na memória. public record Produto(int Id, string Nome); var produto1 = new Produto(1, “Televisão”); var produto2 = new Produto(1, “Televisão”); var produto3 = new Produto(2, “Geladeira”); Console.WriteLine(produto1 == produto2); // True, pois os valores são iguais Console.WriteLine(produto1 == produto3); // False, pois os valores são diferentes O C# compara os valores das propriedades de “produto1” e “produto2” (não a referência de memória). Como ambos têm “Id” igual a “1” e “Nome” igual a “Televisão”, a comparação retorna true. Já para “produto3”, os valores são diferentes, então a comparação retorna false. Esse comportamento é possível porque os records em C# são comparados por valor e não por referência, o que os torna diferentes das classes, que por padrão comparam objetos por referência (ou seja, se apontam para o mesmo local na memória). Criando um Deconstructor com Record Os records em C# também possuem suporte para deconstructors. Isso permite que você extraia as propriedades de um record de forma prática, facilitando o trabalho com os dados armazenados. Quando você define um record, o C# já cria automaticamente um deconstructor que permite “desestruturar” o objeto e acessar suas propriedades. O deconstructor é útil quando você precisa acessar os valores das propriedades diretamente. public record Produto(int Id, string Nome); var produto = new Produto(1, “Produto A”); var (id, nome) = produto; Console.WriteLine($”Id: {id}, Nome: {nome}”); No exemplo acima, o deconstructor do record “Produto” permite que você extraia facilmente os valores de “Id” e “Nome” diretamente para as variáveis “id” e “nome”. Criando cópias de Records com a expressão with A expressão “with” permite criar uma cópia de um record com base em um objeto existente, permitindo modificar algumas de suas propriedades enquanto mantém as demais inalteradas. public record Produto(int Id, string Nome, decimal Preco); var produtoOriginal = new Produto(1, “Televisão”, 2000.00m); var produtoComDesconto = produtoOriginal with { Preco = 1800.00m }; Console.WriteLine(produtoOriginal); Console.WriteLine(produtoComDesconto); No código acima o “produtoOriginal” é copiado para “produtoComDesconto”, mas a propriedade “Preco” é alterada. O produto original permanece imutável, garantindo a segurança de dados. A expressão “with” facilita a criação de novos objetos com base em instâncias existentes, mantendo a imutabilidade do record. Comparação: Class vs Struct vs Record A seguir, uma tabela comparativa entre classes, structs e records: Característica Class Struct Record Tipo Por referência Por valor Por referência Imutabilidade Mutável Geralmente
Bogus: Gerando dados fake para testes em C#

Em muitos cenários de desenvolvimento, seja para testes, prototipagem ou simulação, a geração de dados falsos (fake data) é essencial para garantir que o sistema funcione corretamente em condições controladas. No C#, uma das bibliotecas mais populares para gerar esses dados é o Bogus. Este artigo vai abordar como utilizar o Bogus para gerar dados falsos em suas aplicações de forma simples e eficaz. O que é o Bogus? O Bogus é uma biblioteca de geração de dados falsos para C# que facilita a criação de dados realistas para testes, prototipagem e preenchimento de bases de dados. Ela permite gerar uma ampla gama de informações, como nomes, endereços, e-mails, números de telefone e muito mais, de maneira flexível e altamente configurável. Vantagens Ao contrário de outras abordagens, como escrever manualmente os dados de teste ou usar dados genéricos, o Bogus permite criar dados mais realistas e que atendem às necessidades específicas do seu projeto. Isso é especialmente útil quando se precisa simular cenários complexos em sistemas de testes ou protótipos. Fácil de usar: a API é simples e intuitiva. Configuração flexível: permite gerar dados customizados conforme as necessidades. Desempenho: geração eficiente de grandes volumes de dados. Suporte a múltiplos tipos de dados: nomes, endereços, e-mails, datas, números e até mesmo dados customizados com regras. Instalação no projeto A instalação do Bogus é simples e pode ser feita via console NuGet ou pelo .NET CLI: Via Console do NuGet Install-Package Bogus Via .NET CLI: dotnet add package Bogus Com o pacote instalado, você pode começar a gerar dados falsos imediatamente! Gerando dados básicos O Bogus fornece uma maneira fácil de gerar dados simples. A seguir, veremos como criar uma instância do “Faker<T>” para gerar dados como nomes, e-mails e endereços. using Bogus; public class Program { public static void Main() { var faker = new Faker(); string nome = faker.Name.FullName(); string email = faker.Internet.Email(); string endereco = faker.Address.FullAddress(); Console.WriteLine($”Nome: {nome}”); Console.WriteLine($”E-mail: {email}”); Console.WriteLine($”Endereço: {endereco}”); } } No código acima, nós criamos 3 tipos de dados fake: O Faker.Name.FullName() que gera um nome completo (primeiro e último nome). O Faker.Internet.Email() que gera um e-mail aleatório. E o Faker.Address.FullAddress() que gera um endereço completo com rua, cidade e CEP. Esses métodos são apenas exemplos simples, mas o Bogus oferece uma variedade de opções para gerar diferentes tipos de dados, como números de cartão de crédito, CNPJs, documentos, e até mesmo informações financeiras, como valores de compras e transações. Gerando dados com uma cultura específica Caso você deseje gerar dados em um idioma diferente, é possível especificar o idioma durante a criação do objeto “Faker” utilizando o parâmetro de cultura. Por exemplo, para gerar dados em português do Brasil, basta usar “new Faker(“pt_BR“)”. Assim, os dados serão gerados conforme as convenções culturais e linguísticas do idioma especificado. Gerando múltiplos dados O Bogus também oferece uma maneira mais compacta e eficiente de gerar múltiplos dados de uma vez utilizando o método “Generate(int quantidade)”. Esse método permite gerar uma lista de objetos em uma única linha de código, sem a necessidade de um loop manual. var faker = new Faker() .RuleFor(p => p.Nome, f => f.Commerce.ProductName()) .RuleFor(p => p.Preco, f => f.Commerce.Price(10, 100)); var produtos = faker.Generate(10); O código utiliza o método “Generate(10)” do Bogus para gerar 10 objetos ”Produto”. Ele define regras para as propriedades “Nome” e “Preco” com “RuleFor()”, gerando um nome de produto e preço aleatórios. O método Generate(10) cria e armazena esses 10 objetos em uma lista. Criando dados complexos com classes customizadas Uma das grandes vantagens do Bogus é a capacidade de gerar dados para classes personalizadas, com várias propriedades. Isso é especialmente útil quando se trabalha com entidades mais complexas, como clientes, pedidos, produtos, etc. Vamos ilustrar isso criando um objeto “Cliente”, que possui várias propriedades e um “Endereco” como uma propriedade aninhada: public class Cliente { public string Nome { get; set; } public string Email { get; set; } public Endereco Endereco { get; set; } public DateTime DataNascimento { get; set; } public string Telefone { get; set; } } public class Endereco { public string Rua { get; set; } public string Cidade { get; set; } public string Estado { get; set; } } Agora, vamos usar o Bogus para gerar instâncias dessas classes de forma automática e realista. var faker = new Faker() .RuleFor(c => c.Nome, f => f.Name.FullName()) .RuleFor(c => c.Email, f => f.Internet.Email()) .RuleFor(c => c.Endereco, f => new Endereco { Rua = f.Address.StreetAddress(), Cidade = f.Address.City(), Estado = f.Address.State() }); Cliente cliente = faker.Generate(); Console.WriteLine($”Nome: {cliente.Nome}”); Console.WriteLine($”E-mail: {cliente.Email}”); Console.WriteLine($”Endereço: {cliente.Endereco.Rua}, {cliente.Endereco.Cidade}, {cliente.Endereco.Estado}”); No código acima, estamos criando uma instância de “Faker<Cliente>”, o que indica que queremos gerar um objeto do tipo “Cliente”. O Bogus irá gerar dados para essa classe com base nas regras que definimos a seguir. A função “RuleFor” é utilizada para definir regras de geração de dados para cada propriedade do objeto “Cliente”. O primeiro parâmetro é a propriedade do objeto e o segundo é uma função que descreve como gerar os dados dessa propriedade. Por fim, o método “Generate()” cria uma instância do objeto “Cliente” com os dados gerados aleatoriamente conforme as regras que definimos e apresentamos essas informações na tela. Usando regras específicas Além de gerar dados simples e complexos, o Bogus permite a configuração de regras específicas para criar dados mais realistas. Você pode, por exemplo, gerar números dentro de um intervalo ou criar dados baseados em enumerações personalizadas. var faker = new Faker() .RuleFor(x => x.DataNascimento, f => f.Date.Past(30, DateTime.Now.AddYears(-18))) .RuleFor(x => x.Telefone, f => f.Phone.PhoneNumber(“(0##) 9####-####”)); var cliente = faker.Generate(); Console.WriteLine($”Data de Nascimento: {cliente.DataNascimento}”); Console.WriteLine($”Número de Telefone: {cliente.Telefone}”); No código acima nós criamos duas regras específicas. A primeira gera uma data de nascimento aleatória com até 30 anos de idade e garante que o cliente tenha mais de 18 anos. Já a segunda, gera um número de telefone no formato especificado. Gerando CPF e CNPJ O Bogus oferece suporte para gerar CPF e CNPJ válidos de maneira simples, através de uma extensão
Analisando PDFs com a OpenAI

A API da OpenAI possui recursos de visão que permitem que você envie arquivos, como PDFs, para processamento. Com modelos de IA, você pode extrair informações de documentos complexos, como resumos, respostas a perguntas específicas ou até mesmo análise de conteúdo. Neste artigo, vamos explorar como integrar arquivos PDF com a API da OpenAI de forma prática, utilizando C#. Capacidade de visão dos modelos A OpenAI oferece modelos que suportam a entrada de arquivos, como imagens e PDFs. Esses modelos aproveitam a tecnologia de visão computacional para processar e analisar documentos e imagens de maneira inteligente. Quando você fornece um PDF como entrada, o modelo pode realizar várias tarefas, como: Extração de conteúdo: o modelo pode ler e entender o texto do PDF, identificando parágrafos, seções e outros elementos. Resumos: o modelo pode resumir o conteúdo do documento, oferecendo uma visão geral ou uma versão condensada. Resposta a perguntas: após analisar o documento, o modelo pode responder a perguntas com base no conteúdo fornecido. Análise de estrutura: em documentos mais complexos, o modelo pode identificar a estrutura e organizar as informações de maneira eficiente. Essas funcionalidades são ideais para trabalhar com documentos como contratos, relatórios financeiros, apostilas e muito mais. Implementação O primeiro passo é configurar as variáveis que serão usadas ao longo do código. Vamos criar as variáveis para o caminho do arquivo, a chave da API da OpenAI e as URLs para fazer as requisições. Também vamos criar uma instância do HttpClient para as requisições. using System.Text; using System.Text.Json; using System.Net.Http.Headers; namespace ChatGPTConsoleApp { public class Program { private static readonly HttpClient client = new HttpClient(); private static string filePath = @”D:Arquivo_Analise.pdf”; private static string apiKey = “SUA_CHAVE_API”; private static string urlFiles = “https://api.openai.com/v1/files”; private static string urlBase = “https://api.openai.com/v1/responses”; Agora, dentro do método “Main”, vamos implementar os métodos responsáveis por fazer o upload do arquivo PDF para a OpenAI e enviar uma pergunta ao modelo do GPT sobre o conteúdo do arquivo que foi carregado, utilizando o “idArquivo” retornado na primeira etapa. public static async Task Main(string[] args) { string idArquivo = await UploadPdf(); string resposta = await PerguntarSobreOArquivo(“Escreva um resumo do arquivo”, idArquivo); Console.WriteLine(resposta); } O fluxo do código será o seguinte: primeiro, o método “UploadPdf” envia o arquivo PDF, que está localizado no caminho armazenado na variável “filePath”, e retorna o “idArquivo”. Em seguida, o método “PerguntarSobreOArquivo” utiliza esse “idArquivo” para enviar uma pergunta ao modelo sobre o conteúdo do arquivo. Após receber a resposta do modelo, ela será exibida na tela para o usuário. Agora vamos implementar o primeiro método, “UploadPdf”: public static async Task UploadPdf() { var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath)); fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(“application/pdf”); O método “File.ReadAllBytes(filePath)” lê o arquivo PDF no caminho especificado e o armazena como um array de bytes. Isso é necessário para enviar o conteúdo binário do arquivo em uma requisição HTTP. A classe “ByteArrayContent” permite que você envie dados binários (como o conteúdo de um arquivo) através de uma requisição HTTP. O cabeçalho “Content-Type” é configurado para “application/pdf”, que indica que o conteúdo sendo enviado é um arquivo PDF. using (var formData = new MultipartFormDataContent()) { formData.Add(fileContent, “file”, Path.GetFileName(filePath)); formData.Add(new StringContent(“user_data”), “purpose”); A classe “MultipartFormDataContent” é usada para enviar arquivos e dados em um formato multipart (como o tipo de conteúdo “multipart/form-data”), que é o padrão para uploads de arquivos na web. Após isso, o “formData.Add(fileContent, “file”, Path.GetFileName(filePath))” está adicionando o arquivo à requisição. O nome do campo será “file”, e o nome do arquivo será extraído usando “Path.GetFileName(filePath)”, que retorna apenas o nome do arquivo (sem o caminho). Em seguida o “formData.Add(new StringContent(“user_data”), “purpose”)” adiciona um campo de texto chamado “purpose” com o valor “user_data”, informando à OpenAI que o arquivo está sendo enviado com a finalidade de ser utilizado como dados do usuário. Agora que o arquivo foi configurado, vamos montar o cabeçalho e realizar o envio: client.DefaultRequestHeaders.Add(“Authorization”, $”Bearer {apiKey}”); var response = await client.PostAsync(urlFiles, formData); O cabeçalho de autorização é configurado com o tipo “Bearer”, que é usado para autenticação em APIs. O valor do token “({apiKey})” será substituído pela chave da API que você tem para autenticação na OpenAI. Após isso, enviamos um POST para o endpoint da API da OpenAI. Após processar, receberemos uma resposta: if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync(); using (JsonDocument doc = JsonDocument.Parse(responseContent)) { var fileId = doc.RootElement .GetProperty(“id”) .GetString(); return fileId; } } Vamos validar se o status da requisição foi de sucesso. Depois, lê o conteúdo da resposta da API como uma string. O conteúdo é esperado como um JSON. Com isso, é possível recuperar o campo “id” do json que é o que vamos usar para realizar as perguntas. else { Console.WriteLine(“Erro ao carregar o arquivo: ” + response.ReasonPhrase); return null; } } Se a resposta não for bem-sucedida, a propriedade “ReasonPhrase” contém uma descrição do erro, que é exibida no console. Agora vamos implementar o método “PerguntarSobreOArquivo”. Esse método recebe dois parâmetros, o “prompt” que contém a nossa pergunta e o “fileId” que contém o id do documento que enviamos: public static async Task PerguntarSobreOArquivo(string prompt, string fileId) { var json = JsonSerializer.Serialize(new { model = “gpt-4.1”, input = new[] { new { role = “user”, content = new List { new { type = “input_file”, file_id = fileId }, new { type = “input_text”, text = prompt } } } } }); Primeiro, criamos uma variável “json” que irá armazenar o conteúdo da nossa requisição em formato JSON. Utilizamos o “JsonSerializer” para serializar um objeto anônimo que contém todas as informações necessárias. Esse método inicia informando qual o modelo que será utilizado para a consulta. No caso, estamos usando o modelo “gpt-4.1”. Em seguida, repassamos no “content” um array que contém dois objetos, um “input_file” que recebe o id do arquivo e o “input_text” que recebe a nossa pergunta. Agora vamos criar o corpo da nossa requisição: var requestBody = new StringContent(json, Encoding.UTF8, “application/json”); O JSON gerado na etapa anterior é convertido em uma string e empacotado em um objeto “StringContent”.
Versionamento de APIs em ASP.NET Core

Em sistemas que utilizam APIs RESTful, uma das maiores preocupações é garantir que a evolução do software não quebre a compatibilidade com clientes que já estão em produção. Quando alteramos uma API (adicionando novas funcionalidades, modificando comportamentos ou corrigindo bugs), a introdução de mudanças pode afetar os clientes que ainda utilizam versões anteriores. Por isso, versionar uma API é fundamental para garantir a estabilidade e a continuidade dos serviços. Neste artigo, vamos explorar como implementar o versionamento de APIs no ASP.NET Core, demonstrando as diferentes abordagens para garantir a compatibilidade entre versões e a evolução controlada da API. Por que versionar uma API? O versionamento de uma API é uma prática essencial para o desenvolvimento de software escalável e sustentável. Ele oferece várias vantagens: Compatibilidade com clientes existentes: com o versionamento, você pode garantir que os clientes que já estão utilizando uma versão anterior da API não serão impactados por mudanças incompatíveis. Evolução controlada: à medida que novos recursos são adicionados ou melhorias são feitas na API, o versionamento permite que novas funcionalidades sejam introduzidas sem quebrar as versões anteriores. Estabilidade: o versionamento assegura que as versões antigas da API permaneçam estáveis e funcionais, enquanto novas versões podem ser experimentadas e ajustadas sem causar interrupções. Documentação e manutenção: as versões ajudam a documentar as mudanças feitas ao longo do tempo, facilitando a manutenção e a integração com novos desenvolvedores ou equipes. Formas de versionar APIs ASP.NET Core Existem várias maneiras de versionar uma API no ASP.NET Core. A escolha do método adequado depende do contexto e das necessidades do seu projeto. Abaixo, exploraremos as principais abordagens: Implementando o versionamento Para implementar o controle de versão de API, vamos precisar do seguinte pacote: .NET CLI dotnet add package Asp.Versioning.Mvc.ApiExplorer Package Manager Install-Package Asp.Versioning.Mvc.ApiExplorer Esse pacote oferece as funcionalidades necessárias para o versionamento de API, permitindo que você documente suas versões. Agora, vamos criar dois controladores: um para a versão 1.0 e outro para a versão 2.0 da nossa API. Esses controladores terão a mesma rota, mas com versões diferentes. using Asp.Versioning; using Microsoft.AspNetCore.Mvc; namespace APITest.Controllers.v1; [ApiController] [Route(“api/v{version:apiVersion}/products”)] [ApiVersion(“1.0”)] public class ProdutosV1Controller : ControllerBase { [HttpGet] public IActionResult GetProdutosV1() { return Ok(new { Message = “Versão 1.0 – Produtos” }); } } Aqui, usamos a anotação “[ApiVersion(“1.0”)]” para definir que este controlador é da versão “1.0”. A rota da API inclui a versão da API, o que permite que a versão seja especificada diretamente na URL. Em seguida, vamos criar o controlador para a versão “2.0” da nossa API. O código será muito semelhante ao do controlador da versão “1.0”, mas com algumas melhorias ou novos recursos. using Asp.Versioning; using Microsoft.AspNetCore.Mvc; namespace APITest.Controllers.v2; [ApiController] [Route(“api/v{version:apiVersion}/products”)] [ApiVersion(“2.0”)] public class ProductsV2Controller : ControllerBase { [HttpGet] public IActionResult GetProductsV2() { return Ok(new { Message = “Versão 2.0 – Produtos com novas funcionalidades” }); } } Você pode separá-las em pastas para organizar melhor: Agora que temos os controladores prontos, precisamos configurar o nosso “Program.cs” builder.Services.AddEndpointsApiExplorer(); builder.Services.AddApiVersioning() .AddMvc() .AddApiExplorer(setup => { setup.GroupNameFormat = “‘v’VVV”; setup.SubstituteApiVersionInUrl = true; }); No código acima, nós adicionamos o serviço API Explorer à coleção de serviços da aplicação. Ele permite que a documentação da API seja gerada dinamicamente, incluindo detalhes sobre os métodos HTTP (GET, POST, etc.), os parâmetros e os tipos de resposta. Em seguida, adicionamos o “AddApiVersioning” que adiciona o serviço de versionamento de API à coleção de serviços. Ele vai nos permitir versionar a nossa API. Também adicionamos o “AddMvc” que adiciona o suporte ao MVC (Model-View-Controller) para a aplicação. Embora o nome sugira que ele seja usado para aplicações com interface de usuário, em um contexto de API, ele basicamente habilita a funcionalidade de controller e manipulação de dados, como roteamento de requisições HTTP para métodos específicos em seus controladores. Por fim, o “AddApiExplorer” configura a exploração da API para incluir mais detalhes sobre as versões e os endpoints. O “GroupNameFormat ” define como o nome do grupo de versões da API será formatado. No caso, “’v’VVV” significa que os nomes dos grupos serão algo como v1, v2, v3, etc. Ou seja, ele vai gerar uma versão formatada com “v” seguido pela versão numérica da API. O “SubstituteApiVersionInUrl” garante que o número da versão da API será substituído na URL. Ao configurar isso para true, ele permite que as rotas da API incluam a versão diretamente na URL. Agora colocando a aplicação para executar e acessando os endpoints de cada versão, teremos respostas diferentes. Dessa forma, podemos acessar os endpoints diretamente por meio de suas URLs, com a versão da API incluída na rota. No entanto, se estivermos utilizando o Swagger, não será possível selecionar qual versão queremos utilizar, pois ele não foi configurado para lidar com versões de API. Para permitir a seleção da versão no Swagger, é necessário configurá-lo adequadamente. Siga os passos abaixo para realizar essa configuração. Primeiro, vamos criar uma nova pasta em nosso projeto chamada “SwaggerConfig” (ou qualquer nome que preferir) e, dentro dessa pasta, vamos adicionar um novo arquivo de configuração do Swagger. Vamos chamá-lo de “ConfigureSwaggerGenOptions”. Dentro deste arquivo, vamos criar uma classe chamada “ConfigureSwaggerGenOptions”, que irá implementar a interface “IConfigureOptions<SwaggerGenOptions>”. Essa interface é responsável por configurar as opções do Swagger, permitindo personalizar como a documentação da API será gerada. public class ConfigureSwaggerGenOptions : IConfigureOptions { private readonly IApiVersionDescriptionProvider _apiVersionDescriptionProvider; public ConfigureSwaggerGenOptions(IApiVersionDescriptionProvider apiVersionDescriptionProvider) { _apiVersionDescriptionProvider = apiVersionDescriptionProvider; } No código acima estamos injetando a interface “IApiVersionDescriptionProvider” que fornece informações sobre as versões da API disponíveis. O Swagger usa essas informações para gerar a documentação para cada versão. public void Configure(SwaggerGenOptions options) { foreach (var description in _apiVersionDescriptionProvider.ApiVersionDescriptions) { options.SwaggerDoc(description.GroupName, CreateOpenApiInfo(description)); } } Em seguida, implementamos a classe “Configure” da interface que para cada versão da API fornecida pelo “IApiVersionDescriptionProvider”, estamos chamando o método “SwaggerDoc” do Swagger, que adiciona uma documentação específica para cada versão. O método “CreateOpenApiInfo” (que será implementado a seguir) cria as informações detalhadas para cada versão da API, como o título e a versão da API. private static OpenApiInfo CreateOpenApiInfo(ApiVersionDescription description) { var info = new
Automatizando testes com Selenium

Os testes automatizados são essenciais para garantir que o seu código se comporte como esperado, especialmente em interfaces de usuário, que podem ser afetadas por mudanças em diversas partes do sistema. O Selenium é uma das ferramentas mais populares para automatizar testes de UI e pode ser facilmente integrado em projetos Blazor. Neste artigo, vamos aprender como usar o Selenium para realizar testes automatizados em um projeto Blazor, simulando a navegação entre as telas e a interação com os componentes da interface. O que é o Selenium? O Selenium é uma ferramenta de automação para aplicações web. Ele simula ações realizadas pelo usuário, como clicar em botões, preencher campos de texto e navegar entre páginas, e é amplamente utilizado para validar a funcionalidade de interfaces de usuário. Principais componentes do Selenium WebDriver: interface principal do Selenium para interagir diretamente com o navegador, realizando as ações que um usuário faria. Selenium IDE: uma ferramenta para gravar e reproduzir interações no navegador. Selenium Grid: permite executar testes paralelamente em diferentes navegadores e sistemas operacionais. Neste artigo, focaremos no uso do Selenium WebDriver, integrando-o com um projeto Blazor para automatizar os testes. Instalando o Selenium no projeto de testes Antes de escrever os testes, vamos criar um projeto Blazor com Blazor Server. Aqui, usaremos um projeto Blazor básico com a navegação entre telas. Após isso, vamos criar um projeto de testes para automatizar a navegação entre as páginas Blazor utilizando o Selenium. Para isso, você pode clicar na solução com o botão direito do mouse e ir em “Add” ou “Adicionar” e selecionar a opção “New Project” ou “Novo Projeto…”. Após isso, uma tela será aberta para selecionar um novo projeto. Digite na barra de busca “MSTest” e selecione a opção “MSTest Test Project” ou “Projeto de Teste MSTest”. Continue as configurações e selecione o “MSTest” na opção “Executor de teste” ou “Test Runner”. Agora vamos adicionar o pacote do Selenium WebDriver via NuGet: dotnet add package Selenium.WebDriver dotnet add package Selenium.WebDriver.ChromeDriver Configurando o WebDriver O Selenium pode usar o ChromeDriver (ou Firefox, Edge, etc.) para controlar o navegador. Em nosso exemplo vamos configurar o ChromeDriver para automação. using OpenQA.Selenium; using OpenQA.Selenium.Chrome; namespace BlazorApp.Tests { [TestClass] public class BlazorAppTests { private IWebDriver driver; [TestInitialize] public void Setup() { driver = new ChromeDriver(); } [TestMethod] public void TestLoginPageNavigation() { // Teste de Login } [TestCleanup] public void Cleanup() { driver.Quit(); } } } No código acima, temos a classe “BlazorAppTests”, que recebe o atributo “[TestClass]”. Este atributo identifica que a classe “BlazorAppTests” é uma classe de testes automatizados. Dentro dessa classe, declaramos a variável driver do tipo “IWebDriver”, que é a interface principal do Selenium usada para controlar o navegador durante os testes. Em seguida, o nosso código foi dividido em três partes: o que será feito antes de cada teste, os testes em si, e o que será feito após cada teste. Setup (Inicialização): a classe “Setup” recebe o atributo “[TestInitialize]”, que indica que o código dentro desse método será executado antes de cada teste. Nesse método, inicializamos o navegador Chrome, permitindo que o Selenium controle o navegador para o teste. Testes: o método “TestLoginPageNavigation” recebe o atributo “[TestMethod]”, que identifica que esse método é um teste. Dentro dele, vamos implementar o teste de login na aplicação de exemplo, validando o comportamento esperado da interface. Cleanup (Limpeza): por fim, temos a classe “Cleanup”, que recebe o atributo “[TestCleanup]”. O código dentro deste método será executado após a execução de cada teste. O “Cleanup” é responsável por limpar ou finalizar qualquer recurso que tenha sido utilizado durante o teste, como o fechamento do navegador. Implementando os testes Agora, dentro da nossa classe “TestLoginPageNavigation” vamos implementar o teste de login: driver.Navigate().GoToUrl(“https://localhost:7298/”); Usamos o “GoToUrl” para abrir o navegador na URL da nossa aplicação, que, neste caso, é a página inicial da aplicação. Assert.IsTrue(driver.Title.Contains(“Home”)); Aqui, validamos se a nossa aplicação realmente está na página inicial, verificando se o título da página contém o valor “Home”. Essa verificação assegura que a navegação para a página inicial foi bem-sucedida. driver.FindElement(By.LinkText(“Login”)).Click(); Assert.IsTrue(driver.Url.Contains(“Login”)); Para encontrar um elemento na tela, usamos o método “FindElement”. Ele disponibiliza várias formas de localizar um elemento. No código acima, utilizamos o “LinkText”, que procura dentro dos links da página aquele que contém o texto “Login”. Em seguida, usamos o “Click” para simular o clique nesse link. Após o clique, a aplicação deve redirecionar para a página de login. Para garantir que a navegação foi realizada corretamente, podemos validar se a URL foi alterada para a página de login. A asserção “Assert.IsTrue(driver.Url.Contains(“Login”))” garante que a URL contém a palavra “Login”, o que indica que a navegação ocorreu com sucesso. Agora, vamos adicionar as interações necessárias para preencher os campos de login e submeter o formulário. var emailInput = driver.FindElement(By.XPath(“/html/body/div[3]/div/div/div[1]/form/div/div[1]/div/div/div/input”)); var passwordInput = driver.FindElement(By.XPath(“/html/body/div[3]/div/div/div[1]/form/div/div[2]/div/div/div/input”)); emailInput.SendKeys(“nwe@email.com”); passwordInput.SendKeys(“N3xtW@v3”); driver.FindElement(By.XPath(“/html/body/div[3]/div/div/div[1]/form/div/div[4]/button”)).Click(); Para localizar os campos de e-mail e senha, utilizamos novamente o método “FindElement” e passamos o XPath de cada campo. O XPath é uma forma de localizar elementos na página, especificando seu caminho hierárquico dentro do código HTML. Esse método de localização é útil quando queremos encontrar um elemento com base em sua posição na estrutura da página. Após localizar os campos de e-mail e senha, utilizamos “SendKeys” para preencher os campos com os dados de login, como o e-mail “nwe@email.com” e a senha “N3xtW@v3”. Por fim, localizamos o botão de login, também através do XPath, e utilizamos o método “Click” para simular um clique no botão, submetendo o formulário de login. Para localizar o XPath, você pode clicar com o botão direito do mouse sobre o elemento desejado e selecionar a opção “Inspecionar” ou pressionar “F12” do seu teclado. Em seguida, clique com o botão direito no elemento destacado na ferramenta de inspeção e vá até a opção “Copy”. Por fim, escolha “Copy full XPath” para copiar o XPath completo do elemento. Ao clicar no botão de login, uma requisição é enviada para o backend com o objetivo de validar e autorizar o usuário na aplicação. Dependendo da
Injeção de dependência no ASP.NET Core

A injeção de dependência (DI) é um padrão fundamental de desenvolvimento usado para criar sistemas desacoplados, modularizados e mais fáceis de testar. No contexto do ASP.NET Core, a injeção de dependência é uma técnica essencial que facilita a gestão de objetos e suas dependências. Vamos explorar como funciona esse conceito e como aplicá-lo na prática. O que é injeção de dependência? De maneira simples, injeção de dependência é uma técnica que ajuda a “dar” para um objeto aquilo de que ele precisa, sem que esse objeto precise criar ou saber como obter o que ele necessita. Isso ajuda a reduzir o acoplamento, ou seja, diminui a dependência direta entre os objetos, tornando o código mais fácil de modificar e testar. Em vez de uma classe criar diretamente suas instâncias (por exemplo, utilizando a palavra-chave new), essas instâncias são “injetadas” nela de fora, normalmente através de um construtor, método ou propriedade. Isso ajuda a reduzir o acoplamento entre as classes e melhora a testabilidade do código. O problema do acoplamento forte Vamos ver um exemplo para entender o problema. Imagine que temos uma classe “Cliente” que precisa da classe “Pedido”. Em vez de simplesmente pedir ao sistema que a classe “Pedido” seja criada, a classe “Cliente” vai lá e cria o objeto “Pedido” diretamente, assim: public class Cliente { public void ObterPedidos() { Pedido pedido = new Pedido(); var pedidos = pedido.GetPedidos(); } } Nesse código, a classe “Cliente” está fortemente acoplada à classe “Pedido”. Ou seja, a “Cliente” depende totalmente da “Pedido” e, se a classe “Pedido” mudar, a “Cliente” vai precisar ser alterada também. Isso torna o código mais difícil de manter e testar. Violação do princípio da responsabilidade única (SRP) A classe “Cliente” não deveria ser responsável por criar a instância de “Pedido”. Isso é uma violação do Princípio da Responsabilidade Única (SRP), que diz que uma classe deve ter apenas uma responsabilidade. A classe “Cliente” deveria apenas “pedir” os pedidos, não se preocupar em como criar o “Pedido”. A solução: injeção de dependência A solução para esse problema é usar a injeção de dependência. Em vez de a classe “Cliente” criar o “Pedido”, vamos delegar essa tarefa para outra classe. Ou seja, vamos pedir para que o sistema nos forneça o “Pedido” sempre que precisarmos dele. Primeiro, criamos uma interface “IPedido”, que define o que a classe “Pedido” deve fazer (no nosso caso, retornar os pedidos). public interface IPedido { List GetPedidos(); } Depois, criamos a classe “Pedido” que vai implementar essa interface. A classe “Pedido” é responsável por retornar os pedidos. public class Pedido : IPedido { public List GetPedidos() { return new List { “XTudo”, “XSalada” }; } } Agora, a classe “Cliente” não cria mais o “Pedido” diretamente. Em vez disso, ela vai receber um objeto do tipo “IPedido” e usar ele. public class Cliente { private readonly IPedido _pedido; public Cliente(IPedido pedido) { _pedido = pedido; } public void ObterPedidos() { var pedidos = _pedido.GetPedidos(); foreach (var pedido in pedidos) { Console.WriteLine(pedido); } } } No código acima a palavra-chave “readonly” garante que o campo “_pedido” só possa ser atribuído no momento da construção da classe e que sua referência não possa ser alterada após a criação da instância. O construtor da classe recebe a instância de “IPedido” via injeção de dependência. Isso significa que, ao criar uma instância de “Cliente”, o sistema cuida de passar a instância de “Pedido”, e a classe “Cliente” usa essa dependência sem precisar saber como o “Pedido” foi criado. Configuração no ASP.NET Core Agora que fizemos a classe “Cliente” depender de uma abstração (a interface IPedido), precisamos configurar o container de injeção de dependência do ASP.NET Core para fornecer a instância de “Pedido” sempre que a classe “Cliente” precisar dela. No arquivo “Program.cs”, registramos a interface “IPedido” e sua implementação concreta, “Pedido”: public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Registra a interface IPedido e a classe Pedido no container DI builder.Services.AddScoped(); // Registra a classe Cliente que depende de IPedido builder.Services.AddScoped(); var app = builder.Build(); app.Run(); } } Aqui, usamos “AddScoped” para registrar que sempre que precisarmos de um “IPedido”, o sistema deve nos fornecer uma instância de “Pedido”. O ASP.NET Core vai automaticamente “injetar” o objeto “Pedido” na classe “Cliente”. Tipos de registros no container de injeção de dependência No ASP.NET Core, o container de injeção de dependência (DI) gerencia as dependências da aplicação. Quando registramos os serviços, podemos especificar o tempo de vida desses serviços, ou seja, por quanto tempo eles serão mantidos na memória. O ASP.NET Core oferece três tipos principais de tempo de vida para os serviços: Scoped Singleton Transient Cada tipo de registro tem suas características e é usado em cenários específicos. Vamos entender cada um deles com exemplos práticos. Scoped (Escopo) Os serviços registrados como Scoped são criados uma vez por requisição (ou escopo). Ou seja, toda vez que uma nova requisição HTTP chega à aplicação, o container DI cria uma nova instância desse serviço. Esse tipo de serviço é útil quando queremos manter o estado durante o processamento de uma requisição, mas não precisamos compartilhar essa instância entre diferentes requisições. Use o “Scoped” para serviços que devem ser usados por toda a duração de uma única requisição HTTP. Um exemplo de utilização é em repositórios de banco de dados ou serviços que necessitam manter o estado durante a execução de uma requisição. No “Program.cs”, registramos o serviço como Scoped: builder.Services.AddScoped(); Toda vez que uma requisição HTTP chega, o container cria uma instância de “OrderService”. No entanto, essa instância será usada apenas durante essa requisição e descartada ao final da mesma. Singleton (Único) Os serviços registrados como Singleton são criados uma única vez e compartilhados por toda a vida útil da aplicação. Ou seja, o mesmo objeto será usado durante toda a execução do aplicativo, independentemente do número de requisições ou chamadas ao serviço. Use Singleton para serviços que não mantêm estado entre requisições e não precisam ser recriados, como serviços de
Programação assíncrona e concorrente com Task em C#

A programação assíncrona e concorrente são conceitos fundamentais para a criação de aplicações que precisam lidar com múltiplas operações ao mesmo tempo, como quando é necessário fazer requisições de rede, realizar processamento paralelo ou executar tarefas pesadas sem bloquear a interface do usuário. Em C#, o conceito de Task é uma peça central para implementar essas funcionalidades de maneira eficiente. O que é uma Task? Uma Task é como uma tarefa que pode ser executada de forma independente, sem bloquear o restante do programa. Ela é usada para fazer tarefas em segundo plano enquanto o programa continua executando. Você pode esperar o resultado de uma Task usando o comando “await” ou controlar o que acontece depois dela com métodos como “ContinueWith” ou “WaitAll”. Usando Task, conseguimos evitar que a “thread principal” (a parte do programa que controla a interface com o usuário) fique bloqueada. Isso permite que o programa faça várias ações ao mesmo tempo de maneira mais eficiente, sem travar ou demorar para responder. Task.Run: iniciando tarefas em uma thread separada O método “Task.Run” é utilizado para iniciar uma tarefa em uma thread separada. Isso é particularmente útil quando você precisa evitar o bloqueio da thread principal da aplicação, especialmente em aplicações com interface gráfica (GUI), onde o bloqueio da thread principal pode levar a uma interface congelada. Console.WriteLine(“Iniciando tarefa…”); Task.Run(() => { // Simulando uma operação pesada System.Threading.Thread.Sleep(2000); Console.WriteLine(“Tarefa concluída.”); }); Console.WriteLine(“Aguardando a tarefa…”); Console.ReadLine(); No exemplo acima, a tarefa que simula uma operação pesada (sleep de 2 segundos) é executada em uma thread separada, permitindo que o código continue executando sem bloquear a aplicação. Task.Delay: criando atrasos assíncronos Em alguns casos, é necessário inserir um atraso na execução de uma tarefa sem bloquear a thread principal. Isso é facilmente feito com o método “Task.Delay”, que permite aguardar de forma assíncrona por um tempo especificado. Console.WriteLine(“Iniciando atraso…”); await Task.Delay(3000); Console.WriteLine(“Atraso concluído.”); Neste exemplo, a tarefa aguarda por 3 segundos sem bloquear a thread principal. O uso de “await” permite que o código continue executando enquanto aguarda o atraso. Task: tarefas com retorno de valor Quando você precisa que uma tarefa assíncrona retorne um valor, você pode usar Task<T>. Essa classe permite que você execute uma operação assíncrona que retorna um valor do tipo T, que pode ser qualquer tipo de dado como “int”, “string”, ou até mesmo objetos complexos. public static async Task Main() { Console.WriteLine(“Iniciando tarefa de busca …”); string resultado = await BuscarUsuario(“José”); Console.WriteLine($”Usuário: {resultado} encontrado.”); } public static async Task BuscarUsuario(string nome) { Console.WriteLine($”Buscando {nome} no banco…”); await Task.Delay(3000); return “José Santos”; } Neste exemplo, o método “BuscarUsuario” é uma tarefa assíncrona que retorna um valor do tipo “string”. A operação de busca é realizada após um atraso simulado de 3 segundos, e o valor retornado é o resultado da busca no “banco“. Task.WaitAll: aguardando múltiplas tarefas Quando você tem várias tarefas em execução simultaneamente e precisa aguardar que todas terminem antes de prosseguir, o método “Task.WaitAll” é ideal. Ele permite aguardar a conclusão de várias tarefas de uma vez. public static async Task Main() { Console.WriteLine(“Iniciando múltiplas tarefas…”); Task task1 = Task.Run(() => ProcessaTarefa(1)); Task task2 = Task.Run(() => ProcessaTarefa(2)); Task task3 = Task.Run(() => ProcessaTarefa(3)); await Task.WhenAll(task1, task2, task3); Console.WriteLine(“Todas as tarefas concluídas.”); } public static void ProcessaTarefa(int id) { System.Threading.Thread.Sleep(2000); Console.WriteLine($”Tarefa {id} concluída.”); } Neste exemplo, três tarefas são executadas simultaneamente. Usando “Task.WhenAll”, a execução do código só prossegue após todas as tarefas terem sido completadas. Task.ContinueWith: encadeando tarefas O método “Task.ContinueWith” permite encadear a execução de uma tarefa após a conclusão de outra. Isso é útil quando você precisa realizar uma série de operações assíncronas em sequência. Console.WriteLine(“Iniciando tarefa…”); Task.Run(() => { // Simulando tarefa inicial System.Threading.Thread.Sleep(2000); Console.WriteLine(“Tarefa 1 concluída.”); }) .ContinueWith(task => { // Tarefa a ser executada após a primeira System.Threading.Thread.Sleep(1000); Console.WriteLine(“Tarefa 2 concluída.”); }); Console.WriteLine(“Aguardando tarefas…”); Console.ReadLine(); Neste exemplo, a segunda tarefa será executada assim que a primeira for concluída, sem a necessidade de aguardar explicitamente a primeira tarefa usando “await”. Task vs async/await: quando usar cada abordagem Embora “Task” seja uma abstração importante para programação assíncrona, o “async/await” é uma maneira mais moderna e legível de lidar com operações assíncronas. A principal diferença entre os dois é que “async/await” proporciona uma sintaxe mais simples e menos propensa a erros ao escrever código assíncrono. Quando usar Task: Quando você precisa de mais controle sobre o comportamento da tarefa, como no caso de encadear tarefas ou esperar por múltiplas tarefas. Quando a operação assíncrona não precisa ser aguardada diretamente no método. Quando usar async/await: Quando você deseja uma sintaxe mais simples e legível. Quando a operação assíncrona precisa ser aguardada diretamente no fluxo do método. Em muitas situações, você pode combinar as duas abordagens. Por exemplo, você pode usar “Task.Run” para executar algo em segundo plano e, dentro de um método “async”, aguardar essa tarefa com “await”. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão O uso de Task no C# permite que desenvolvedores criem aplicativos mais rápidos e responsivos ao evitar o bloqueio de threads e executar operações em segundo plano. Técnicas como “Task.Run”, “Task.Delay”, “Task.WaitAll” e “Task.ContinueWith” são ferramentas muito úteis para gerenciar tarefas assíncronas e concorrentes. Quando combinado com “async/await”, você pode escrever código assíncrono de forma clara e eficiente, garantindo uma melhor experiência para o usuário.
Trabalhando com XML em C#

O XML (Extensible Markup Language) é uma linguagem de marcação amplamente utilizada para armazenar e transportar dados de forma estruturada. Sua principal vantagem é a flexibilidade na representação de dados e a capacidade de ser lido por diferentes sistemas e plataformas. No desenvolvimento de software, XML é frequentemente utilizado para troca de dados entre sistemas e persistência de configurações. Conceitos básicos de XML O XML usa tags para definir a estrutura de dados, com a possibilidade de criar um formato hierárquico. Um arquivo XML pode ser representado por uma série de elementos (tags) com atributos e valores: O Pequeno Príncipe Antoine de Saint-Exupéry 1943 1984 George Orwell 1949 O exemplo acima mostra uma estrutura XML contendo uma tag “<livros>” que contém vários “<livro>”, e cada “<livro>” com elementos como “<titulo>”, “<autor>” e “<ano>”. Se fizermos uma analogia com C# teriamos uma coleção de “livros” que possui vários objetos “livro” e cada objeto “livro”, possui as propriedades “titulo”, ”autor”, ”ano”. Serializando e desserializando objetos com XmlSerializer No C#, a classe “XmlSerializer” é usada para converter objetos em XML e vice-versa. Vamos ver como serializar e desserializar objetos. Serialização A serialização é o processo de converter um objeto C# em um formato XML. Para isso podemos criar uma classe C#: public class Livro { public string Titulo { get; set; } public string Autor { get; set; } public int Ano { get; set; } } Para serializar um objeto “Livro” em XML, usamos o seguinte código: using System.IO; using System.Xml.Serialization; var livro = new Livro { Titulo = “O Pequeno Príncipe”, Autor = “Antoine de Saint-Exupéry”, Ano = 1943 }; XmlSerializer serializer = new XmlSerializer(typeof(Livro)); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, livro); string xml = writer.ToString(); Console.WriteLine(xml); } No código acima, após criar um objeto do tipo “Livro”, criamos um objeto do tipo “XmlSerializer”, passando o tipo “Livro” para indicar que será serializado. Em seguida, utilizamos um “StringWriter”, que é um tipo de “TextWriter” para armazenar o XML em memória. O “using” é usado para garantir que o “StringWriter” seja descartado corretamente. O “serializer.Serialize” converte o objeto “livro” em uma string XML, que é armazenada no “StringWriter”. Finalmente, a string XML gerada é recuperada com “ToString()” e exibida no console. A saída será um arquivo XML representando o objeto: O Pequeno Príncipe Antoine de Saint-Exupéry 1943 Desserialização A desserialização é o processo de converter um XML de volta para um objeto C#. Para isso, usamos o método “Deserialize” da classe “XmlSerializer”. string xml = @” O Pequeno Príncipe Antoine de Saint-Exupéry 1943 “; XmlSerializer serializer = new XmlSerializer(typeof(Livro)); using (StringReader reader = new StringReader(xml)) { Livro livro = (Livro)serializer.Deserialize(reader); Console.WriteLine($”Título: {livro.Titulo}, Autor: {livro.Autor}, Ano: {livro.Ano}”); } No código acima, temos uma string xml armazenada na variável “xml” que contém um XML representando um objeto “Livro”. Criamos um “XmlSerializer” passando o tipo “Livro” para indicar que queremos desserializar o XML para esse tipo de objeto. Em seguida, utilizamos um “StringReader”, que é um tipo de “TextReader” usado para ler o XML armazenado na variável “xml”. A chamada “serializer.Deserialize(reader)” converte o XML em um objeto “Livro”, e os valores do objeto são exibidos no console. A saída será: “Título: O Pequeno Príncipe, Autor: Antoine de Saint-Exupéry, Ano: 1943” Manipulando arquivos XML com o XDocument Além de serializar e desserializar objetos, também podemos manipular diretamente arquivos XML usando as classes “XDocument” que é parte do LINQ to XML, que oferece uma maneira moderna e fluente de trabalhar com XML. var livro = new Livro{ Titulo = “O Pequeno Príncipe”, Autor = “Antoine de Saint-Exupéry”, Ano = 1943 }; XDocument doc = new XDocument( new XElement(“Livros”, new XElement(nameof(Livro), new XElement(nameof(Livro.Titulo), livro.Titulo), new XElement(nameof(Livro.Autor), livro.Autor), new XElement(nameof(Livro.Ano), livro.Ano) ) ) ); doc.Save(@”D:livro.xml”); No código acima nós estamos transformando um objeto manualmente para XML, e para isso, após criar o objeto “Livro”, o código cria um documento XML utilizando as classes “XDocument” e “XElement”. O “XDocument” é usado como o contêiner principal do documento XML, enquanto o “XElement” representa os elementos do XML. O primeiro parâmetro de “XElement” define a tag do elemento, e os parâmetros seguintes contêm os elementos filhos que estarão dentro desse elemento (new XElement(“Tag”, “Elementos”)). Nesse caso, a tag raiz é chamada “Livros”, e dentro dela, há um elemento chamado “Livro”, que contém elementos como “Titulo”, “Autor” e “Ano”. O uso do “nameof” garante que os nomes dos elementos XML correspondam exatamente aos nomes das propriedades da classe. Essa abordagem melhora a consistência e flexibilidade do código. Por fim, o método “Save” é utilizado para armazenar o XML gerado em um arquivo específico, permitindo que os dados sejam persistidos de forma eficiente e possam ser utilizados posteriormente. Lendo e editando um documento XML Agora vamos ver como é possível ler e editar um arquivo XML. Leitura XDocument doc = XDocument.Load(@”D:livro.xml”); foreach (var livro in doc.Descendants(“Livro”)) { Console.WriteLine($”Título: {livro.Element(“Titulo”).Value}, Autor: {livro.Element(“Autor”).Value}, Ano: {livro.Element(“Ano”).Value}”); } Para realizar a leitura de um arquivo XML, podemos utilizar o método “Load” da classe “XDocument”. Quando fornecemos o caminho do arquivo XML como parâmetro, o método carrega o arquivo XML em memória, permitindo que possamos manipulá-lo. Após carregar o documento, podemos usar o método “Descendants” para acessar todos os elementos de um determinado tipo dentro do XML. No caso do nosso código, estamos procurando por todos os elementos “<Livro>”, que são os registros de livros dentro do XML. Dentro do “foreach”, percorremos cada elemento “<Livro>”. Para acessar os dados de cada livro, utilizamos o método “Element”, passando o nome do elemento que queremos acessar (por exemplo, “titulo” ou “autor”). O método “Element” retorna um “XElement”, e a propriedade “Value” é usada para obter o valor contido nesse elemento. Edição doc.Descendants(“Livro”).First().Element(“Ano”).Value = “1945”; doc.Save(@”D:livro.xml”); Para editar o XML, usamos o método “Descendants” para localizar todos os elementos “<Livro>”. Em seguida, o método “First()” é utilizado para acessar o primeiro elemento da coleção retornada. O “First()” é útil quando você quer alterar apenas o primeiro elemento que corresponde ao critério (neste caso, o primeiro “<Livro>”). Se desejasse acessar
Manipulando arquivos Excel com EPPlus

A manipulação de arquivos Excel em aplicações .NET é uma necessidade comum, seja para gerar relatórios, importar ou exportar dados ou para integração com outros sistemas. Com a biblioteca EPPlus, é possível criar, ler e modificar arquivos Excel de forma eficiente. Neste artigo, vamos explorar como utilizar o EPPlus para manipular arquivos Excel em C# de forma simples e eficaz. O que é o EPPlus? O EPPlus é uma biblioteca .NET que permite criar, ler e modificar arquivos Excel sem a necessidade de ter o Excel instalado. Ele é amplamente utilizado para tarefas como geração de relatórios e automação de manipulação de dados em planilhas Excel. Instalação do EPPlus Para começar a usar o EPPlus em seu projeto C#, você pode instalar o pacote via CLI ou NuGet: .NET CLI dotnet add package EPPlus Package Manager Install-Package EPPlus O EPPlus oferece uma API com muitos recursos para trabalhar com planilhas Excel, além de ser eficiente e de fácil integração. Para mais detalhes sobre como utilizar a biblioteca, você pode acessar a documentação oficial. Leitura de planilhas: acessando células, linhas e colunas Agora vamos realizar a manipulação de um arquivo Excel: using OfficeOpenXml; using System.IO; ExcelPackage.License.SetNonCommercialOrganization(“Next Wave Education”); var fileInfo = new FileInfo(“C:/Local/Arquivo.xlsx”); using (var package = new ExcelPackage(fileInfo)) { var worksheet = package.Workbook.Worksheets[0]; var valor = worksheet.Cells[1, 1].Text; Console.WriteLine(valor); } No início do código, definimos o tipo de licença que será utilizada. Como estamos utilizando a versão gratuita (não comercial), chamamos o método “SetNonCommercialOrganization” para definir a licença como não comercial e associá-la à organização “Next Wave Education”. Esse passo é obrigatório. Após isso, nós criamos uma instância de “FileInfo”, onde passamos o caminho do arquivo Excel que desejamos manipular. Em seguida, abrimos o arquivo e acessamos a primeira planilha do nosso documento (Worksheets[0]). No Excel essa informação fica na parte inferior: Para acessar uma célula específica, utilizamos a propriedade “Cells[Coluna, Linha]”, onde o primeiro valor refere-se à coluna (em formato numérico) e o segundo valor refere-se à linha. Por exemplo, Cells[1, 1] refere-se à célula A1. Para capturar o valor da célula como texto, utilizamos a propriedade “.Text”, que converte o conteúdo da célula para uma string. Esse valor é então armazenado na variável “valor”. Escrita de dados: criando e salvando arquivos Excel Agora vamos aprender como criar uma nova planilha, adicionar dados e salvar o arquivo Excel. using (var package = new ExcelPackage(fileInfo)) { var worksheet = package.Workbook.Worksheets.Add(“Pessoas”); worksheet.Cells[1, 1].Value = “Nome”; worksheet.Cells[1, 2].Value = “Idade”; worksheet.Cells[2, 1].Value = “João”; worksheet.Cells[2, 2].Value = 30; worksheet.Cells[3, 1].Value = “Maria”; worksheet.Cells[3, 2].Value = 25; package.Save(); } Para criarmos uma nova planilha utilizando o método “Worksheets.Add(“Pessoas”)”, onde passamos o nome da nova planilha. Essa planilha será criada dentro do arquivo Excel. A nova planilha será adicionada ao arquivo Excel e será acessada através da variável “worksheet”. Para adicionar dados à planilha, utilizamos a propriedade “Cells[Coluna, Linha]”. Neste exemplo, usamos o “Value” para informar que queremos adicionar um valor naquela célula. Acima, estamos preenchendo os cabeçalhos nas células A1 e B1, e os dados nas células A2, B2, A3 e B3. Depois de adicionar os dados, é necessário chamar o método “Save()” para salvar as alterações feitas no arquivo Excel. Manipulação avançada: estilos, fórmulas e validações Agora que aprendemos a realizar manipulações básicas em arquivos Excel, vamos explorar alguns recursos mais avançados que o EPPlus oferece. using (var package = new ExcelPackage(fileInfo)) { var worksheet = package.Workbook.Worksheets[1]; var headerRange = worksheet.Cells[1, 1, 1, 2]; headerRange.Style.Font.Bold = true; headerRange.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; headerRange.Style.Fill.BackgroundColor.SetColor(Color.Yellow); worksheet.Cells[5, 2].Formula = “SUM(B2;B3)”; package.Save(); } O código acima a planilha que estamos manipulando é acessada através da propriedade “Worksheets[1]”, que nos dá acesso à planilha “Pessoas” que criamos anteriormente. Após isso, usamos o “Cells[linhaInicial, colunaInicial, linhaFinal, colunaFinal]” para identificar um intervalo de células. No caso, estamos selecionando um intervalo que vai da célula A1 (1, 1) até a célula B1 (1, 2). Após identificar o intervalo de células, aplicamos um estilo de negrito à fonte das células selecionadas. Em seguida, definimos o preenchimento de fundo das células selecionadas. Aqui, estamos fazendo duas coisas: PatternType = ExcelFillStyle.Solid: isso define que o preenchimento será sólido, ou seja, uma cor uniforme, sem gradientes ou padrões. BackgroundColor.SetColor(Color.Yellow): isso define a cor de fundo das células selecionadas como amarela. O EPPlus também permite adicionar fórmulas nas células. No exemplo, estamos inserindo a fórmula “SUM(B2,B3)” na célula B5 (5, 2), que calcula a soma dos valores da célula B2 e B3. Para adicionar a fórmula, usamos a propriedade “.Formula”. Essa fórmula será avaliada automaticamente quando a planilha for aberta no Excel Boas práticas para performance e manipulação segura de arquivos grandes Ao trabalhar com arquivos grandes, é importante garantir que sua aplicação seja eficiente e não consuma excessivamente os recursos de memória. Aqui estão algumas dicas para melhorar a performance: Evitar leitura desnecessária de planilhas inteiras: ao ler um arquivo, evite carregar planilhas inteiras se você só precisa de uma parte do arquivo. Utilize índices para acessar as células de forma eficiente. Fechar arquivos apropriadamente: certifique-se de sempre fechar os arquivos corretamente após a leitura ou escrita, usando o bloco “using” para garantir que os recursos sejam liberados. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão EPPlus é uma excelente escolha para trabalhar com arquivos Excel em C#, especialmente quando se busca uma solução eficiente e sem dependência do Excel instalado. A biblioteca oferece uma API rica e simples, que cobre uma ampla gama de cenários, desde leitura e escrita de dados até manipulação avançada de planilhas.
Modificadores ref, out, in e params em C#

Em C#, a maneira como os parâmetros são passados para os métodos pode impactar diretamente a flexibilidade e o comportamento do código. Três modificadores que desempenham um papel crucial nessa área são “ref”, “out” e “params”. Cada um deles permite uma forma distinta de manipular os argumentos passados, seja alterando seus valores dentro do método, retornando múltiplos resultados ou aceitando um número variável de parâmetros. ref (referência de parâmetro) Para entender como o “ref” funciona, é importante compreender como os valores são passados para os métodos em C#. Passagem por valor: quando um parâmetro é passado por valor, uma cópia do valor da variável é criada e enviada para o método. Isso significa que qualquer modificação feita no parâmetro dentro do método não afetará a variável original, pois estamos lidando com uma cópia do valor. Na imagem acima, criamos uma variável “int num” com o valor 5 armazenado nela. Na memória do sistema, esse valor é guardado em um espaço específico, que é identificado por uma referência de memória (0x1234). Quando passamos esse valor para o método “AoQuadrado”, um novo espaço de memória é alocado para armazenar a cópia desse valor (x04321), e essa cópia é enviada para o método. Dentro do método, o valor é multiplicado por ele mesmo (ao quadrado) e o resultado é armazenado na variável. No entanto, se observarmos a referência de memória original (0x1234), notamos que ela não foi modificada. Isso ocorre porque, ao passarmos o valor para o método, estamos lidando com uma cópia do valor, e não com a referência direta à variável original. Assim, a variável original não sofre alterações, mesmo que o valor dentro do método tenha sido alterado. Passagem por referência: Quando um parâmetro é passado por referência, o método recebe uma referência ao local na memória onde a variável original está armazenada. Nesse caso, qualquer modificação feita dentro do método afeta diretamente o valor original, já que estamos lidando com o endereço de memória da variável e não com uma cópia. Agora quando passamos “num” para o método “AoQuadrado” com o modificador “ref”, estamos enviando a referência de memória de “num”, e não uma cópia do valor. O método recebe, portanto, o endereço de memória “0x1234”. Dentro do método “AoQuadrado”, o valor de “num” é multiplicado por ele mesmo (ao quadrado), resultando em “25”. Como o método está operando diretamente na variável original (referenciada por 0x1234), a modificação do valor de “num” altera diretamente a variável original. out (Parâmetro de Saída) O modificador “out” é usado para indicar que um parâmetro será somente de saída. Ou seja, o valor do parâmetro não precisa ser inicializado antes de ser passado para o método, e o método é responsável por atribuir um valor a ele. Após a execução do método, o valor do parâmetro “out” será atualizado com o valor que o método atribuiu. Características: O parâmetro “out” deve ser atribuído a um valor dentro do método antes de ser utilizado. Ex: Dividir(10, 0, out resultado); Não é necessário inicializar o parâmetro antes de passá-lo. Ex: int resultado; Usado quando um método precisa retornar múltiplos valores. Ex: Dividir(10, 0, out resultado, out mensagem); Vamos entender melhor o seu funcionamento com um exemplo: public static void Main() { int resultado; string mensagem; Dividir(10, 0, out resultado, out mensagem); Console.WriteLine($”Resultado: {resultado}, Mensagem: {mensagem}”); } public static void Dividir(int numerador, int denominador, out int resultado, out string mensagem) { if (denominador == 0) { resultado = 0; mensagem = “Erro: Divisão por zero!”; } else { resultado = numerador / denominador; mensagem = “Divisão realizada com sucesso!”; } } No exemplo acima, o método “Dividir” recebe dois parâmetros de entrada, numerador e denominador, que são usados no cálculo da divisão. Além disso, o método recebe dois parâmetros com o modificador “out”. Esses parâmetros são preenchidos dentro do método com o valor calculado e uma mensagem indicando o sucesso ou erro da operação. Se o denominador for 0, a divisão não é realizada e uma mensagem de erro é atribuída. Caso contrário, o resultado da divisão e uma mensagem de sucesso são retornados. in (Somente Leitura) O modificador “in” é usado principalmente para passar parâmetros de maneira eficiente para métodos, sem permitir que o valor do argumento seja alterado dentro do método. Ele foi introduzido no C# 7.2 e pode ser usado em parâmetros de métodos ou funções para garantir que o valor não seja modificado dentro do método, mas sem fazer uma cópia do valor, o que pode melhorar a performance. Para usar o modificador “in” basta adiciona-lo no método e antes de adicionar o valor para o método: public static void Main() { int num = 5; AoQuadrado(in num); } public static void AoQuadrado(in int num) { Console.WriteLine($”O quadrado de {num} é {num * num}”); } Acima, tudo ocorrerá normalmente, pois em nenhuma parte do método “AoQuadrado” estamos tentando modificar a variável “num”. No entanto, se alterarmos o código e tentarmos modificar o valor da variável, ocorrerá um erro: public static void AoQuadrado(in int num) { num = num * num; } Na IDE, você verá um erro informando que não é possível modificar a variável, pois ela foi passada com o modificador “in” e é somente leitura dentro do método. params (Parâmetro Variável) O modificador “params” em C# é usado para permitir que um método receba um número variável de argumentos de um tipo específico. Ele permite que você passe qualquer quantidade de parâmetros para um método, desde nenhum até muitos, sem precisar sobrecarregar o método para diferentes quantidades de argumentos. O “params” é uma forma prática de lidar com parâmetros que podem ser passados em número variável. Quando você usa “params”, você não precisa passar todos os parâmetros de uma vez. Você pode passar os parâmetros individualmente ou como um array. Como funciona o params Para usar o “params”, ele deve ser colocado por último na lista de parâmetros de um método. O tipo de parâmetro “params” pode ser qualquer tipo de valor (tipos primitivos ou objetos), mas todos