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

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

Alternativas ao AutoMapper em C#

O AutoMapper é uma biblioteca popular em C# para mapeamento de objetos, facilitando a transformação de dados entre camadas. No entanto, recentemente foi anunciado que o AutoMapper passará a ser pago. Neste artigo, vamos explorar alternativas gratuitas e de código aberto, como TinyMapper, ExpressMapper e Mapster, destacando suas funcionalidades. A necessidade de ferramentas para mapeamento de objetos No desenvolvimento de software, é comum precisar realizar a transformação de dados entre diferentes tipos de objetos. Por exemplo, ao trabalhar com camadas distintas em uma aplicação, como a camada de persistência de dados (entidades) e a camada de apresentação (DTOs), a conversão entre esses tipos de objetos é fundamental. Realizar esse mapeamento manualmente pode ser demorado e propenso a erros, além de tornar o código mais difícil de manter. Por isso, ferramentas que automatizam esse processo de mapeamento tornam-se essenciais. Elas permitem que os desenvolvedores se concentrem nas regras de negócios e na lógica da aplicação, enquanto a ferramenta cuida da transformação dos dados. TinyMapper O TinyMapper é uma das alternativas mais simples e focadas em desempenho. Sua proposta é ser leve e extremamente rápida, fornecendo uma solução sem dependências adicionais e uma API de configuração simples.   Para instalar o TinyMapper, adicione o pacote pelo .NET CLI ou NuGet no seu projeto: .NET CLI: dotnet add package TinyMapper NuGet: Install-Package TinyMapper O TinyMapper não exige configurações complexas. Basta informar quais objetos serão mapeados. Isso pode ser feito no “Program.cs” ou em outro local adequado: TinyMapper.Bind(); O mapeamento é feito automaticamente com base nas propriedades que possuem o mesmo nome entre as classes de origem e destino. var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; var enderecoFinal = TinyMapper.Map(enderecoInicial); Mapeamento personalizado Para casos mais específicos, é possível usar mapeamentos personalizados. TinyMapper.Bind(c => { c.Ignore(s => s.Id); c.Bind(s => s.Avenida, d => d.Rua); c.Bind(s => s.NumeroCEP, d => d.CEP); c.Bind(s => s.NomeCidade, d => d.Cidade); c.Bind(s => s.NomeEstado, d => d.Estado); }); No exemplo acima, temos dois objetos cujas propriedades possuem nomes diferentes. É possível omitir o mapeamento de uma propriedade, como foi feito com o “Id”, e também especificar explicitamente qual propriedade de um objeto deve ser mapeada para outra. Mapeamento objetos aninhados O TinyMapper também oferece suporte para mapear objetos aninhados. Você pode fazer isso especificando manualmente como cada propriedade do objeto interno deve ser mapeada. Para esse exemplo e os demais vamos usar esse objeto: public class Endereco { public string Rua { get; set; } public string Cidade { get; set; } } public class Pessoa { public string Nome { get; set; } public Endereco Endereco { get; set; } } Podemos mapear “Pessoa” para “PessoaDTO”, incluindo o “Endereco”, dessa forma: TinyMapper.Bind(config => { config.Bind(s => s.Nome, d => d.Nome); config.Bind(s => s.Endereco.Rua, d => d.Endereco.Rua); config.Bind(s => s.Endereco.Cidade, d => d.Endereco.Cidade); }); ou desta outra maneira: TinyMapper.Bind(config => { config.Bind(s => s.Rua, d => d.Rua); config.Bind(s => s.Cidade, d => d.Cidade); }); TinyMapper.Bind(config => { config.Bind(s => s.Nome, d => d.Nome); config.Bind(s => s.Endereco, d => d.Endereco); }); Na primeira forma,  mapeamos as propriedades dentro do objeto “Endereco” diretamente para as propriedades do “PessoaDTO”. Esse tipo de mapeamento é fácil de implementar quando você lida com objetos aninhados com um número reduzido de níveis. Já na segunda forma, nós criamos um mapeamento para “Pessoa” e outro para “Endereco”. Quando “Pessoa” precisa realizar o mapeamento do objeto “Endereco”, ele já sabe como deve proceder. ExpressMapper O ExpressMapper é uma alternativa que se destaca pela configuração rápida e por oferecer suporte a expressões Lambda para personalizar mapeamentos. Ele foca na simplicidade e flexibilidade, permitindo um mapeamento rápido e eficiente. Para instalar o ExpressMapper, adicione o pacote NuGet no seu projeto: .NET CLI: dotnet add package Expressmapper NuGet: Install-Package ExpressMapper A configuração básica do ExpressMapper é rápida e direta. Basta registrar o mapeamento entre as classes de origem e destino: ExpressMapper.Mapper.Register(); var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; O mapeamento é feito automaticamente, usando as convenções de nome de propriedades. Para realizar o mapeamento: var enderecoFinal = ExpressMapper.Mapper.Map(enderecoInicial); Mapeamento personalizado Caso precise de mapeamentos mais específicos, como quando as propriedades de origem e destino possuem nomes diferentes, você pode usar expressões Lambda para personalizar a transformação de dados: ExpressMapper.Mapper.Register() .Ignore(dest => dest.Id) .Member(dest => dest.Rua, orig => orig.Avenida) .Member(dest => dest.CEP, orig => orig.NumeroCEP) .Member(dest => dest.Cidade, orig => orig.NomeCidade) .Member(dest => dest.Estado, orig => orig.NomeEstado); No exemplo acima, o ExpressMapper permite personalizar o mapeamento, especificando que a propriedade “Id” não deve ser mapeada usando “Ignore”. Após isso usamos o método “Member” para ligar o parâmetro de destino com o de origem. Mapeamento objetos aninhados Para realizarmos o mapeamento de objetos aninhados no ExpressMapper, podemos fazer da seguinte forma: ExpressMapper.Mapper.Register() .Member(s => s.Nome, d => d.Nome) .Member(s => s.Endereco.Rua, d => d.Endereco.Rua) .Member(s => s.Endereco.Cidade, d => d.Endereco.Cidade); ou ExpressMapper.Mapper.Register(); ExpressMapper.Mapper.Register() .Member(s => s.Nome, d => d.Nome) .Member(s => s.Endereco, d => d.Endereco); Semelhante ao TinyMapper, podemos realizar o mapeamento do objeto “Endereco” dentro de “Pessoa” ou podemos realizar o seu mapeamento separadamente. Mapster O Mapster é uma das alternativas mais poderosas e flexíveis ao AutoMapper. Ele suporta mapeamento manual, via configuração ou atributos, tornando-se ideal para projetos mais complexos que exigem flexibilidade e personalização. Para instalar o Mapster, adicione o pacote NuGet no seu projeto: .NET CLI: dotnet add package Mapster NuGet: Install-Package Mapster A configuração básica do Mapster pode ser feita diretamente, registrando a configuração do mapeamento: TypeAdapterConfig.NewConfig(); O mapeamento pode ser feita da seguinte forma: var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; var enderecoFinal = enderecoInicial.Adapt(); O Mapster utiliza o método “Adapt<T>()”, que é um método de extensão da classe “Object”. Isso significa que todos os tipos de objetos em C# podem utilizar esse método para se adaptar a

Como funcionam os desconstrutores em C#

A partir do C# 7.0, uma nova funcionalidade foi introduzida na linguagem, permitindo uma maneira simples e poderosa de desconstruir objetos: os desconstrutores. Essa funcionalidade é especialmente útil quando trabalhamos com tuplas ou classes que contêm múltiplos valores e desejamos acessar seus componentes de maneira mais direta e legível. Neste artigo, vamos explorar o conceito de desconstrutores e como podemos utilizá-los no C#. O que é um desconstrutor? Um desconstrutor é um método especial que permite “desempacotar” um objeto em seus componentes individuais. Ele facilita a extração dos valores de um objeto sem precisar acessar suas propriedades diretamente, o que pode melhorar a clareza e a concisão do código. Em termos simples, o desconstrutor é usado para extrair os membros de um objeto em variáveis separadas, como se fosse um processo de “desconstrução” do objeto. Sintaxe básica de um desconstrutor O desconstrutor é implementado através de um método especial chamado “Deconstruct”. Esse método é, essencialmente, um método de instância que “desmonta” o objeto e passa seus componentes para as variáveis de sua escolha. Aqui está um exemplo básico de como isso pode ser feito: public class Ponto { public int X { get; } public int Y { get; } public Ponto(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } } No exemplo acima, a classe “Ponto” tem um desconstrutor chamado “Deconstruct”. Esse método recebe duas variáveis (x e y) e as preenche com os valores das propriedades “X” e “Y” do objeto. Como as variáveis são passadas com o modificador “out”, o método “Deconstruct” é responsável por atribuir os valores a elas. Isso permite que, ao instanciar um objeto da classe “Ponto”, possamos facilmente obter seus valores. Depois de implementado, podemos usar o desconstrutor para extrair os valores de um objeto de forma simples e direta: var ponto = new Ponto(5, 10); var (x, y) = ponto; Console.WriteLine($”X: {x}, Y: {y}”); Neste exemplo, ao utilizar a sintaxe “var (x, y) = ponto”, o C# invoca automaticamente o método “Deconstruct” da classe “Ponto”, atribuindo os valores das propriedades “X” e “Y” às variáveis “x” e “y”, respectivamente. Exemplo de desconstrução com Tuplas Tuplas são tipos de dados que permitem armazenar múltiplos valores de diferentes tipos. Elas são especialmente úteis para retornar vários resultados de uma função ou para representar estruturas simples de dados. Para mais detalhes, confira nosso artigo sobre tuplas. Aqui está um exemplo de como usar desconstrutores com tuplas: public class Pessoa { public string Nome { get; } public int Idade { get; } public Pessoa(string nome, int idade) { Nome = nome; Idade = idade; } public void Deconstruct(out string nome, out int idade) { nome = Nome; idade = Idade; } } var pessoa = new Pessoa(“João”, 30); // Desconstruindo a instância de Pessoa em uma tupla var (nome, idade) = pessoa; Console.WriteLine($”Nome: {nome}, Idade: {idade}”); Neste exemplo, a classe “Pessoa” possui um método “Deconstruct” que permite desconstruir um objeto “Pessoa” em duas variáveis: “nome” e “idade”. Vantagens de usar desconstrutores O uso de desconstrutores oferece várias vantagens em termos de clareza e concisão do código: Facilidade de acesso aos componentes: não é necessário acessar cada propriedade individualmente. Em vez disso, podemos desconstruir um objeto em suas partes e usá-las diretamente. Redução de código: o processo de desconstrução permite escrever código mais enxuto e expressivo, especialmente em casos onde temos objetos compostos, como tuplas ou classes com múltiplas propriedades. Integração com Tuplas: em C# 7.0, as tuplas foram aperfeiçoadas, e desconstrutores permitem um uso ainda mais eficaz dessas estruturas. Ao usar tuplas, podemos facilmente extrair seus valores em variáveis nomeadas. 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 Os desconstrutores proporcionam uma maneira eficiente e limpa de acessar os componentes de um objeto. Eles se tornam especialmente úteis quando trabalhamos com objetos compostos ou tuplas, permitindo um código mais legível e conciso. Ao usar desconstrutores, podemos reduzir a complexidade do código, especialmente em cenários onde a extração de múltiplos valores de um objeto é frequente. Como vimos, a sintaxe é simples e pode melhorar significativamente a clareza do código. Se você estiver desenvolvendo em C# 7.0 ou superior, vale a pena explorar o uso de desconstrutores para tornar seu código mais expressivo e eficiente!