Regex em C#: Trabalhando com Expressões Regulares

As expressões regulares (ou Regex) são padrões utilizados para identificar, validar, buscar e manipular textos de forma eficiente. Em outras palavras, elas são uma maneira de “descrever” o que você está procurando em um texto. Sejam validações de formulários, extração de dados ou ajustes em strings, o uso de Regex pode simplificar bastante essas tarefas. No C#, você pode usar o namespace “System.Text.RegularExpressions” para trabalhar com Regex de maneira simples e prática. Como funcionam as expressões regulares? Uma expressão regular combina caracteres especiais para formar padrões. Esses padrões podem ser usados para dizer ao programa exatamente o que procurar. Vamos explorar alguns exemplos para entender melhor. Verificando o início de uma string O metacaractere “^” é usado para verificar se uma string começa com determinados tipos de caracteres. Para aceitar letras minúsculas, você pode usar “^[a-z]”. Essa expressão indica que o texto deve começar com uma letra minúscula de “a” a “z”. Para aceitar letras maiusculas, minúsculas ou números, use “^[a-zA-Z0-9]”. Essa expressão define que o texto pode iniciar com uma letra (maiuscula ou minúscula) ou um número. Em muitos casos, como na validação de senhas, você pode querer garantir que a string comece com uma letra maiuscula. Para isso, utiliza-se a expressão: string senha = “Segura123”; bool comecaComMaiuscula = Regex.IsMatch(senha, “^[A-Z]”); Console.WriteLine(comecaComMaiuscula ? “Senha válida” : “Senha inválida”); Se a senha for “Segura123”, a saída será “Senha válida”. Verificando o final de uma string com $ Já o símbolo “$” é usado para verificar se uma string termina com algo específico. Isso é útil, por exemplo, para validar extensões de arquivos. Com exemplo, imagine que você deseja garantir que o nome de um arquivo termina com a extensão “.txt”, pode usar a expressão “.txt$” string arquivo = “documento.txt”; bool ehTxt = Regex.IsMatch(arquivo, “.txt$”); Console.WriteLine(ehTxt ? “Arquivo de texto” : “Outro tipo de arquivo”); Se o arquivo for “documento.txt”, a saída será “Arquivo de texto”. Combinando esses elementos, é possível criar padrões mais complexos para resolver problemas comuns de maneira eficiente. Classes principais em System.Text.RegularExpressions Como vimos nos exemplos acima, a classe “Regex” é a utilizada para trabalhar com expressões regulares no C#. Ela oferece métodos para correspondência, validação e substituições em strings. IsMatch O método “IsMatch” responde com um “true” ou “false” para saber se um padrão é encontrado no texto. string input = “ABC123″; string padrao = @”^ABC”; if (Regex.IsMatch(input, padrao)) { Console.WriteLine($”‘{input}’ começa com ‘ABC’.”); } else { Console.WriteLine($”‘{input}’ não começa com ‘ABC’.”); } O padrão “^ABC” é composto por duas partes: ^: Indica que a correspondência deve ocorrer no início da string. ABC: Representa o texto literal “ABC”. Este regex verifica se a string começa exatamente com os caracteres “ABC”. Match Com o método “Match”, você pode encontrar a primeira correspondência de um padrão no texto. Match match = Regex.Match(“Meu telefone é 123-456-7890″, @”d{3}-d{3}-d{4}”); if (match.Success) { Console.WriteLine(match.Value); } O padrão “d{3}-d{3}-d{4}” é usado para encontrar um número de telefone no formato “123-456-7890”. Ele é composto por: d{3}: Representa exatamente 3 dígitos (o d significa “qualquer dígito” e {3} especifica que queremos 3 dígitos). -: Um hífen literal que deve estar entre os grupos de números. d{3}: Outro grupo de 3 dígitos. -: Outro hífen. d{4}: Um grupo de 4 dígitos. Este regex encontra a primeira sequência que corresponde a esse formato. No exemplo, o número “123-456-7890” é encontrado e exibido. Observação: ao colocar o “@” antes de uma string, você pode usar barras invertidas () sem precisar duplicá-las. Por exemplo, ao invés de escrever “\d” para representar um dígito, você pode usar “d”, tornando o código mais simples e fácil de ler. Matches O método “Matches” retorna todas as correspondências encontradas em uma string. MatchCollection matches = Regex.Matches(“123, 456, 789″, @”d+”); foreach (Match m in matches) { Console.WriteLine(m.Value); } O padrão “d+” é usado para encontrar qualquer sequência de um ou mais dígitos: d: Significa “qualquer dígito”. +: Indica “um ou mais” da expressão que o precede. Esta regex encontra todas as sequências numéricas na string. No exemplo, ele encontra “123”, “456”, e “789” separadamente, porque cada grupo de números é delimitado por uma vírgula e espaço. Replace: Substituir partes do texto Com o método Replace, você pode substituir partes de uma string com base em um padrão. string result = Regex.Replace(“2024-12-22”, “-“, “/”); Console.WriteLine(result); O método “Regex.Replace” substitui todas as ocorrências do hífen (-) pelo caractere barra (/). No exemplo, a string “2024-12-22” é transformada em “2024/12/22” ao substituir cada hífen por uma barra. Aplicações práticas no dia a dia Agora que você conhece os fundamentos, vamos explorar um exemplo prático de expressões regulares que pode ser usado em situações reais. Para isso, vamos criar um regex de validação de e-mails que será retornado se o e-mail é válido ou não. string email = “nextwaveeducation@mail.com”; string padraoEmail = @”^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$”; bool emailValido = Regex.IsMatch(email, padraoEmail); Console.WriteLine(emailValido ? “Email válido” : “Email inválido”); O regex utilizado acima para realizar a validação do e-mail está separado da seguinte forma: O “^[a-zA-Z0-9._%+-]+” verifica se o texto começa com um ou mais caracteres que podem ser letras maiúsculas (A-Z), letras minúsculas (a-z), números (0-9), ponto (.), sublinhado (_), percentual (%), mais (+) ou hífen (-). Após isso, é adicionado o símbolo “@”, que é obrigatório e indica o início do domínio. Essa parte exige a presença de letras, números, pontos e hífens logo após o símbolo “@”. Isso é representado pelo trecho “[a-zA-Z0-9.-]+”. A terceira parte informa que é necessário um ponto (.), que representa um ponto literal. Por fim, a última parte, “[a-zA-Z]{2,}$”, exige que o e-mail termine com pelo menos duas letras, indicando a extensão do domínio, como “.com” ou “.org”’. O “$” garante que o texto termina exatamente nesta condição. Construção e teste de Regex Caso você queira praticar ou testar expressões regulares, existem alguns sites que podem ser extremamente úteis para auxiliar nesse processo. Regex101 : Uma plataforma popular para praticar expressões regulares de forma interativa. Ela destaca cada parte do padrão com explicações detalhadas. RegexStorm : Ideal para quem desenvolve em .NET,
Trabalhando com Filas (Queues) em C#

Imagine que você está em um banco. Quem chega primeiro é atendido primeiro, seguindo o princípio básico de uma fila comum, conhecido como FIFO (First In, First Out). No entanto, há situações em que algumas pessoas têm prioridade, como idosos ou gestantes, que são atendidas antes, independentemente da ordem de chegada. Em programação, esse conceito é representado pelas filas normais e filas prioritárias. Neste artigo exploraremos esses dois tipos de filas e seus conceitos, aplicações práticas e como implementá-los em C#. Aplicação do conceito de filas Filas são amplamente utilizadas em diversas situações, como: Gerenciamento de tarefas: você pode usar filas para garantir que as tarefas sejam processadas na ordem em que foram recebidas. Simulações de processos reais: como filas de espera, onde os clientes são atendidos conforme sua ordem de chegada. Armazenamento temporário de dados: quando você precisa armazenar dados enquanto espera que sejam processados assincronamente. Algoritmos de busca: Como a busca em largura (BFS), que explora elementos em uma ordem específica. A classe Queue no .NET No .NET, a classe “Queue<T>” é uma implementação de fila genérica que fornece métodos simples para manipulação de dados em uma fila. Ela oferece eficiência e facilidade de uso. Principais métodos da classe Queue<T> Enqueue(T item): adiciona um item ao final da fila. Dequeue(): remove e retorna o item no início da fila. Se a fila estiver vazia, uma exceção será lançada. Peek(): retorna o item no início da fila sem removê-lo, mas lança uma exceção se a fila estiver vazia. Count: retorna o número total de itens na fila. Clear(): remove todos os itens da fila. Agora vamos ver um exemplo de uso: using System; using System.Collections.Generic; class Cliente { public string Nome { get; set; } public int SenhaAtendimento { get; set; } } public class Program { public static void Main() { Queue fila = new Queue(); fila.Enqueue(new Cliente { Nome = “José Rodrigues”, SenhaAtendimento = 101 }); fila.Enqueue(new Cliente { Nome = “Marcos Silva”, SenhaAtendimento = 102 }); fila.Enqueue(new Cliente { Nome = “Ana Souza”, SenhaAtendimento = 103 }); Console.WriteLine(“nProcessando a fila:”); while (fila.Count > 0) { Cliente proximoCliente = fila.Peek(); Console.WriteLine($”Próximo: {proximoCliente.Nome} (Senha: {proximoCliente.SenhaAtendimento})”); Cliente clienteAtual = fila.Dequeue(); Console.WriteLine($”Atendendo:{clienteAtual.Nome}(Senha: {clienteAtual.SenhaAtendimento})”); } Console.WriteLine($”nClientes restantes na fila: {fila.Count}”); } } No exemplo acima criamos uma fila (Queue<Cliente>) para armazenar objetos do tipo “Cliente”. Isso garante que apenas objetos desse tipo possam ser adicionados. Utilizamos o método “Enqueue” para inserir clientes na fila. O loop while processa os clientes enquanto houver elementos na fila. Antes de atender um cliente, usamos “Peek” para obter uma prévia do próximo cliente na fila sem removê-lo. Isso simula um painel informando quem será o próximo a ser atendido. Após isso, o método “Dequeue” remove o primeiro cliente da fila e retorna seu valor. Assim, o cliente é efetivamente atendido e retirado da fila. Após processar todos os clientes, exibimos a contagem restante usando “fila.Count”, que será igual a “0”. Fila Prioritária (Priority Queue) Diferentemente das filas tradicionais, nas quais os elementos são processados na ordem em que foram adicionados (FIFO), as filas prioritárias organizam os elementos com base em uma prioridade associada a cada item. O elemento com a maior prioridade é processado primeiro, independentemente da ordem de chegada. Quando usar uma Fila Prioritária? As filas prioritárias são úteis em diversas situações, como: Sistemas de saúde: para garantir que pacientes em estado grave sejam atendidos antes dos outros. Sistemas operacionais: no agendamento de processos, onde tarefas críticas têm precedência. Jogos: para gerenciar eventos em tempo real, priorizando os mais importantes. A classe PriorityQueue no .NET No .NET 6 foi introduzida a classe “PriorityQueue<TElement, TPriority>”, que oferece uma implementação eficiente e fácil de usar para filas prioritárias. using System; using System.Collections.Generic; public class Program { public static void Main() { PriorityQueue filaPrioritaria = new PriorityQueue(); filaPrioritaria.Enqueue(“Paciente Leve”, 3); filaPrioritaria.Enqueue(“Paciente Moderado”, 2); filaPrioritaria.Enqueue(“Paciente Grave”, 1); Console.WriteLine(“Processando a fila prioritária:”); while (filaPrioritaria.Count > 0) { string proximoPaciente = filaPrioritaria.Peek(); Console.WriteLine($”Próximo a ser atendido: {proximoPaciente}”); string pacienteAtendido = filaPrioritaria.Dequeue(); Console.WriteLine($”Atendendo: {pacienteAtendido}”); } Console.WriteLine(“Todos os pacientes foram atendidos.”); } } Criamos uma fila prioritária de strings (PriorityQueue<string, int>) em que o nome do paciente é o elemento e sua prioridade é representada por um número inteiro. Os elementos com menor valor de prioridade (ex.: 1) têm precedência sobre os de maior valor. Usamos o método “Enqueue” para adicionar pacientes com suas respectivas prioridades. Em seguida o loop while processa os pacientes, atendendo primeiro os de maior prioridade (menor valor de prioridade numérica). Por fim, exibimos o próximo paciente com “Peek” antes de removê-lo e atendê-lo com “Dequeue”. Esse modelo é ideal para cenários que exigem um atendimento organizado por relevância ou urgência, como sistemas hospitalares ou gerenciamento de eventos crí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 As filas são estruturas de dados bastente utilizadas para resolver problemas que requerem ordenação e processamento sequencial ou baseado em prioridade. Com a classe “Queue<T>” e a “PriorityQueue<TElement, TPriority>”, você pode implementar soluções para cenários que vão desde a simulação de processos reais até o desenvolvimento de algoritmos eficientes. Escolher o tipo correto de fila para cada caso de uso é fundamental para garantir a eficiência e a clareza do seu código.
Utilizando transações no Entity Framework Core

Transações são um conceito essencial no gerenciamento de bancos de dados, pois garantem a consistência e a integridade dos dados durante a execução de operações. No contexto de um aplicativo que interage com um banco de dados, as transações desempenham um papel crucial para garantir que, quando múltiplas operações são realizadas, elas ocorram como uma única unidade atômica. Neste artigo vamos explorar como implementar transações no Entity Framework Core. O que são transações? Uma transação é um conjunto de operações que são tratadas como uma única unidade de trabalho. As transações possuem quatro propriedades essenciais, conhecidas pela sigla ACID: Atomicidade: ou seja, ou todas as operações dentro da transação são completadas com sucesso, ou nenhuma delas é. Em outras palavras, se uma falha ocorrer durante a execução da transação, todas as alterações realizadas até aquele ponto devem ser revertidas. Consistência: ao final da transação, o banco de dados deve estar em um estado consistente, ou seja, deve respeitar todas as regras de integridade e as restrições impostas. Isolamento: as alterações realizadas dentro de uma transação não devem ser visíveis para outras transações até que a transação atual seja completada (commit). Durabilidade: após o commit de uma transação, as alterações realizadas são permanentes, mesmo que ocorra uma falha de sistema. Quando se trabalha com um banco de dados, é comum realizar múltiplas operações (como inserir, atualizar ou excluir dados) em uma única execução. Sem transações, se uma dessas operações falhar, o banco de dados pode ficar em um estado inconsistente. É aqui que as transações se tornam essenciais. Transações explícitas com EF Core O EF Core oferece suporte a transações explícitas, onde você pode criar e gerenciar a transação de maneira manual. Isso é útil quando você precisa de maior controle sobre o ciclo de vida da transação, como em operações complexas que envolvem várias etapas ou interações com diferentes fontes de dados. Imaginemos uma situação em que você precisa salvar informações de um cliente e de um pedido em tabelas diferentes. Assegurando que ambos os registros sejam inseridos corretamente, podemos usar transações explícitas. using (var context = new AppDbContext()) { using var transaction = context.Database.BeginTransaction(); try { // Operação 1: Inserir um cliente var cliente = new Cliente { Nome = “João Silva” }; context.Clientes.Add(cliente); context.SaveChanges(); // Operação 2: Inserir um pedido relacionado var pedido = new Pedido { ClienteId = cliente.Id, Total = 100.50m }; context.Pedidos.Add(pedido); context.SaveChanges(); // Confirmar a transação transaction.Commit(); } catch (Exception ex) { // Reverter as alterações em caso de erro transaction.Rollback(); Console.WriteLine($”Erro ao processar transação: {ex.Message}”); throw; } } O início de uma transação no Entity Framework Core é feito através de “context.Database.BeginTransaction()”, que cria uma nova transação. Durante a execução, as alterações realizadas no banco de dados são registradas como o “SaveChanges()”, mas não são efetivadas até que o método “transaction.Commit()” seja chamado, confirmando todas as mudanças feitas até aquele momento. Caso ocorra algum erro durante o processo, é possível reverter todas as alterações executadas na transação utilizando o método “transaction.Rollback()”, garantindo que o banco de dados permaneça em um estado consistente. Este exemplo mostra como garantir que ambas as operações (inserção de cliente e pedido) sejam tratadas como uma unidade. Se qualquer uma das operações falhar, as alterações são revertidas e o banco de dados permanece inalterado. Transações implícitas no EF Core Por padrão, o EF Core utiliza transações implícitas em chamadas para “SaveChanges()”. Isso significa que, a cada vez que você salva alterações no banco de dados, o EF Core automaticamente inicia, executa e finaliza uma transação. Isso é útil para operações simples, mas pode ser insuficiente quando você deseja realizar múltiplas operações em um único processo. try { context.Clientes.Add(new Cliente { Nome = “Maria Silva” }); context.Pedidos.Add(new Pedido { ClienteId = 1, Total = 200.00m }); context.SaveChanges(); } catch (Exception ex) { Console.WriteLine($”Erro ao salvar: {ex.Message}”); } Neste exemplo, o EF Core cria automaticamente uma transação para realizar as operações de inserção do cliente e do pedido. Se ocorrer um erro, a transação é revertida. Combinando EF Core com outros frameworks Em alguns cenários, você pode querer combinar o EF Core com outros frameworks como o Dapper, que também oferece um acesso direto e performático ao banco de dados. O EF Core permite que você compartilhe a mesma transação entre essas diferentes abordagens. using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Dapper; using (var context = new AppDbContext()) { using var transaction = context.Database.BeginTransaction(); var connection = context.Database.GetDbConnection(); try { // Operação com EF Core context.Clientes.Add(new Cliente { Nome = “Pedro” }); context.SaveChanges(); // Operação com Dapper var sql = “INSERT INTO Pedidos (ClienteId, Total) VALUES (@ClienteId, @Total)”; var parameters = new { ClienteId = 1, Total = 150.00m }; connection.Execute(sql, parameters, transaction: transaction.GetDbTransaction()); // Confirmar transação transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); Console.WriteLine($”Erro: {ex.Message}”); } } Usamos “GetDbConnection” para obter a conexão do banco de dados gerenciada pelo EF Core. “GetDbTransaction” permite que a transação seja compartilhada entre o EF Core e o Dapper. Isso é útil quando você precisa executar operações rápidas e de baixo nível com o Dapper enquanto mantém o controle completo da transação com o EF Core. Nível de isolamento da transação O nível de isolamento é um mecanismo que controla como uma transação interage com outras transações em execução simultânea, determinando a visibilidade dos dados alterados e o comportamento de bloqueios durante as operações. Sua utilidade está em balancear a consistência dos dados com a performance, evitando problemas como leituras incorretas ou conflitos entre transações concorrentes. No Entity Framework Core, você pode especificar o nível de isolamento ao iniciar uma transação explícita da seguinte forma: using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted)) { try { //Sua transação. } catch { transaction.Rollback(); throw; } } ReadCommitted (Padrão) O nível apresentado acima é o “ReadCommitted”, que garante que uma transação só possa ler dados que já foram confirmados (comitados) por outras transações, evitando leituras “sujas” (dados que podem ser revertidos por outra transação). Imagine um sistema de estoque onde uma transação verifica o saldo de um produto antes de
Executando tarefas agendadas no .NET com Hangfire

Imagine uma aplicação que precisa enviar milhares de e-mails, gerar relatórios complexos ou realizar integrações com outros sistemas sem afetar a experiência do usuário. Tarefas como essas podem sobrecarregar o servidor se forem executadas no mesmo fluxo da aplicação principal. A solução? Processamento em segundo plano. Com o Hangfire, você pode lidar com essas operações de maneira confiável, agendando e monitorando cada tarefa. Neste artigo vamos explorar passo a passo como integrar e aproveitar todo o potencial dessa ferramenta em seus projetos .NET, trazendo exemplos práticos para diferentes cenários. O que é o Hangfire? O Hangfire é uma biblioteca open source que permite executar tarefas em segundo plano dentro de aplicações .NET, sem a necessidade de criar serviços separados ou manipular threads manualmente. Ele é baseado em um modelo persistente que armazena as tarefas em um banco de dados, garantindo confiabilidade mesmo em caso de falhas do servidor. Os seus principais recursos são: Processamento de tarefas em segundo plano: execute tarefas de forma assíncrona sem bloquear o fluxo principal da aplicação. Jobs recorrentes: programe tarefas para serem executadas em intervalos específicos (por exemplo, diariamente, semanalmente). Jobs agendados: execute tarefas em horários ou datas específicas. Painel de administração: um dashboard integrado que permite monitorar, reexecutar ou excluir tarefas. Configurando o Hangfire No terminal ou no Gerenciador de Pacotes NuGet, instale os seguintes pacotes: Install-Package Hangfire Install-Package Hangfire.SqlServer dotnet add package Hangfire dotnet add package Hangfire.SqlServer O pacote “Hangfire” fornece a funcionalidade principal, enquanto o “Hangfire.SqlServer” permite armazenar os jobs em um banco de dados SQL Server. Após a instalação dos pacotes, abra o arquivo “Program.cs” para configurarmos o Hangfire: using Hangfire; using Hangfire.SqlServer; var builder = WebApplication.CreateBuilder(args); // Configurar o Hangfire com SQL Server builder.Services.AddHangfire(config => config.UseSqlServerStorage(builder.Configuration.GetConnectionString(“DefaultConnection”))); // Adicionar o Hangfire Server builder.Services.AddHangfireServer(); var app = builder.Build(); // Configurar o Dashboard do Hangfire app.UseHangfireDashboard(); O “builder.Services.AddHangfire” configura o Hangfire para usar o SQL Server como mecanismo de armazenamento. A chamada “UseSqlServerStorage” especifica o banco onde os jobs serão salvos. Em seguida o “builder.Services.AddHangfireServer” adiciona o servidor do Hangfire ao pipeline da aplicação. Esse servidor vai processar as filas de jobs. Por fim, o “app.UseHangfireDashboard” adiciona o painel de administração para monitorar jobs. Para acessá-lo vamos usar o endpoint “/hangfire”. Com isso, sua aplicação já está pronta para criar e executar jobs com o Hangfire. Tipos de jobs no Hangfire Agora vamos detalhar os tipos de jobs com exemplos de uso. Tarefas em fila (Enqueue) O primeiro tipo de jobs que vamos falar é o “Enqueue”, com o qual criamos uma tarefa em fila, que será executada assim que possível. Para entendermos o seu funcionamento vamos considerar um caso em que queremos enfileirar a execução de um método específico em um serviço. [Route(“api/[controller]”)] [ApiController] public class EmailController : ControllerBase { [HttpPost(“Enviar”)] public IActionResult EnviarEmail([FromBody] EmailRequest request) { BackgroundJob.Enqueue(service => service.EnviarEmail(request.Destinatario, request.Assunto, request.Mensagem)); return Ok(“E-mail enfileirado para envio!”); } } No código acima, adicionamos à fila do Hangfire um job para realizar o envio de um e-mail como notificação para o usuário. O método “Enqueue<IEmailService>” é usado para informar ao Hangfire que queremos utilizar os métodos definidos na implementação da interface “IEmailService”. Após definir o tipo do serviço (IEmailService), utilizamos uma expressão lambda “service => service.EnviarEmail(…)” para indicar qual método queremos executar — no caso, o método “EnviarEmail”. Através dessa expressão, temos acesso aos métodos disponíveis no serviço implementado e podemos passar os argumentos necessários, como o destinatário, o assunto e a mensagem do e-mail. Perceba que quando usamos o tipo genérico no método “Enqueue<T>()” do Hangfire, não é necessário realizar a injeção de dependência manualmente no controlador ou serviço em que a tarefa está sendo enfileirada. O Hangfire é responsável por resolver a dependência do tipo genérico especificado (T) usando o contêiner de injeção de dependência configurado na aplicação. A seguir vamos ver as tarefas agendadas e nela vamos implementar o exemplo usando a injeção de dependência. Tarefas agendadas (Schedule) Tarefas agendadas são úteis quando você precisa realizar um job em um momento futuro, mas com uma espera configurada. O método “BackgroundJob.Schedule” permite que você agende a execução de um job em um intervalo específico. Imagine que você tem uma aplicação que precisa enviar um lembrete para o usuário sobre uma reunião que está prestes a ocorrer. Você pode agendar o envio da notificação para ocorrer alguns minutos antes do evento. private readonly INotificacaoService _notificacaoService; public ReuniaoController(INotificacaoService notificacaoService) { _notificacaoService = notificacaoService; } [HttpPost(“agendar-reuniao”)] public ActionResult AgendarReuniao(DateTime dataReuniao, string usuarioEmail) { var intervalo = dataReuniao – DateTime.Now; BackgroundJob.Schedule( () => _notificacaoService.EnviarLembrete(usuarioEmail), intervalo.Subtract(TimeSpan.FromMinutes(10)) ); return Ok(); } O método “BackgroundJob.Schedule” agenda a execução do método “EnviarLembrete” para ocorrer daqui a 10 minutos, enviando, assim, um e-mail de lembrete para o usuário. No exemplo de implementação do método “Schedule”, utilizamos a interface “INotificacaoService”, que foi injetada no controlador por meio do construtor. Essa abordagem é uma alternativa ao exemplo anterior, em que não foi necessária a injeção direta da dependência no controlador, pois o Hangfire resolve automaticamente as dependências ao usar o tipo genérico no método “Enqueue”. Tarefas recorrentes (Recurring) As tarefas recorrentes são indicadas para jobs que precisam ser executados em intervalos regulares, como enviar relatórios diários ou realizar sincronizações de dados. Se você tem uma aplicação que precisa enviar um relatório diário de atividades para um administrador, você pode usar o Hangfire para configurar esse job para rodar automaticamente todos os dias. public class AgendamentoDeTarefas { private readonly IRelatorioService _relatorioService; public AgendamentoDeTarefas(IRelatorioService relatorioService) { _relatorioService = relatorioService; } public void AgendarRelatorioDiario() { RecurringJob.AddOrUpdate( “relatorio-diario”, () => _relatorioService.EnviarRelatorioDiario(), Cron.Daily ); } } O “AddOrUpdate” possui um identificador que no exemplo acima é “relatorio-diario”. Esse identificador é útil para identificar o job recorrente e também quando for necessário atualizar ou remover esse job. Trataremos sobre isso mais à frente. Em seguida ele recebe o método que deve ser executado e, por fim, a frequência com que o job será executado.O Hangfire oferece suporte à sintaxe de “cron” para configurar facilmente horários recorrentes. Cron.Daily: executa o job uma vez ao dia, geralmente à meia-noite.
Tratamento global de exceções no ASP.NET

O tratamento global de exceções é um aspecto essencial no desenvolvimento de APIs REST robustas. Neste artigo, veremos como implementar essa abordagem em aplicações ASP.NET, garantindo que erros não tratados sejam capturados e gerenciados de forma centralizada. Esse método permite oferecer respostas padronizadas aos usuários, evitar a exposição de detalhes sensíveis e manter a aplicação funcionando de maneira controlada, mesmo diante de falhas inesperadas. Caso queira saber como tratar exceções em partes específicas do código, você pode acessar nosso artigo sobre Tratamento de exceções em C#. O que é o tratamento global de exceções? O tratamento global de exceções é a prática de capturar exceções não tratadas em um único ponto da aplicação, garantindo que: O usuário receba respostas amigáveis e consistentes. O sistema registre logs para diagnóstico de erros. Exceções críticas sejam tratadas sem comprometer a estabilidade da aplicação. No ASP.NET Core, isso é feito por meio de middlewares, que interceptam requisições HTTP no pipeline, permitindo a captura de erros de forma centralizada. Criando um middleware personalizado Os middlewares são componentes no pipeline de requisições do ASP.NET Core que processam solicitações e respostas HTTP. Cada middleware pode executar lógica antes ou depois de passar o controle para o próximo middleware na cadeia. Agora, vamos criar um middleware para capturar exceções, retornar mensagens amigáveis e garantir que os códigos HTTP sejam apropriados. Classe base do middleware Todo middleware no ASP.NET Core precisa de um “RequestDelegate”, que representa o próximo passo no pipeline de execução. Isso garante que, após o middleware executar sua lógica, ele possa passar a solicitação para o próximo middleware registrado, mantendo a cadeia de execução fluida. Para isso, crie uma classe que podemos dar o nome de “ExceptionHandlingMiddleware”: using Microsoft.AspNetCore.Http; using System.Net; using System.Text.Json; public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; public ExceptionHandlingMiddleware(RequestDelegate next) { _next = next; } //Continua aqui } Aqui, estamos recebendo o próximo middleware via injeção no construtor e armazenando sua referência na variável “_next”. Agora vamos implementar o método “InvokeAsync” que é obrigatório em um middleware e define o comportamento do middleware ao processar a requisição HTTP. public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { context.Response.StatusCode = 500; context.Response.ContentType = “application/json”; var errorResponse = new { Message = “Ocorreu um erro inesperado.” }; await context.Response.WriteAsJsonAsync(errorResponse); } } No código acima, a execução do próximo middleware é envolvida em um bloco “try-catch” para capturar qualquer exceção que ocorra durante o processamento da requisição. Caso uma exceção seja gerada, o middleware define o código de status HTTP da resposta como “500”, indicando que ocorreu um erro interno no servidor. Em seguida, define o tipo de conteúdo da resposta como JSON, já que a mensagem de erro será retornada nesse formato. A linha “var errorResponse = new { Message = “Ocorreu um erro inesperado.” };” cria um objeto anônimo com uma mensagem simples informando sobre o erro. Por fim, envia a resposta ao cliente no formato JSON, garantindo que ele receba uma mensagem de erro controlada e compreensível. Registrando o middleware no pipeline de requisições Depois de criar o middleware personalizado, o próximo passo é registrá-lo no pipeline de requisições do ASP.NET Core. Isso pode ser feito no arquivo “Program.cs”: var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseMiddleware(); app.MapControllers(); app.Run(); No código acima, registramos o middleware personalizado no pipeline de requisições do ASP.NET Core, garantindo que ele seja executado durante o processamento das requisições HTTP. Com isso, qualquer erro que ocorrer durante a execução da aplicação será automaticamente tratado pelo nosso middleware personalizado. Exemplo: Personalizando respostas de erro A resposta padrão criada no exemplo anterior é simples e atende casos genéricos. No entanto, em uma aplicação real, é importante fornecer mais informações estruturadas sem comprometer a segurança. Por exemplo, você pode incluir o código de erro específico para identificar facilmente a natureza do problema. Uma mensagens específicas para diferentes tipos de exceções, como “NotFoundException”, “ValidationException” etc. Vamos criar um exemplo aprimorado que inclui tratamento específico para exceções personalizadas: public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { context.Response.ContentType = “application/json”; var statusCode = ex switch { ArgumentNullException => (int)HttpStatusCode.BadRequest, KeyNotFoundException => (int)HttpStatusCode.NotFound, _ => (int)HttpStatusCode.InternalServerError }; context.Response.StatusCode = statusCode; var errorResponse = new { Message = ex.Message, Details = _env.IsDevelopment() ? ex.StackTrace : “” }; await context.Response.WriteAsJsonAsync(errorResponse); } } No código iniciamos configurando o tipo de conteúdo da resposta para JSON, garantindo que a resposta será formatada corretamente para o cliente. Em seguida, usamos um “switch” para determinar o código de status HTTP com base no tipo da exceção. Depois, criamos um objeto anônimo para representar a resposta de erro, contendo uma mensagem simples sobre o erro (Message). A propriedade “Details” inclui o stack trace, mas apenas se o ambiente for de desenvolvimento, para não expor informações sensíveis em produção. Por fim, usamos “WriteAsJsonAsync” para serializar o objeto de erro em JSON e enviá-lo de volta ao cliente. Nota: Para que a linha “Details = _env.IsDevelopment() ? ex.StackTrace : null” funcione corretamente, é necessário injetar a dependência “IHostEnvironment” no construtor do middleware, permitindo o acesso ao ambiente da aplicação e ajustando a visibilidade dos detalhes de erro conforme o contexto de execução. Quando uma exceção ocorre em ambiente de desenvolvimento, a propriedade “details” exibe informações úteis que ajudam o desenvolvedor a identificar e corrigir o erro rapidamente. Já em ambiente de produção, os detalhes do erro são ocultados para evitar a exposição de informações sensíveis e proteger a aplicação. Tratando exceções com o IExceptionHandler Uma alternativa ao método ensinado acima é a utilização do “IExceptionHandler” que foi introduzido no ASP.NET Core 8 trazendo melhorias no tratamento global de exceções. Esta abordagem oferece uma alternativa simples e flexível de configurar o gerenciamento de erros em um middleware dedicado, permitindo respostas consistentes e centralizadas. O método TryHandleAsync Ao implementar um middleware que herda da interface “IExceptionHandler”, é necessário sobrescrever o método “TryHandleAsync”. Este método tenta lidar com a exceção fornecida e determina se a exceção foi manipulada retornando “true” que indica
Implementando filtros em aplicações ASP.NET

Filtros são um recurso do ASP.NET que permite interceptar e executar lógica personalizada antes ou depois de determinadas etapas do ciclo de vida de uma requisição. Eles oferecem uma maneira eficaz de adicionar funcionalidades transversais (como autenticação, autorização, manipulação de resultados ou tratamento de erros) sem poluir a lógica de seus controladores ou ações. Tipos de filtros no ASP.NET O ASP.NET oferece vários tipos de filtros que podem ser aplicados em diferentes estágios do ciclo de vida de uma requisição: Authorization Filters O primeiro deles é o Authorization Filter, que determina se o usuário tem permissão para acessar um recurso específico. Eles são executados antes que qualquer outra parte da requisição seja processada. Digamos que precisamos de um filtro de autorização para poder restringir o acesso de usuários a áreas administrativas da aplicação. Neste filtro, somente usuários com o nível de acesso de administrador podem acessar determinadas rotas. using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; public class CustomAuthorizationFilter : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { if (!context.HttpContext.User.Identity.IsAuthenticated) { context.Result = new RedirectResult(“/Account/Login”); return; } if (!context.HttpContext.User.IsInRole(“Admin”)) { context.Result = new ForbidResult(); } } } No código acima, herdamos de “Attribute” para tornar a classe um filtro, sem precisar instanciá-la diretamente. A interface “IAuthorizationFilter” define a lógica de autorização antes da execução da ação, garantindo o acesso adequado. A implementação do método verifica se o usuário está autenticado. Caso não esteja, ele é redirecionado para a tela de login. Se o usuário estiver autenticado, verificamos se ele possui a role “Admin”. Se a role não estiver registrada, o acesso é negado com um retorno de status 403 Forbidden. Agora que a lógica do filtro foi criada, precisamos aplicar o filtro em um controller. Para isso, basta decorá-lo com o atributo correspondente. Isso garante que todas as actions dentro dessa controller sejam submetidas ao filtro antes de serem executadas: [CustomAuthorizationFilter] public class DashboardController : Controller { public IActionResult Index() { return View(); } public IActionResult Details() { return View(); } } Se você quiser aplicar o filtro apenas a uma action específica, pode decorá-la diretamente. Nesse caso, somente essa ação passará pelo filtro: public class DashboardController : Controller { public IActionResult Index() { return View(); } [CustomAuthorizationFilter] public IActionResult Details() { return View(); } } Action Filters Os Action Filters permitem executar lógica antes ou depois da execução de uma ação do controlador. Como exemplo podemos imaginar que seja necessário medir o tempo de execução de ações para identificar gargalos de desempenho na aplicação. Para isso, podemos criar o seguinte filtro. public class LogExecutionTimeFilter : Attribute, IActionFilter { private readonly ILogger _logger; private Stopwatch _stopwatch; public LogExecutionTimeFilter(ILogger logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { _stopwatch = Stopwatch.StartNew(); _logger.LogInformation($”Iniciando a execução da ação: {context.ActionDescriptor.DisplayName}”); } public void OnActionExecuted(ActionExecutedContext context) { _stopwatch.Stop(); _logger.LogInformation($”Ação {context.ActionDescriptor.DisplayName} concluída em {_stopwatch.ElapsedMilliseconds}ms.”); } } No código acima, utilizamos o “Stopwatch” para medir o tempo de execução da ação. O método “OnActionExecuting” é chamado antes da execução da ação, iniciando a medição do tempo, enquanto o “OnActionExecuted” é chamado após a execução da ação, registrando o tempo total gasto. Result Filters Os Result Filters permitem modificar ou executar lógica antes ou depois que um resultado de ação (como um ViewResult ou JsonResult) é processado e enviado ao cliente. Eles são úteis para tarefas como modificar cabeçalhos de resposta ou padronizar o formato de saída. using Microsoft.AspNetCore.Mvc.Filters; public class AddCustomHeaderFilter : Attribute, IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.Headers.Add(“X-Custom-Header”, “Este e um cabecalho personalizado.”); } public void OnResultExecuted(ResultExecutedContext context) { // Aqui você pode adicionar a lógica que desejar } } Assim como no filtro anterior, temos dois métodos que são executados em momentos diferentes. O método “OnResultExecuting” é chamado antes que o resultado da ação seja processado, permitindo modificar o contexto da resposta antes de ser enviada ao cliente. Já o método “OnResultExecuted” é chamado após a execução do resultado, permitindo realizar ações adicionais, como limpeza ou modificação da resposta final. Exception Filters Os Exception Filters são usados para capturar e tratar exceções não tratadas que ocorrem durante a execução de uma ação ou resultado. Eles permitem padronizar o tratamento de erros, como log de exceções ou retorno de mensagens de erro personalizadas. using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; public class CustomExceptionFilter : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { Console.WriteLine($”Exceção capturada: {context.Exception.Message}”); context.Result = new JsonResult(new { Message = “Ocorreu um erro inesperado. Por favor, tente novamente mais tarde.” }) { StatusCode = 500 }; context.ExceptionHandled = true; } } Registrando filtros globalmente No ASP.NET Core você pode registrar filtros globalmente no método “AddControllers” no arquivo “Program.cs”. Para isso, você precisa adicionar os filtros na coleção “options.Filters”. var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); }); No exemplo acima todos os filtros criados no artigo foram adicionados de forma centralizada. Dessa forma, eles serão executados em todas as requisições da aplicação, seguindo a ordem do ciclo de vida de execução dos filtros no ASP.NET Core. Boas práticas Ao utilizar filtros em ASP.NET Core, é importante seguir algumas boas práticas para garantir que seu código seja manutenível, reutilizável e eficiente. Aqui estão algumas recomendações: Simplicidade: mantenha a lógica dentro dos filtros simples e objetiva. Evite implementar lógica complexa, pois filtros são executados frequentemente durante o ciclo de vida de uma requisição e podem impactar o desempenho e a legibilidade do código. Reutilização: sempre que possível, crie filtros genéricos que possam ser reutilizados em várias partes da aplicação. Filtros genéricos são mais flexíveis e ajudam a reduzir a duplicação de código. Evite repetições: utilize filtros globais para lógica comum, como autenticação, autorização ou logging, que devem ser executados em várias partes da aplicação. Isso ajuda a evitar a repetição de lógica nas controllers e ações. Essas boas práticas visam melhorar a manutenibilidade e a escalabilidade da aplicação, garantindo que os filtros sejam eficientes e fáceis de gerenciar no futuro. 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
Comparando objetos e tipos em C#: == vs Equals()

No C#, ao realizar comparações entre objetos e tipos, é comum se deparar com duas formas de comparação: o operador “==” e o método “Equals()”. Embora ambas as abordagens possam, aparentemente, realizar a mesma tarefa, elas têm comportamentos distintos e é importante entender as diferenças entre elas para evitar erros sutis que podem ser difíceis de identificar. Neste artigo vamos explorar essas diferenças de maneira detalhada, fornecendo explicações claras e exemplos práticos. O operador == O operador “==” em C# é um operador binário que, por padrão, compara dois valores. No entanto, o comportamento desse operador pode variar dependendo do tipo de dado que está sendo comparado. Para tipos primitivos (como int, double, char, bool), o operador “==” compara os valores diretamente. Ou seja, se duas variáveis contiverem o mesmo valor, a comparação retorna “true”, independentemente de estarem na mesma posição de memória ou não. int num1 = 5; double num2 = 5.0; bool resultado = (num1 == num2); Para tipos de referência, como classes, o comportamento do “==” muda. Ele passa a comparar os endereços de memória das instâncias, ou seja, se duas variáveis de tipo referência apontam para o mesmo objeto na memória. Se os endereços de memória forem diferentes, o resultado será “false”, mesmo que os valores internos dos objetos sejam idênticos. public class Pessoa { public string Nome { get; set; } } Pessoa pessoa1 = new Pessoa { Nome = “Maria” }; Pessoa pessoa2 = new Pessoa { Nome = “Maria” }; bool resultado = (pessoa1 == pessoa2); No exemplo acima, mesmo que a propriedade “Nome” dos dois objetos “pessoa1” e “pessoa2” sejam iguais, o operador “==” irá comparar os endereços de memória das duas instâncias e, como são objetos distintos, o resultado será “false”. Agora se fizermos “pessoa2” receber “pessoa1”, temos algo diferente. Vamos ver um exemplo: Pessoa pessoa1 = new Pessoa { Nome = “Maria” }; Pessoa pessoa2 = pessoa1; Agora “pessoa2” não é um novo objeto com as mesmas propriedades que “pessoa1”. Em vez disso, “pessoa2” faz referência à mesma posição que “pessoa1” na memória. Se fizermos a comparação: bool resultado = pessoa1 == pessoa2; Console.WriteLine(resultado); O resultado será “true” porque “pessoa1” e “pessoa2” apontam para o mesmo objeto na memória. O método Equals() O método “Equals()” é definido pela classe base “Object” e, como todos os tipos em C# herdam de “Object”, todos os tipos de dados (inclusive tipos primitivos e tipos de referência) possuem uma implementação desse método assim como podemos ver no código abaixo. int numero = 0; numero.Equals(0); Venda venda = new Venda(); venda.Equals(null); Para tipos primitivos o método “Equals()” se comporta de maneira diferente do operador “==”. Ele vai levar em consideração o tipo da variável. Caso os valores que estejam armazenados nas variáveis seja igual, mas os tipos sejam diferentes, será retornado “false”. int num1 = 5; double num2 = 5.0; bool resultado = num1.Equals(num2); No exemplo acima, percebemos que quando ele foi executado utilizando o “==” o resultado foi “true”. Porém, utilizando o “Equals()” o resultado foi “false”. Já para tipos de referência o método Equals() funciona de forma semelhante ao operador ==, comparando, por padrão, os endereços de memória das instâncias. Isso significa que, se duas variáveis referirem-se ao mesmo objeto na memória, o método retornará true; caso contrário, retornará false. No entanto, o método Equals() pode ser sobrescrito em classes para permitir comparações baseadas nos valores dos atributos, tornando a verificação de igualdade mais personalizada e semântica. public class Pessoa { public string Nome { get; set; } public override bool Equals(object obj) { if (obj is Pessoa other) { return this.Nome == other.Nome; } return false; } } Pessoa p1 = new Pessoa { Nome = “Maria” }; Pessoa p2 = new Pessoa { Nome = “Maria” }; bool resultado = p1.Equals(p2); No exemplo acima, a classe “Pessoa” sobrescreve o método “Equals()” para comparar os valores da propriedade “Nome”, em vez de simplesmente verificar se as variáveis apontam para o mesmo objeto. Com isso, o resultado da comparação será “true”. Comparação com structs Os structs em C# são tipos de valor. Isso significa que eles são comparados com base nos valores de seus campos, diferentemente dos tipos de referência, que são comparados pelo endereço de memória. Por padrão, o operador “==” não está definido para structs. Isso ocorre porque o compilador não sabe quais campos deve considerar na comparação. Já o método “Equals()” está disponível para todos os structs e realiza comparações com base nos valores dos campos do objeto. Ele verifica cada propriedade do struct para determinar se os valores são iguais. public struct Produto { public string Nome { get; set; } public decimal Valor { get; set; } public bool Disponivel { get; set; } } Produto prod1 = new Produto { Nome = “TV”, Valor = 3000, Disponivel = true }; Produto prod2 = new Produto { Nome = “TV”, Valor = 3000, Disponivel = true }; bool resultado = prod1.Equals(prod2); Na representação acima, podemos observar que o método “Equals()” ao verificar um struct percorre suas propriedades, comparando se os valores de “Nome”, “Valor” e “Disponivel” são iguais nas duas variáveis. 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 Compreender as diferenças entre “==” e “Equals()” é essencial para evitar comportamentos inesperados em seu código. Essa distinção é fundamental, especialmente ao trabalhar com tipos de referência e tipos de valor no C#. Esperamos que este artigo tenha esclarecido o tema e que ajude você na hora de implementar comparações em seus projetos.
Registrando logs em .NET com Serilog

Registrar logs de forma eficiente é fundamental para monitorar e solucionar problemas em aplicações. O Serilog é uma biblioteca popular no ecossistema .NET, conhecida por sua flexibilidade e suporte a logs estruturados. Com ele, é fácil enriquecer mensagens com contexto adicional e direcionar logs para diversos destinos, como arquivos, console ou sistemas externos. Neste artigo veremos como configurar e usar o Serilog para tornar o registro de logs mais detalhado e simples em seus projetos .NET. Por que utilizá-lo? O termo “logs estruturados” refere-se a logs que possuem um formato consistente e detalhado, permitindo análises mais ricas e filtragem avançada. Ao contrário de logs tradicionais em texto simples, logs estruturados são especialmente úteis para monitoramento de aplicações e diagnóstico de problemas, porque permitem enriquecer cada mensagem com propriedades e contexto adicionais. O Serilog é amplamente utilizado devido à sua facilidade de configuração, flexibilidade e ampla gama de sinks para destinos de logs (como arquivos, bancos de dados e serviços externos como Seq). Configurando o Serilog Para compreender melhor o funcionamento do Serilog, vamos criar uma aplicação ASP.NET Core simples e integrá-lo para gerenciar o registro de logs. A ideia é configurar o Serilog para registrar primeiramente logs no console e em um arquivo e por fim configurá-lo para registrar logs no banco de dados. Em sua aplicação ASP.NET Core instale os seguintes pacotes via .NET CLI ou NuGet : Serilog: A biblioteca principal para registro de logs. dotnet add package SerilogInstall-Package Serilog Serilog.AspNetCore: Permite integrar o Serilog ao pipeline de logs do ASP.NET Core. dotnet add package Serilog.AspNetCoreInstall-Package Serilog.AspNetCore Serilog.Sinks.Console: Para exibir logs no console. dotnet add package Serilog.Sinks.ConsoleInstall-Package Serilog.Sinks.Console Serilog.Sinks.File: Para salvar logs em arquivos. dotnet add package Serilog.Sinks.FileInstall-Package Serilog.Sinks.File Esses pacotes são suficientes para iniciarmos. Conforme a necessidade, você pode adicionar outros sinks específicos, como bancos de dados, que abordaremos adiante. Com os pacotes instalados, vamos configurar o Serilog para capturar e registrar logs no console e em um arquivo. Abra o arquivo “Program.cs” e configure o Serilog como o gerenciador de logs da aplicação: using Serilog; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .WriteTo.File(“logs/actions_log.txt”, rollingInterval: RollingInterval.Day) .Enrich.FromLogContext() .CreateLogger(); builder.Host.UseSerilog(); var app = builder.Build(); Através do “LoggerConfiguration”, você pode personalizar como os logs serão registrados na aplicação. Ao atribuir a “Log.Logger”, você está configurando o logger global que será utilizado em toda a aplicação. O “.MinimumLevel.Debug()” define o nível mínimo de logs que o Serilog irá registrar. O nível “Debug“ garante que logs de nível “Debug”, “Information”, “Warning”, “Error” e “Fatal” sejam registrados, ou seja, irá capturar logs de menor severidade até os mais críticos. Você pode alterar de acordo com a sua necessidade. O “.WriteTo.Console()” especifica que os logs devem ser enviados para a saída de console (terminal). Isso é útil durante o desenvolvimento, pois permite que os logs sejam exibidos diretamente na linha de comando enquanto a aplicação está rodando. Já o “.WriteTo.File(“logs/log.txt”, rollingInterval: RollingInterval.Day)” vai definir que os logs também serão registrados em um arquivo chamado “actions_log.txt” dentro da pasta “logs”. A opção “rollingInterval: RollingInterval.Day” indica que, a cada dia, o Serilog criará um novo arquivo de log (com a data no nome) para evitar que o arquivo de log se torne muito grande ao longo do tempo. O resultado será uma pasta com logs diários. Podemos também adicionar informações adicionais ao log, como propriedades de contexto (ex: informações sobre a requisição HTTP em uma API) utilizando o “.Enrich.FromLogContext()”. Ele permite enriquecer os logs com dados que podem ser importantes para depuração ou rastreamento, como o ID da requisição ou o nome do usuário. Por fim, o “.CreateLogger()” finaliza a configuração e cria o logger com as opções definidas até esse ponto. Agora, a aplicação pode começar a gerar logs usando o Serilog. Agora devemos integrar o Serilog ao ciclo de vida do host da aplicação utilizando o “builder.Host.UseSerilog();” que vai permitir que o Serilog seja utilizado para registrar logs durante a execução da aplicação, inclusive capturando logs automaticamente de outras bibliotecas que a aplicação possa utilizar (como ASP.NET Core, por exemplo). Isso substitui o logger padrão do .NET pelo Serilog. Agora, ao executar o projeto, os logs aparecerão no console e serão salvos no arquivo logs/actions_log.txt. Tela do console Arquivo TXT Alterando o formato dos logs Ao observarmos o registro de um log, podemos perceber que ele segue uma estrutura padrão definida pelo Serilog. Essa configuração padrão atende à maioria dos casos, mas pode ser personalizada para atender a requisitos específicos utilizando a propriedade “outputTemplate”. Por exemplo, ao configurar o um arquivo de texto como destino de logs, podemos modificar o template para reorganizar o formato que data e hora será apresentado. Na configuração acima, alteramos o formato da data e hora que segue o padrão ano, mês, dia para dia, mês, ano que é utilizado aqui no Brasil. Usando o sink do Serilog para SQL Server Registrar logs em um banco de dados pode ser útil para aplicações que necessitam de persistência de logs a longo prazo, análise avançada ou integração com outras ferramentas de monitoramento. O Serilog permite que você envie logs diretamente para um banco de dados, como o SQL Server, de forma simples e eficiente. Para começar vamos adicionar o pacote “Serilog.Sinks.MSSqlServer”: Install-Package Serilog.Sinks.MSSqlServer ou dotnet add package Serilog.Sinks.MSSqlServer Agora, vamos configurar o Serilog para gravar os logs em uma tabela do SQL Server. var builder = WebApplication.CreateBuilder(args); Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.MSSqlServer( connectionString: “SuaConnectionString”, tableName: “Logs”, autoCreateSqlTable: true ) .Enrich.FromLogContext() .CreateLogger(); builder.Host.UseSerilog(); var app = builder.Build(); Assim como configuramos logs em arquivos e no console, agora os direcionamos para um banco de dados SQL Server com “.WriteTo.MSSqlServer”. Passamos a string de conexão, o nome da tabela (Logs) e ativamos “autoCreateSqlTable: true” para criar a tabela automaticamente, caso não exista. Com isso, os logs serão registrados diretamente no banco, facilitando consultas e análises. Registrando mensagens com diferentes níveis de Log Com o Serilog configurado, você pode registrar mensagens com diversos níveis de log diretamente no código. public class MinhaClasse { private readonly ILogger _logger;
Sobrecarga de operadores em C#

Sobrecarga de operadores em C# permite que você personalize o que operadores como +, -, * e == fazem quando usados com seus objetos. Isso significa que, ao somar dois objetos de uma classe que você criou, você pode definir exatamente como essa operação funciona. Isso torna o código mais fácil de entender e usar, especialmente quando seus objetos representam coisas como números, pontos ou outros conceitos onde operações matemáticas ou comparações fazem sentido O que é a sobrecarga de operadores? Sobrecarga de operadores é o processo de definir ou redefinir a funcionalidade dos operadores para tipos específicos. Em outras palavras, você pode “ensinar” operadores como + ou == a funcionar de uma maneira particular quando aplicados a objetos criados por você. A sobrecarga é feita definindo métodos especiais na classe ou estrutura. Para entendermos melhor, vamos para um exemplo. Implementando a sobrecarga de operadores A implementação da sobrecarga de operadores é feita através de métodos especiais na classe que utilizam a palavra-chave “operator” seguida pelo operador a ser sobrecarregado, ou seja, “operator +” ou “operator “-”. Vamos implementar uma classe “Vetor2D” que representa um vetor com duas coordenadas “X” e “Y”. Vamos sobrecarregar o operador “+” para somar dois vetores. Primeiro, vamos definir a classe “Vetor2D” com um construtor para inicializar as coordenadas X e Y: public class Vetor2D { public double X { get; } public double Y { get; } public Vetor2D(double x, double y) { X = x; Y = y; } public static Vetor2D operator +(Vetor2D v1, Vetor2D v2) { return new Vetor2D(v1.X + v2.X, v1.Y + v2.Y); } public override string ToString() => $”({X}, {Y})”; } No código acima, implementamos a sobrecarga do operador “+” para a classe “Vetor2D”. O método “operator +” recebe dois objetos “Vetor2D” como parâmetros e retorna um novo “Vetor2D” com a soma dos componentes X e Y de ambos os vetores. Por fim, sobrescrevemos o método “ToString()” para formatar a exibição de um objeto “Vetor2D” no formato (X, Y). Dessa forma, quando repassarmos o objeto dentro do “Console.WriteLine” ele já estará formatado para string. Agora vamos usar o operador “+” que foi sobrecarregado para poder somar os vetores e apresentar o resultado no console: public static void Main(string[] args) { Vetor2D vetor1 = new Vetor2D(2.0, 3.0); Vetor2D vetor2 = new Vetor2D(4.0, 1.0); Vetor2D soma = vetor1 + vetor2; Console.WriteLine($”Soma dos vetores: {soma}”); } Com isso, temos o resultado de “Soma dos vetores: (6, 4)”. Observe que conseguimos somar dois objetos da classe “Vetor2D” de forma direta, pois o operador “+” foi sobrecarregado para entender essa operação específica. Isso permite que ele saiba exatamente como lidar quando objetos dessa classe são somados. Caso essa sobrecarga não existisse, o operador “+” não saberia como agir com objetos dessa classe, gerando um erro de compilação ou comportamento indesejado, porque, por padrão, operadores como “+” não são definidos para tipos personalizados, a menos que você os instrua explicitamente. Impacto da sobrecarga do operador + em outras operações Além de sobrecarregar o operador “+”, também é possível utilizar o operador composto “+=” com a mesma definição de “operator +”. Isso ocorre porque, ao implementar a sobrecarga do operador “+”, o operador “+=” é automaticamente suportado para somar e atribuir valores diretamente às instâncias. public static void Main(string[] args) { Vetor2D vetor1 = new Vetor2D(1.0, 2.0); Vetor2D vetor2 = new Vetor2D(3.0, 4.0); vetor1 += vetor2; Console.WriteLine($”Resultado após += : {vetor1}”); } O código acima mostra como “vetor1 += vetor2” utiliza a sobrecarga de “operator +” para calcular e atribuir o resultado diretamente a “vetor1”. O mesmo conceito pode ser aplicado a outros operadores como “-” e “-=”, permitindo operações subtrativas com instâncias de “Vetor2D”. Sobrecarga do operador de igualdade == Outro exemplo comum de sobrecarga de operadores é o operador “==”. Quando sobrecarregamos este operador, devemos implementar uma lógica específica para verificar a igualdade entre duas instâncias. Isso garante que a comparação seja feita com base nos atributos que definem a identidade ou igualdade de objetos. Na classe “Vetor2D” adicione: public static bool operator ==(Vetor2D v1, Vetor2D v2) { if (ReferenceEquals(v1, v2)) return true; if (v1 is null || v2 is null) return false; return v1.X == v2.X && v1.Y == v2.Y; } No código acima, iniciamos verificando se a referência na memória dos dois objetos é igual usando “ReferenceEquals(v1, v2)”. Se for verdade, podemos retornar “true” imediatamente, pois estamos lidando com o mesmo objeto. Em seguida, verificamos se um dos objetos é “null”. Caso seja, retornamos “false” porque um objeto “null” não pode ser considerado igual a um objeto válido, garantindo que não ocorra uma comparação com uma referência nula. Por fim, realizamos a comparação das propriedades X e Y dos vetores. Se ambas as propriedades de “v1” e “v2” forem iguais, retornamos “true”, indicando que as duas instâncias de “Vetor2D” são consideradas iguais. Se os valores forem diferentes, retornaremos “false”. Com isso, podemos criar uma lógica de validação personalizada para o operador “==”, permitindo que a comparação de objetos seja feita com base nos critérios mais relevantes para o contexto específico da classe. Dessa forma, é possível definir como a igualdade será verificada, seja por comparação de propriedades, valores específicos ou até mesmo regras mais complexas, tornando o uso dos operadores mais intuitivo e alinhado com a lógica de negócio do sistema. Regras para sobrecarga de operadores Ao trabalhar com a sobrecarga de operadores em C#, é importante ter em mente algumas regras e diretrizes: Somente operadores definidos podem ser sobrecarregados: você não pode criar operadores completamente novos. Pode apenas modificar o comportamento de operadores já existentes, como “+”, “-”, “*”, “==”, “!=”, etc. Sobrecarga de operadores deve ser feita com métodos públicos e estáticos: os métodos que implementam a lógica para operadores devem ser declarados como “public static”. Isso garante que eles podem ser usados com os operadores sem a necessidade de uma instância da classe. Mantenha a consistência ao sobrecarregar operadores: se você sobrecarregar “==” para comparar dois objetos, você deve sobrecarregar ” !=” para fornecer uma comparação inversa. Acelere
Integrando o Google Gemini em aplicações .NET

A API do Google Gemini permite criar conteúdo de forma automatizada, utilizando a inteligência artificial para gerar texto baseado em modelos avançados. Neste artigo vamos aprender a integrar API Gemini em um projeto .NET usando uma aplicação de console, incluindo desde a configuração inicial até um exemplo prático de requisição. Criando a chave de API no Google AI Studio Acesse o Google AI Studio e faça login na sua conta. Após o login, clique em “Get API Key” e, em seguida, em “Criar chave de API”. Se você ainda não tiver criado uma chave de API, ela será gerada automaticamente. Caso contrário, será necessário escolher o projeto desejado e criar uma nova chave. Após a criação da chave, copie o valor gerado e armazene-o em um local seguro, pois usaremos essa chave para realizar as requisições à API. Configurando o projeto Agora, vamos criar um projeto de console para implementar o nosso exemplo. Para isso, execute os comandos abaixo para iniciar um novo projeto usando a .NET CLI: dotnet new console -n GoogleGeminiExemplo cd GoogleGeminiExemplo Implementação O primeiro passo será criar duas constantes para armazenar a chave da API e a URL base para a requisição à API do Google Gemini. Substitua “COLOQUE_AQUI_SUA_KEY” pela sua chave de API que criamos anteriormente. public static class Program { private const string ApiKey = “COLOQUE_AQUI_SUA_KEY”; private const string BaseUrl = “https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=”; } Essas variáveis são necessárias para configurar a requisição e permitir a comunicação com a API. Agora, vamos configurar o ponto de entrada da aplicação. No método “Main”, criaremos o conteúdo de texto (prompt) que será enviado para a API e chamaremos o método para fazer a requisição. public static async Task Main(string[] args) { var prompt = “Quais as vantagens de estudar programação?”; using var client = new HttpClient(); var requestBody = new { contents = new[] { new { parts = new[] { new { text = prompt } } } } }; // Processo de requisição continua aqui… } No código acima, nós criamos uma variável chamada “prompt” para poder armazenar a pergunta que será enviada. Também vamos criar uma nova instância do “HttpClient” para podermos enviar a nossa requisição e criar uma variável “requestBody” que vai conter o corpo da nossa requisição. Segundo a documentação da API, a requisição deve seguir o formato abaixo: Para representar esse formato em C#, podemos utilizar um tipo anônimo que terá a estrutura igual a variável “requestBody”. Esse tipo anônimo possui a mesma estrutura, com uma chave “contents”, que é um array contendo objetos com a chave “parts”, que por sua vez contém um array de objetos com a chave “text”. O valor de “text” será o prompt que passamos como argumento para o método. Em seguida, enviamos a requisição usando o método “PostAsJsonAsync”, que serializa automaticamente o “requestBody” em JSON e define o “Content-Type” corretamente. var response = await client.PostAsJsonAsync($”{BaseUrl}{ApiKey}”, requestBody); Após a requisição ser enviada, vamos verificar se a resposta foi bem-sucedida. Caso positivo, a resposta será desserializada diretamente para um objeto do tipo “RespostaGemini”. Caso contrário, apresentamos o erro na tela do console. if (response.IsSuccessStatusCode) { var resultado = await response.Content.ReadFromJsonAsync(); Console.WriteLine(“Response from API:”); if (resultado?.Candidates != null && resultado.Candidates.Count > 0) { Console.WriteLine(resultado.Candidates[0].Content.Parts[0].Text); } } else { var errorResponse = await response.Content.ReadAsStringAsync(); Console.WriteLine($”Error: {errorResponse}”); } No código acima, verificamos se a propriedade “Candidates” não é nula e se contém pelo menos um item. Se ambas as condições forem satisfeitas, exibimos o texto que contém a resposta para a pergunta enviada na requisição. Para desserializar a resposta da API de maneira adequada, precisamos criar classes que representam a estrutura do JSON retornado. O exemplo a seguir mostra como isso pode ser feito: public class RespostaGemini { public List Candidates { get; set; } } public class Candidate { public Content Content { get; set; } } public class Content { public List Parts { get; set; } } public class Part { public string Text { get; set; } } Essas classes correspondem à estrutura do JSON retornado pela API, permitindo que possamos acessar o conteúdo gerado de forma simples. Agora, se colocarmos a nossa aplicação para ser executada, vamos receber uma resposta semelhante a esta: 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 Integrar a API do Google Gemini em um projeto .NET é um processo que oferece uma forte capacidade de geração de conteúdo utilizando a inteligência artificial de forma simples e eficaz. Certifique-se de sempre monitorar suas requisições, implementar boas práticas de segurança e otimização e estar atento às atualizações das APIs para garantir que suas soluções estejam alinhadas com o melhor que a tecnologia pode oferecer. Assim, você estará preparado para impulsionar suas aplicações ao próximo nível com o poder da inteligência artificial.