.NET

21 jan, 2019

ASP.NET Core: dicas úteis para o dia a dia de um desenvolvedor – Parte 01

Publicidade

Este artigo dá início a uma série com dicas que considero úteis para desenvolvedores que empregam o ASP.NET Core na implementação de soluções. Tais recomendações têm por objetivo:

  • Simplificar o desenvolvimento
  • Escrever menos código
  • Implementar soluções mais elegantes
  • Empregar padrões de mercado

Suporte a JSON no SQL Server

Embora no .NET Core exista o suporte às principais tecnologias relacionais do mercado, muitas aplicações ASP.NET Core utilizam bases de dados a partir de servidores SQL Server.

Desde a versão 2016, o SQL Server conta com o suporte ao formato JSON, inclusive com a possibilidade de se formatar e converter o resultado de uma query para este padrão.

Tal característica acaba por se revelar como muito útil em projetos Web, uma vez que dispensará a escrita de várias classes para retorno de informações e trará ganhos de performance, evitando diversas operações de transformação de dados.

Na listagem a seguir temos um exemplo de uso deste recurso (cláusula FOR JSON):

using System.Data.SqlClient;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Dapper;

namespace ExemploJsonSqlServer.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CotacoesController : ControllerBase
    {
        [HttpGet]
        public ContentResult Get(
            [FromServices]IConfiguration config)
        {
            string valorJSON;
            using (SqlConnection conexao = new SqlConnection(
                config.GetConnectionString("ExemplosDapper")))
            {
                valorJSON = conexao.QueryFirst<string>(
                    "SELECT Sigla " +
                          ",NomeMoeda " +
                          ",UltimaCotacao " +
                          ",ValorComercial AS 'Cotacoes.Comercial' " +
                          ",ValorTurismo AS 'Cotacoes.Turismo' " +
                    "FROM dbo.Cotacoes " +
                    "ORDER BY NomeMoeda " +
                    "FOR JSON PATH, ROOT('Moedas')");
            }

            return Content(valorJSON, "application/json");
        }
    }
}

O projeto em questão faz uso do micro-ORM Dapper e foi disponibilizado no GitHub:

Tendo ainda sido apresentado em detalhes no seguinte artigo:

Habilitando o uso de CORS

Ativar a utilização de CORS (Cross-origin resource sharing) corresponde a uma prática extremamente em projetos que contém com APIs REST e front-ends baseados em JavaScript. No caso específico do ASP.NET Core, este procedimento envolverá ajustes na classe Startup de uma API:

  • Uma chamada ao método AddCors em ConfigureServices
  • Acionar ainda o uso do middleware UseCors no método Configure
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Swashbuckle.AspNetCore.Swagger;
using Agenda.Models;

namespace Agenda
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            // ...
            
            services.AddCors();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            // ...
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env,
            AgendaContext dbContext)
        {
            // ...

            app.UseCors(builder => builder.AllowAnyMethod()
                                          .AllowAnyOrigin()
                                          .AllowAnyHeader()
                                          .AllowCredentials());

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Extension Methods para uma melhor organização na classe Startup

É praticamente certo que ajustes serão realizados na classe Startup nos mais variados tipos de projetos baseados no ASP.NET Core. Independente das necessidades da aplicação, há uma forte tendência de que a quantidade de código envolvendo acertos de configuração aumente consideravelmente com o decorrer do tempo.

Como então evitar que a classe Startup atinja então centenas ou, até mesmo, milhares de linhas de código?

Extensões/Extension Methods contribuem para um código mais limpo, além de promover o reuso (com a criação de bibliotecas contendo tais extensões) e evitar o surgimento de diversas classes derivadas de tipos básicos da plataforma.

O artigo a seguir aborda essa questão através de um exemplo prático, abordando as configurações para utilização de JWT (JSON Web Tokens) em uma API REST:

ASP.NET Core: dica simples para obter um código mais limpo na classe Startup

Configurando a cultura

Muitos desenvolvedores se esquecem que o deployment de aplicações em Produção pode acontecer em ambientes com configurações regionais diferentes das encontradas em suas máquinas. Isso muito provavelmente acarretará em problemas como erros na conversão de dados ou formatação de valores.

A solução para isso passa por ajustes com o intuito de especificar as culturas/padrões esperados para o projeto via mecanismo de Localization do ASP.NET Core.

No próximo artigo temos um exemplo disso, no qual foi apresentada uma alternativa para corrigir problemas de conversão em uma aplicação que faz uso de Docker:

Gerando um site para documentação/testes de APIs REST com Swagger

O Swagger é uma solução compatível com as principais plataformas de desenvolvimento Web e que possibilita a geração de um site documentando APIs com pouquíssimo código, além de contar com funcionalidades para testes e geração de um documento JSON contendo toda a estrutura de um projeto.

Para ativar o uso do Swagger em APIs REST criadas com o ASP.NET Core, será necessário:

  • Adicionar o package Swashbuckle.AspNetCore ao projeto
  • Invocar o método AddSwaggerGen em ConfigureServices na classe Startup, repassando ao mesmo dados informativos sobre o projeto
  • Acionar ainda os middlewares UseSwagger e UseSwaggerUI no método Configure de Startup, a fim de habilitar o Swagger na aplicação

Na listagem a seguir podemos observar os ajustes esperados para a classe Startup:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;

namespace APITemperatura
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
 
            services.AddSwaggerGen(c => {

                c.SwaggerDoc("v1",
                    new Info
                    {
                        Title = "Conversor de Temperaturas",
                        Version = "v1",
                        Description = "Exemplo de API REST criada com o ASP.NET Core 2.1 para a conversão de temperaturas",
                        Contact = new Contact
                        {
                            Name = "Renato Groffe",
                            Url = "https://github.com/renatogroffe"
                        }
                    });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            // Ativando middlewares para uso do Swagger
            app.UseSwagger();
            app.UseSwaggerUI(c => {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Agenda API V1");
            });

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Este exemplo foi também disponibilizado no GitHub:

Na próxima imagem está um exemplo de site de documentação gerado pelo Swagger:

Retornando objetos a partir de configurações declaradas no arquivo appsettings.json

Uma das grandes novidades do ASP.NET Core foi a substituição do Web.config pelo arquivo appsettings.json. Essa mudança de XML para JSON foi acompanhada por uma série de facilidades oferecidas pela plataforma, simplificando de maneira bastante significativa a manipulação de itens de configuração.

Na listagem a seguir temos um exemplo de conjunto de configurações com dados do Brasil no arquivo appsettings.json de um projeto ASP.NET Core:

{
  "DadosPais": {
    "Nome": "Brasil",
    "NomeOficial": "Republica Federativa do Brasil",
    "Capital": "Brasilia",
    "Populacao": {
      "NumHabitantes": 208494900,
      "DensidadeHabKm2": 23.8
    },
    "Economia": {
      "PIB": 3217000000000.0,
      "RendaPerCapita": 15646.0
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Já a próxima listagem traz uma estrutura de classes utilizada para representar os dados vinculados ao item DadosPais em appsettings.json:

namespace APIDadosBrasil.Models
{
    public class DadosPais
    {
        public string Nome { get; set; }
        public string NomeOficial { get; set; }
        public string Capital { get; set; }
        public DadosPopulacao Populacao { get; set; }
        public DadosEconomia Economia { get; set; }
    }

    public class DadosPopulacao
    {
        public int NumHabitantes { get; set; }
        public double DensidadeHabKm2 { get; set; }
    }

    public class DadosEconomia
    {
        public double PIB { get; set; }
        public double RendaPerCapita { get; set; }
    }
}

Uma possibilidade para carregamento das informações declaradas em appsettings.json está indicada na seguinte implementação da classe Startup:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using APIDadosBrasil.Models;

namespace APIDadosBrasil
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            // Carregando as informações vinculadas a DadosPais em appsettings.json
            var dadosPais = new DadosPais();
            dadosPais.Nome = Configuration["DadosPais:Nome"];
            dadosPais.NomeOficial = Configuration["DadosPais:NomeOficial"];
            dadosPais.Capital = Configuration["DadosPais:Capital"];

            var dadosPopulacao = new DadosPopulacao();
            dadosPais.Populacao = dadosPopulacao;
            dadosPopulacao.NumHabitantes = Convert.ToInt32(
                Configuration["DadosPais:Populacao:NumHabitantes"]);
            dadosPopulacao.DensidadeHabKm2 = Convert.ToDouble(
                Configuration["DadosPais:Populacao:DensidadeHabKm2"]);

            var dadosEconomia = new DadosEconomia();
            dadosPais.Economia = dadosEconomia;
            dadosEconomia.PIB = Convert.ToDouble(
                Configuration["DadosPais:Economia:PIB"]);
            dadosEconomia.RendaPerCapita = Convert.ToDouble(
                Configuration["DadosPais:Economia:RendaPerCapita"]);

            services.AddSingleton(dadosPais);

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Através da classe ConfigureFromConfigurationOptions (namespace Microsoft.Extensions.Options) podemos simplificar esse conjunto de instruções de leitura, que englobou 16 linhas de código, reduzindo esta ação para um equivalente de apenas três linhas. É o que demonstra a listagem a seguir:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using APIDadosBrasil.Models;

namespace APIDadosBrasil
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            var dadosPais = new DadosPais();

            new ConfigureFromConfigurationOptions<DadosPais>(
                Configuration.GetSection("DadosPais"))
                    .Configure(dadosPais);

            services.AddSingleton(dadosPais);

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Na próxima imagem é possível constatar que o objeto do tipo DadosPais foi carregado corretamente por meio do uso de ConfigureFromConfigurationOptions:

Referências