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;

Tratamento de eventos com JavaScript

Criar páginas web interativas com JavaScript exige a compreensão e o controle dos eventos que ocorrem durante a navegação do usuário. Os eventos permitem que desenvolvedores capturem e respondam a ações como cliques, digitação, movimentação do mouse ou envio de formulários. Neste artigo vamos apresentar exemplos práticos para adicionar, manipular e controlar eventos, transformando elementos estáticos em componentes dinâmicos. O que são eventos na web? Eventos na web são interações que ocorrem em uma página ou aplicação, representando ações realizadas pelos usuários ou pelo sistema. Eles englobam desde simples cliques em botões até a digitação de caracteres e a movimentação do mouse. Quando um evento é acionado, ele pode desencadear respostas específicas do navegador, permitindo interações dinâmicas com elementos da página. Alguns exemplos de eventos comuns: Clique (click): quando o usuário clica em um elemento; Movimentação do mouse (mousemove): quando o mouse se move sobre um elemento; Teclado (keydown, keyup): quando uma tecla é pressionada ou liberada; Carregamento de página (load): quando um recurso ou a página inteira é carregado; Formulários (submit): quando um formulário é enviado. Estes eventos são a base para criar comportamentos dinâmicos, e a manipulação adequada deles possibilita melhorar a interatividade da página e proporcionar uma experiência do usuário mais rica. Manipulação de eventos com JavaScript A manipulação de eventos em JavaScript é feita por meio de “ouvintes de eventos” (event listeners), que “ouvem” quando um evento específico acontece e, em seguida, executam uma função chamada manipulador de eventos (event handler). Adicionando um event listener O método “addEventListener” é uma maneira moderna e flexível de adicionar manipuladores de eventos a elementos HTML. Clique aqui! No exemplo acima, temos um botão com o atributo “id” definido como “MeuBotao”. Utilizamos o método “getElementById” para capturar esse botão e armazenar sua referência em uma constante chamada “button”. A partir disso, usamos o método “addEventListener” para associar um ouvinte ao evento de clique (click). Sempre que o botão for clicado, o navegador executará a função fornecida, que no caso, exibe um alerta na tela. Parâmetros nos Eventos Alguns eventos fornecem informações adicionais através de um objeto de evento, acessível como parâmetro da função associada. Este objeto contém detalhes como o elemento acionador e a posição do cursor. Exibição no navegador: Aqui, a função associada recebe um parâmetro chamado “event”, que contém informações sobre o evento. Utilizamos “event.target.tagName” para exibir no console o nome do elemento clicado (neste caso, será “INPUT”). Isso pode ser útil para validar entradas de formulário em tempo real, fornecendo feedback instantâneo ao usuário, rastrear cliques, analisar o comportamento do usuário ou criar elementos interativos baseados na posição do clique. Removendo um Event Listener Quando precisamos evitar que um comportamento previamente associado a um evento continue ocorrendo, podemos utilizar o método “removeEventListener”. Essa abordagem é útil em situações onde a funcionalidade precisa ser desativada temporária ou permanentemente, como pausar interações em um botão após um clique ou desativar um evento de movimento do mouse para melhorar o desempenho. Para usar esse método, é necessário que a função associada ao evento tenha um nome definido. Ativar Desativar A função “mostrarMensagem” é adicionada ao evento click do botão “Ativar” usando “addEventListener”. No botão “Desativar”, usamos “removeEventListener” para remover o evento associado ao botão “Ativar”. Um alerta é exibido para informar que o evento foi desativado. Eventos comuns Existem diversos eventos disponíveis em JavaScript. Vamos explorar os mais utilizados, organizados por categoria. Eventos do Mouse Evento Descrição click  Disparado ao clicar em um elemento. dblclick Disparado ao dar um duplo clique. mouseenter Disparado ao mover o cursor para um elemento. mouseout Disparado ao mover o cursor para fora de um elemento. mousemove Disparado ao mover o cursor dentro de um elemento. Agora vamos exemplificar utilizando o evento “’mousemove”: Passe o mouse aqui No console do navegador fica da seguinte forma: No exemplo acima, o evento “mousemove” é usado para detectar o movimento do cursor dentro da área. O objeto “event” fornece as coordenadas do cursor, acessadas através de “event.clientX” e “event.clientY”, que são exibidas no console. Eventos de input Esses eventos monitoram interações em campos de entrada de dados. Eles podem ser utilizados para destacar visualmente os campos ativos em um formulário, ajudando o usuário a identificar claramente onde está digitando. Evento Descrição input Disparado sempre que o valor de um campo muda. focus Disparado quando um campo recebe o foco. blur Disparado quando um campo perde o foco. Exemplo com evento “focus” e “blur”: No navegador será apresentado da seguinte forma: O evento focus altera a cor de fundo do campo para amarelo quando ele recebe o foco, ou seja, quando clicamos no input. Já o evento blur restaura a cor original quando o campo perde o foco. Eventos do teclado Esses eventos lidam com interações relacionadas ao teclado. Eles são úteis para implementar funcionalidades como atalhos de teclado, captura de comandos personalizados, preenchimento dinâmico de campos ou interações baseadas em teclas específicas Evento Descrição keydown Disparado ao pressionar uma tecla. keyup Disparado ao soltar uma tecla. Abaixo temos um exemplo de tratamento do evento keydown   Neste exemplo, o evento “keydown” é usado para capturar a tecla pressionada pelo usuário. O texto exibido no elemento com id definido como “Resultado” é atualizado com a tecla correspondente, acessada através de “event.key”. Conclusão Existem muitos outros eventos além dos mostrados, e para cada necessidade específica, você deve buscar o evento que melhor se adequa ao seu caso. A escolha do evento certo depende do comportamento que você deseja capturar, como interações do usuário, mudanças no conteúdo ou animações. Com a prática, você aprenderá a combinar eventos e a criar comportamentos dinâmicos para melhorar a experiência do usuário. Consulte a documentação para explorar mais opções e aprimorar suas habilidades em JavaScript.

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.

Métodos LINQ avançados: Distinct, Except, Intersect e Union

Neste artigo abordaremos quatro métodos muito importantes do LINQ (Language Integrated Query): Distinct, Except, Intersect e Union. Além disso, exploraremos suas versões aprimoradas (com o sufixo “By”), que permitem a definição de uma chave específica para comparação. Vamos examinar como esses métodos funcionam e em que situações as variações com o “By” são mais úteis, com exemplos práticos em C#. Distinct e DistinctBy O método “Distinct” é utilizado para remover duplicatas de uma coleção, retornando apenas os elementos únicos. Ele é útil quando queremos evitar valores repetidos. Suponha que temos uma lista de números e queremos obter apenas os valores únicos: List numeros = new List { 1, 2, 3, 2, 4, 4, 5 }; var numerosUnicos = numeros.Distinct().ToList(); Console.WriteLine(string.Join(“, “, numerosUnicos)); O resultado será uma lista sem repetições de elementos, ou seja, “1, 2, 3, 4, 5”. Já o “DistinctBy” é uma versão mais flexível que permite definir uma chave específica para determinar a unicidade dos elementos. Assim, ele é útil para comparar apenas um campo ou propriedade, mantendo elementos únicos com base na chave escolhida. Por exemplo, considere uma lista de objetos “Pessoa”, em que queremos apenas uma instância de cada pessoa com base na” Cidade”. List pessoas = new List { new Pessoa { Nome = “João”, Cidade = “São Paulo” }, new Pessoa { Nome = “Maria”, Cidade = “Rio de Janeiro” }, new Pessoa { Nome = “Pedro”, Cidade = “São Paulo” } }; var pessoasDistintasPorCidade = pessoas.DistinctBy(p => p.Cidade).ToList(); foreach (var pessoa in pessoasDistintasPorCidade) { Console.WriteLine($”{pessoa.Nome} – {pessoa.Cidade}”); } O resultado será: “João – São Paulo, Maria – Rio de Janeiro”. Aqui, o “DistinctBy” elimina duplicatas com base no campo “Cidade”, retornando uma lista onde cada cidade aparece apenas uma vez. Except e ExceptBy O método “Except” retorna os elementos de uma coleção que não estão presentes em outra, funcionando como uma operação de diferença. Ele é útil para excluir elementos comuns entre duas listas. Considere duas listas de números. Queremos saber quais números estão na primeira lista, mas não na segunda. List lista1 = new List { 1, 2, 3, 4, 5 }; List lista2 = new List { 3, 4, 5, 6, 7 }; var diferenca = lista1.Except(lista2).ToList(); Console.WriteLine(string.Join(“, “, diferenca)); O resultado final será uma lista com os elementos 1 e 2, pois eles são os únicos elementos que não estão na “lista2”. Já o “ExceptBy” permite que você defina uma chave específica para realizar a comparação, sendo especialmente útil quando queremos comparar apenas um campo entre duas coleções. Agora, suponha duas listas de objetos “Produto”. Queremos saber quais produtos na primeira lista não estão na segunda, mas comparando apenas pelo campo “Codigo”. List produtosLoja1 = new List { new Produto { Codigo = 1, Nome = “Camiseta” }, new Produto { Codigo = 2, Nome = “Calça” }, new Produto { Codigo = 3, Nome = “Tênis” } }; List produtosLoja2 = new List { new Produto { Codigo = 3, Nome = “Tênis” }, new Produto { Codigo = 4, Nome = “Boné” } }; var produtosExclusivosLoja1 = produtosLoja1.ExceptBy(produtosLoja2.Select(p => p.Codigo), p => p.Codigo).ToList(); foreach (var produto in produtosExclusivosLoja1) { Console.WriteLine($”{produto.Nome} – {produto.Codigo}”); // Saída: Camiseta – 1, Calça – 2 } O resultado será a lista “produtosExclusivosLoja1” contendo apenas os itens camiseta e calça. Intersect e IntersectBy O método “Intersect” vai realizar a ação inversa ao método “Except”. Ele vai retornar apenas os elementos que estão presentes em ambas as coleções, funcionando como uma operação de interseção. É útil quando queremos saber quais itens duas coleções compartilham. Vamos supor que temos duas listas de números e queremos saber quais números estão presentes em ambas. List numeros1 = new List { 1, 2, 3, 4 }; List numeros2 = new List { 3, 4, 5, 6 }; var intersecao = numeros1.Intersect(numeros2).ToList(); Console.WriteLine(string.Join(“, “, intersecao)); Com isso, o nosso resultado será armazenado na lista “intersecao ” contendo apenas os valores 3 e 4. Já o “IntersectBy” permite fazer essa interseção que seja baseada em uma chave específica, que é ideal para cenários onde queremos comparar apenas um campo ou propriedade entre duas listas. Agora, considere duas listas de objetos “Aluno”. Queremos saber quais alunos estão matriculados em ambas as turmas, comparando apenas pelo “Id”. List turmaA = new List { new Aluno { Id = 1, Nome = “Carlos” }, new Aluno { Id = 2, Nome = “Ana” }, }; List turmaB = new List { new Aluno { Id = 2, Nome = “Ana” }, new Aluno { Id = 3, Nome = “Mariana” } }; var alunosEmAmbasTurmas = turmaA.IntersectBy(turmaB.Select(a => a.Id), a => a.Id).ToList(); foreach (var aluno in alunosEmAmbasTurmas) { Console.WriteLine($”{aluno.Nome} – {aluno.Id}”); } Neste exemplo, o “IntersectBy” retorna apenas a aluna “Ana” que está em ambas as turmas. Union e UnionBy Por fim, o método “Union” combina duas coleções, retornando uma única lista que contém todos os elementos, sem duplicatas. Ele é útil para unir listas de mesmo tipo enquanto remove repetições. Suponha que temos duas listas de números e queremos combiná-las, mas mantendo apenas valores únicos. List numerosA = new List { 1, 2, 3 }; List numerosB = new List { 3, 4, 5 }; var uniao = numerosA.Union(numerosB).ToList(); Console.WriteLine(string.Join(“, “, uniao)); O código acima nos retornará uma lista com os elementos 1,2,3,4,5. A sua variação, o “UnionBy”, permite definir uma chave específica para determinar a unicidade dos elementos combinados, sendo prático para garantir que objetos com valores duplicados em uma propriedade específica sejam removidos. Agora, imagine duas listas de objetos “Carro” onde queremos combinar as listas e manter apenas uma instância de cada carro com base na “Placa”. List carrosGaragem1 = new List { new Carro { Placa = “ABC1234”, Modelo = “Sedan” }, new Carro { Placa = “DEF5678”, Modelo = “SUV” } }; List carrosGaragem2 = new List { new Carro { Placa = “DEF5678”, Modelo = “SUV” }, new Carro { Placa = “GHI9101”, Modelo = “Hatch” } }; var todosCarros = carrosGaragem1.UnionBy(carrosGaragem2, c => c.Placa).ToList(); foreach (var carro

Formulários em HTML: Conheça os tipos de input

Formulários em HTML são uma das principais ferramentas para coletar informações dos usuários em websites. Através deles, podemos enviar dados para o servidor, registrar informações de login, realizar pesquisas, fazer cadastros, entre outros. A tag “<form>” é a estrutura básica para criar um formulário, e dentro dela podemos usar diversos tipos de inputs para coletar diferentes tipos de dados. Neste artigo, vamos explorar os diferentes tipos de input que podem ser usados dentro de um formulário, como utilizá-los corretamente, e discutir boas práticas para garantir que os formulários sejam acessíveis e funcionais. Estrutura básica de um formulário A estrutura básica de um formulário em HTML envolve a tag “<form>”, que envolve todos os elementos do formulário, incluindo os inputs. O formulário também deve conter o atributo “action”, que define para onde os dados serão enviados, e o atributo “method”, que define o método HTTP utilizado para o envio (geralmente GET ou POST). Além disso, um “<input type=”submit”>” é responsável por enviar as informações preenchidas no formulário para o destino especificado no “action”. Este botão desencadeia o envio dos dados e a execução do método definido. Agora, vamos examinar os diferentes tipos de inputs que podem ser usados dentro de um formulário. Campo de texto O campo de texto é um dos tipos de input mais comuns, usado para capturar informações simples, como nomes e endereços. Nome: Como fica no navegador: No exemplo acima, utilizamos um campo de texto para capturar o nome do usuário. Uma “” foi adicionada acima para orientar sobre a informação a ser inserida. Abaixo, há um “input” do tipo “submit” que envia o formulário preenchido ao servidor, criando uma interface clara. Podemos adicionar configurações como o atributo “id”, que identifica de forma única o elemento na página e permite que ele seja vinculado à “” por meio do atributo “for”. Isso melhora a acessibilidade e facilita a interação com scripts e CSS. O atributo “name” define a chave para enviar os dados ao submeter o formulário. No exemplo, “name=”nome”” faz com que o valor inserido seja enviado como parte dos dados com a chave “nome”, para que o servidor o processe. O atributo “required” indica que o campo é obrigatório. Se o formulário for enviado sem preencher esse campo, o navegador mostra uma mensagem de validação, garantindo que o usuário insira as informações necessárias antes de prosseguir. Campo de senha O campo de senha é usado quando queremos esconder a entrada do usuário, como em formulários de login ou cadastros onde o usuário precisa informar uma senha e não queremos que ela fique visível. Senha: Como fica no navegador: Campo de email Este campo é utilizado para coletar um endereço de e-mail e já realiza validação automática para verificar se o formato do e-mail está correto (por exemplo, nwe@dominio.com). E-mail: Como fica no navegador: Campo de número O campo de número permite que o usuário insira apenas números, e você pode definir atributos como “min” e “max” para restringir os valores permitidos. Idade: Como fica no navegador: Se o usuário inserir um valor fora do intervalo especificado, o navegador exibirá uma mensagem de erro. Por exemplo, ao informar um número menor que 18 ou maior que 100, será mostrada uma mensagem de validação para que o usuário corrija o valor. Campo de data Esse tipo de input permite que o usuário selecione uma data no formato adequado, com o calendário sendo exibido automaticamente na maioria dos navegadores. Data do Evento: Como fica no navegador:   Você também pode utilizar os atributos “min” e “max” para restringir as datas selecionáveis no campo de data. No exemplo abaixo, as datas anteriores a 9 de novembro de 2024 e as datas posteriores a 31 de dezembro de 2024 não são permitidas: Assim, o usuário será obrigado a escolher uma data dentro do intervalo especificado, garantindo maior controle sobre as entradas: Caixa de Seleção As caixas de seleção permitem que o usuário selecione uma ou mais opções. Pode ser útil em formulários de consentimento ou preferências. Receber Newsletter: Como fica no navegador:   Botões de Opção Os botões de opção (radio buttons) são usados quando o usuário deve escolher apenas uma opção entre várias. Todos os botões de opção dentro de um grupo devem ter o mesmo valor para o atributo name. Sexo: Masculino Feminino Como fica no navegador:     Campo de Arquivo Este campo permite que o usuário selecione arquivos de seu dispositivo para enviar junto com o formulário. É comumente usado, por exemplo, quando um usuário precisa adicionar uma foto de perfil ou enviar documentos. Escolha um Arquivo: Como fica no navegador:   Após selecionar o arquivo, o nome e a extensão aparecem para que o usuário tenha certeza do arquivo escolhido antes de enviá-lo.    Este tipo de input também permite a adição de atributos como accept para definir quais tipos de arquivos são permitidos (ex: imagens, PDFs), proporcionando maior controle sobre as entradas aceitas. Neste exemplo, o campo de upload aceitará apenas arquivos com as extensões .jpg, .jpeg ou .png. Isso restringe o tipo de arquivo que o usuário pode selecionar, proporcionando maior controle sobre os uploads e melhorando a experiência do usuário ao evitar arquivos não desejados. Campo Textarea Enquanto o input do tipo “text” representa um campo de texto de apenas uma linha, às vezes é necessário incluir um texto maior, com múltiplas linhas. Para esses casos usamos a tag “<textarea>”. Diferente dos anteriores, ela não é uma variação da tag “<input>”, mas sim um elemento diferente. Descrição: Os atributos “rows” e “cols” definem, respectivamente, a quantidade de linhas e colunas padrão do campo. Consequentemente, definem seu tamanho inicial. O resultado no browser é o seguinte: Campo Dropdown Também é possível criar campos de seleção do tipo “dropdown” no HTML. Para isso usamos a tag “<select>” e cada opção é representada pela tag “<option>”: Horário: Manhã Tarde Noite Observe que assim como nas tags “<input>” e “<textarea>”, também usamos os atributos “id” e “name” para identificar o campo. Já na tag “<option>”,

Tratamento de exceções em C#

O tratamento de exceções é uma parte fundamental do desenvolvimento de software, garantindo que erros inesperados sejam tratados de maneira controlada e que a aplicação continue funcionando de forma estável. No C#, as exceções podem ser tratadas utilizando as estruturas “try”, “catch”, “finally” e “throw”. Neste artigo vamos explorar como utilizar essas estruturas para capturar, tratar e limpar recursos e como tratar exceções de forma global. O que são exceções? Exceções são condições anormais ou erros que ocorrem durante a execução de um programa. Elas podem ser causadas por uma variedade de razões, como falha em uma operação de leitura de arquivos, tentativa de divisão por zero, entre outros. O tratamento adequado dessas exceções é crucial para garantir que a aplicação não falhe silenciosamente e que o usuário receba uma resposta apropriada. Bloco try O bloco “try” é utilizado para encapsular código que pode gerar uma exceção. Se uma exceção ocorrer, a execução será desviada para o bloco “catch” correspondente (caso exista). Aqui está um exemplo de como usar o “try-catch” para tratar exceções: try { var caminho = “D:\Arquivo\Inexistente\arquivo.txt”; var conteudo = File.ReadAllText(caminho); Console.WriteLine(conteudo); } catch (FileNotFoundException ex) { Console.WriteLine($”Erro: Arquivo não encontrado – {ex.Message}”); } Neste exemplo, qualquer tentativa de leitura de um arquivo inexistente gerará uma exceção que será capturada pelo bloco catch. Sem o uso de “try-catch”, o programa falharia abruptamente se ocorresse uma exceção. Por exemplo, tente executar o código abaixo sem tratamento de exceção: var caminho = “D:\Arquivo\Inexistente\arquivo.txt”; string conteudo = File.ReadAllText(caminho); Console.WriteLine(conteudo); Uma exceção de difícil compreensão será lançada, resultando em uma mensagem genérica de erro que pode não fornecer informações claras sobre a causa do problema. Isso dificulta o diagnóstico e a resolução do erro. Por outro lado, ao usar o “try-catch”, podemos capturar e tratar a exceção de forma controlada, personalizando a mensagem de erro para que ela seja mais informativa e fácil de compreender. Bloco catch Como vimos acima, o “catch” captura e trata exceções que foram lançadas em um bloco “try”. Você pode definir múltiplos blocos “catch” para tratar tipos específicos de exceções. try { int divisor = 0; int resultado = 100 / divisor; Console.WriteLine($”Resultado: {resultado}”); } catch (DivideByZeroException) { Console.WriteLine(“Erro: Não é possível dividir por zero.”); } catch (Exception) { Console.WriteLine(“Ocorreu um erro”); } Neste exemplo, se “divisor” for zero, a exceção “DivideByZeroException” será capturada. Outros erros serão tratados pelo bloco genérico “catch”. Aqui a ordem de definição dos blocos catch é importante: exceções mais específicas devem ser tratadas primeiro, enquanto as mais genéricas, como a classe “Exception”, deve vir depois.  Ainda no bloco “catch” é importante notar que você pode ou não declarar uma variável para receber os detalhes da exceção. Note que no exemplo acima especificamos apenas o nome da classe de exceção que está sendo tratada. Dessa forma, capturamos a exceção, mas não temos acesso aos seus dados. Se desejarmos acessar maiores detalhes sobre o erro, podemos fazer da seguinte forma: catch (Exception ex) { Console.WriteLine($”Erro genérico: {ex.Message}”); } Agora a variável “ex” recebe a instância da exceção lançada e, a partir dela, podemos acessar os detalhes do erro. Aqui cabe destacar as propriedades Message e StackTrace das classes de exceção. A primeira traz uma mensagem descritiva sobre o problema, enquanto a segunda traz o detalhamento da pilha de chamadas no código que gerou a exceção. Essa informação é crucial para identificarmos qual trecho do código causou o erro. Também é possível adicionar filtros no bloco “catch” para realizar tratamentos mais personalizados para cada cenário. Por exemplo: public int GetItem(int index) { List items = [1, 2, 3, 4, 5]; try { return items[index]; } catch(ArgumentOutOfRangeException) when (index < 0) { Console.WriteLine("Índice não pode ser negativo"); throw; } catch(ArgumentOutOfRangeException) { Console.WriteLine($"Índice incorreto. A lista só contém {items.Count} itens."); throw; } } No código acima estamos tratando o mesmo tipo de exceção duas vezes. Porém, no primeiro caso adicionamos um filtro para que o bloco “catch” só seja utilizado quando o parâmetro “index” for menor que zero. Para quaisquer outros valores inválidos de “index”, o segundo bloco “catch” será acionado. Bloco finally O bloco “finally” é utilizado para garantir que o código de limpeza será executado, independentemente de uma exceção ter sido lançada ou não. Ele pode ser usado com ou sem o bloco “catch” e é muito comum para realizar o descarte de recursos após algum processamento. public void AbrirConexao() { SqlConnection conexao = null; try { conexao = new SqlConnection("sua_conexao"); conexao.Open(); } catch (SqlException ex) { Console.WriteLine($"Erro de SQL: {ex.Message}"); } finally { if (conexao != null) { conexao.Close(); Console.WriteLine("Conexão fechada."); } } } No código acima o bloco “finally” fecha a conexão com o banco de dados, mesmo que ocorra uma exceção durante a execução. Instrução throw A instrução “throw” é utilizada para lançar uma exceção, seja criando uma nova ou re-lançando uma exceção já capturada. Isso permite que a exceção seja tratada em um nível superior ou forneça informações mais detalhadas sobre o erro. Por exemplo, no código abaixo, se a idade for menor que 18, uma exceção “ArgumentException” é lançada: public void ValidarIdade(int idade) { if (idade < 18) { throw new ArgumentException("A idade deve ser maior ou igual a 18 anos."); } } Além disso, o “throw” pode ser usado dentro de um bloco “catch” para re-lançar uma exceção capturada, permitindo que ela seja tratada em outro nível, como mostrado abaixo: try { if (idade < 18) { throw new ArgumentException("A idade deve ser maior ou igual a 18 anos."); } } catch (Exception ex) { Console.WriteLine($"Log de erro: {ex.Message}"); throw; } Esse uso do “throw” ajuda a criar um fluxo de controle eficiente para exceções, possibilitando um tratamento mais flexível e organizado. 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

Introdução aos Worker Services no .NET

Um Worker Service no .NET é uma aplicação que roda em segundo plano, projetada para executar tarefas contínuas ou agendadas, sem a necessidade de interação direta com o usuário. Isso torna o Worker Service uma excelente opção para tarefas como processamento de dados, envio de e-mails, ou integração com filas de mensagens, especialmente em projetos que já possuem uma API existente. Uma das grandes vantagens é que você pode implementar um Worker Service dentro de um projeto de API já existente, sem a necessidade de criar um novo projeto separado. Isso facilita o gerenciamento e a manutenção do código, além de permitir a reutilização de serviços e configurações da API. Implementando um Worker Service em um projeto de API existente O Worker Service pode ser implementado dentro da estrutura do projeto existente, por exemplo, em uma nova pasta chamada “Workers”. Essa abordagem centraliza o código e simplifica a manutenção. Após criar uma pasta no local desejado, crie a classe. Para o nosso exemplo, eu criei uma classe chamada  “ProcesamentoPedidosWorker.cs” dentro dessa pasta, simulando um serviço que realiza o processamento de pedidos de um e-commerce: public class ProcesamentoPedidosWorker : BackgroundService { private readonly ILogger _logger; public ProcesamentoPedidosWorker(ILogger logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation(“Processando pedidos”); await Task.Delay(5000, stoppingToken); } } } Em nosso exemplo acima, a classe “ProcesamentoPedidosWorker”, herda da classe “BackgroundService”, uma abstração fornecida pelo .NET para facilitar a criação de serviços de segundo plano. No construtor de “ProcesamentoPedidosWorker”, o serviço de log (ILogger) é injetado para registrar informações, erros ou outras mensagens durante a execução do Worker. A injeção de dependência permite que o Worker use os serviços registrados no container da aplicação (como repositórios, serviços de e-mail, etc.). Neste caso, estamos injetando um logger para registrar logs da execução. O método mais importante em um Worker Service é o “ExecuteAsync”, que contém a lógica principal que será executada em segundo plano. Nele criamos um loop “while” que  garante que o Worker continue executando enquanto o serviço não for cancelado. O “stoppingToken” é um token de cancelamento que sinaliza quando o serviço deve parar (como ao desligar a aplicação).  Após isso, usamos o “ILogger” para registrar uma mensagem a cada execução. Por fim, o  método “Task.Delay(5000)” cria uma pausa de 5 segundos entre cada ciclo de execução, simulando a execução de uma tarefa periódica. O “stoppingToken” é passado para permitir que a tarefa seja interrompida caso o Worker seja cancelado. Registre o Worker no Program.cs Para que o Worker Service seja executado em conjunto com a sua API, você precisa registrá-lo no “Program.cs”. No arquivo “Program.cs”, adicione o registro do serviço: builder.Services.AddHostedService(); No exemplo acima, utilizamos o método “AddHostedService” para registrar o Worker Service na coleção de serviços da aplicação. A classe “ProcesamentoPedidosWorker” é passada como o tipo do serviço, indicando ao .NET que essa classe será executada em segundo plano enquanto a API estiver ativa. Isso permite que o “ProcesamentoPedidosWorker” execute suas tarefas paralelamente, sem bloquear as operações da API, facilitando a implementação de processos contínuos ou agendados no seu projeto. Injeção de Dependências no Worker Service Assim como acontece nas APIs, o Worker Service pode aproveitar o ServiceProvider para a injeção de dependências. Isso permite o uso de serviços e repositórios que já existem no seu projeto, facilitando a integração. No exemplo abaixo, vamos supor que você tenha um serviço “IEmailService” que envia e-mails, e deseja utilizá-lo no Worker: public interface IEmailService { Task SendEmailAsync(string to, string subject, string body); } public class EmailService : IEmailService { public Task SendEmailAsync(string to, string subject, string body) { Console.WriteLine($”Enviando e-mail para {to}”); //Implementação do envio de e-mail. return Task.CompletedTask; } } Agora, no seu Worker, você pode injetar esse serviço e utilizá-lo para enviar e-mails: public class ProcesamentoPedidosWorker: BackgroundService { private readonly ILogger _logger; private readonly IEmailService _emailService; public ProcesamentoPedidosWorker(ILogger logger, IEmailService emailService) { _logger = logger; _emailService = emailService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation(“Processando pedidos”); await _emailService.SendEmailAsync(“cliente@exemplo.com”, “Pedido aprovado”, “Seu pedido foi aprovado!”); await Task.Delay(5000, stoppingToken); } } } E não se esqueça de registrar o serviço “IEmailService” no “Program.cs”: builder.Services.AddSingleton(); Dessa forma, o serviço de e-mail será usado pelo Worker sempre que ele for executado. 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 A integração de um Worker Service em um projeto de API existente no .NET oferece uma solução eficiente e centralizada para tarefas em segundo plano. O Worker Service pode ser facilmente adicionado em uma pasta do projeto, aproveitando toda a infraestrutura existente, como injeção de dependências e configurações. Além disso, ele é ideal para cenários como tarefas agendadas, monitoramento de serviços e processamento de dados. Esse recurso permite criar uma arquitetura mais robusta, escalável e modular, facilitando o gerenciamento de processos de segundo plano na aplicação e proporcionando uma implementação eficiente e organizada para atender as necessidades específicas do sistema.