Padrão Strategy: Evitando condicionais complexos

No universo do desenvolvimento de software, a capacidade de criar sistemas flexíveis e de fácil manutenção é um desafio constante. Padrões de projeto surgem como soluções comprovadas para problemas recorrentes, oferecendo um vocabulário comum e diretrizes para a estruturação do código. Entre eles, o padrão de projeto Strategy se  destaca como uma ferramenta para lidar com a variação de algoritmos e comportamentos em uma aplicação. O que é o padrão Strategy? O padrão Strategy é uma forma de organizar o código para que ele possa usar diferentes comportamentos (ou algoritmos) de maneira flexível. Em vez de escrever toda a lógica diretamente em uma única classe, você separa cada comportamento em sua própria classe. Dessa forma, a classe principal (que chamamos de contexto) pode trocar facilmente o comportamento que está usando, sem precisar ser alterada. Isso permite, por exemplo, mudar a forma de calcular um valor, aplicar descontos ou processar dados, apenas trocando o objeto que representa a estratégia. Em resumo: o Strategy ajuda você a escolher e trocar comportamentos em tempo de execução, deixando o código mais organizado, reutilizável e fácil de manter. Qual problema ele resolve? Imagine uma aplicação que precisa realizar uma mesma tarefa de diversas maneiras,  como calcular diferentes tipos de frete, aplicar diferentes taxas de juros ou processar pagamentos por métodos variados. Uma abordagem comum, mas problemática, seria utilizar estruturas condicionais (como if-else ou switch-case ) para selecionar o algoritmo apropriado com base em algum critério. Essa abordagem leva a um código “inchado”, difícil de manter e estender. Cada nova variação do algoritmo exigiria modificações na classe principal Aplicação do padrão Para ilustrar o padrão Strategy, vamos considerar um cenário simples onde precisamos modelar um sistema de cálculo de frete com diferentes estratégias. Usaremos o padrão Strategy para encapsular essas operações e permitir que sejam trocadas dinamicamente. Primeiro, definimos a interface “IFreteStrategy” , que declara o método “CalcularFrete”. Todas as estratégias concretas deverão implementar este método. public interface IFreteStrategy { decimal CalcularFrete(decimal peso); } Em seguida, vamos criar as implementações concretas da interface “IFreteStrategy”. Cada uma delas implementa uma lógica diferente para o método “CalcularFrete”. public class FreteNormal : IFreteStrategy { public decimal CalcularFrete(decimal peso) => peso * 1.0m; } public class FreteExpresso : IFreteStrategy { public decimal CalcularFrete(decimal peso) => peso * 2.0m + 10.0m; } public class FreteRetiradaLoja : IFreteStrategy { public decimal CalcularFrete(decimal peso) => 0.0m; } Cada classe implementa a interface “IFreteStrategy” com uma lógica própria: FreteNormal: calcula o frete de forma linear com base no peso. FreteExpresso: aplica um multiplicador maior e uma taxa fixa adicional. FreteRetiradaLoja: define um frete com valor fixo zero (para casos de retirada na loja). Esta abordagem encapsula cada algoritmo em sua própria classe, permitindo a intercambialidade conforme a necessidade. public class CalculadoraFrete { private IFreteStrategy _strategy; public CalculadoraFrete(IFreteStrategy strategy) { _strategy = strategy; } public void DefinirEstrategia(IFreteStrategy strategy) { _strategy = strategy; } public decimal Calcular(decimal peso) { return _strategy.CalcularFrete(peso); } } A classe “CalculadoraFrete” atua como o contexto. Ela não se preocupa com os detalhes de cada algoritmo, apenas delega o cálculo para a estratégia atual. O método “DefinirEstrategia” possibilita trocar o comportamento em tempo de execução, mantendo o código aberto para novas estratégias sem modificar sua estrutura interna – um claro exemplo do princípio do aberto/fechado. public class Program { public static async Task Main() { decimal peso = 5.0m; var calculadora = new CalculadoraFrete(new FreteNormal()); var valorFreteNormal = calculadora.Calcular(peso); Console.WriteLine($”Frete Normal: R$ {valorFreteNormal}”); calculadora.DefinirEstrategia(new FreteExpresso()); var valorFreteExpresso = calculadora.Calcular(peso); Console.WriteLine($”Frete Expresso: R$ {calculadora.Calcular(peso)}”); calculadora.DefinirEstrategia(new FreteRetiradaLoja()); var valorFreteRetiradaLoja = calculadora.Calcular(peso); Console.WriteLine($”Retirada na Loja: R$ {calculadora.Calcular(peso)}”); } } Na primeira parte do código, o contexto “CalculadoraFrete” é instanciado com a estratégia “FreteNormal”. Ao chamar o método “Calcular”, o frete é computado com base na lógica definida em “FreteNormal”. O resultado deste cálculo será “Frete Normal: R$ 5,00”. Na segunda parte, realizamos o mesmo procedimento, mas trocamos a estratégia para “FreteExpresso”. Neste caso o resultado será diferente: “Frete Expresso: R$ 20,00”. Na terceira parte, alteramos novamente a estratégia para “FreteRetiradaLoja”, por conta disso, o valor do frete será de R$ 0, pois o cliente irá realizar a retirada na loja. Essa dinâmica demonstra como a estratégia permite alterar o comportamento em tempo de execução sem modificar o cliente ou o contexto. Benefícios e desvantagens do padrão Strategy Benefícios Flexibilidade: permite alterar ou estender comportamentos sem impactar o restante do sistema. Facilidade de manutenção: cada estratégia é isolada e pode ser testada e modificada independentemente. Reutilização: estratégias podem ser reutilizadas em diferentes contextos ou aplicações. Aderência ao princípio do aberto/fechado: novas estratégias podem ser adicionadas sem modificar o código existente, o que torna o sistema mais robusto a mudanças futuras. Desvantagens Aumento no número de classes: cada comportamento exige uma nova classe, o que pode tornar a estrutura do projeto mais complexa. Complexidade desnecessária: em cenários simples, o uso do padrão pode ser excessivo quando uma simples função ou delegate já resolveria o problema. Gerenciamento de estado: se os algoritmos dependem fortemente do estado do contexto, pode ser necessário passar muitas informações como parâmetros, o que pode complicar a implementação. 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 padrão Strategy facilita a variação de comportamentos em um sistema, promovendo modularidade e escalabilidade. Ele separa algoritmos do contexto em que são usados, tornando o código mais flexível e preparado para mudanças futuras. Além de melhorar a manutenção e testabilidade, o Strategy evita estruturas condicionais complexas. Embora aumente o número de classes, os benefícios em organização e reutilização fazem dele uma abordagem eficiente para sistemas dinâmicos e evolutivos.

FluentEmail: Enviando e-mails em C#

O envio de e-mails em aplicações é uma funcionalidade comum e crucial em muitos tipos de projetos. Seja para enviar um simples e-mail de boas-vindas, um alerta de sistema ou uma notificação de evento, a escolha da ferramenta para lidar com esse processo pode impactar significativamente a legibilidade e manutenção do código. O FluentEmail é uma biblioteca que visa facilitar o envio de e-mails com uma sintaxe fluente, moderna e altamente configurável em projetos .NET. Neste artigo, vamos explorar o que é o FluentEmail, como configurá-lo, utilizá-lo com templates e integrações, e discutir boas práticas para seu uso em diferentes cenários de projetos. Instalação e configuração inicial Para começar a usar o FluentEmail em seu projeto .NET, você precisa instalar alguns pacotes NuGet. Abra o Package Manager Console no Visual Studio ou use a linha de comando do .NET CLI para instalar os pacotes necessários: dotnet add package FluentEmail.Core dotnet add package FluentEmail.Smtp Esses pacotes incluem o núcleo da biblioteca, suporte ao envio via SMTP. Se você for utilizar outro serviço de envio, como SendGrid ou Mailgun, será necessário instalar os pacotes adicionais correspondentes. Em nosso exemplo, vamos usar o  SendGrid: dotnet add package FluentEmail.SendGrid Com esses pacotes instalados, podemos configurar o FluentEmail no projeto. Configuração do FluentEmail no Program.cs Uma vez que os pacotes estão instalados, é hora de configurar o FluentEmail. No ASP.NET Core, podemos usar a Dependency Injection para configurar e utilizar o FluentEmail em nossos serviços. No arquivo “Program.cs”, adicione a configuração do FluentEmail da seguinte maneira: builder.Services .AddFluentEmail(“usuario@dominio.com”) .AddSmtpSender( “smtp.mailersend.net”, 2525, “usuario@dominio.com”, “sua_chave_de_api_aqui” ); Aqui, usamos o método “AddFluentEmail” para adicionar o serviço FluentEmail ao container de dependências do ASP.NET Core. Este método define o endereço de e-mail do remetente que será usado em todos os e-mails enviados pela aplicação. Após configurar o remetente, adicionamos o método “AddSmtpSender”, que é responsável por definir as configurações de envio de e-mails via SMTP. Esse método configura o servidor que o FluentEmail utilizará para enviar as mensagens. “smtp.mailersend.net”: O servidor SMTP usado para enviar os e-mails. O MailerSend fornece esse endereço para permitir a comunicação com o servidor de e-mail. 2525: A porta utilizada para a comunicação SMTP. A porta 2525 é escolhida por ser uma alternativa à porta padrão 25, que pode ser bloqueada. “usuario@dominio.com”: O nome de usuário para autenticação no servidor SMTP, geralmente um e-mail ou subdomínio associado à sua conta. “sua_chave_de_api”: A senha ou chave de API usada para autenticação. Para encontrar as configurações basta acessar a área de e-mails, clicar em domínios, acessar ou criar o domínio desejado e selecionar a opção de SMPT. As configurações aparecerão para você da seguinte forma. Agora vamos implementar o código de envio: [ApiController] [Route(“[controller]”)] public class EmailNotificationController : ControllerBase { private readonly IFluentEmail _fluentEmail; public EmailNotificationController(IFluentEmail fluentEmail) { _fluentEmail = fluentEmail; } public async Task SendNotificationEmail(string email, string subject, string message) { await _fluentEmail .To(email) .Subject(subject) .Body(message) .SendAsync(); return Ok(); } } Neste código, recebemos os parâmetros do e-mail diretamente no endpoint: o destinatário do e-mail é passado para o campo “To”, o “subject” define o assunto da mensagem e o “body” contém o corpo do e-mail. Por fim, o método “SendAsync” é chamado para enviar a mensagem de forma assíncrona. Dessa forma, garantimos que o envio seja realizado sem bloquear a execução da aplicação. Boas práticas Aqui estão algumas boas práticas a serem seguidas ao usar o FluentEmail para enviar e-mails em projetos .NET: Assincronia: sempre utilize “SendAsync()” para enviar e-mails de forma assíncrona e evitar bloquear o fluxo da aplicação. Manutenção do Código: organize a configuração de e-mails e o envio de mensagens em um serviço separado (como o “EmailService”), seguindo o princípio da separação de responsabilidades. Segurança: nunca armazene diretamente informações sensíveis, como credenciais SMTP ou chave de API do SendGrid, no código. Use o “appsettings.json” ou variáveis de ambiente para armazenar essas informações de maneira segura. Em nosso exemplo, utilizamos as informações diretamente no código apenas para fins didáticos. 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 Neste artigo, abordamos como integrar o envio de e-mails em aplicações .NET utilizando a biblioteca FluentEmail. A solução proposta facilita a configuração do envio de mensagens de forma fluente e assíncrona, otimizando o processo e garantindo maior clareza no código. A flexibilidade oferecida pela integração com Dependency Injection e o suporte a servidores SMTP tornam o FluentEmail uma excelente escolha para quem busca uma forma prática e eficiente de enviar e-mails, sem a complexidade dos métodos tradicionais. Essa abordagem permite enviar notificações e outras mensagens de forma simples e escalável em projetos .NET.

Encapsulamento em C#: utilizando get e set

A programação orientada a objetos (POO) é um paradigma que organiza o código de forma modular e reutilizável. Um dos pilares desse paradigma é o encapsulamento, que visa proteger os dados e o comportamento de uma classe, tornando-os acessíveis e modificáveis de maneira controlada. Um dos principais mecanismos para alcançar o encapsulamento em C# é o uso de propriedades com os modificadores “get” e “set”. Neste artigo, exploraremos como essas propriedades podem ser utilizadas para proteger os dados, promovendo uma arquitetura mais segura, modular e de fácil manutenção. O que é encapsulamento? O encapsulamento é o princípio que busca esconder os detalhes internos de implementação de uma classe, permitindo que sua interface pública (as propriedades e métodos) seja usada sem que o consumidor da classe precise entender ou acessar diretamente seus dados internos. Esse conceito não só protege o estado interno da classe, mas também oferece a oportunidade de validar e controlar como os dados são acessados e modificados. Para entendermos melhor, vamos imaginar que estamos construindo uma classe “ContaBancaria”. Sem encapsulamento, qualquer código poderia acessar diretamente o saldo da conta e alterá-lo sem restrições: public class ContaBancaria { public decimal Saldo; } var conta = new ContaBancaria(); conta.Saldo = -500; // O saldo foi alterado para um valor inválido Com encapsulamento, podemos proteger esse campo, tornando o acesso ao saldo restrito e controlado: public class ContaBancaria { private decimal saldo; // Mudamos o campo para privado public decimal Saldo { get { return saldo; } set { if (value < 0) throw new ArgumentException("O saldo não pode ser negativo."); saldo = value; } } } Nesse exemplo, usamos uma propriedade com um “set” que inclui uma lógica de validação para garantir que o saldo (value) não seja negativo, protegendo o estado da classe. Propriedades com get e set Em C#, uma propriedade é um mecanismo que permite encapsular o acesso a um campo de uma classe. O uso das palavras-chave “get” e “set” permite criar uma interface controlada para acessar e modificar o valor de um campo. Propriedades automáticas Quando não é necessária lógica adicional para o “get” ou “set”, você pode usar propriedades automáticas, onde o compilador cuida internamente da implementação dos campos: public class Pessoa { public string Nome { get; set; } } Esse código permite o acesso à propriedade “Nome” com os métodos “get” e “set”. No entanto, esse método não permite nenhuma validação ou controle. Propriedades personalizadas com lógica interna Você pode adicionar lógica personalizada dentro dos métodos “get” e “set”. Vamos usar um exemplo de uma classe “Pessoa” que valida a idade antes de permitir que ela seja modificada: public class Pessoa { private int idade; public int Idade { get { return idade; } set { if (value < 0) throw new ArgumentException("Idade não pode ser negativa."); idade = value; } } } Neste exemplo, a propriedade “Idade” controla a alteração do campo “idade”, garantindo que a idade seja sempre válida. Modificadores de acesso em propriedades Em C#, você pode definir a visibilidade de um “get” ou “set” usando modificadores de acesso. Esses modificadores determinam a acessibilidade da propriedade e seu valor. private set Se você deseja que uma propriedade seja somente de leitura para os consumidores da classe, mas ainda permita modificações dentro da própria classe, você pode usar “private set”: public class Pessoa { public string Nome { get; private set; } public Pessoa(string nome) { Nome = nome; } } var pessoa = new Pessoa("João"); Console.WriteLine($"Nome: {pessoa.Nome}"); Neste caso, a propriedade “Nome” só pode ser atribuída por meio do construtor, impedindo a modificação direta do valor (por exemplo, Pessoa.Nome = “Pedro” não seria permitido). O método “get” não é privado, o que significa que a propriedade pode ser acessada e lida de fora da classe. Somente leitura (get sem set) Quando você deseja que uma propriedade seja somente leitura, ou seja, seu valor pode ser acessado mas nunca modificado após a inicialização, você pode criar uma propriedade com apenas um “get”: public class Produto { public decimal Preco { get; } public Produto(decimal preco) { Preco = preco; } } Neste caso, “Preco” só pode ser atribuído durante a construção do objeto, garantindo que o valor não seja alterado acidentalmente após a criação. Campos privados e propriedades públicas Uma das práticas recomendadas em C# é manter os campos de dados internos de uma classe privados e expô-los de forma controlada através de propriedades públicas. Isso permite que você adicione lógica de validação, formatação e outras operações sem expor diretamente os campos. public class Pessoa { private string nome; public string Nome { get { return nome; } set { nome = value.Trim(); } // Remover espaços extras ao atribuir } } Neste exemplo, a propriedade “Nome” controla o acesso ao campo privado “nome” e aplica uma lógica para remover espaços extras antes de atribuir um valor ao campo. Exemplo Agora que entendemos como funciona o encapsulamento, vamos aplicá-lo em uma situação real. Vamos simular um sistema de venda de lanches onde vamos criar uma classe “Pedido” com uma lista de “Itens”. Cada Item terá um preço, e a classe “Pedido” calculará o “PrecoTotal” dinamicamente com base nos preços dos itens. Vamos iniciar criando a classe “Item” e “Pedido”: public class Item { public string Nome { get; set; } public decimal Preco { get; set; } public Item(string nome, decimal preco) { Nome = nome; Preco = preco; } } public class Pedido { public List Itens { get; set; } = new List(); public decimal PrecoTotal { get { return Itens.Sum(item => item.Preco); } } public void AdicionarItem(Item item) { Itens.Add(item); } } No objeto “Pedido”, teremos uma lista de “Item” contendo os itens da venda. Além disso, teremos uma propriedade chamada “PrecoTotal”, que será uma propriedade calculada, responsável por retornar o preço total do pedido. Esse cálculo é feito utilizando o método “Sum” do LINQ, que soma todos os valores presentes na propriedade “Preco” de cada item. Também vamos adicionar o método “AdicionarITem” para adicionar novos

Entendendo o Content-Type em requisições HTTP

Ao desenvolver aplicações web e APIs REST, é essencial entender como os dados são enviados nas requisições HTTP. Um dos componentes fundamentais para isso é o cabeçalho Content-Type, que informa ao servidor (ou cliente) qual o formato dos dados presentes no corpo da requisição. Neste artigo, vamos explorar os principais tipos de Content-Type, explicando quando utilizar cada um, suas estruturas, exemplos práticos, vantagens, desvantagens e dicas úteis para fazer a escolha correta em seu projeto. O que é o Content-Type? O cabeçalho Content-Type faz parte da especificação HTTP e indica o tipo de mídia (MIME type) do corpo da requisição ou resposta. Ele é essencial para que o servidor saiba como interpretar os dados recebidos e respondê-los adequadamente. POST /api/usuario HTTP/1.1 Content-Type: application/json { “nome”: “João”, “email”: “joaonwe@nwe.com” } Ao definir “Content-Type: application/json”, estamos informando explicitamente que os dados enviados no corpo da requisição estão estruturados como um objeto JSON. Isso é fundamental para que o servidor saiba como processar essas informações, convertendo-as corretamente para um objeto ou estrutura interna apropriada. Sem esse cabeçalho, o servidor pode interpretar o conteúdo de forma incorreta, ocasionando erros de leitura, falhas na conversão dos dados ou respostas inesperadas. application/json O “Content-Type: application/json” é o mais utilizado atualmente em APIs REST. Ele informa que os dados no corpo da requisição estão estruturados em formato JSON (JavaScript Object Notation), um formato leve e de fácil leitura, usado para representar objetos, arrays, strings, números e valores booleanos. Esse tipo de conteúdo é ideal quando se está trabalhando com serviços web modernos, pois quase todas as linguagens de programação oferecem suporte nativo para leitura e escrita de JSON. É usado tanto para enviar quanto para receber dados estruturados, como ao criar ou atualizar um recurso em uma API. Vamos supor que você esteja enviando uma requisição para criar um novo post em uma API. Veja como isso é feito em C# com “HttpClient”: var httpClient = new HttpClient(); var url = “https://jsonplaceholder.typicode.com/posts”; var novoPost = new { title = “Tipos de envio de requisições HTTP (Content-Type)”, body = “Aprendendo sobre tipos de requisições na NWE”, userId = 1 }; var json = JsonSerializer.Serialize(novoPost); var content = new StringContent(json, Encoding.UTF8, “application/json”); var response = await httpClient.PostAsync(url, content); No código acima, inicializamos uma instância do “HttpClient” e criamos um objeto anônimo que representa os dados a serem enviados na requisição. Em seguida, utilizamos o “JsonSerializer” para converter esse objeto em uma string JSON. Para compor o corpo da requisição, criamos um “StringContent”, onde informamos o conteúdo serializado, a codificação (UTF-8) e o cabeçalho “Content-Type”, que neste caso é “application/json”. Vantagens: Formato leve e de fácil leitura para humanos e máquinas. Suporte nativo na maioria das linguagens de programação. Ideal para APIs RESTful modernas. Boa integração com frameworks e bibliotecas front-end (como Angular, React, Vue). Permite aninhamento de estruturas (objetos e arrays). Desvantagens: Não é adequado para envio de arquivos binários (como imagens). Pode exigir validação e serialização manual em sistemas que não têm suporte automático. multipart/form-data O “Content-Type: multipart/form-data” é utilizado principalmente quando precisamos enviar arquivos ou dados mistos (como textos e arquivos) em uma única requisição HTTP — especialmente formulários HTML que incluem input “type=”file””. Esse tipo de conteúdo divide o corpo da requisição em várias partes, separadas por um delimitador (chamado boundary). Cada parte representa um campo ou arquivo enviado, permitindo que tipos diferentes de dados sejam incluídos na mesma requisição. Para entendermos o seu funcionamento, vamos simular o envio de um formulário com nome e uma imagem usando “HttpClient”, e testaremos na URL pública “https://httpbin.org/post” que retorna exatamente o que foi recebido (útil para inspecionar o resultado). var httpClient = new HttpClient(); var url = “https://httpbin.org/post”; var caminhoArquivo = “@”C:UsersPicturesfoto.png”; using var fileStream = File.OpenRead(caminhoArquivo); using var form = new MultipartFormDataContent(); form.Add(new StringContent(“João da Silva”), “nome”); form.Add(new StreamContent(fileStream), “arquivo”, “foto.png”); var response = await httpClient.PostAsync(url, form); var resposta = await response.Content.ReadAsStringAsync(); Primeiro, abrimos o arquivo “foto.png” em modo leitura e criamos um objeto “MultipartFormDataContent”, que é responsável por estruturar corretamente as partes da requisição. Em seguida, adicionamos um campo de texto chamado “nome” com o valor “João da Silva” e um campo de arquivo chamado “arquivo”, onde enviamos o conteúdo binário da imagem. A chamada “httpClient.PostAsync(url, form)” envia os dados para a URL especificada.  Ao lermos a resposta podemos ver que nos foi retornado o arquivo enviado em  base64  e o texto enviado. Vantagens: Permite o envio de arquivos binários (imagens, PDFs etc.) e campos de texto simultaneamente. Amplamente utilizado em formulários HTML que envolvem upload de arquivos. Bem suportado por navegadores e frameworks. Desvantagens: Mais complexo para serializar/deserializar do que JSON ou x-www-form-urlencoded. Requisições maiores e menos eficientes em termos de tamanho do payload. application/x-www-form-urlencoded O “Content-Type: application/x-www-form-urlencoded” é um dos formatos mais antigos e ainda amplamente utilizados para o envio de dados em requisições HTTP — especialmente em formulários HTML simples. Nesse formato, os dados são codificados como pares “chave=valor”, separados por &. Caracteres especiais são escapados com encoding URL (ex: espaço vira %20 ou +). Esse conteúdo é colocado geralmente no corpo da requisição POST. var httpClient = new HttpClient(); var url = “https://httpbin.org/post”; var formData = new Dictionary { { “usuario”, “joao” }, { “idade”, “19” } }; var content = new FormUrlEncodedContent(formData); var response = await httpClient.PostAsync(url, content); var resposta = await response.Content.ReadAsStringAsync(); Primeiro, criamos um dicionário com os dados que queremos enviar no corpo da requisição — no caso, um campo “usuario” com valor “joao” e um campo “idade” com valor “19”. Em seguida, utilizamos a classe “FormUrlEncodedContent” para codificar esses dados no formato “application/x-www-form-urlencoded”, que os transforma em uma string como “usuario=joao&senha=123456”. Vantagens: Leve e fácil de gerar (ideal para formulários simples). Compatível com todos os navegadores e fácil de ser manipulado com HttpClient ou jQuery. Simples de integrar em sistemas legados ou com baixa complexidade. Desvantagens: Não suporta envio de arquivos. Dados codificados manualmente (ex: espaços, acentos), o que pode causar erros se não for bem tratado. Estrutura plana — não permite aninhamento de objetos ou arrays

Estruturas de dados em  C#

As estruturas de dados são formas específicas de organizar e armazenar dados em um programa, permitindo manipulação eficiente. A escolha correta da estrutura de dados impacta diretamente no desempenho da aplicação, influenciando o tempo de execução e o uso de memória. Neste artigo, vamos explorar as principais estruturas de dados em C#, explicar como funcionam e quando utilizá-las. O que são estruturas de dados? As estruturas de dados são formas específicas de organizar e armazenar dados de maneira que possibilitem uma manipulação eficiente. Elas são essenciais para garantir que as operações sobre os dados, como inserção, remoção, e acesso, sejam feitas de forma rápida e eficiente. O conceito de estrutura de dados envolve a organização dos dados em diferentes formatos para atender a necessidades específicas, como: Eficiência no uso da memória: algumas estruturas de dados são mais compactas e otimizadas para armazenar grandes volumes de informações. Eficiência na execução de operações: dependendo do tipo de estrutura, certas operações podem ser mais rápidas, como buscar um elemento ou adicionar um novo item. Estruturas de dados lineares As estruturas de dados lineares são aquelas em que os elementos são organizados de forma sequencial, ou seja, cada elemento tem um único predecessor e um único sucessor (exceto os elementos nas extremidades). Elas são ideais quando há a necessidade de acessar ou manipular dados de forma simples e direta, respeitando a ordem de inserção. Vamos conhecer algumas das estruturas lineares mais comuns em C#: Array Um array é uma estrutura de dados fixa, onde os elementos são armazenados de maneira contígua na memória. Eles têm um tamanho definido no momento da criação, e esse tamanho não pode ser alterado após a criação. Ele se caracteriza por: Acesso rápido aos elementos, usando o índice. Tamanho fixo, o que significa que o número de elementos deve ser conhecido de antemão. A memória é alocada de forma contígua, ou seja, os elementos são armazenados em sequência, um logo após o outro, o que torna o acesso mais rápido. A criação de um array pode ser feita da seguinte forma: int[] numeros = { 1, 2, 3, 4, 5 }; Console.WriteLine(numeros[2]); O código acima, acessa o elemento que está na  posição 2 que é igual a  3. List<T> A classe “List<T>” é uma coleção dinâmica em C# que permite armazenar elementos de forma sequencial. Diferente de um array, o tamanho da lista pode ser alterado dinamicamente, o que a torna mais flexível. Suas características são: Capacidade de redimensionar-se automaticamente conforme os elementos são adicionados ou removidos. Acesso a elementos via índice, como em um array, mas com a vantagem de não precisar definir o tamanho de antemão. A sua  construção pode ser feita da seguinte forma: List numeros = new List { 1, 2, 3, 4, 5 }; numeros.Add(6); Console.WriteLine(numeros[5]); No código acima, nós criamos uma lista com 5  elementos. Após isso, adicionamos mais um elemento (6). Por fim,  apresentamos no  console o elemento que está na posição 5 da lista, que neste caso é o 6. LinkedList<T> A ”LinkedList<T>” é uma lista encadeada onde cada elemento (nó) contém uma referência ao próximo elemento. Ao contrário de arrays e listas, os elementos da LinkedList não são armazenados de forma contígua na memória. Algumas características são: Cada elemento contém um ponteiro para o próximo elemento, o que permite inserções e remoções eficientes, especialmente no início ou no meio da lista. Acesso sequencial aos elementos, não é possível acessar diretamente um elemento por índice. Agora vamos ver um exemplo de LinkedList: LinkedList numeros = new LinkedList(); numeros.AddLast(1); numeros.AddLast(2); numeros.AddFirst(0); Console.WriteLine(numeros.First.Value); No código acima, criamos uma LinkedList de inteiros e adicionamos três valores: dois no final da lista e um no início. Em seguida, utilizamos a propriedade First para acessar o primeiro valor da lista, que, neste caso, é o 0. A LinkedList permite uma navegação flexível, sendo possível adicionar ou remover elementos em qualquer posição de forma eficiente. Quando usar: útil quando você precisa realizar inserções e remoções frequentes em qualquer posição da lista, especialmente no início ou no meio, onde uma List<T> poderia ser menos eficiente. Stack<T> A Stack<T> e uma estrutura de dados do tipo LIFO (Last In, First Out). Isso significa que o último item a ser inserido é o primeiro a ser removido, ou seja, a pilha funciona como uma “pilha de pratos”, onde você adiciona um prato no topo e retira o último prato colocado. Esse comportamento é útil em situações em que você precisa processar dados de trás para frente ou manter um controle sobre operações anteriores, como em um histórico de navegação ou execução de funções recursivas. Vamos ver o código do exemplo acima: Stack pilha = new Stack(); pilha.Push(1); pilha.Push(2); Console.WriteLine(pilha.Pop()); Console.WriteLine(pilha.Pop()); Quando usamos o “Pop()”, ele remove o último elemento inserido, que, no caso, é o 2, seguido pelo 1. Queue<T> A Queue<T> é uma estrutura de dados do tipo FIFO (First In, First Out). Isso significa que o primeiro item a ser inserido é o primeiro a ser removido.  Imagine uma fila de pessoas, onde a primeira pessoa a entrar na fila é a primeira a ser atendida. Esse comportamento é útil em situações onde os dados precisam ser processados na ordem em que foram recebidos, como em sistemas de processamento de tarefas ou controle de requisições. Vamos ver o código do exemplo acima: Queue fila = new Queue(); fila.Enqueue(1); fila.Enqueue(2); Console.WriteLine(fila.Dequeue()); Console.WriteLine(fila.Dequeue()); No código acima, o “Dequeue()” remove o primeiro elemento inserido. Assim, o primeiro Dequeue remove o 1 e o segundo Dequeue remove o 2, seguindo o princípio FIFO. Se você quiser se aprofundar mais sobre o Queue, pode conferir o artigo completo sobre o assunto. 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

Injeção de dependência com Reflection no .NET

A injeção de dependência (DI) é um dos pilares do desenvolvimento moderno com .NET, promovendo desacoplamento e facilitando testes e manutenção. Em aplicações ASP.NET Core, é comum registrarmos nossos serviços manualmente utilizando instruções como “builder.Services.AddScoped<IInterface, Implementacao>();” Mas… e se pudéssemos automatizar esse processo? Neste artigo, você aprenderá como usar Reflection para registrar automaticamente serviços, com base em convenções de nomes e interfaces, reduzindo repetição de código e aumentando a escalabilidade do projeto. O que é Reflection? O Reflection é uma funcionalidade da plataforma .NET que permite inspecionar, acessar e manipular metadados de tipos em tempo de execução. Em termos simples, com o Reflection, podemos descobrir informações sobre classes, interfaces, métodos, propriedades, atributos e até criar instâncias de objetos dinamicamente — sem saber seus tipos em tempo de compilação. Por exemplo, imagine que sua aplicação possui dezenas de serviços que seguem o padrão “INomeServico” e “NomeServico”. Com Reflection, podemos encontrar todos os tipos no projeto que seguem essa convenção, identificar suas interfaces e registrar automaticamente no container de injeção de dependência do ASP.NET Core. Essa análise é possível graças a classes presentes no namespace “System.Reflection”, como: Type: representa informações sobre um tipo (classe, interface, enum etc.); Assembly: representa um assembly carregado na aplicação; MethodInfo, PropertyInfo, ConstructorInfo: fornecem detalhes sobre os membros do tipo; Attribute: permite acessar atributos personalizados aplicados a tipos e membros. Vamos ver, na próxima seção, como aplicar esses conceitos na prática para automatizar o registro de serviços no ASP.NET Core usando Reflection com convenções. Criando as interfaces base para os serviços Para que o Reflection funcione de forma segura e organizada, vamos adotar uma convenção simples: todos os serviços que devem ser registrados automaticamente implementarão uma interface de marcação chamada “IService”. Essa interface não precisa declarar membros, ela serve apenas para identificar tipos que fazem parte do nosso sistema de injeção automática. public interface IService { } Além disso, vamos definir duas interfaces reais de serviço que herdam de “IService”, que serão utilizadas mais adiante. public interface IPessoaService : IService { Task BuscarPessoas(); } public interface IProdutoService : IService { Task BuscarProdutos(); } Criando as implementações concretas dos serviços Agora que definimos nossas interfaces “IPessoaService” e “IProdutoService”, que herdam da interface de marcação “IService”, vamos criar as implementações concretas: “PessoaService” e “ProdutoService”. Essas classes seguem uma convenção de nomenclatura fundamental para o registro automático via Reflection: a classe concreta deve ter o mesmo nome da interface, mas sem o prefixo I. Essa convenção será utilizada mais adiante para identificar qual classe deve ser registrada para qual interface, sem que precisemos fazer isso manualmente. public class PessoaService : IPessoaService { public async Task BuscarPessoas() { return await Task.FromResult(new List { “João da Silva”, “Maria Oliveira”, “Carlos Souza” }); } } public class ProdutoService : IProdutoService { public async Task BuscarProdutos() { return await Task.FromResult(new List { “Notebook Dell”, “Monitor LG”, “Teclado Mecânico” }); } } Agora vamos criar uma nova classe chamada “ServiceCollectionExtensions” onde vamos implementar o Reflection:     Registrando serviços automaticamente com Reflection Crie um método de extensão que adiciona os serviços dinamicamente. Vamos chamá-la de “RegistrarServicosComReflection”: public static class ServiceCollectionExtensions { public static IServiceCollection RegistrarServicosComReflection(this IServiceCollection services) { //Nosso código } } Agora vamos adicionar o nosso código: var tiposEncontrados = typeof(IService).Assembly .GetTypes() .Where(t => typeof(IService).IsAssignableFrom(t)); Neste trecho, usamos “typeof(IService).Assembly” para obter o assembly onde a interface “IService” está definida. Em seguida, usamos o método ”GetTypes()” para recuperar todos os tipos definidos neste assembly (classes, interfaces, enums, etc). Depois, com “Where(t => typeof(IService).IsAssignableFrom(t))”, filtramos apenas os tipos que implementam ou herdam de “IService”. Isso inclui tanto as interfaces concretas (IPessoaService, IProdutoService) quanto suas implementações (PessoaService, ProdutoService). var interfaces = tiposEncontrados.Where(t => t.IsInterface).ToList(); var classesConcretas = tiposEncontrados .Where(t => t.IsClass && !t.IsAbstract) .ToList(); Aqui fazemos uma separação entre os tipos encontrados no passo anterior: interfaces: seleciona apenas os tipos que são interfaces (t.IsInterface == true), como “IPessoaService” ou “IProdutoService”. classesConcretas: seleciona apenas as classes que são concretas, ou seja, não são abstratas e são classes reais que podem ser instanciadas (IsClass == true && IsAbstract == false). Exemplo: “PessoaService”, “ProdutoService”. Essa separação é essencial para sabermos qual implementação deve ser registrada para qual interface. foreach (var implementacao in classesConcretas) { var nomeEsperado = $”I{implementacao.Name}”; var interfaceCorrespondente = interfaces.FirstOrDefault(i => i.Name == nomeEsperado); Neste laço “foreach”, percorremos cada classe concreta que foi encontrada. Para localizar a interface correspondente, usamos a convenção de nome que assumimos anteriormente onde a interface tem o mesmo nome da classe, com um “I” no início, ou seja, a interface da classe “PessoaService” será “IPessoaService”. Montamos esse nome com “var nomeEsperado = $”I{implementacao.Name}””. Em seguida, usamos “FirstOrDefault” para buscar entre as interfaces encontradas aquela cujo nome bate com o nome esperado. if (interfaceCorrespondente != null) { services.AddScoped(interfaceCorrespondente, implementacao); } } Se a interface correspondente foi encontrada, registramos a combinação de interface e implementação no container de injeção de dependência do ASP.NET Core usando “services.AddScoped(…)”. Aqui usamos o tempo de vida Scoped, o que significa que será criada uma nova instância do serviço para cada requisição HTTP. Por fim, é só chamar o método de registro automático no “Program.cs”: builder.Services.RegistrarServicosComReflection(); Considerações e boas práticas Impacto de performance: o Reflection é mais lento do que chamadas diretas. Mas como esse processo ocorre apenas na inicialização  da aplicação. Convenções obrigatórias: o nome da interface deve seguir a convenção I{NomeDaClasse}. Se você tiver uma classe chamada “UsuarioService”, a interface deve se chamar “IUsuarioService”. Interface de marcação obrigatória: para que um serviço seja incluído automaticamente na injeção de dependência, ele deve implementar a interface de marcação IService. Outros tempos de vida: no exemplo implementado, os serviços são registrados apenas com o tempo de vida “Scoped”. Para suportar outros tipos, como “Singleton” ou “Transient”, é necessário adaptar o código para identificar e aplicar o tempo de vida adequado durante o registro. 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

Utilizando transações com  Dapper

As transações são essenciais para garantir a consistência e integridade dos dados em operações que envolvem múltiplos comandos no banco de dados. No .NET, o Dapper oferece uma maneira simples e eficiente de gerenciar transações com a interface “IDbTransaction”. Neste artigo, vamos explorar como implementar transações com Dapper. Já discutimos anteriormente como trabalhar com transações utilizando o Entity Framework em outro artigo, mas agora vamos focar na abordagem com o Dapper, que é uma alternativa mais leve e performática para acessar dados diretamente no banco. O que são transações As transações são um conjunto de operações executadas como uma única unidade de trabalho. Elas garantem que todas as alterações no banco de dados sejam concluídas com sucesso ou, em caso de falha, revertidas, mantendo a integridade dos dados. As transações seguem os princípios ACID: Atomicidade: ou todas as operações ocorrem, ou nenhuma ocorre. Consistência: o banco de dados sempre vai de um estado válido para outro. Isolamento: transações concorrentes não interferem umas nas outras. Durabilidade: alterações confirmadas são permanentes. As transações são essenciais para evitar dados inconsistentes ou falhas de integridade, especialmente em sistemas críticos. Elas garantem que, mesmo diante de falhas, o banco de dados permaneça em um estado confiável. Por que utilizá-las? Em sistemas reais, muitas operações exigem que várias ações no banco de dados sejam realizadas juntas. Se uma dessas ações falhar, não faz sentido que as outras permaneçam. Sem transações, o banco pode ficar em um estado inconsistente, o que é perigoso especialmente em sistemas financeiros, de estoque ou de controle de pedidos. Imagine um sistema de vendas. Ao registrar um novo pedido, duas ações são feitas: inserir os dados do pedido no banco de dados e atualizar o estoque, reduzindo a quantidade de itens disponíveis. Agora veja o seguinte cenário sem o uso de transações: using (var connection = new SqlConnection(connectionString)) { connection.Open(); // Passo 1: Inserir o pedido connection.Execute(“INSERT INTO Pedido (…) VALUES (…)”); // Passo 2: Deduzir o item do estoque connection.Execute(“UPDATE Produto SET Quantidade = Quantidade – 1 WHERE Id = @id”, new { id = 1 }); } Caso ocorra alguma  falha na segunda operação, como um erro de item inexistente ou estoque insuficiente, o pedido terá sido registrado sem que o estoque tenha sido atualizado. O banco de dados agora está inconsistente: o pedido existe, mas o produto não foi removido do estoque. Criando transações com IDbTransaction Para contornar essa situação, podemos utilizar a interface “IDbTransaction” do  Dapper, que permite iniciar, controlar e finalizar transações de forma eficiente. Primeiro, precisamos abrir uma conexão com o banco de dados. No Dapper, isso é feito utilizando um objeto “IDbConnection”, que pode ser configurado com a string de conexão adequada. using (IDbConnection dbConnection = new SqlConnection(connectionString)) { dbConnection.Open(); using (var transaction = dbConnection.BeginTransaction()) { // Operações dentro da transação } } Neste código, a conexão é criada dentro de um bloco “using”, o que significa que a instância será descartada automaticamente ao final do bloco. Após isso, criamos uma instância de “IDbConnection” e repassamos a string de conexão para o banco de dados. Com isso, podemos abrir a conexão com o  banco usando o método  “Open()”.  Após abrir a conexão com o banco de dados, iniciamos a transação. Para isso, usamos o método “BeginTransaction”, que cria e retorna um objeto “IDbTransaction”. Esse objeto será responsável por controlar o início e o fim da transação. Agora que a transação está ativa, podemos executar comandos SQL dentro dessa transação usando Dapper. Para garantir que a operação faça parte da transação, devemos passar o objeto transaction como parâmetro para os métodos de execução, como “Execute” ou “Query”. // Primeira operação: Inserir um pedido string queryInsertPedido = “INSERT INTO Pedidos (ClienteId, DataPedido) VALUES (@ClienteId, @DataPedido)”; dbConnection.Execute(queryInsertPedido, new { ClienteId = 1, DataPedido = DateTime.Now }, transaction: transaction); // Segunda operação: Atualizar o estoque de um produto string queryUpdateEstoque = “UPDATE Produtos SET Estoque = Estoque – @Quantidade WHERE Id = @ProdutoId”; dbConnection.Execute(queryUpdateEstoque, new { Quantidade = 1, ProdutoId = 758 }, transaction: transaction); Aqui, inserimos um novo pedido na tabela “Pedidos” e atualizamos o estoque de um produto na tabela “Produtos”. Essa operação subtrai 1 unidade do estoque de um produto específico. Essas duas ações devem acontecer com sucesso para que as alterações sejam salvas. Caso contrário, as modificações serão revertidas. Após a execução dos comandos, o próximo passo é decidir se as alterações devem ser confirmadas ou revertidas. Caso tudo ocorra sem erros, utilizamos “Commit()” para persistir as alterações. Se algo der errado, chamamos “Rollback()” para desfazer as operações realizadas até aquele momento. try { //Código implementado acima. transaction.Commit(); } catch (Exception) { transaction.Rollback(); throw; } Neste exemplo, se houver uma falha será lançada uma exceção e a transação é revertida com “Rollback()”, garantindo que o banco de dados não seja alterado de forma inconsistente. Boas práticas com transações Ao trabalhar com transações, algumas boas práticas devem ser seguidas: Manter transações curtas: transações longas podem causar bloqueios no banco de dados, impactando o desempenho. Evite manter transações abertas por tempo prolongado. Sempre usar using: usar a instrução using garante que os objetos de conexão e transação sejam descartados corretamente após o uso, evitando vazamentos de recursos. Sem o “using” seria necessário fechar a transação  manualmente com o             “await connection.CloseAsync();” Capturar exceções específicas: ao capturar exceções, evite usar blocos catch genéricos. Isso ajuda a diagnosticar problemas mais rapidamente. 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 As transações são essenciais para garantir a integridade dos dados em sistemas que exigem operações atômicas. Ao utilizar Dapper, temos a vantagem de um controle direto sobre a transação, o que torna o processo eficiente e flexível. Seguir boas práticas e

Protegendo senhas em C#: como implementar hashing seguro com salt

A segurança das informações é fundamental no desenvolvimento de aplicações. Entre os dados mais sensíveis, as senhas se destacam, pois são usadas para autenticar usuários e proteger recursos restritos. Armazenar senhas em texto simples representa um grande risco, pois, se comprometidas, podem expor credenciais valiosas. Para evitar isso, a criptografia de senhas tornou-se uma prática essencial, garantindo a proteção das informações de forma eficaz e segura. Neste artigo, veremos como implementar a criptografia de senhas em C#, utilizando técnicas modernas e recomendadas. Por que senhas devem ser criptografadas (ou melhor, “hashadas”) Como falado anteriormente, armazenar senhas em texto simples representa um grande risco à segurança. Se um banco de dados for comprometido, os invasores terão acesso direto às credenciais dos usuários. Em vez de armazenar senhas em texto simples, a prática recomendada é utilizar hashing, que transforma a senha em uma string irreversível, tornando a recuperação impossível em caso de vazamento. Sem o uso de hashing, senhas ficam vulneráveis a ataques de força bruta e ataques de dicionário, onde os invasores tentam todas as combinações possíveis ou utilizam listas comuns de senhas. O uso de algoritmos de hashing fortes e salt aumenta a segurança, dificultando a quebra da senha, garantindo maior proteção contra vazamentos e ataques. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo. O que é salt e por que usá-lo Salt é um valor aleatório adicionado à senha antes de aplicar o algoritmo de hash. Seu principal objetivo é garantir que senhas idênticas tenham hashes diferentes, dificultando ataques como rainbow table, que são baseados em tabelas pré computadas de hashes comuns. Ao adicionar salt, mesmo que dois usuários escolham a mesma senha, seus hashes serão únicos, aumentando a segurança do armazenamento de senhas. Hash vs criptografia A diferença fundamental entre hash e criptografia está no fato de que a criptografia é um processo reversível, enquanto o hash é irreversível. A criptografia transforma dados em uma forma que pode ser revertida, ou seja, os dados criptografados podem ser descriptografados usando uma chave específica. Isso é útil quando precisamos recuperar a informação original, como em comunicações seguras. No entanto, quando aplicada a senhas, a criptografia pode apresentar riscos, pois, se a chave de descriptografia for comprometida, as senhas podem ser recuperadas facilmente. Já o hashing gera um valor fixo e único a partir da senha, mas sem a possibilidade de reverter esse processo. Isso significa que, mesmo que o hash da senha seja exposto, não é possível obter a senha original a partir dele. O uso de algoritmos de hash fortes, juntamente com técnicas como salt, torna o processo muito mais seguro, dificultando ataques como força bruta ou dicionário. Por essas razões, o hashing é a abordagem mais segura para senhas, pois não há risco de reversão e a integridade das senhas permanece protegida mesmo em caso de vazamento dos dados hashados. Usando PBKDF2, BCrypt ou Argon2 Para garantir uma boa segurança ao armazenar senhas, é essencial usar algoritmos de hashing robustos, juntamente com técnicas como salt para proteger contra ataques de rainbow table. Vamos explorar alguns dos algoritmos mais comuns e como implementá-los em C#. PBKDF2 O  PBKDF2 (Password-Based Key Derivation Function 2) é uma função de derivação de chave projetada especificamente para fortalecer senhas através de várias iterações e a adição de salt. Isso torna o processo de hashing muito mais demorado e seguro contra ataques de força bruta e rainbow table. public static string HashPassword(string password, string salt) { byte[] saltBytes = Encoding.UTF8.GetBytes(salt); var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000); byte[] hashedPassword = pbkdf2.GetBytes(32); return Convert.ToBase64String(hashedPassword); } Neste exemplo, utilizamos a classe “Rfc2898DeriveBytes”, que é uma implementação do algoritmo PBKDF2. Ela recebe a senha, o salt e o número de iterações (geralmente recomendado um valor elevado, como 10.000). Quanto maior o número de iterações, mais difícil se torna realizar um ataque de força bruta, pois o processo de hashing fica mais demorado e complexo. Após o processamento, os bytes resultantes são armazenados em uma variável, e então retornamos a string do hash gerado. Como armazenar o salt e o hash Uma prática bastante utilizada para armazenar senhas de forma segura é armazenar o salt e o hash separadamente no banco de dados. O salt deve ser único para cada usuário. O hash gerado pela senha concatenada com o salt pode ser armazenado de forma segura. CREATE TABLE Users ( Id INT PRIMARY KEY, Username VARCHAR(255), PasswordHash VARCHAR(255), Salt VARCHAR(255) ); Validando a senha Agora que vimos como criar e armazenar o hash e o salt, vamos ver como validar a senha fornecida pelo usuário durante o login. Para isso, você precisa comparar o hash da senha fornecida com o hash armazenado no banco de dados. public static bool VerifyPassword(string enteredPassword, string storedSalt, string storedHash) { string enteredHash = HashPassword(enteredPassword, storedSalt); return enteredHash == storedHash; } No código acima, o salt armazenado é recuperado do banco de dados e combinado com a senha fornecida pelo usuário. O processo de hash é então repetido usando o mesmo algoritmo (como PBKDF2) para gerar um novo hash. Esse hash gerado é comparado com o hash armazenado. Se os dois hashes coincidirem, a senha fornecida é válida. BCrypt Agora vamos falar sobre o BCrypt que é um algoritmo de hashing de senha projetado para ser lento e resistente a ataques de força bruta. Ele usa salt de maneira automática e é muito eficaz para proteger senhas de maneira segura. BCrypt é amplamente recomendado para aplicações modernas devido à sua resistência a ataques. Para usar o “BCrypt” em C#, você pode utilizar a biblioteca “BCrypt.Net” (disponível no NuGet). Install-Package BCrypt.Net-Next Para criar o hash de uma senha basta implementar o “BCrypt” da seguinte forma:   public static string HashPassword(string password) { return BCrypt.HashPassword(password); } Já para verificar se uma senha está correta, pode ser feito da seguinte forma: public static bool VerifyPassword(string password, string storedHash) { return BCrypt.Verify(password, storedHash); } O “BCrypt” faz todo o trabalho para você, incluindo o gerenciamento do

Padrão de projeto Facade em C#: simplificando interfaces complexas

O padrão de projeto Facade é um dos padrões estruturais descritos no catálogo de Design Patterns de Gang of Four. Sua principal finalidade é fornecer uma interface simplificada para sistemas complexos. Ao utilizar o padrão Facade, você oculta a complexidade interna de subsistemas, apresentando uma interface unificada e fácil de usar. Neste artigo, vamos explorar o que é o padrão Facade, como ele resolve problemas de acoplamento entre subsistemas, apresentar um exemplo prático em C#. O que é o padrão Facade?   Imagine que você tem um sistema com várias partes complexas, como pagamento, estoque, envio de produtos, etc. Sem o padrão Facade, para fazer uma compra online, você precisaria interagir diretamente com cada uma dessas partes. Isso torna o código confuso e difícil de manter. O padrão Facade é uma solução para isso. Ele cria uma “fachada” (uma camada externa) que simplifica a interação com o sistema, ocultando a complexidade e oferecendo uma interface mais simples e fácil de usar. Em vez de interagir diretamente com várias partes do sistema, você usa a fachada para realizar tudo de forma simples. A definição formal do padrão Facade, segundo o catálogo de Design Patterns, é: “O padrão Facade fornece uma interface unificada para um conjunto de interfaces em um subsistema. A fachada define uma interface de mais alto nível que torna o subsistema mais fácil de usar.” Problema que o padrão resolve O padrão Facade resolve um problema muito comum em sistemas grandes: o alto acoplamento entre os componentes. Sem a fachada, o cliente precisa conhecer os detalhes de cada parte do sistema e interagir com elas diretamente. Isso torna o código mais difícil de entender e de mudar no futuro. Usando o padrão Facade, o cliente interage com um único ponto (a fachada) e não precisa se preocupar com os detalhes internos de cada parte do sistema. O código fica mais limpo, organizado e mais fácil de manter. Entendendo o padrão no código Agora, vamos ver um exemplo prático em C#. Vamos imaginar que estamos desenvolvendo um sistema de loja online. Para que um cliente faça uma compra, várias etapas precisam ser realizadas, como: Verificar o estoque: precisamos verificar se o produto está disponível. Processar o pagamento: depois, o pagamento precisa ser processado. Realizar o envio: após o pagamento aprovado, o produto precisa ser enviado ao cliente. Enviar notificação: e, por fim, o cliente deve ser notificado sobre o status do pedido. Sem o padrão Facade, o cliente do sistema precisaria interagir diretamente com cada uma dessas partes de forma individual, o que ficaria algo assim: Estoque estoque = new Estoque(); bool produtoDisponivel = estoque.VerificarDisponibilidade(produtoId); Pagamento pagamento = new Pagamento(); bool pagamentoAprovado = pagamento.ProcessarPagamento(valor); Envio envio = new Envio(); envio.RealizarEnvio(endereco); Notificacao notificacao = new Notificacao(); notificacao.EnviarNotificacao(email); Ou seja, o cliente teria que entender e interagir com várias partes do sistema (estoque, pagamento, envio e notificação), o que torna o código mais difícil de ler, entender e manter. Cada uma dessas classes tem seu próprio conjunto de métodos e detalhes internos. Com o padrão Facade, podemos encapsular essas interações em uma única classe fachada, que oferece um único ponto de entrada para o cliente. O cliente não precisa mais interagir com cada subsistema individualmente, mas sim com a fachada, que cuida de orquestrar tudo internamente. public class LojaOnlineFacade { private Estoque _estoque; private Pagamento _pagamento; private Envio _envio; private Notificacao _notificacao; public LojaOnlineFacade() { _estoque = new Estoque(); _pagamento = new Pagamento(); _envio = new Envio(); _notificacao = new Notificacao(); } public void RealizarCompra(int produtoId, double valor, string endereco, string email) { if (_estoque.VerificarDisponibilidade(produtoId)) { if (_pagamento.ProcessarPagamento(valor)) { _envio.RealizarEnvio(endereco); _notificacao.EnviarNotificacao(email); Console.WriteLine(“Compra realizada com sucesso!”); } else { Console.WriteLine(“Falha no pagamento.”); } } else { Console.WriteLine(“Produto indisponível no estoque.”); } } } Agora, o cliente só precisa chamar a fachada e passar as informações necessárias para a compra, sem se preocupar com os detalhes internos: var loja = new LojaOnlineFacade(); loja.RealizarCompra(1, 100.0, “Rua A, 100”, “nwe@email.com”); Benefícios do Facade Ao utilizar o padrão Facade, o cliente do sistema não precisa entender como cada uma dessas partes funciona. O código fica muito mais simples, e a interação com o sistema é muito mais direta. Em vez de ter que se preocupar com detalhes sobre como o estoque, o pagamento, o envio e a notificação funcionam, o cliente só interage com uma única classe, a fachada. Outros Benefícios: Encapsulamento da complexidade: o Facade esconde a complexidade dos subsistemas, oferecendo uma interface simples e intuitiva. Redução de dependências: o cliente não depende de cada um dos sistemas internamente, apenas da fachada. Facilidade de manutenção: se, no futuro, o funcionamento de qualquer subsistema (como o pagamento ou o envio) mudar, você só precisará alterar o código dentro da fachada, sem afetar o código do cliente. Como isso facilita a manutenção? Imagina que no futuro, por algum motivo, o processo de pagamento muda e você precisa alterar a forma como o pagamento é processado. Sem o Facade, você teria que modificar todas as partes do sistema que interagem com o pagamento. Mas com o Facade, você faz a alteração apenas dentro da classe “Pagamento”, e a fachada se encarrega de manter a interface simples para o cliente. Quando aplicar o padrão Facade O padrão Facade é ideal para: Sistemas complexos: quando você tem um sistema com múltiplos subsistemas interdependentes, o Facade ajuda a simplificar o acesso e reduzir o acoplamento. Manutenção e legibilidade: quando a manutenção e a legibilidade do código são preocupações, a utilização de uma fachada pode tornar o código mais modular e fácil de entender. Integrações com múltiplos sistemas: se o seu sistema integra-se com várias bibliotecas ou sistemas externos, a fachada pode fornecer uma interface única e simples para essas integrações. Ao usar o padrão Facade, você mantém seu código mais modular e organizado, facilitando futuras manutenções e alterações no sistema. 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

Stack e Heap: Como C# usa a memória do computador

A compreensão de como a memória é gerida pelo .NET durante a execução de uma aplicação é fundamental para escrever código mais eficiente e seguro. Em C#, as duas principais áreas de alocação de memória são a Stack e a Heap. Neste artigo, vamos explorar essas duas áreas de memória, seus comportamentos e como elas funcionam. Separação na memória Antes de adentrarmos nas especificidades da Stack e da Heap, é importante entender como a memória do computador é organizada e separada durante a execução de um programa. No contexto do .NET e do C#, a memória é dividida em diferentes seções, cada uma com uma função específica: Área de código (Code Area): é onde o código executável do programa é armazenado. Aqui ficam as instruções de execução do seu programa. Área de dados (Data Area): aqui são armazenados os dados globais e estáticos, ou seja, as variáveis que são persistentes durante toda a execução do programa, como constantes e variáveis estáticas. Stack: como abordaremos mais adiante, é a memória usada para armazenar dados temporários, como variáveis locais e parâmetros de funções. Cada thread tem sua própria Stack. Heap: é a área de memória usada para armazenar objetos e dados dinâmicos. Diferente da Stack, a alocação na Heap não é automática e é gerida pelo Garbage Collector. Stack A Stack é uma área de memória usada para armazenar variáveis locais e tipos de valor durante a execução de um programa. Sua principal característica é que a memória nela alocada é liberada automaticamente quando o bloco de código em que foi criada sai de escopo, ou seja, a memória é desalocada de maneira muito eficiente. Como funciona a Stack? Quando um método é chamado, uma pilha de execução é criada para armazenar as variáveis locais desse método. Cada vez que um novo método é chamado, um novo bloco de memória é alocado na pilha para guardar suas variáveis. Quando a execução retorna do método, o bloco é destruído, e a memória é liberada. Esse processo é extremamente rápido porque o gerenciamento de memória na pilha segue o padrão LIFO (Last In, First Out), o que significa que a memória alocada é liberada na ordem inversa da alocação. No exemplo acima, podemos ver uma ilustração do funcionamento da memória Stack. Temos um método “VendaProduto”, onde é declarada uma variável “valorProduto” com o valor “25.99”. Essa variável é alocada na Stack. Após isso, chamamos o método “CalcularImposto”, passando o valor de “valorProduto” como argumento. Dentro do método “CalcularImposto”, são criadas duas variáveis: uma chamada “imposto”, que armazena a taxa de imposto (0.2), e outra chamada “valorImposto”, que armazena o resultado do cálculo do imposto. Ambas as variáveis são alocadas na Stack. Quando o método “CalcularImposto” termina sua execução e retorna para o método “VendaProduto”, as variáveis “imposto” e “valorImposto” são descartadas da Stack, pois não são mais necessárias. Esse processo de alocação e desalocação de memória é gerido automaticamente pela Stack, garantindo que a memória seja utilizada de forma eficiente e liberada quando não for mais necessária. O que é armazenado na Stack? Na Stack, são armazenadas: Variáveis locais: como int, double, decimal, struct… Parâmetros de método: são passados para funções durante a execução. Referências a objetos na Heap: a Stack não armazena os objetos diretamente, mas pode armazenar referências a eles. Veremos isso mais a frente. Características da Stack Desalocação automática: quando a execução de uma função termina, a memória utilizada por ela na Stack é automaticamente liberada. Desempenho: a alocação e desalocação de memória são muito rápidas, pois não envolvem a complexidade do gerenciamento de objetos. Limitação de tamanho: a Stack tem um tamanho fixo e limitado, o que pode levar a “StackOverflowException” se houver recursão excessiva ou alocações grandes demais. Heap A Heap é uma área de memória usada para armazenar objetos e tipos por referência. Ao contrário da Stack, a alocação de memória na Heap não é automática, e o gerenciamento dessa memória é feito pelo Garbage Collector (GC). Como funciona a Heap? A alocação de objetos na Heap ocorre dinamicamente, o que significa que os objetos não são destruídos quando saem de escopo. Em vez disso, o Garbage Collector realiza a limpeza dos objetos da Heap quando não há mais referências a eles. No exemplo acima, vemos a alocação de um objeto “Produto” na Heap. O objeto é criado no método “VendaProduto”, onde a variável “produto” é uma referência para esse objeto, armazenada na Stack. A Stack, por sua vez, armazena apenas o endereço de memória do objeto criado, não o objeto em si. Quando o método “ProcessarVenda” é chamado, o parâmetro produto recebe essa referência e o método pode acessar diretamente o objeto na Heap. Após a execução do método, o objeto “Produto” permanece na Heap, aguardando para ser coletado pelo Garbage Collector, caso não haja mais referências a ele. O Garbage Collector é responsável por liberar a memória quando o objeto não for mais utilizado. Características da Heap Tipos por referência: como classes, arrays e objetos em geral. Gerenciamento: O Garbage Collector cuida da desalocação da memória. Capacidade: A Heap tem uma capacidade muito maior que a Stack, permitindo armazenar objetos de tamanhos variáveis. Comparação entre Stack e Heap A tabela abaixo mostra uma comparação entre os dois tipos de memória: Característica Stack Heap Tipo de alocação LIFO (Last In, First Out) Alocação dinâmica Gerenciamento Desalocação automática Gerenciado pelo Garbage Collector Armazenamento Tipos por valor (int, double, struct) Tipos por referência (classe, objeto) Velocidade Muito rápido Mais lento devido ao GC Tamanho Limitado Muito grande Uso Variáveis locais, parâmetros Objetos, arrays 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: