Índices e intervalos em arrays no C#

Os arrays são estruturas fundamentais em C#, permitindo armazenar e manipular coleções de elementos do mesmo tipo. A partir do C# 8.0, novas funcionalidades foram introduzidas para tornar o trabalho com arrays ainda mais eficiente. Neste artigo vamos explorar desde os índices tradicionais até as últimas novidades como índices negativos com o operador “^” e intervalos “(..)” para criar subarrays. Índices tradicionais em arrays Nos arrays, os índices são utilizados para acessar elementos individuais. Eles começam do zero, ou seja, o primeiro elemento está no índice 0, o segundo no índice 1, e assim por diante. Se você tentar acessar um índice fora dos limites do array (numeros[5]), uma exceção “IndexOutOfRangeException” será lançada. Índices negativos com o operador ^ Com o C# 8.0, foi introduzido o operador “^” para trabalhar com índices relativos ao final do array. Isso simplifica o acesso ao último elemento ou a elementos próximos ao fim. Aqui, o número após o operador “^” indica a posição a ser acessada de trás para frente no array. Por exemplo: int[] numeros = { 10, 20, 30, 40, 50 }; Console.WriteLine(numeros[^1]); Console.WriteLine(numeros[^2]); No exemplo acima, o console exibirá os valores 50 (último elemento) e 40 (penúltimo elemento). Intervalos com o operador .. O operador “..” permite criar subarrays (fatias ou slices) de maneira concisa. Ele utiliza dois índices, um inicial e um final, separados por “..”. Regras para usar o operador .. Ao definir um intervalo como [inicial..final], você especifica onde o intervalo começa e termina. int[] numeros = { 10, 20, 30, 40, 50 }; int[] fatia1 = numeros[1..3]; Console.WriteLine(string.Join(“, “, fatia1)); Nesse exemplo, a fatia inclui os elementos “numeros[1]” (20) e “numeros[2]” (30), mas exclui o elemento “numeros[3]”. Se você omitir o índice inicial (..final), o intervalo começa no primeiro elemento do array e vai até o índice especificado (não incluído). int[] fatia2 = numeros[..3]; Console.WriteLine(string.Join(“, “, fatia2)); O intervalo “[..3]” inclui os elementos “numeros[0]”, “numeros[1]” e “numeros[2]”. Se você omitir o índice final (inicial..), o intervalo vai do índice inicial especificado até o último elemento do array. int[] fatia3 = numeros[2..]; Console.WriteLine(string.Join(“, “, fatia3)); Nesse caso, o intervalo “[2..]” inclui todos os elementos a partir de “numeros[2]” até o final do array. Se você omitir tanto o índice inicial quanto o final (..), o intervalo abrange o array inteiro. Essa sintaxe equivale a criar uma cópia completa do array. int[] fatiaCompleta = numeros[..]; Console.WriteLine(string.Join(“, “, fatiaCompleta)); Combinação com o operador ^ O operador “^” pode ser combinado com “..” para criar intervalos relativos ao final do array. Isso é útil para trabalhar com os últimos elementos de uma coleção. int[] ultimos = numeros[^3..]; Console.WriteLine(string.Join(“, “, ultimos)); O [ ^3.. ] começa no terceiro elemento a partir do final e vai até o final do array. A saída será 30, 40, 50. int[] semOsUltimos = numeros[..^2]; Console.WriteLine(string.Join(“, “, semOsUltimos)); O [ ..^2 ] vai do início do array até dois elementos antes do final. O resultado será 10, 20, 30 Combinando intervalos com métodos de extensão Uma outra possibilidade do C# é a de combinar intervalos com métodos de extensão como “Where”, “Select”, e outros disponíveis no LINQ. Isso permite criar filtros e transformações dinâmicas diretamente sobre fatias de arrays. Você pode usar o operador “..” para criar um subarray e aplicar um filtro dinâmico com o método “Where”: int[] numeros = { 10, 15, 20, 25, 30 }; var pares = numeros[1..4].Where(n => n % 2 == 0); Console.WriteLine(string.Join(“, “, pares)); O intervalo “[1..4]” seleciona os elementos do índice 1 ao índice 3  (15, 20, 25). Após isso, o método “Where” filtra os elementos que são pares. A saída resulta em 20. Além de filtrar, você pode transformar os valores de um intervalo utilizando o método “Select”: int[] numeros = { 10, 15, 20, 25, 30 }; var dobrados = numeros[2..].Select(n => n * 2); Console.WriteLine(string.Join(“, “, dobrados)); Neste exemplo, o intervalo “[2..]” seleciona todos os elementos a partir do índice 2 (20, 25, 30). O método “Select” aplica a transformação “n * 2” a cada elemento do intervalo. O resultado será uma nova lista com os números 40, 50, 60 armazenados na variável “dobrados”. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão Os índices negativos e intervalos no C# oferecem novas possibilidades para manipular arrays e coleções de forma simples e eficiente. Com essas funcionalidades, o código se torna mais expressivo, eliminando a necessidade de cálculos manuais de índices. Além disso, intervalos e índices negativos podem ser combinados com outros recursos modernos do C# para criar soluções robustas e claras. Explore essas funcionalidades em seus projetos e veja como elas podem simplificar suas implementações!

Como implementar upload de arquivos no ASP.NET MVC

Quando desenvolvemos aplicações web, é comum precisar de funcionalidades para adicionar imagens ou arquivos, como fotos de perfil de usuários, imagens de produtos, documentos anexados e muito mais. Implementar essa funcionalidade de forma eficiente e segura é essencial para uma boa experiência do usuário e para a integridade do sistema. Neste artigo vamos explorar como configurar o upload de arquivos em uma aplicação ASP.NET MVC, abordando cenários práticos, como salvar arquivos no sistema de arquivos local, armazená-los em um banco de dados. Configurando o controlador para upload Crie um controlador chamado “FileUploadController” com as seguintes ações: public class FileUploadController : Controller { public IActionResult Index() { return View(); }; [HttpPost] public async Task Upload(IFormFile file) { //código que recebe o arquivo. } } No código acima criamos duas actions no controlador “FileUploadController”. A primeira, “Index”, é responsável por exibir a tela com o formulário para envio de arquivos, onde o usuário poderá selecionar o arquivo desejado.  Já a segunda, “Upload”, é a action que recebe o arquivo enviado pelo formulário. Para isso nós usamos a interface “IFormFile”, que é responsável por encapsular os dados do arquivo enviados pelo cliente e permitir o acesso diretamente no lado do servidor. Mais adiante vamos detalhar a implementação da lógica na action “Upload” para processar o arquivo enviado pelo usuário. Esse método seguirá etapas para validar o arquivo e salvar o conteúdo de forma segura. Criando a view com o formulário de upload Agora vamos criar a view com o nome “Index.cshtml” com o nosso formulário: Escolha o arquivo: Enviar No codigo acima, nós criamos um formulário informando que ele deve ser enviado para action “Upload” que está na controller “FileUpload”. Logo abaixo, vamos adicionar as mensagens que serão retornadas em caso de erro ou sucesso: @if (TempData[“Message”] != null) { @TempData[“Message”] } @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) { @error.ErrorMessage } Validando o se o arquivo é válido A primeira validação é para saber se foi enviado um arquivo válido. if (file == null || file.Length == 0) { ModelState.AddModelError(“”, “Selecione um arquivo válido.”); return View(“Index”); } Se o arquivo recebido for nulo ou tiver tamanho zero, o sistema exibirá uma mensagem de erro informando o problema e redirecionará o usuário de volta à tela inicial (Index). Isso evita processar uploads inválidos. Validação de extensão Agora vamos adicionar uma validação para aceitar apenas arquivos com determinadas extensões. var allowedExtensions = new[] { “.jpg”, “.png”, “.pdf” }; var extension = Path.GetExtension(file.FileName).ToLower(); if (!allowedExtensions.Contains(extension)) { ModelState.AddModelError(“”, “Formato de arquivo não permitido.”); return View(“Index”); } Nesta etapa, permitimos apenas arquivos com as extensões .jpg, .png e .pdf. Qualquer outro formato será rejeitado com uma mensagem de erro. Isso protege a aplicação de uploads de arquivos maliciosos. Validação de tamanho máximo Além disso, teremos uma validação tamanho máximo do arquivo. if (file.Length > 5 * 1024 * 1024) { ModelState.AddModelError(“”, “O arquivo não pode exceder 5 MB.”); return View(“Index”); } Isso vai evitar sobrecarga no servidor e uploads indesejados, impomos um limite de tamanho (5 MB). Arquivos maiores serão rejeitados com uma mensagem adequada. Armazenando o arquivo na aplicação Agora que adicionamos as validações, vamos armazenar o arquivo na aplicação, garantindo que a pasta de destino exista antes de salvar o arquivo. Primeiro, definimos o diretório onde os arquivos serão armazenados: var uploadDirectory = Path.Combine(Directory.GetCurrentDirectory(), “wwwroot/uploads”); Aqui, indicamos que os arquivos serão salvos na pasta “wwwroot/uploads”. Caso essa pasta não exista, podemos criá-la da seguinte forma: if (!Directory.Exists(uploadDirectory)) { Directory.CreateDirectory(uploadDirectory); } Essa verificação evita erros no caso de a pasta ainda não estar criada no servidor ou em ambientes de desenvolvimento. Em seguida, definimos o caminho completo para o arquivo: var filePath = Path.Combine(uploadDirectory, file.FileName); Agora, usamos um FileStream para gravar os dados do arquivo no local especificado: using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } Essa operação realiza a gravação de forma assíncrona, permitindo que a aplicação permaneça responsiva mesmo durante o upload de arquivos. //Implementar lógica de gravação do nome do arquivo no banco. TempData[“Message”] = “Arquivo enviado com sucesso!”; return RedirectToAction(“Index”); Após salvar o arquivo, você pode implementar um procedimento para salvar as informações do arquivo (nome, extensão …) no banco de dados. Após isso, armazenamos uma mensagem de sucesso em “TempData” e redirecionamos o usuário de volta à tela inicial. Essa mensagem será exibida na view para confirmar que o upload foi concluído. Salvando em banco de dados Para salvar uma imagem diretamente no banco de dados você pode criar uma entidade “FileRecord” para armazenar as informações do arquivo. Nesta entidade a propriedade “Data” armazena os dados reais do arquivo em formato binário (array de bytes). É nessa propriedade que os dados do arquivo serão salvos. public class FileRecord { public int Id { get; set; } public string FileName { get; set; } public string ContentType { get; set; } public byte[] Data { get; set; } } Agora podemos reaproveitar a action que “Upload” que criamos anteriormente para processar o arquivo recebido. public async Task Upload(IFormFile file) { //Adicione as validações do arquivo recebido. using var memoryStream = new MemoryStream(); await file.CopyToAsync(memoryStream); var fileRecord = new FileRecord { FileName = file.FileName, ContentType = file.ContentType, Data = memoryStream.ToArray() }; context.FileRecords.Add(fileRecord); await context.SaveChangesAsync(); return RedirectToAction(“Index”); } Primeiro, criamos uma variável do tipo “MemoryStream”, que serve para armazenar temporariamente os dados do arquivo em memória.  Em seguida, utilizamos o método “CopyToAsync” do arquivo enviado (file), que copia os dados do arquivo diretamente para o “memoryStream”. Após obter os dados do arquivo, criamos uma entidade do tipo “FileRecord”, que representa a estrutura do arquivo que será salvo no banco de dados. Com a entidade “fileRecord” criada e preenchida, nós a adicionamos ao banco de dados usando o método “Add” no contexto da aplicação (context). Concluímos redirecionando o fluxo para a view “Index”, proporcionando uma experiência contínua e amigável. Observação: Neste exemplo, não incluímos validações específicas para o arquivo recebido. Entretanto, em aplicações reais, é recomendável implementar validações para garantir segurança e consistência. Dicas de segurança Validação do tipo de arquivo:

Serialização avançada com System.Text.Json em C#

A serialização e desserialização de objetos para JSON são tarefas comuns em projetos .NET. No ecossistema .NET, o “System.Text.Json” se destaca como uma biblioteca nativa, eficiente e flexível para lidar com JSON. Embora seja amplamente utilizado para casos simples, situações complexas frequentemente surgem, como trabalhar com formatos de data e hora personalizados ou lidar com enums representados como strings no JSON. Neste artigo, exploraremos como o “System.Text.Json” pode ser adaptado para atender a essas demandas, com foco na criação e uso de conversores personalizados. Introdução ao System.Text.Json Antes de nos aprofundarmos em técnicas mais avançadas, é importante compreender como funciona a serialização e desserialização básica com o System.Text.Json. Essa biblioteca oferece suporte nativo para converter objetos C# em JSON e vice-versa. Vejamos o exemplo abaixo: using System.Text.Json; public class Pessoa { public string Nome { get; set; } public int Idade { get; set; } } var pessoa = new Pessoa { Nome = “Joao”, Idade = 30 }; // Serialização: Objeto C# para JSON string json = JsonSerializer.Serialize(pessoa); Console.WriteLine(json); //Resultado apresentado no console: {“Nome”:”João”,”Idade”:30} // Desserialização: JSON para Objeto C# var desserializado = JsonSerializer.Deserialize(json); Console.WriteLine($”{desserializado.Nome}, {desserializado.Idade}”); //Resultado apresentado no console: Joao, 30 Neste exemplo, o “System.Text.Json” utiliza automaticamente os nomes das propriedades da classe para mapear os campos no JSON. Esse comportamento é conveniente em cenários onde o formato do JSON corresponde diretamente à estrutura da classe. Agora que entendemos o funcionamento básico, vamos explorar como lidar com situações mais complexas Conversores personalizados Conversores personalizados são úteis quando: O formato JSON não corresponde diretamente às propriedades da classe. É necessário manipular valores especiais, como enums representados por strings ou formatos de data específicos. O JSON contém propriedades dinâmicas ou ausentes. Criando um conversor personalizado Os conversores personalizados são implementados estendendo a classe “JsonConverter<T>”. Para usar um conversor, você o registra no nível da propriedade (com [JsonConverter]) ou globalmente, nas opções do serializador. Imagine um sistema que utiliza o formato de data “dd/MM/yyyy”. Um conversor personalizado pode mapear esse formato para “DateTime”. { “Cliente”: “Maria”, “DataEntrega “: “25/12/1990″ } Para realizarmos a serialização do json em um objeto C#, vamos criar uma classe chamada “Pedido”: public class Pedido { public string Cliente { get; set; } [JsonConverter(typeof(DateFormatConverter))] public DateTime DataEntrega { get; set; } } A classe “Pedido” possui as propriedades “Cliente” um campo string simples, que será mapeado automaticamente no JSON e “DataEntrega” um campo “DateTime”, que usa o atributo “[JsonConverter]” para associar um conversor personalizado, “DateFormatConverter” que será criado logo abaixo. Esse atributo é necessário porque o formato padrão de serialização para “DateTime” no “System.Text.Json” usa ISO 8601 (“yyyy-MM-ddTHH:mm:ss.fffZ”), o que não corresponde ao formato específico (“dd/MM/yyyy”) usado neste exemplo. Agora vamos criar o conversor personalizado: public class DateFormatConverter : JsonConverter { private const string DateFormat = “dd/MM/yyyy”; public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return DateTime.ParseExact(reader.GetString(), DateFormat, null); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString(DateFormat)); } } O “DateFormatConverter” é uma classe personalizada que estende o comportamento do “System.Text.Json” ao serializar e desserializar datas no formato “dd/MM/yyyy”. Ele herda de “JsonConverter<DateTime>” e implementa dois métodos principais: Read (Desserialização): Esse método é chamado quando um JSON está sendo convertido em um objeto C#. Ele utiliza o “Utf8JsonReader” para ler o valor da propriedade no JSON e, em seguida, usa “DateTime.ParseExact” para interpretar a string no formato específico (“dd/MM/yyyy”) e transformá-la em um objeto “DateTime”.  Write (Serialização): Durante a serialização, este método é chamado para converter um valor “DateTime” do objeto C# em uma string JSON formatada como “dd/MM/yyyy”. O método utiliza o “Utf8JsonWriter” e chama “value.ToString(DateFormat)” para garantir que o formato de saída corresponda ao esperado. Para testar podemos utilizar o seguinte código: var json = @”{ “”Cliente””: “”João””, “”DataEntrega””: “”15/12/2024″” }”; var pedido = JsonSerializer.Deserialize(json); Console.WriteLine($”{pedido.Cliente} – {pedido.DataEntrega:yyyy-MM-dd}”); Tratamento de Enums como Strings Enums podem ser representados como strings personalizadas no JSON, e um conversor facilita essa tradução, permitindo que os valores do enum sejam manipulados de maneira mais flexível ao serializar ou desserializar objetos: public enum Status { Ativo, Inativo } public class StatusConverter : JsonConverter { public override Status Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetString() switch { “ativo” => Status.Ativo, “inativo” => Status.Inativo, _ => throw new JsonException(“Valor inválido”) }; } public override void Write(Utf8JsonWriter writer, Status value, JsonSerializerOptions options) { writer.WriteStringValue(value == Status.Ativo ? “ativo” : “inativo”); } } No código acima, o método “Read” verifica qual valor no JSON corresponde aos casos definidos no “switch” e retorna o valor do enum correspondente. Já o método “Write” converte o valor do enum em uma string para ser escrita no JSON. Agora, vamos criar a classe “Usuario” que irá receber o enum “Status” com o conversor personalizado: public class Usuario { [JsonConverter(typeof(StatusConverter))] public Status Status { get; set; } } Com isso, podemos criar um exemplo para testar o processo de desserialização: var usuarioJson = @”{ “”Status””: “”ativo”” }”; var usuario = JsonSerializer.Deserialize(usuarioJson); Console.WriteLine(usuario.Status); Neste exemplo, o JSON com a string “ativo” é convertido para o valor “Status.Ativo” na propriedade “Status” do objeto “Usuario”. Esse processo permite um mapeamento flexível entre os valores do enum e suas representações em string no JSON, facilitando a integração com sistemas que utilizam formatos específicos para representar enums. Manipulação de Dados Dinâmicos Em muitos casos, ao trabalhar com dados JSON, a estrutura pode ser imprevisível ou variar de acordo com diferentes cenários, como quando consumimos APIs externas ou lidamos com sistemas de terceiros. Nesses casos, não é prático ou possível definir previamente classes para representar todos os formatos possíveis de JSON. Para esses cenários, o “System.Text.Json” fornece ferramentas como “JsonElement” e “JsonDocument”, que permitem acessar e manipular os dados de forma dinâmica, sem a necessidade de modelos específicos.  Para entendermos melhor, imagine que você está consumindo uma API que retorna informações de pedidos, mas o formato do JSON pode variar dependendo dos itens no pedido ou de atualizações no sistema. var json = @” { “”PedidoId””: 12345, “”Cliente””: “”João Silva””, “”Itens””: [ { “”Produto””: “”Notebook””,

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

Trabalhando com pseudo-classes no CSS

No design de páginas web, o CSS é uma ferramenta essencial para criar uma apresentação visual atraente e funcional. As pseudo-classes, por sua vez, oferecem recursos avançados para aplicar estilos de forma inteligente, com base em ações do usuário ou características específicas dos elementos na página. Elas permitem que o desenvolvedor altere a aparência de um site em tempo real, sem a necessidade de interações externas, como JavaScript, garantindo uma navegação mais fluida e interativa. Neste artigo vamos entender o que são as pseudo-classes, como elas funcionam e explorar diversas formas de usá-las para melhorar a experiência do usuário e otimizar o design de suas páginas. O que são pseudo-classes? Antes de aprendermos a usar as pseudo-classes, é importante entender o que elas são. Elas são palavras-chave no CSS que aplicam estilos a elementos com base em condições específicas, como quando um campo recebe foco, um usuário interage com um botão ou um elemento ocupa determinada posição na página. Diferente dos seletores comuns, que trabalham com elementos fixos no HTML, as pseudo-classes permitem criar estilos dinâmicos que respondem ao comportamento do usuário ou à estrutura do DOM. Abaixo conseguimos ver a sintaxe básica de uma pseudo-classes. Após o seletor vamos adicionar “:” e informar qual seletor queremos utilizar: Aqui conseguimos ver um exemplo básico utilizando o “hover”: :hover Vamos iniciar falando sobre a pseudo-classe “:hover”, que é uma das mais populares no CSS, especialmente para adicionar interatividade aos elementos. Quando o cursor do mouse passa sobre um elemento, ela é ativada. Imagine que você deseja criar um menu no qual os links mudem de cor ao serem destacados, oferecendo um feedback visual claro. Passe o mouse sobre o link Quando o usuário passa o mouse sobre o link, ele muda de azul para vermelho e aparece sublinhado, indicando que é clicável. Esse comportamento melhora a usabilidade e cria uma experiência mais interativa. :focus Digamos que você esteja criando um formulário e queira destacar os campos que estão em foco. A pseudo-classe “:focus” ajuda a identificar visualmente qual campo está ativo, tornando o formulário mais acessível e intuitivo. Nome: Quando o usuário clica no campo de texto, a borda muda para verde, destacando o campo ativo. :nth-child Dentro da pseudo-classe “:nth-child”, você pode usar os valores odd (ímpar) e even (par) para selecionar respectivamente os elementos de posição ímpar ou par dentro de um container. Essas opções são úteis para aplicar estilos alternados a elementos de listas, tabelas ou qualquer outro grupo de elementos, sem a necessidade de selecionar manualmente cada um. odd: seleciona os elementos que estão em posições ímpares (1, 3, 5, 7, etc.). even: seleciona os elementos que estão em posições pares (2, 4, 6, 8, etc.). Esses valores são frequentemente usados para alternar as cores de fundo de itens de uma lista, criando um efeito visual de “zebra”, onde os itens alternam entre cores claras e escuras. Item 1 Item 2 Item 3 Item 4 No código acima conseguimos ver que o “li:nth-child(odd):” aplica o estilo ao primeiro, terceiro, quinto, e outros itens em posições ímpares. Já o “li:nth-child(even):” aplica o estilo ao segundo, quarto, sexto, e outros itens em posições pares. :not A pseudo-classe “:not” é bastante útil quando você precisa estilizar quase todos os elementos de um determinado tipo, mas deseja excluir um subconjunto específico. Imagine que você está desenvolvendo um sistema em que botões secundários devem ter um estilo padronizado, enquanto o botão principal precisa de um destaque visual diferente. Você pode usar “:not” para aplicar um estilo aos botões secundários sem interferir no botão principal. Botão Principal Botão Secundário 1 Botão Secundário 2 O “‘button:not(.primary)” aplica estilos a todos os botões que não possuem a classe “.primary”. :checked A pseudo-classe “‘:checked” é indicada quando você quer destacar visualmente elementos de formulário que foram selecionados. Isso é muito útil em cenários de listas de tarefas, filtros, ou qualquer interface que dependa da interação do usuário com caixas de seleção ou botões de rádio. Imagine que você está criando uma lista de tarefas onde itens concluídos devem ser riscados para indicar que estão finalizados. Tarefa 1 Tarefa 2 Tarefa 3 Quando o checkbox é marcado, o rótulo associado ao elemento recebe um estilo de texto riscado e cor cinza. Conclusão Agora que você compreendeu como as pseudo-classes funcionam e viu exemplos práticos, experimente aplicá-las em seus projetos. Teste diferentes combinações e explore sua criatividade para criar interfaces mais dinâmicas e interativas. O uso inteligente dessas ferramentas pode transformar a experiência do usuário e elevar a qualidade do seu design. O próximo passo é colocar esse conhecimento em prática e descobrir como ele pode fazer a diferença nos seus trabalhos.

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

Filtrando elementos HTML com JavaScript

Manipular elementos HTML com JavaScript é uma das tarefas mais comuns no desenvolvimento web moderno, especialmente em aplicações que exigem interatividade em tempo real. Para isso, é fundamental saber como buscar, filtrar e modificar elementos do DOM de forma eficiente. Neste artigo vamos explorar diversas técnicas para selecionar e filtrar elementos HTML com base em atributos, classes e outros critérios, utilizando métodos como “getElementById” e “querySelector”. Por que utilizar filtros? Filtros de elementos HTML são indispensáveis para aplicações interativas, permitindo manipular apenas os elementos necessários de uma página. Isso melhora a eficiência do código, otimiza o desempenho e enriquece a experiência do usuário. A seguir, veremos como aplicar esses filtros de maneira prática e eficiente com JavaScript. getElementById O método “getElementById” é usado para selecionar um único elemento com base no atributo id. É uma das maneiras mais rápidas e eficientes para buscar um elemento e ideal para casos em que o identificador do elemento é único dentro da página. No exemplo abaixo vemos como capturar o valor de um campo de entrada (input) e exibi-lo dinamicamente enquanto o usuário digita. Exemplo de Uso do getElementById O texto aparecerá aqui. No código acima, o “getElementById” seleciona o campo de entrada (input) e o parágrafo (p) pelo atributo id. O “addEventListener” escuta o evento input, disparado sempre que o usuário digita algo. Por fim, o “textContent” atualiza o conteúdo do parágrafo com o valor atual do campo de entrada. getElementsByClassName Já o método “getElementsByClassName” permite selecionar todos os elementos de uma página que compartilham uma mesma classe. Ele retorna uma HTMLCollection, que é uma lista de elementos que pode ser acessada por índice ou iterada para manipulações em massa. Esse método é útil para aplicar alterações em grupos de elementos que possuem o mesmo estilo ou funcionalidade, como mudar a cor de várias caixas com a mesma classe. Exemplo de Uso do getElementsByClassName Caixa 1 Caixa 2 Caixa 3 Mudar Cor Aqui, o “getElementsByClassName” é utilizado para selecionar todas as caixas com a classe “box”. Quando o botão é clicado, o JavaScript percorre a coleção e altera o fundo de todas as caixas para “lightblue”. querySelector Se você precisa de maior flexibilidade para selecionar elementos, o “querySelector” é uma boa opção. Esse método utiliza um seletor CSS e retorna o primeiro elemento que corresponde ao seletor. Ele é bastante versátil, permitindo a combinação de classes, ids, e pseudo-classes CSS em uma única busca. Por exemplo, é possível selecionar apenas o primeiro botão dentro de uma seção específica e aplicar um estilo. Exemplo de Uso do querySelector Botão 1 Botão 2 No exemplo acima, “querySelector” é utilizado para selecionar o primeiro botão dentro da seção que tem a classe “btn”. Quando o botão é clicado, seu texto é alterado para “Clicado!”. Esse método é ideal para buscas mais específicas em situações onde queremos encontrar um único elemento com base em um seletor CSS. querySelectorAll Se o objetivo for selecionar todos os elementos que correspondem a um seletor, você pode utilizar o “querySelectorAll”. Esse método é indicado quando precisamos realizar ações em vários elementos ao mesmo tempo, como alterar o estilo de todos os links de uma lista ou adicionar um evento de clique em todos os botões de uma página. Exemplo de Uso do querySelectorAll Link 1 Link 2 Link 3 No exemplo acima, o “querySelectorAll” é utilizado para selecionar todos os links dentro de uma lista não ordenada. Em seguida, um evento de clique é adicionado a cada link, e um alerta é mostrado com o texto do link clicado. Conclusão Filtrar e manipular elementos HTML com JavaScript é uma habilidade essencial para criar páginas interativas e dinâmicas. Através dos métodos discutidos neste artigo, você pode selecionar elementos de maneira eficiente e aplicar ações ou mudanças em um ou mais deles. Conhecer a fundo as diferenças entre esses métodos e entender quando usá-los vai otimizar seu trabalho e garantir um código mais limpo e eficiente.