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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business

Paginação nas Grades do Bootstrap do CORE MVC (que funciona)

Paginação nas Grades do Bootstrap do CORE MVC (que funciona)

Há diversos componentes para fazer paginação de grades no Core MC. Alguns não funcionam, outros estão desatualizados. E a sugestão dada pela própria Microsoft (referência 1), é muito pobre e limitada. Você pode usar o componente cloudscribe.Web.Pagination, que é atualizado e bem poderoso.

Você pode baixar / clonar este repo e executar o projeto PagingDemo.Web para ver várias páginas de demonstração usando o paginador em várias configurações, incluindo ajax e ajax dentro de um modal bootstrap.

Se você vai operar com procedures, antes de tudo instale pelo Gerenciador do NuGet – se ainda não o tem instalado na sua aplicação, o Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Instalando o cloudscribe

Prérequistos:

Para instalar a partir do nuget.org, abra o arquivo project.json do seu aplicativo da Web e na seção de dependências adicione:

"cloudscribe.Web.Pagination": "1.1.*"

Com o Visual Studio 2017 não precisa fazer nada além de ir no gerenciador de pacotes, pesquisar o cloudscribe e pedir para instalar no seu projeto/aplicação. Pode também instalar, se quiser, direto pelo Packet Manager Console. As duas opções estão no menu de Ferramentas (Tools).

Começando pelo Modelo

No diretório de Modelos (Models), crie um modelo que contenha os dados que você quer mostrar na grade (se for um modelo simples, de uma única tabela que você já tenha, use o modelo dessa tabela). O modelo que usei tem dados de duas tabelas (um de pessoas cadastradas no sistema e outra de consultorias (empresas) cadastradas. Meu objetico é mostrar todos os consultores e suas respectivas consultorias (sendo que uma consultoria pode ter um ou mais consultores no sistema).

Modelo de dados

Criei na pasta Models o arquivo Consultores.cs, com os seguites campos:

using cloudscribe.Web.Pagination;
using System.ComponentModel.DataAnnotations;


namespace Coaching.Models //somente para visualização
{
    public class Consultores
    {
        // arquivo de pessas
        [Key]
        public int IdUsuario { get; set; }
        [Display(Name = "Nível de Acesso")]
        public int? NiveldeAcesso { get; set; }
        [Display(Name = "Nome do Consultor")]
        public string Nome { get; set; }
        //arquivo de consultorias
        public int IdConsultoria { get; set; }
        [Required(ErrorMessage = "Favor entrar com o nome da sua consultoria (ou seu nome).")]
        [Display(Name = "Nome da Consultoria")]
       
        public string NomeConsultoria { get; set; }
        public string Telefones { get; set; }
        public string Cidade { get; set; }
        [Display(Name = "Link de Acesso")]
        public string LinkAcesso { get; set; }
        
    }
}

Modelo (OBRIGATÓRIO) de Auxílio para a Paginacão

Crie um modelo muito simples para auxiliar na paginação. No caso, criei o arquivo Paginacao.cs na pasta de modelos (Models). Qualquer que seja o seu modelo de dados (como o acima), este arquivo será “comum” a todas as grids do seu sistema – e nelas quiser ter a paginação.

using cloudscribe.Web.Pagination;
using System.Collections.Generic;


namespace Coaching.Models
{
    public class Paginacao

    {
        public Paginacao()
        {
            Paging = new PaginationSettings();
        }

        public string Query { get; set; } = string.Empty; //só use se for implementar um grid com pesquisa, se não não precisa estar no modelo

        //public List<Product> Products { get; set; } = null; //não precisa neste exemplo
        //public List<Consultores> ListaConsultores { get; set; } = null; //não precisa neste exemplo

        public PaginationSettings Paging { get; set; }
    }
}

Implantando o serviço de Paginação no Startup

No seu Startup.cs você precisará disso em ConfigureServices:

services.AddCloudscribePagination();

É isso.

Implantando o Tag Helper de Paginação de uma forma genérica

Como você pode querer usar a paginação em qualquer página que tenha uma grade (grid), a opção de incluir num arquivo auxiliar de visualização que é sempre lido (o _ViewImports.cshtml) é a opção mais inteligente. Você, assim, não precisa “solicitar” o tag help nas páginas de visualização. Ele estará sempre presente.

Assim, no arquivo  _ViewImports.cshtml, inclua:

@addTagHelper "*, cloudscribe.Web.Pagination"

Colocando o Tag Helper de Paginação na Visualização (View)

Você vai precisar de alguma coisa parecida com isso:

</p>
<p>&amp;amp;lt;div&amp;amp;gt;<br />
                    &amp;amp;lt;span class=&quot;Navigator&quot;&amp;amp;gt;<br />
                        @*navegador*@<br />
                        @*paginação*@<br />
                        &amp;amp;lt;cs-pager cs-paging-pagesize=&quot;@ViewBag.PageSize&quot; cs-paging-pagenumber=&quot;@ViewBag.PageNumber&quot; cs-paging-totalitems=&quot;@ViewBag.TotalItems&quot; cs-show-first-last=&quot;true&quot; cs-pagenumber-param=&quot;page&quot; cs-suppress-empty-nextprev=&quot;true&quot; cs-suppress-inactive-firstlast=&quot;true&quot; cs-first-page-text=&quot;Primeiro&quot; cs-last-page-text=&quot;Último&quot; cs-pager-li-current-class=&quot;active&quot; cs-pager-li-non-active-class=&quot;disabled&quot; asp-controller=&quot;AcRedePessoas&quot; asp-action=&quot;ConsultoresLista&quot;&amp;amp;gt;&amp;amp;lt;/cs-pager&amp;amp;gt;<br />
                    &amp;amp;lt;/span&amp;amp;gt;<br />
&amp;amp;lt;/div&amp;amp;gt;</p>
<p>

Os parametros de tamanho da página (ViewSize), número da página (PageNumber) e número de itens a serem mostrados (TotalItens) são passados através do controlador (controller), que lê os dados da tabela (que veremos mais adiante). No caso deste exemplo, o controlador (controller) é o AcRedePessoas (todos usuários do sistema) que tem uma ação (Action) chamada ConsultoresLista.

Voce pode colocar esta div no final de sua grade. Ou repetí-la, colocando antes do início da grade e depois do término da grade. No caso deste exemplo, optei por deixá-la só no final da grade. O arquiivo de listagem (vizualização) que chamei de ConsultoresLista.cshtml foi gerado com um “scafolding” no modelo de visualização definido no início.

Atenção: se seu molelo não fizer parte do contexto (não for uma tabela simples do sistema), você precisará incluir o mesmo no seu ArquivodeContexto.cs, dentro da pasta de modelos (Models). Só depois disso você recompila o projeto e faz o scafolding.

No meu caso, a inclusão foi feita assim:

using Microsoft.EntityFrameworkCore;

namespace Coaching.Models
{
    public partial class Alicecapella119Context : DbContext
    {
        public virtual DbSet<AcRedepessoas> AcRedepessoas { get; set; }
        public virtual DbSet<Avaliacoes> Avaliacoes { get; set; }
        public virtual DbSet<AvaliacoesRespostas> AvaliacoesRespostas { get; set; }
        public virtual DbSet<CompetenciasBancoGeral> CompetenciasBancoGeral { get; set; }
        public virtual DbSet<Consultorias> Consultorias { get; set; }
        public virtual DbSet<EmailsEnviados> EmailsEnviados { get; set; }
        public virtual DbSet<Empresas> Empresas { get; set; }
        public virtual DbSet<Log> Log { get; set; }
        public virtual DbSet<ParametrosClicks> ParametrosClicks { get; set; }
        public virtual DbSet<PerguntasBancoGeral> PerguntasBancoGeral { get; set; }
        public virtual DbSet<QuestionarioTipos> QuestionarioTipos { get; set; }
        public virtual DbSet<QuestionariosModelos> QuestionariosModelos { get; set; }
        public virtual DbSet<Reguas> Reguas { get; set; }
        public virtual DbSet<ReguasEscalas> ReguasEscalas { get; set; }
        public virtual DbSet<Relacionamento> Relacionamento { get; set; }
        
        //para procedure ou vizualição via linq
        public virtual DbSet<FormularioViewModels.FormularioViewModel> Formulario { get; set; } //visualiza_lista_respostas
        public virtual DbSet<Consultores> Consultores { get; set; } //visualiza_consultores

Note que a última linha acrescenta o modelo de Consultores, como se fosse uma tabela falsa (fake). É útil apenas para visualização, não serve para edição ou deleção ou criação.

Último passo: o controlador da visualizaçao (view)

O controlador é recheado de “mancadas”. Na verdade, é o segredo da paginação.

Itens por página

Primeiro, você tem que definir quantos itens serão mostrados por página, cada vez que ela é mostrada. Isso é feito definindo-se uma variável privada:

private const int DefaultPageSize = 2;

Isso: no caso, quero que cada página apresente apenas 2 registros. Essa constante você define logo no início do controlador, antes das definições dos Actions.

public class AcRedepessoasController : Controller
    {
        private readonly Alicecapella119Context _context;
        private readonly IEmailService _emailService;
        //para navegação. Se tiver mais de uma ação e quiser diferentes tamanhos, defina mais constantes
        private const int DefaultPageSize = 8; //aqui vai sua constante
       
        public AcRedepessoasController(Alicecapella119Context context
            , IEmailService emailService
            )
        {
            _context = context;
            _emailService = emailService;
        }

Página Inicial

Segundo: quando você entra pela primeira vez na visualização (view), pode não ter uma indicação da página que quer ver. Assim é preciso definir que você quer a primeira página:

//Lista Consultores
        public IActionResult ConsultoresLista(int? page)
        {

            if (page == null)
            {
                page = 1;
            }

Total de Registros

Você não sabe quantos registros existem. No exemplo, quantos consultores existem. A alternativa é descobrir. No meu caso, os consultores são todos aqueles registrados com Nível de Acesso = 3:

var TotalDeConsultores = _context.AcRedepessoas.Where(x => x.NiveldeAcesso == 3).Count();

Carregando os dados numa Lista

Esta etapa é fundamental. Os dados precisam ser mostrados no vizualizador que foi montado pelo scafolding. No caso deste exemplo, a lista de consultores que quero mostrar vem de duas tabelas. Assim, criei uuma ListaDeConsultores, da seguinte forma:

var ListaDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                          NiveldeAcesso = i.NiveldeAcesso,
                                          Nome = i.Nome,
                                          IdConsultoria = k.IdConsultoria,
                                          Cidade = k.Cidade,
                                          LinkAcesso = k.LinkAcesso,
                                          Telefones = k.Telefones
                                      })
                                    .OrderBy(p => p.Nome)
                                    .Skip(offset)
                                    .Take(DefaultPageSize)
                                    .ToList();

Observe que os campos definido obedecem cegamente aos campos do modelo definido em Consultores.cs, E que o modelo pega exatamente o número de registros necessários a partir de um  default (evitando sobrecarregar a memória do sistema), colocando-os numa lista.

Instanciando dados para a paginação

Para permitir o controle de paginação, você deve adicionar dados ao modelo de paginção, o que é gfeito da seguinte forma:

var model = new Paginacao();
            model.Paging.CurrentPage = currentPageNum;
            model.Paging.ItemsPerPage = DefaultPageSize;
            model.Paging.TotalItems = TotalDeConsultores;

Porém, como nosso modelo é simples, precisamos passar para a visualização (view), dados que permitam atualizar o tag helper. Isso é feito com View.Bags.

ViewBag.CurrentPage = currentPageNum;
            ViewBag.ItemsPerPage = DefaultPageSize;
            ViewBag.TotalItems = TotalDeConsultores;
            ViewBag.PageSize = DefaultPageSize;
            ViewBag.PageNumber = currentPageNum;

Uma visão geral  do controller seria, com todas as dicas:

//Lista Consultores
        public IActionResult ConsultoresLista(int? page)
        {
            if (page == null)
            {
                page = 1;
            }
            var currentPageNum = Convert.ToInt32(page);
            var offset = (DefaultPageSize * currentPageNum) - DefaultPageSize;
  
            var ListaDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      //join j in _context.AcRedepessoas on p.IdAvaliador equals j.IdUsuario
                                      //where p.DonorId.Equals(filter)
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                          NiveldeAcesso = i.NiveldeAcesso,
                                          Nome = i.Nome,
                                          IdConsultoria = k.IdConsultoria,
                                          Cidade = k.Cidade,
                                          NomeConsultoria=k.NomeConsultoria,
                                          LinkAcesso = k.LinkAcesso,
                                          Telefones = k.Telefones
                                      })
                                    .OrderBy(p => p.Nome)
                                    .Skip(offset)
                                    .Take(DefaultPageSize)
                                    .ToList();
            var TotalDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                      })
                                    .Count();
            var model = new Paginacao();
  
            model.Paging.CurrentPage = currentPageNum;
            model.Paging.ItemsPerPage = DefaultPageSize;
            model.Paging.TotalItems = TotalDeConsultores;

            ViewBag.CurrentPage = currentPageNum;
            ViewBag.ItemsPerPage = DefaultPageSize;
            ViewBag.TotalItems = TotalDeConsultores;
            //ViewBag.PageSize = DefaultPageSize;
            ViewBag.PageSize = DefaultPageSize; //Convert.ToInt32(Math.Ceiling(Convert.ToDouble(TotalDeConsultores) / Convert.ToDouble(DefaultPageSize)));//currentPageNum;
            //ViewBag.PageNumber = Convert.ToInt32( Math.Ceiling(Convert.ToDouble(TotalDeConsultores) / Convert.ToDouble(DefaultPageSize)));//currentPageNum;
            ViewBag.PageNumber = currentPageNum;
            return View(ListaDeConsultores);
      }

Note que também são passados os parâmetros para o modelo de paginação, ante (ou depois, não importa) dos ViewBags.

 

O Resultado

O resultado é muito bom. Veja imagens:

Ou ainda, depois de clicar em Último:

Conclusão

O tag helper suporta MUITAS opções. Vejamos quais são:

  • cs-paging-pagenumber – 1 (tem que ser um inteiro)
  • cs-paging-totalitems – 1 (é o total de  itens no banco de dados)
  • cs-paging-maxpageitems – 10 (o máximo a ser mostrado por pégina)
  • cs-pagenumber-param – pageNumber (o nome do parametro vai estar no seu controlador – (“controller”)
  • cs-show-first-last – false (se mostra primeiro ou final no controle)
  • cs-first-page-text – < (string do mostra primeiro)
  • cs-first-page-title – First Page (Titulo do Mostra primeiro)
  • cs-last-page-text – > (texto da última página)
  • cs-last-page-title – Last Page (título da última página)
  • cs-previous-page-text – « (símbolo da página anterior)
  • cs-previous-page-title – Previous page (título da Página anterior)
  • cs-next-page-text – » (símbolo da próxima página)
  • cs-next-page-title – Next page (título da próxima página)
  • cs-pager-ul-class – pagination
  • cs-pager-li-current-class – active
  • cs-pager-li-non-active-class – disabled
  • cs-ajax-target – empty – should be the id of the html element to target for ajax updates
  • cs-ajax-mode – replace
  • cs-ajax-success – empty – a callback
  • cs-ajax-failure – empty – a callback
  • cs-ajax-loading – empty – the #id of an image ie animated gif to show while loading
  • cs-ajax-loading-duration – empty – corresponds to data-ajax-loading-duration

O componente é “inteligente” e só aparece na página se o número de registros for MAIOR que a quantidade máxima por página… Por isso, não se assuste nos testes se ele não aparecer… Para testar, use um valor pequeno como 2 o 3 itens por página, principalmente se seu arquivo ainda não está populado.

Para usar o ajax, você deve incluir jquery.unobtrusive-ajax.js na página. Para usar o AlphaPagerTagHelper, você adicionaria algo assim no seu arquivo visualizador (view):

<cs-alphapager cs-alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    cs-selected-letter="@Model.Query"
    cs-all-label="All"
    asp-controller="Paging"
    asp-action="ProductList" 
    cs-selected-letter-param="query"
    ></cs-alphapager>

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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business

Usando SQL Storage Procedures no CORE MVC – Parte 1

Usando SQL Storage Procedures no CORE MVC – Parte 1

Um dos problemas complicados no Core MVC é que não dá para usar as Views do SQL. Quando você tem de mostrar dados que tem diversas tabelas agrupadas, há algumas formas de contornar isso, usando procedures do SQL.

Se você vai operar com procedures, antes de tudo instale pelo Gerenciador do NuGet – se ainda não o tem instalado na sua aplicação, o Microsoft.AspNetCore.Identity.EntityFrameworkCore.

No arquivo do controlador que tiver actions com SQL, em alguns casos poderá será preciso acrescentar a diretiva using System.Data.SqlClient;.

Casos do método FromSql

Para executar procedures, use o método FromSql executa consultas RAW SQL. Mas existem algumas limitações para executar consultas RAW SQL ou procedures.

  • As consultas SQL só podem ser usadas para retornar tipos de entidades que fazem já parte do seu modelo. Portanto, não podem conter dados relacionados ou “montados” através de procedures. Existe uma forma de contornar isso, mas ninguém explica direito. Você deve montar um modelo “fake”, falso mesmo. Que não existe fisicamente no banco de dados. Depois de montar o modelo tem que fazer duas coisas:
    • incluir esse modelo “fake” no seu arquivo SuaAplicacaoContext.cs, dentro do diretório de modelos (Models);
    • o modelo tem que ter uma chave única (uniq key). No arquivo do modelo “fake” a chave da tabela combinada da procedure tem que ser precedida pelo comando [Key] (e para isso é preciso acrescentar a diretiva, no topo do arquivo de modelo, using System.ComponentModel.DataAnnotations). Aí, mesmo sem ser um arquivo físico ,a procedure ser comportará como se assim o fosse;
  • A consulta SQL sempre deve retornar dados para todas as propriedades da entidade. Então, basicamente, sua consulta SQL sempre vai ser algo como “Select * from {NomeDaTabela}” (sempre de uma única tabela dentre as que já estão mapeadas ou o da tabela “fake”).
    É até possível filtrar a tabela, usando parâmetros que funcionarão como uma cláusula “where”. Porém a procedure sempre irá retornar todos os campos da tabela (simples ou “fake”), retornando listas (claro que a procedure poderá trabalhar com outras tabelas, fazendo outras operações, mas o método FromSql sempre retornará uma lista do tipo “select * from Tabela” ou,em alguns casos, um valor único como “Select Count(*) from Tabela“. Você é quem decide: se o resultado é uma lista, injeta num .ToList(). Se o resultado é único, injeta num .SingleOrDefault();
  • Os nomes das colunas no conjunto de resultados sempre devem corresponder exatamente aos nomes das colunas às quais as propriedades são mapeadas (no modelo, ou Model, do contrário vai dar erro e s colunas que não batem virão com valores nulos).

Não há sentido em se usar o FromSql se não houver necessidade de filtrar a tabela com parâmetros ou necessidade de fazer operações acessórias dentro da procedure (atualizando outras tabelas). A procedure abaixo é praticamente inútil, no sentido de que poderia ser facilmente obtida sem nenhuma procedure:

CREATE PROCEDURE lista_todas_consultorias
AS
BEGIN
	SET NOCOUNT ON;
	SELECT * from consultorias
END
GO

Ela teria que usar, no controller, algo do tipo:

List<Consultorias> ListaDeConsultorias = _context.Consultorias.FromSql("lista_todas_consultorias").ToList();

Mas isso poderia ser obtido de várias outras formas, pois a tabela Consultorias já é mapeada, sendo possível obter a lista como no como por exemplo:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();

Procedures simples com parâmetros usando FromSql

Já quando você que utilizar uma tabela já mapeada usando filtros (passando parâmetros), começa a fazer sentido usar o FromSql. Por exemplo, selecionar pessoas que estão ligadas a uma determinada Consultoria (considerando que as pessoas estão numa tabela já mapeada):

CREATE PROCEDURE seleciona_pessoas_duma_consultoria 
	@idConsultoria int
AS
BEGIN
	SET NOCOUNT ON;
	SELECT *  FROM dbo.ac_redepessoas
WHERE        (idConsultoria = @idConsultoria)
END
GO

Mesmo este uso, considerando que a tabela de pessoas já é mapeada e que contém o campo IdConsultoria, é bobinho. A instrução FromSql seria:

int idConsultoria = 1;
List<AcRedepessoas> ListagemDePessoasDaConsultoria = _context.AcRedepessoas.FromSql("seleciona_pessoas_duma_consultoria @p0", idConsultoria).ToList;

Mas isso também seria facilmente obtido diretamente no Core:

ListagemDePessoasDaConsultoria =  _context.AcRedepessoas.Where(x => x.IdConsultoria == idConsultoria).ToList();

Então, o uso de FromSql deve ocorrer em situações onde o Core MVC não pode, automaticamente, atualizar alguma tabela secundária ou fazer operações intermediárias. Também não se presta para operações do tipo Insert, Update e Delete.

Passando mais de um parâmetro

Algumas vezes você precisa passar mais de um parâmetro. O FromSql() tem um “overload”que aceita os parâmetros do objeto []. A primeira coisa a notar é o nome do parâmetro é “@p0”. Se houver mais parâmetros, incremente o contador como @p1, @p2 etc… O código seguinte mostra como passar vários parâmetros:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();
int idConsultoria = 1;
var NomesDePessoas = "Roberto";
var ListagemDePessoasDaConsultoria = _context.AcRedepessoas
    .FromSql("ListaPessoasPorConsultoriaENome @p0, @p1",
    parameters: new[] { idConsultoria.ToString(), NomesDePessoas })
    .ToList();

Um cuidado: ao passar números inteiros, converta para string antes (se não vai dar erro no SQL). Existe também outra maneira de passar os parâmetros. Substitua @p0 por {0} (e se tiver mais parâmetros, @p1 por {1} e assim por diante.

int idConsultoria = 1;
var parametroConsultoria = new SqlParameter("@idDaConsultoria", idConsultoria);
List<acRedepessoas> ListaDePessoas = _context.acRedePessoas
            .FromSql("proc_lista_pessoas @idDaConsultoria", parametroConsultoria)
            .ToList();

Nesse formato, não é preciso antes converter o inteiro em “string“. Para passar 4 parâmetros, por exemplo:

var userType = _context.acRedepessoas.FromSql("dbo.AlgumaProcedure @Id = {0}, @Nome = {1}", 45, "Ada");

Para casos mais complexos, é preciso usar ExecuteSqlComand

Quando se trata de alterar um arquivo com procedure, este é comando que executa a procedure: ExecuteSqlComand, adequado para operações do tipo Insert, Update e Delete.

O que quase lugar nenhum explica é o seguinte: o comando ExecuteSqlComand não retorna listas. Retorna um inteiro. 0, se a operação foi bem sucedida. É só isso. Novamente: não há sentido em se usar esse comando a menos que você esteja atualizando outras tabelas intermediárias ou fazendo cálculos complexos antes de alterar ou inserir em registro.

A procedure a seguir insere um registro na tabela de Consultorias Categorias.

CREATE PROCEDURE usp_InsereConsultoria
 @NomeDaConsultoria nvarchar(300)
AS
BEGIN
    SET NOCOUNT ON;
    Insert into Consultorias Values (@NomeDaConsultoria)
END
GO

ExecuteSqlCommand deve ser usado em context.Database (na minha aplicação de exemplo _context = contexto;). Você pode usá-lo da seguinte maneira.

var NomeConsultoria= "José Consultores e Associados";
_context.Database
           .ExecuteSqlCommand("usp_InsereConsultoria @p0", NomeConsultoria);

É possível rodar o ExecuteSqlCommand no modo assíncrono também, como por exemplo:

await _context.Database.ExecuteSqlCommandAsync("usp_CriaConsultoria @p0, @p1", 
        parameters: new[] { "Nome Completo da Consultoria", "Nome de Guerra" });

Outro exemplo, usando diretamente dados de um modelo (model) diretamente na chamada da procedure. Após salvar os dados em edição, a procedure executa diversas tarefas de atualização em outras tabelas – e por isso é chamada logo após o “save”:

_context.Update(avaliacoesRespostas);
            await _context.SaveChangesAsync();
            await _context.Database.ExecuteSqlCommandAsync("atualiza_nota @p0, @p1",
        parameters: new[] { avaliacoesRespostas.Nota, avaliacoesRespostas.IdRespostaAvaliacao });
            return RedirectToAction("RespondeFormulario", "Avaliacoes", new { id = avaliacoesRespostas.IdAvaliacao });

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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business

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…

a2 hostoing para core mvc

 

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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business

Views (visualizadores) no Core MVC: Renderizando HTML

Views (visualizadores) no Core MVC: Renderizando HTML

Os controladores (controllers) do Core MVC do ASP.NET podem retornar resultados formatados usando visualizadores (views)

O que são Views (Visualizadores)?


No padrão Model-View-Controller (MVC), o visualizador (view) encapsula os detalhes da apresentação da interação do usuário com o aplicativo. As visualizações são modelos HTML, com código “embutido”, que geram conteúdo para enviar ao cliente.

As visualizações usam a sintaxe chamada Razor, que permite que o código interaja com HTML com um mínimo de código.

As visualizações do ASP.NET Core MVC são arquivos .cshtml armazenados por padrão em uma pasta Views no aplicativo. Estas Views estão intimamente ligadas à pasta Controllers (controladores). Haverá um arquivo de controle para controlar as visualizações se cada sub-pasta em Views. O arquivo de controle contém ao menos uma ação para cada View estabelecida. Tipicamente o arquivo de controle (controller) tem ações para adicionar dados, visualizar somente, editar, apagar, listar, etc. E existe sempre uma visualização (view) correspondente a cada ação.

Abaixo, por exemplo, a sub-pasta Home Contém visualizadores para a página principal (index) e páginas de Contato (contact) e Sobre Nós (about).

Pasta de views (visualizadores)Na sub-pasta “Shared”  são definidas views especiais, com ações muito específicas: Views  parciais (Partial Views), Layouts e outros arquivos especiais que ajudam a reduzir repetição de código e permitem sua reutilização em diversas Views das demais Sub-pastas.


Benefícios de usar Views (visualizadores)

Os visualizadores ajudam a separar suas preocupações dentro de um aplicativo MVC. Eles encapsulam o nível de interface do usuário, separando-o da lógica de negócios (que é feita pelos controllers). As visualizações do ASP.NET MVC usam a sintaxe do Razor para tornar indolor a troca entre o código HTML e a lógica de dados no lado do servidor. Os aspectos comuns e repetitivos da interface do usuário do aplicativo podem ser facilmente reutilizados entre visualizações usando layout e diretivas compartilhadas ou visualizações parciais.


Criando uma View

As visualizações específicas de um controlador são criadas na sub-pasta Views/ [Nome do controlador].

As visualizações que são compartilhadas entre os controladores são colocadas na sub-pasta Views / Shared.

Muito importante: nomeie o arquivo de exibição o mesmo nome que a ação do controlador associado – e adicione a extensão de arquivo .cshtml.

Por exemplo, para criar uma visualização para a ação Sobre Nós  (About) no controlador Home, você criaria o arquivo About.cshtml na pasta / Views / Home. Um arquivo de exibição de exemplo (About.cshtml):

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>Use this area to provide additional information.</p>

O código Razor é  sempre precedido pelo símbolo @.

As instruções C # são executadas dentro dos blocos de código Razor são sempre configuradas por chaves ({}), como a atribuição de “About” ao elemento ViewData [“Título”] mostrado acima.

O Razor pode ser usado para exibir valores dentro do HTML simplesmente fazendo referência ao valor com o símbolo @, conforme mostrado nos elementos <h2> e <h3> acima.

Esta View se concentra apenas na parte da saída para a qual é responsável. O resto do layout da página, e outros aspectos comuns à visualização, são especificados em outro lugar. Conheça mais detalhes lendo sobre o layout e a lógica de exibição compartilhada.


Como os Controladores (controllers) especificam os Visualizadores (Views)?

As visualizações geralmente são retornadas de ações como ViewResult. Seu método de ação pode criar e retornar um ViewResult diretamente, mas, mais comumente, se seu controlador herda do Controller, você simplesmente usará o método View helper (um simples View()), como demonstra esse exemplo: HomeController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

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

        public IActionResult About()
        {
            ViewData["Message"] = "Página de descrição da aplicação.";

            return View();
        }

        public IActionResult Contact()
        {
            ViewData["Message"] = "Página de Contato.";

            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

O método View() possui várias sobrecargas (overloads) para tornar as visualizações retornáveis mais fáceis para desenvolvedores de aplicativos. Você pode opcionalmente especificar uma visualização para retornar, bem como um objeto “modelo” para passar para a view. (no Visual Studio, clicando em View e pressionando F12, você tem acesso a todos os possíveis overloads. Isso vale para qualquer objeto, comando, etc… Não é perfeito mas ajuda muito).

Quando essa ação retorna, a exibição About.cshtml mostrada acima é renderizada:

página about

 

 

 

 


Onde está a View??? Descobrindo onde está!

Quando uma ação retorna uma visualização, ocorre um processo chamado de descoberta de visualização.

Esse processo determina qual arquivo de exibição será usado. A menos que um arquivo de exibição específico seja determinado, o CORE MVC procura primeiro uma visão específica do controlador. Se não encontrou, em seguida procura o nome da exibição correspondente numa pasta Compartilhada.

Muito Importante: Quando uma ação retorna o método View, como assim, Return View () ;, o nome da ação é usado como o nome da exibição.

Por exemplo, se isso fosse chamado a partir de um método de ação chamado “Index”, seria equivalente a passar um nome de exibição chamado “Index”.

Um nome de exibição pode ser explicitamente passado para o método (return View (“SomeView”);). Em ambos os casos, a pesquisa de views percorre o seguinte caminho:

  1. Views//. Cshtml
  2. Views / Shared / .cshtml

[! DICA] Recomendamos seguir a convenção de simplesmente retornar View () das ações sempre que possível, pois resulta em um código mais flexível e mais fácil de reutilizar.

Você pode especificar um path (um caminho do arquivo de exibição), em vez de simplesmente o nome do visualizador. Nesse caso, a extensão .cshtml deve ser especificada como parte do caminho do arquivo.

O caminho (path) deve ser relativo à raiz da aplicação (e, opcionalmente, pode começar com “/” ou “~ /”). Por exemplo: return View (“Views/Home/About.cshtml”);

[! NOTA] As visualizações parciais e os componentes de exibição (view components) usam mecanismos de descoberta semelhantes (mas não idênticos).[! NOTA] Você pode personalizar a convenção padrão sobre onde as visualizações estão localizadas no aplicativo usando um IViewLocationExpander personalizado.[! DICA] Os nomes de exibição podem ser sensíveis a maiúsculas e minúsculas, dependendo do sistema de arquivos do sistema operacional. Para compatibilidade em todos os sistemas operacionais, combine sempre as maiúsculas e minúsculas entre o nome do controlador e a ação, bem como o nome das pastas e nomes de arquivos associados.


Passando dados para as Views (visualizadores)

Você pode passar dados para visualizações usando vários mecanismos.

A abordagem mais robusta é especificar um tipo de modelo na visualização (comumente referido como um modelo de exibição, para distingui-lo de outros tipos de modelo de domínio do negócio) e, em seguida, passar uma instância desse tipo para o visualizador da ação.

Recomenda-se que você use sempre use um modelo ou modelo de exibição para passar dados para uma exibição. Isso permite que o visualizador aproveite, de maneira muito forte, a verificação dos dados (contidas já na definição do modelo). No arquivo de visualização, você pode especificar um modelo para uma visualização sempre usando a diretiva @model:

@model WebApplication1.ViewModels.Address
   <h2>Contato</h2>
   <address>
       @Model.Rua<br />
       @Model.Cidade, @Model.Estado @Model.CodigoPostal<br />
       <abbr title="Telefone">P:</abbr>
       11.555.0100
   </address>

Uma vez que um modelo foi especificado para um visualizador, a instância enviada para a exibição pode ser acessada de forma fortemente tipada, usando @Model como mostrado acima. Para fornecer uma instância do tipo de modelo para o visualizador, o controlador passa os dados como se fosse um parâmetro:

public IActionResult Contact()
   {
       ViewData["Message"] = "Sua página de Contato.";

       var viewModel = new Address()
       {
           Nome= "Microsoft",
           Rua= "One Microsoft Way",
           Cidade= "Redmond",
           Estado= "WA",
           CodigoPostal= "98052-6399"
       };
       return View(viewModel);
   }

Não há restrições sobre os tipos que podem ser fornecidos a uma visualização como um modelo. Recomenda-se passar modelos de visualização Plain Old CLR Objects (POCO), com pouco ou nenhum comportamento. Assim, a lógica de negócios pode ser encapsulada em outro lugar no aplicativo. Um exemplo dessa abordagem é o modelo de exibição de endereço usado no exemplo acima:

namespace WebApplication1.ViewModels
   {
       public class Address
       {
           public string Nome { get; set; }
           public string Rua { get; set; }
           public string Cidade { get; set; }
           public string Estado { get; set; }
           public string CodigoPostal { get; set; }
       }
   }

[! NOTA] Nada impede que você use as mesmas classes que seus tipos de modelo de negócios e seus tipos de modelo de exibição. No entanto, mantê-los separados permite que suas visualizações variem independentemente do seu modelo de domínio ou modelo de persistência. Isso também pode oferecer alguns benefícios de segurança (para os modelos em que os usuários enviarão dados ao aplicativo usando o modelo).

Loosely Typed Data (Dados não necessariamente em banco de dados)

Além das visualizações fortemente consistidas na digitação ou oriundas de bases de dados digitadas, todas as Views (visualizadores)  têm acesso a coleções de dados mais, digamos assim, livres. Qualquer coleção pode ser referenciada através de propriedades como ViewData ou ViewBag, tanto nos Controllers (controladores) como nas Views (visualizadores).

ViewBag e ViewData

A propriedade ViewBag é uma espécie de “empacotador de dados” em torno do ViewData que fornece uma visão dinâmica sobre de uma determinada coleção. Não é uma coleção separada.

ViewData é um objeto do dicionário, acessado através de chaves tipo string.

Você pode armazenar e recuperar objetos nele. O que você precisará lançá-los para um tipo específico quando for  extrair os dados.

Você pode usar ViewData para passar dados de um controlador para um visualizador. E também pode usá-los onde quiser dentro dos visualizadores (views – mesmo visualizações parciais e layouts). Dados tipo “string” podem ser armazenados e usados diretamente, sem a necessidade de “transmití-los” do Controller para  a View.

Definindo alguns valores para ViewData dentro de uma ação de um controller

public IActionResult NomeDaAcao()
   {
       ViewData["Saudacao"] = "Alo!";
       ViewData["Endereco"]  = new Endereco()
       {
           Nome = "Estevan",
           Rua = "Julio Paiva 1050",
           Cidade = "Valinhos",
           Estado = "SP",
           CodigoPostal = "13236-025"
       };

       return View();
   }

Trabalhando com os dados de uma ViewData em uma View

@{
       // Requisita a transmissão do controller para a View
       var endereco = ViewData["Endereco"] as Endereco;
   }

   @ViewData["Saudacao"], Mundo cruel!

   <endereco>
       @endereco.Nome<br />
       @endereco.Ruat<br />
       @endereco.Cidade, @aendereco.Estado - CEP: @endereco.CodigoPostal
   </endereco>

Já os objetos ViewBag fornecem acesso dinâmico aos objetos armazenados no ViewData. Isso pode ser mais conveniente para se trabalhar, uma vez que não requer o passo da transmissão para o visualizador (View).

Veja o mesmo exemplo acima, usando ViewBag em vez de uma instância de endereço dentro do View:

@ViewBag.Saudacao , Mundo Cruel

   <endereco>
       @ViewBag.Endereco.Nome<br />
       @ViewBag.Endereco.Rua<br />
       @ViewBag.Endereco.Cidade, @ViewBag.Endereco.Estado - CEP:  @ViewBag.Endereco.CodigoPostal
   </endereco>

[! NOTA] Uma vez que ambos se referem à mesma coleção ViewData subjacente, você pode misturar e combinar entre ViewData e ViewBag ao ler e escrever valores, se conveniente

Visualizações Dinâmicas (Dynamic Views)

São visualizações que não declaram um tipo de modelo, mas que possuem uma instância modelo passada para elas, de forma dinâmica. Por exemplo, se uma instância de Endereço é passada para uma visão que não declara um @model (no topo do arquivo de visualização), a visualização (view) ainda poderá se referir às propriedades da instância, como é mostrado a seguir:

   <endereco>
       @Model.Rua<br />
       @Model.Cidade, @Model.Estado @Model.CodigoPostal<br />
       <abbr title="Telefone">Fone:</abbr>
       19 9555.0100
   </endereco>

Esse recurso pode oferecer alguma flexibilidade, mas não oferece proteção de compilação ou mesmo o apoio do IntelliSense, dificultando a escrita do código Razor. Se a propriedade não existir, a página falhará no tempo de execução, obrigando você a voltar e corrigí-la.


Mais Recursos para as Visualizações (Views)

Os Tag Helpers (ajudantes de tags) facilitam a adição de comportamentos, do lado do servidor, às tags HTML existentes, evitando a necessidade de usar códigos personalizados ou ajudantes nas visualizações.

Os Tag Helpers são ajudantes que são aplicados como se fossem simples atributos de elementos HTML, que são ignorados por editores que não estão familiarizados com eles, permitindo que a marcação de exibição seja editada e renderizada em uma variedade de ferramentas.

Os Tag Helpers têm muitos usos e, em particular, podem tornar muito mais fácil o trabalho com formulários.

A geração de marcação HTML personalizada pode ser alcançada com muitos HTML Helpers já existentes. Mais que isso, é possível construir novos TAG HELPERS, embutindo-os na aplicação. Esses Tag Hepers podem ter seus próprios requisitos em termos de dados, permitido uma interface de usuário (UI) mais complexa, que fica encapsulada nos componentes das VIews (View Compnents).

Os componentes das views tem a mesma separação de preocupações que os controladores e as visualizações oferecem e podem eliminar a necessidade criação de ações e visualizações com dados, para lidar com dados, por usarem elementos muito simples na interface de usuário (UI).

Como ocorre com muitos outros aspectos do ASP.NET Core, as Views suportam a injeção de dependência (dependency injection),  permitindo que os serviços sejam injetados nas visualizações.


Referências:

  1. Rendering HTML with views in ASP.NET Core MVC

 

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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business

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.

local de arquivos secret manager

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.

Você quer ter uma franquia de hospedagem com tudo para marketing digital e faturamento recorrente?

franquia builderall business