Escolha uma Página
Listas DropDown usando o Select Tag Helper no CORE MVC

Listas DropDown usando o Select Tag Helper no CORE MVC

Usando o tag helper SELECT, é muito fácil ter listas dropdown nas suas páginas de criação e edição. Só que como as funcionalidades mudaram muito desde o início do Core MVC, há muita informação na Internet que simplesmente não funciona mais. E você pode perder horas – ou mesmo dias – tentando entender o que está acontecendo até conseguir um simples select com uma lista dropdown. Este artigo mostra como fazer isso rapidamente.

Usando o Select Tag Helper

O tag help para um select é usado adicionando o atributo asp-for a um elemento selecionado. Por exemplo, considere uma classe de modelo de visão muito simples, simples contendo uma propriedade Cidade:

public class MeuModeloDeCidades
{
    public string Cidade{ get; set; }
}

Para gerar o seu select basta instanciar a propriedade Cidade no comando de select, dentro da sua página de visualização (View):

<select asp-for="Cidade"></select>

O tag helper irá gerar o seguinte código HTML:

<select name="Cidade" 
           id="Cidade">
</select>

Este é o princípio lógico. Mas fazendo isso, você não tem nenhuma lista de opções (as cidades) dentro do seu select – e ele não serve para nada. É preciso popular o select com todas opções desejadas.

Acrescentando opções no Select do Tag Helper

Se as opções são poucas – e não vem de um banco de dados, você poder fazer isso no próprio arquivo de visualização (View):

<select asp-for="Cidade">
    <option value="São Paulo">São Paulo</option>
    <option value="Rio de Janeiro">Rio de Janeiro</option>
    <option value="--">Outras Cidades</option>
</select>

Nesse caso, as opções especificadas na marcação serão incluídas no HTML gerado. A opção selecionada será determinada automaticamente com base no valor da propriedade do modelo. Por exemplo, se a propriedade Cidade estiver definida como ‘São Paulo’ no modelo, o seguinte HTML será gerado:

<select name="Cidade" id="Cidade">
    <option selected="selected" value="São Paulo">São Paulo</option>
    <option value="Rio de Janeiro">Rio de Janeiro</option>
    <option value="--">Outras Cidades</option>
</select>

Se a lista de opções for carregada dinamicamente a partir de uma base de dados, você pode usar o atributo auxiliar chamado tag asp-items. Tudo o que você precisa fazer é definir o atributo asp-items para um IEnumerable. Por exemplo, se tivéssemos uma lista de cidades disponíveis numa ViewBag, poderíamos especificar as opções de seleção da seguinte forma:

<select asp-for="Cidades" 
         asp-items="ViewBag.ListaDeCidades">
</select>

IMPORTANTE: Não se esqueça de fechar o elemento <SELECT> com </SELECT>.

Este é o princípio. Mas como popular a ViewBag??? A VIewBag é passado do controlador (controller) para o visualizador (view).

Carregando as opções do Select através do Banco de Dados

Há duas formas de fazer isso. Em seu arquivo de controle (controller), na ação (action) que vai estar associada à visualização, você popula o ViewBag. Vamos imaginar neste exemplo que a Viewbag será usada num visualizador para CRIAR um novo registro. Neste caso, o cliente poderá escolher qualquer opção (não há opção pré definida).

Jeito 1:

public IActionResult Cria()
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            Lista.Add(new SelectListItem //adiciona uma opção que convida a escolher uma das possíveis opções
            {
                Text = "Selecione uma Consultoria",
                Value = ""
            });
            foreach (var Linha in Dados)
            {
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdRelacionamento.ToString(),
                    Text = Linha.TipoDeRelacaoPublico,


                });
            }
            ViewBag.Selecionador = Lista;
         
            return View();
    }

Ou seja, em meu banco de dados tenho um arquivo de Consultorias, que faz parte do contexto (_context).  Quando registro um novo consultor no sistema, tenho de associá-lo a uma consultoria (já cadastrada). A tabela de pessoas vai guardar a Id da Consultoria no cadastramento da pessoa “consultora”. Por isso preciso do select, que chama a tabela de consultorias, mostra as mesmas pelo seu nome, mas ao gravar o arquivo o registro que é passado leva não o nome da consultoria – e sim a ID da consultoria.

Note que no comando foreach posso ordenar o select pelo nome das consultorias (.OrderBy(x=>x.NomeConsultoria). Também poderia incluir algum tipo de filtro acrescentado  .(Where x=>x.NomedoCampo == Uma string  ou um inteiro, conforme o campo).

Note também que ao passar valores inteiros (como uma id, é feita a conversãopara strign (z.IdConsultoria.ToString(), no caso).

A chamada, dentro do arquivo de visualização (View) fica da seguinte forma:

<div class="form-group">
            <label asp-for="IdConsultoria" class="col-md-2 control-label"></label>
            <div class="col-md-10">
             
                <select asp-for="IdConsultoria" class="form-control" asp-items="ViewBag.idConsultoria"></select>
                <span asp-validation-for="IdConsultoria" class="text-danger"></span>
              
            </div>
</div>

Jeito 2

Esta forma tem o “defeito” de não permitir a passagem, pelo controlador (controller) de uma opção tipo “Selecione uma das Consultorias”. Mas funciona.

public IActionResult Cria()
        {
           
         var Lista2 = from x in _context.Consultorias
                         orderby x.NomeConsultoria
                         select x;
            ViewBag.IdConsultoria2 = new SelectList(Lista2.AsNoTracking(), "IdConsultoria", "NomeConsultoria", "");

            return View();
    }

Mas, como no caso anterior, é possível ordenar e filtrar a seleção de opções usando clausulas where e orderby.

Para contornar esta limitação, você pode incluir no visualizador (vView) a seguinte linha, dentro da tag helper do select:

  <option value="">Selecione uma Consultoria</option>

Ficando assim:

<div class="form-group">
            <label asp-for="IdConsultoria" class="col-md-2 control-label"></label>
            <div class="col-md-10">
              <select asp-for="IdConsultoria" class="form-control" asp-items="ViewBag.idConsultoria2">
                    <option value="">Selecione uma Consultoria</option>
                </select>
                <span asp-validation-for="IdConsultoria" class="text-danger"></span>
   
            </div>
</div>

Habilitando Tag Helpers de forma genérica

Para que tag helpers funcionem em qualquer arquivo de visualização, a opção é habilitar no arquivo auxiliar de visualização que é sempre lido (o _ViewImports.cshtml) .

Assim, no arquivo  _ViewImports.cshtml, inclua:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Isso pode ser feito com outros tag helpers que você vier a construir (além dos padrões Microsoft já existentes no Visual Studio).

Criando Funções para Popular os Tag Helper de Selects nos controladores de criação e edição

Ocorre que você pode precisar do mesmo “dropdown” não só em em páginas de criação, mas também páginas de edição.

No caso de edição, já havia uma opção pré escolhida..

.Não é justo fazer com que o usuário tenha que, novamente, definir uma opção que já tinha definido antes. Ele pode mudar de ideia, mas sempre a partir da ideia que já teve antes.

Então, o conceito aqui é gerar o conteúdo da ViewBag considerando que pode ter havido uma opção anterior. É criada uma função privada, que sempre é chamada pelos controladores (controllers) de criação ou edição. Exemplos:

Lendo dados do arquivo Users (quando o sistema tem login)

//FUNCOES POPULA LOOKUP PARA EDICAO
        //PopulateAvaliado(id1); -> chadado pelo controlador
        //PopulateAvaliador(id2);
        //PopulateTipoDeAvaliador(id3);
        private void PopulateAvaliado(object PessoaSelecionada = null)
        {
            var pessoaQuery = from d in _userManager.Users
                              orderby d.UserName
                              select d;

            ViewBag.PessoaAvaliadoId = new SelectList(pessoaQuery.AsNoTracking(), "Id", "Email", PessoaSelecionada);
            return;
         }

Uma forma que funciona muito bem e apresenta o Selecionador somente nos caos de criação (em que não existe opção pré selecionada:

private void PopulateTipoDeAvaliador(object TipoSelecionado = null)
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            if (TipoSelecionado == null)
            {
                Lista.Add(new SelectListItem()
                { Value = null, Text = "<<Selecione Tipo de Relacionamento>>", Selected = true });
            }

            var Dados = from r in _context.Relacionamento orderby r.TipoDeRelacaoPublico select r;
            foreach (var Linha in Dados)
            {
                bool valor = false;
                if (Linha.IdRelacionamento == Convert.ToInt32(TipoSelecionado))
                {
                    valor = true;
                }
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdRelacionamento.ToString(),
                    Text = Linha.TipoDeRelacaoPublico,
                    Selected = valor

                });
            }
            ViewBag.Selecionador = Lista;
        }

Lendo dados de um arquivo de Contexto (_context)

private void PopulateTipoDeAvaliador(object TipoSelecionado = null)
        {
            var relacaoQuery = from e in _context.TipoDeAvaliador
                               orderby e.TextoTipoDeAvaliador
                               select e;
            ViewBag.TipoDeRelacaoId = new SelectList(relacaoQuery.AsNoTracking(), "TipoDeAvaliadorID", "TextoTipoDeAvaliador", TipoSelecionado);
            return;
        }

Chamando os “lookups” dos selects do controlador de Criação:

// GET: Avaliacoes/Create
        public IActionResult Create()
        {
            //var id1 = avaliacoes.AvaliadoId;
            //var id2 = avaliacoes.AvaliadorId;
            // var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado();
            PopulateAvaliador();
            PopulateTipoDeAvaliador();
            return View();
        }

        // POST: Avaliacoes/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("AvaliacaoId,AvaliadoId,AvaliadorId,DataCadastroAvaliacao,DataConviteAvaliacao,DataFimAvaliacao,DataUltimaAlteracao,DataUltimoLembrete,FlagFim,MediaDasRespostas,PerguntasRespondidas,TipoDeAvaliadorID,TotalDePerguntas")] Avaliacoes avaliacoes)
        {

            if (ModelState.IsValid)
            {
                _context.Add(avaliacoes);
                await _context.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

Chamando os “lookups” dos selects do controlador de Edição:

// GET: Avaliacoes/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var avaliacoes = await _context.Avaliacoes.SingleOrDefaultAsync(m => m.AvaliacaoId == id);
            if (avaliacoes == null)
            {
                return NotFound();
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

        // POST: Avaliacoes/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("AvaliacaoId,AvaliadoId,AvaliadorId,DataCadastroAvaliacao,DataConviteAvaliacao,DataFimAvaliacao,DataUltimaAlteracao,DataUltimoLembrete,FlagFim,MediaDasRespostas,PerguntasRespondidas,TipoDeAvaliadorID,TotalDePerguntas")] Avaliacoes avaliacoes)
        {
            if (id != avaliacoes.AvaliacaoId)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(avaliacoes);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!AvaliacoesExists(avaliacoes.AvaliacaoId))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction("Index");
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

É isso. Se tiver sugestões, contribua.

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!

Diferença de datas (datediff) no Core MVC

Diferença de datas (datediff) no Core MVC

O cálculo de diferença de datas (datediff) no Core é muito simples (mas há muita confusão n Internet sobre o assunto.

Um exemplo dentro de uma View, que tira a diferença de datas entre o dia corrente e uma data advinda do modelo da própria view:

@{var hoje = DateTime.Today;
        var limite = Model.DataFim;
        var DiasFaltantes = (limite - hoje).Value.Days;

    }

A variável hoje é um inteiro, tendo valores negativos se hoje for maior que a data na variável limite (capturada no modelo da View como Model.DataFim.

Isso ajuda muito, dentro do view, a tomar decisões de mostrar layouts diferentes (texto e ou dados) para cada uma das situações. Exemplo:

@{var hoje = DateTime.Today;
        var limite = Model.DataFim;
        var DiasFaltantes = (limite - hoje).Value.Days;

    }
    <div class="col-sm-4">
        @*/////botão edita questionário*@

        @if (DiasFaltantes >= 0) //ainda pode preencher avaliação
        {

            @*preenche avaliação*@
        if (Model.FlagFim == true) // preencheu completamente a avaliação, só pode editar
        {
            <a href="@Url.Action("Action", "Controller", new { @idAvaliacao = Model.IdAvaliacao })" class="btn btn-xs btn-success">
                Edite a Avaliação  @*Preenche Avaliação*@
                <span class="glyphicon glyphicon-list" aria-hidden="true"></span>
            </a>
        }
        else
        {
            @*edita avaliação*@
            <a href="@Url.Action("Action", "Controller", new { @idAvaliacao = Model.IdAvaliacao })" class="btn btn-xs btn-success">
                Preencha/Edite a Avaliação  @*Preenche Avaliação*@
                <span class="glyphicon glyphicon-list" aria-hidden="true"></span>
            </a>
            }

        }
        else//nao pode mais preencher avaliação
        {
            <h4 class="alert-danger text-center">Avaliação encerrada</h4>
        }
        }
    </div>

 

 

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!

Formatando datas nas views do CORE MCV

Formatando datas nas views do CORE MCV

Para  ver as datas formatadas em qualquer view que utilize um campo de data, basta alterar o model que essa view utilza. A partir daí todas as views que usarem o mesmo model ficarão com as datas ajustadas.

Deverá ser acrescentada a linha (no caso para formatar datas no formato brasileiro, dia, mês e ano, sem hora:

[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]

Cada campo data do modelo ficará assim:

using System;
using System.ComponentModel.DataAnnotations;

namespace Coaching.Models
{
    public partial class Avaliacoes
    {
        public int IdAvaliacao { get; set; }
        public int IdAvaliado { get; set; }
        public int IdAvaliador { get; set; }
        public string TipoDeAvaliador { get; set; }
        public int IdTipoquestionario { get; set; }
        public bool? FlagFim { get; set; }
        public string Comentario { get; set; }
        public bool? FlagComentario { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataFim { get; set; }
        public int? NumRespostas { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataAltera { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataConvite { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataLembrete { get; set; }
        public int? NumPerguntas { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataCadastro { get; set; }
    }
}

Em cada arquivo de modelo onde fizer isso, deverá sempre acrescentar no topo:

using System.ComponentModel.DataAnnotations;

Essas alterações valem para toda aplicação. Onder que que essas datas entrarem, nas views, eles estarão formatadas.

Outra forma de fazer, mais consistente

Ajuste geral da Cultura do seu projeto

No arquivo Startup.cs, coloque as seguintes instruções logo no começo do método public void ConfigureServices(IServiceCollection services). Isso aplica essas definições para todas páginas da sua aplicação.

Ajuste nos modelos que tem data (date)

Em todos os modelos em que tiver data, formaste o campo data da seguinte forma:

 

Acerto das Views (todas que mostram a data)

Simplesmente substitua todas as linhas (em geral nos arquivos index.cshtml e details.cshtm) que tenham:

<td>
@Html.DisplayFor(modelItem => item.DataCadastro)
</td>

Substitua por:

<td>
@Html.TextBoxFor(modelItem => item.DataCadastro, “{0:dd-MM-yyyy}”)
</td>

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!

Como hospedar Core 1.1 MVC fora da Azure

Como hospedar Core 1.1 MVC fora da Azure

Instale sua aplicação CORE MVC 1.0 ou 1.1 num provedor convencional – e liberte-se da Azure

Usando Visual Studio 2017 você não consegue instalar o o Core 1.1 MVC 6  nem no Godaddy., nem na LOCAWEB, o que é uma pena. Os dois provedores não oferecem nenhuma instrução de como proceder e fazer o upload por FTP dos arquivos gerados localmente no diretório “\bin\Release\PublishOutput” não funciona. No Godaddy você ainda encontra referencias de como rodar MVC3 ou MVC4, mas que não funcionam para a tecnologia atual (essencialmente, mudar o CAS da configuração para TRUST ALL e custom errors=OFF, sem esquecer de dar um recycle no ASPNET (essencialmente, desligar e ligar novamente o IIS). Não funciona e não há qualquer referência na WEB at´este mês de abril de 2017. Na LOCAWEB, parece que nunca ouviram falar no assunto.

Instalando o Core 1.1 MVC fora da Azure

A alternativa foi procurar um provedor americano. E foi rápido descobrir que os provedores indicados pela própria Microsoft não estão preparados para o Core MVC 1.1. Param no Core 1.0, MVC 5…. O único que encontrei foi o provedor A2HOSTING. em https://www.a2hosting.com, e que tem preços razoáveis.

O Visual Studio 2017 (bem como o 2015 release 3) funciona perfeitamente para fazer o deploy no Azure, mas descobri que a opção de instalar os sites no Azure custa uma tinta mensal (“serviços de aplicativos standard”): você não gasta menos que R$ 200/mês mesmo que faça um site mambembe, sem banco de dados. Pior: a opção de instalação via FTP em qualquer provedor, no momento, é uma enganação: o programa não sobe nenhum arquivo do diretório “bin” – embora “complete a tarefa sem nenhum erro. O jeito é gerar o site para “file” (no diretório “\bin\Release\PublishOutput”) e depois subir tudo por FTP para  o provedor.

Já a instalação do SQL Server e de banco de dados no Azure é muito barata e, ao menos em tempo de desenvolvimento, você não gasta mais que uns R$10,00 mensais. Minha ideia, independentemente de onde fica o banco de dados, foi ter um provedor muito mais barato que a Azure. O custo do A2Hosting é de uns US$11/mensais (planos pagos mensalmente).Cuidado na escolha do plano: pegue o ASP.NET TURBO CORE MVC HOSTING. Além do que, você tem certificados grátis e pode ter SSL e SSD, ou seja, não precisa gastar R$ 120 comprando na Locaweb cada certificado (e renovando isso anualmente!). Sus sites podem rodar em https, já que desde janeiro deste ano o Google está dando prioridade para os sites seguros na sua indexação orgânica…

 

A aplicação CORE MVC funciona perfeitamente!

Criei uma aplicação Core MVC 1.1 e subi para o provedor (sem login, sem base de dados), usando o Visual Studio 2017. Funcionou perfeitamente, de primeira.

Depois subi via FTP, usando o Visual Studio 2015, uma aplicação Core MVC 1.0 com login e uma base de dados SQL residente no Azure. Aí a aplicação deu erro, mas devido ao ao firewall do Azure. Da mesma forma que você precisa informar o IP da sua máquina para o firewall do Azure, permitindo que seu “localhost” acesse o SQL da Azure, você deve também acrescentar o IP da máquina do provedor (no caso, da A2 Hosting). A própria mensagem de erro orienta você a fazer isso. Se isso ocorrer com você, vai notar que mesmo autorizando o IP do provedor no firewall do SQL Azure, a aplicação não vai entrar. Não se desespere. Você tem que aguardar uns 5 a 10 minutos (a dica também está na mensagem de erro do provedor). Deve ser algum tempo de cache do provedor ou do IIS. Bem, depois de uns 5 minutos a aplicação entrou perfeitamente. Estou livre, finalmente, do caríssimo Gerenciador de Aplicativos do Azure (que só deve valer a pena para grandes aplicações, que exijam escalabilidade).

Migrando a base de dados SQL também

Meu próximo passo foi me libertar do MS SQL do Azure, usando o próprio MS do plano A2Hosting. Você pode criar quantas bases de dados quiser – e sem nenhum custo adicional. Duro mesmo foi arrancara base de dados do Azure, que não tem nenhuma ferramenta (ou não encontrei no meio de tanta coisa). O jeito foi acessar a base com um cliente SQL Server na minha máquina local, gerar todos os scripts das bases incluído (importante!) os dados. Criei uma base nova no A2Hosting, rodei os scripts e em minutos lá estava minha base SQL. Alterei o acesso nos programas fontes do Core e atualizei os arquivos FTP, depois de republicá-los no meu computador (um “rebuild” completo da aplicação, seguido de um “publish to file”). Funcionou de primeira. Pronto: liberdade total do Azure.

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!

Armazenamento seguro de segredos de aplicativos durante o desenvolvimento em Core MVC (app secrets)

Armazenamento seguro de segredos de aplicativos durante o desenvolvimento em Core MVC (app secrets)

Usando a ferramenta “Secret Manager” no Visual Studio 2017

Você pode usar a ferramenta Secret Manager no desenvolvimento de seus aplicativos e com ele manter seus segredos fora do seu código. Assim, você nunca deve armazenar senhas ou outros dados confidenciais no código-fonte. Além do que, você não deve e precisa usar segredos de produção em modo de desenvolvimento e teste (principalmente se usar repositórios públicos).

Você pode configurar o aplicativo para ler esses valores de variáveis de ambiente ou de valores armazenados usando a ferramenta Secret Manager. Com a ferramenta Secret Manager você evita que dados confidenciais sejam verificados nos programas fonte.

Usando Variáveis de Ambiente

Para evitar armazenar segredos de aplicativos em código ou em arquivos de configuração local, você pode armazenar segredos em variáveis de ambiente. Você pode configurar a estrutura de configuração para ler valores de variáveis de ambiente chamando AddEnvironmentVariables. Você pode então usar variáveis de ambiente para substituir valores de configuração para todas as fontes de configuração previamente especificadas.

Exemplo: se você criar um novo aplicativo da Web ASP.NET Core com contas de usuário individuais, ele adicionará uma seqüência de conexão padrão ao arquivo appsettings.json no projeto com a chave DefaultConnection. A cadeia de conexão padrão é inicialmente configurada para usar o LocalDB, que é executado no modo usuário e não requer uma senha. Mas quando você implanta seu aplicativo em um servidor de teste ou produção, você terá que substituir o valor da chave DefaultConnection com uma configuração de variável de ambiente que conterá toda a seqüência de conexão (potencialmente com credenciais sensíveis, como usuário e senha), para que seja acessado o banco de dados no servidor de teste ou de produção.

Cuidado

As variáveis de ambiente geralmente são armazenadas em texto simples e não são criptografadas. Se a máquina ou o processo estiverem comprometidos, então as partes não confiáveis podem acessar as variáveis de ambiente. Medidas adicionais para evitar a divulgação de segredos de usuários ainda podem ser necessárias…

Usando a ferramenta Secret Manager

A ferramenta Secret Manager armazena dados confidenciais para o trabalho de desenvolvimento fora da árvore de arquivos do seu projeto. Que pode ser usada para armazenar segredos no seu  projeto do .NET Core durante o desenvolvimento. Com a ferramenta Secret Manager, você pode associar segredos de aplicativos a um projeto específico e compartilhá-los em vários projetos.

Cuidado

A ferramenta Secret Manager não criptografa os segredos armazenados e não deve ser tratada como uma “depósito” confiável. Serve apenas para fins de desenvolvimento. As chaves e os valores são armazenados em um arquivo de configuração JSON no diretório do perfil do usuário….

Visual Studio 2017: Instalando a ferramenta Secret Manager

Clique com o botão direito do mouse no projeto, dentro da janela Solution Explorer. Selecione Editar <bome_do_projeto> .csproj no menu de contexto. Adicione a linha destacada ao arquivo .csproj e salve para restaurar o pacote NuGet associado (em XML):

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

  <PropertyGroup>
    <UserSecretsId>My-USER-SECRET-ID-HERE-c23d27a4-eb88</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.1" />
  </ItemGroup>

<!-- ACRESCENTAR -->
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.1" />
  </ItemGroup>
<!-- FIM ACRESCENTAR -->

</Project>

Agora clique com o botão direito do mouse no projeto, na janela do Solution Explorer e selecione Gerenciar Segredos de Usuário (Manage User Secrets),  no menu de contexto. Esta ação adicionará um novo nó UserSecretsId dentro de um PropertyGroup do arquivo .csproj. Ele também abre um arquivo secrets.json no editor de texto. Adicione o seguinte ao secrets.json:

{
    "MeuSegredo": "ValorDoMeuSegredo"
}

Se precisar colocar mais de um “segredo”, separe com vígulas:

{
    "MeuSegredo1": "ValorDoMeuSegredo1",
    "MeuSegredo2": "ValorDoMeuSegredo2",
    "MeuSegredo3": "ValorDoMeuSegredo3"
}

Teste a ferramenta Secret Manager executando o seguinte comando (na CONSOLE):

dotnet user-secrets -h

Você deve estar no mesmo diretório que o arquivo .csproj para executar as ferramentas definidas nos nós DotNetCliToolReference do arquivo .csproj.

A ferramenta Secret Manager exibirá uso, opções e comandos de ajuda:

dotnet user-secrets -h

Usage: dotnet-user-secrets [options] [command]
Options:
  -?|-h|--help  Show help information
  -v|--verbose  Verbose output
Commands:
  clear   Deletes all the application secrets
  list    Lists all the application secrets
  remove  Removes the specified user secret
  set     Sets the user secret to the specified value

A ferramenta Secret Manager opera com parâmetros de configuração específicas de projeto, que ficam armazenados no diretório do seu perfil de usuário. Para usar seus segredos de “usuário”, o projeto deve especificar um valor UserSecretsId dentro do seu arquivo .csproj. O valor de UserSecretsId é arbitrário, mas geralmente é algo exclusivo de um projeto seu. Os desenvolvedores tipicamente geram um GUID para o UserSecretsId.

GUID (ou UUID) é um acrônimo para ‘Globally Unique Identifier’ (ou ‘Universal Unique Identifier’). É um número inteiro de 128 bits usado para identificar recursos. O termo GUID é geralmente usado por desenvolvedores que trabalham com tecnologias da Microsoft. Exemplo de GUID:

4f750817-77be-48ec-90e3-dc7bf92671b2

Você pode gerar on-line um GUID se quiser: Online GUID Generator

Depois disso adicione o UserSecretsId no seu projeto, no arquivo .csproj (em XML):

<PropertyGroup>
  <UserSecretsId>MINHA-ID-DE-USUARIO-SECRETA-AQUI-c23d27a4-eb88</UserSecretsId>
</PropertyGroup>

Se fosse a GUID do nosso exemplo, ficaria:

<PropertyGroup>
  <UserSecretsId>4f750817-77be-48ec-90e3-dc7bf92671b2</UserSecretsId>
</PropertyGroup>

Você pode usar a ferramenta Secret Manager para estabelecer um segredo por linha de comando. Por exemplo, em uma janela de comando no diretório do projeto, digite o seguinte:

dotnet user-secrets set MySecret ValueOfMySecret

Você pode executar a ferramenta Secret Manager de outros diretórios, mas você deve usar a opção –project para passar no caminho para o arquivo .csproj:

dotnet user-secrets set MySecret ValueOfMySecret --project c:\work\WebApp1\src\webapp1

Você também pode usar a ferramenta Secret Manager para listar, remover e limpar segredos de aplicativos.

Acessando seus segredos de usuário por meio do arquivo de configuração do projeto

Você acessa os segredos do Secret Manager através do aruquivo de configuração do sistema (método Startup.cs). Adicione o pacote Microsoft.Extensions.Configuration.UserSecrets e execute, na console,  dotnet restore.

dotnet restore

 

Adicione a fonte de configuração dos segredos de usuário ao método Startup:

Adicione a configuração dos segredos no arquivo Startup.cs, como no exemplo:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace UserSecrets
{
    public class Startup
    {
        string _testSecret = null;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder();

            if (env.IsDevelopment())
            {
                   //VAI USAR NO AMBIENTE DE DESENVOLVIMENTO
                  builder.AddUserSecrets<Startup>();
            }

            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            //A VARIAVEL PODE SER CHAMADA DE OUTROS FONTES C#
            var _testaSegredo = Configuration["NomeDoMeuSegredo"];
        }

        public void Configure(IApplicationBuilder app)
        {
            var result = string.IsNullOrEmpty(_testaSegredo) ? "Null" : "Not Null";
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"Meu segredo é {result}");
            });
        }
    }
}

Note que as variáveis não são usadas em ambiente de produção, etc. Nestes ambientes você pode usar Variáveis de Ambiente ou configurações do Azure que sobrescrevem os arquivos de configuração (web.config ou appsettings.json).

Quando quisermos utilizar os segredos no nosso código, fazemos algo do tipo:

var emailConfig = new EmailConfiguration(Configuration["ContaDeEmail"], Configuration["SenhaDeEmail"]);

ContaDeEmail e SenhaDeEmail são informações que você poder guardar no UserSecrets, no ambiente de desenvolvimento. No ambiente de produção podem estar nas configurações do Azure. Não será necessário mudar seu código pois o ASP.NET Core abstrai isso para você.

Um jeito mais elegante (e  poderoso) de usar as variáveis geradas na Ferramenta Secret Manager

Já vimos que os segredos de usuário são armazenados nos diretórios de aplicação (APPDATA) de cada usuário. Você deve atribuir um userSecretsId exclusivo para cada aplicativo que usa segredos.  Isso é usado para criar um diretório de aplicativos exclusivo no diretório de perfil do usuário que armazenará o arquivo secrets.json contendo segredos de usuário.

Esse local varia conforme o sistema operacional:

Windows: %APPDATA%\microsoft\UserSecrets\<usersecretsid>\secrets.json
Linux: ~/.microsoft/usersecrets/<usersecretsid>/secrets.json
Mac: ~/.microsoft/usersecrets/<usersecretsid>/secrets.json

Para criar um arquivo com os dados:

{
  "DataService.PublicKey": "abcd",
  "DataService.PrivateKey": "efgh"
}

Você digita no diretório da aplicação o comando dotnet user-secrets set . que é executado e gera uma linha de informação dando feedback  você se tudo deu certo:

dotnet user-secrets set DataService.PublicKey abcd
info: Successfully saved DataService.PublicKey = abcd to the secret store.

dotnet user-secrets set DataService.PrivateKey efgh
info: Successfully saved DataService.PrivateKey = efgh to the secret store.

Configuração ASP.NET Core e User Secrets

A dependência Microsoft.Extensions.Configuration.UserSecrets adicionada anteriormente adiciona um método de extensão que nos permite chamar AddUserSecrets ao criar a Configuração do núcleo do ASP.NET. Os segredos de dados DataService.PublicKey e DataService.PrivateKey podem ser lidos a partir da configuração e usados para ajudar a configurar o DataService. Você pode fazer tudo isso na classe de Inicialização, no arquivo Startup.cs.

using NomeDaSuaAplicacao.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace NomeDaSuaAplicacao {
    public class Startup {
        public Startup(IHostingEnvironment env) {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath);

            builder.AddUserSecrets();

            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services) {
            services.AddMvc();

            var publicKey = Configuration["DataService.PublicKey"];
            var privateKey = Configuration["DataService.PrivateKey"];

            services.AddTransient<IDataService>(_ => new DataService(publicKey, privateKey));
        }

        public void Configure(IApplicationBuilder app) {
            app.UseMvcWithDefaultRoute();
        }
    }
}

E definir uma classe para esses serviços:

namespace NomeDaSuaAPlicacao.Services {
    public interface IDataService {
        string PublicKey { get; }
    }

    public class DataService : IDataService {
        private readonly string _publicKey;
        private readonly string _privateKey;

        public DataService(string publicKey, string privateKey) {
            _publicKey = publicKey;
            _privateKey = privateKey;
        }

        public string PublicKey { get { return _publicKey; } }
    }
}

Controlador (controller) e Visualizador (view) do ASP.NET Core

Certifique-se de que isso esteja funcionando como esperado, criaando um ASP.NET Core MVC Controller e uma View para exibir a chave pública armazenada no arquivo secrets.json.

Controlador (controller)

using Microsoft.AspNetCore.Mvc;

namespace NomeDaSuaAplicacao.Controllers {
    public class HomeController : Controller {
        public IActionResult Index() {
            return View();
        }
    }
}

Visualizador (view)

@using NomeDaSuaAplicaco.Services
@inject IDataService dataService
<!DOCTYPE html>
<html>
<head>
    <title>Serviço de Leitura de Chave da Aplicação</title>
</head>
<body>
    <h1>Nome da Sua Aplicação - Leitura de Chave Pública</h1>
    <p>Chave Pública: @dataService.PublicKey</p>
</body>
</html>

ASP.NET Core View Injection

Uma outra forma de visualizar os dados: ao invés de injetar o DataService no controlador, injete DataService diretamente no visualizador (view) usando @inject, logo no cabeçalho do arquivo.

@inject IDataService dataService

Uma vez que o serviço é injetado no visualizador, é possível exibir o segredo do usuário em qualquer parte do Razor de qualquer página de Visualização.

<p>Chave Pública: @dataService.PublicKey</p>

Você pode usar esta técnica para mostrar dados que se repetem em vários locais ou mesmo dados em views compartilhadas (shared), como a página _Layout.cshtml, que em geral é o pano de fundo para as demais (ou várias) páginas do sistema. Outro uso para o segredos são as chaves de API (autenticação) de serviços do Google e provedores de e-mail como SendGrid.

Algumas outras formas de implementar são apresentadas nas referências 5 a 8, abaixo. São ótimos artigos com boas dicas de implementação prática.

Se você tiver dúvidas ou contribuições, deixe-me um comentário abaixo.

Referências:  

  1. Safe storage of app secrets during development (MICROSOFT)
  2. Configuration in ASP.NET Core
  3. ASP.NET Core – usando UserSecrets para armazenar informações sensíveis
  4. USER SECRETS IN ASP.NET CORE MVC WEB APPLICATION
  5. User Secrets in ASP.NET Core
  6. User Secrets – Storing sensitive data in ASP.NET Core projects
  7. User Secret management in ASP.NET Core
  8. Working with user secrets in ASP.​NET Core applications

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!

Gerenciando e tratando Erros (Errors) em Core MVC

Gerenciando e tratando Erros (Errors) em Core MVC

ASP.NET CORE e MVC são diferentes…

O ASP.NET Core continua a ser a principal plataforma subjacente para a construção de aplicativos da Web no .NET Core. Já o MVC ainda é uma estrutura web opcional, que pode ser conectada ao pipeline ASP.NET Core.

É basicamente uma biblioteca NuGet que fica no topo do ASP.NET Core e oferece alguns recursos adicionais para o padrão de design Model-View-Controller.

O que isso significa em termos de tratamento de erros é que qualquer capacidade de gerenciamento de exceção oferecida pela MVC ficará limitada ao MVC. Isso se tornará muito mais evidente quando analisarmos a arquitetura ASP.NET Core.

O middleware do ASP.NET CORE

O ASP.NET Core é completamente modular e o pipeline de solicitações é definida principalmente pelo “middleware” instalado em uma aplicação.

Para entender melhor vamos criar uma nova aplicação MVC e analisar o método

"void Configure (...)"

que fica sempre dentro do arquivo Startup.cs.

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Porque esse é um código muito extenso para um aplicativo web simples, vou cortá-lo para os principais pontos de interesse:

app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes => ...);

O que você pode ver aqui é essencialmente auto-explicativo, mas há algumas coisas importantes para entender nesse código. As chamadas de método app.Use … () (extensão-) estão ativando vários middlewares registrando-os com o objeto IApplicationBuilder. Cada middleware será responsável por chamar o próximo middleware no pipeline de solicitações. É por isso que a ordem de chamada dos métodos do app.Use … () é importante!

Por exemplo, este é um esqueleto do Static File Middleware:

public StaticFileMiddleware(RequestDelegate next, ...)
{
    // Alguma coisa antes

    _next = next;

    // Alguma coisa deposi}

public Task Invoke(HttpContext context)
{
    // Um monte de código para ver se esse middleware pode
    // servir um arquivo estático que corresponde a uma solicitação HTTP ...

    // Caso contrário, o código chegará a esta linha:/
    return _next(context);
}

Foi cortado algum ruído para destacar o uso da variável RequestDelegate.

Como você pode ver, cada middleware deve aceitar um objeto RequestDelegate no construtor. E cada middleware deve implementar um método de tipo Task Invoke (no contexto HttpContext).

O RequestDelegate é, como seu nome sugere, um delegador que representa o próximo middleware no ciclo de vida da aplicação. ASP.NET Core tem a responsabilidade de invocá-lo, a partir do próprio middleware atual. Por exemplo, se o StaticFileMiddleware não conseguir encontrar um arquivo estático que corresponda à solicitação HTTP recebida, então ele invocará o próximo middleware chamando return _next (context); no fim. Por outro lado, se conseguiu encontrar o arquivo estático solicitado, ele o devolverá ao cliente e nunca mais invocará o próximo ou qualquer middleware subseqüente.

É por isso que a ordem do método app.Use … () é importante. Quando você pensa sobre isso, o padrão subjacente de qualquer requisição HTTP pode ser visto em camadas, como se fosse  uma “cebola”:

Uma requisição HTTP viajará do middleware de nível superior até o último middleware, a menos que um middleware no meio possa satisfazer a solicitação e retornar uma resposta HTTP mais cedo para o cliente.

Em contraste, uma exceção não tratada viajaria de baixo para cima. Começando no middleware onde ele foi parado, ela voltaria até o topo do middleware esperando por algo que possa pegá-lo. Em teoria, um middleware também poderia tentar fazer alterações na resposta depois de ter invocado o próximo middleware, mas isso normalmente não é o caso. o que não é aconselhável. Porque isso poderia resultar em uma exceção se o outro middleware já estivesse trabalhando na resposta à requisição HTTP.

O tratamento de erros deve estar no primeiro middleware

Com isso em mente, fica claro que para capturar qualquer exceção não tratada, um erro,  seu gerenciamento  deve ser o primeiro no pipeline do middleware.  Aí, então, se pode garantir uma captura final – se nada mais captar a exceção antes.

Como o MVC normalmente é registrado no final do pipeline do middleware, também é claro que os recursos de gerenciamento de exceção (como os infames ExceptionFilters) dentro do MVC não poderão capturar todas as exceções.

Para mais informações sobre middleware, verifique a documentação oficial sobre middleware.

Manipuladores de Exceção Personalizados (Custom Exception Handlers)

Entendendo melhor o funcionamento do middleware e a necessidade de tratar as exceções. é possível que você crie o seu próprio manipulador de exceção global no ASP.NET Core.

Embora já existam alguns manipuladores de exceção úteis no pacote NuGet Microsoft.AspNetCore.Diagnostics disponível, ainda pode fazer sentido criar o seu próprio. Por exemplo, você pode querer ter um manipulador de exceção que registre exceções críticas no Sentry (que ajuda\ a saber quando os aplicativos brecam, enviando notificações por e-mail, sms, etc.)usando o Raven Client for .NET do Sentry ou pode querer implementar uma integração com uma ferramenta de rastreamento de erros e registrar um novo ticket para cada NullReferenceException que ocorra. Outra opção seria uma integração com o elmah.io (outro monitorador de sites).

Bons motivos para você ter seus próprios Manipuladores de Exceção

Há razões muitas boas pelas quais você  pode querer criar manipuladores de exceção adicionais. Pode ser muito útil ter vários manipuladores de exceções, registrados de uma só vez. Por exemplo, o primeiro manipulador de exceção registra um “ticket” em um sistema de rastreamento de “bugs” (falhas) e relança a exceção original. Em seguida, o próximo manipulador de exceção poderá registrar o erro no ELMAH e reiniciar novamente a exceção original. O manipulador de exceção final pode capturar a exceção e retornar uma página de erro amigável para o cliente.

Ao ter cada manipulador de exceção concentrando-se em uma única atividade, no seu conjunto eles se tornam automaticamente mais reutilizáveis em vários projetos. também é possível usar diferentes combinações de manipuladores em diferentes ambientes (por exemplo, em dev / staging / production).

Um bom exemplo de escrever seu próprio middleware de gerenciamento de exceção é o padrão ExceptionHandlerMiddleware no ASP.NET Core.

Um “gabarito” de um manipulador de exceção “padrão” seria mais ou menos assim:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace SuaAplicacao
{
    public sealed class CustomExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public CustomExceptionHandlerMiddleware(
            RequestDelegate next,
            ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.
                    CreateLogger<CustomExceptionHandlerMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                try
                {
                    // Faz alguma coisa
                    // Pode ser algo tão simples como uma chamada para logger.LogError

                    // se você não quer retomar a exceção original
                    // então chame o return:
                    // return;
                }
                catch (Exception ex2)
                {
                    _logger.LogError(
                        0, ex2, 
                        "Uma exeção ocorreu tentando " + 
                        "ao utilizar o manipulador de exceções.");
                }

                // Caso contrário o manipulador irá
                // retentar a exceção original
                throw;
            }
        }
    }
}

Adicionalmente ao RequestDelegate, o construtor também aceita um ILoggerFactory – que pode ser usado para instanciar um novo objeto do ILogger.

No método Task Invoke (HttpContext context), o manipulador de erros basicamente não faz nada além de chamar imediatamente o próximo middleware. Somente se uma exceção for lançada, ela entrará em ação, capturando-a no bloco de captura. O que você colocou no bloco de captura depende de você, mas seria uma boa prática para envolver qualquer código não trivial em um segundo bloco try-catch e padrão de volta ao log básico se todo o resto estiver desmoronando.

Se você tiver dúvidas ou contribuições, deixe-me um comentário abaixo.

Referências:  

  1. Error Handling in ASP.NET Core (Dusted Codes) e 
  2. Error Handling in ASP.NET Core (Microsoft)

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Tudo para Marketing Digital numa única Plataforma!