Componentes de exibição no ASP.NET Core

De Rick Anderson

Componentes da exibição

Os componentes de exibição são semelhantes às exibições parciais, mas são muito mais eficientes. Os componentes de exibição não utilizam a associação de modelos, eles dependem dos dados passados quando você chama o componente de exibição. Este artigo foi escrito utilizando controladores e exibições, mas os componentes de exibição funcionam com o Razor Pages.

Um componente de exibição:

  • Renderiza uma parte em vez de uma resposta inteira.
  • Inclui os mesmos benefícios de capacidade de teste e separação de interesses e encontrados entre um controlador e uma exibição.
  • Pode ter parâmetros e uma lógica de negócios.
  • É geralmente invocado em uma página de layout.

Os componentes de exibição são destinados a qualquer lugar em que a lógica de renderização reutilizável seja muito complexa para uma exibição parcial, como, por exemplo:

  • Menus de navegação dinâmica
  • Nuvem de marcas, na qual você consulta o banco de dados
  • Painel de Entrada
  • Carrinho de compras
  • Artigos publicados recentemente
  • Conteúdo da barra lateral em um blog
  • Um painel de entrada que seria renderizado em todas as páginas e mostraria os links para sair ou entrar, dependendo do estado de entrada do usuário

Um componente de exibição consiste em duas partes:

  • A classe, normalmente derivada de ViewComponent
  • O resultado que ele retorna, normalmente uma exibição.

Assim como os controladores, um componente de exibição pode ser um POCO, mas a maioria dos desenvolvedores aproveita os métodos e as propriedades disponíveis ao derivar de ViewComponent.

Ao considerar se os componentes de exibição atendem às especificações de um aplicativo, considere utilizar os componentes Razor. Os componentes Razor também combinam a marcação com o código C# para produzir unidades de interfaces do usuário reutilizáveis. Os componentes Razor foram projetados para aumentar a produtividade do desenvolvedor ao fornecer a lógica e a composição da interface do usuário no lado do cliente. Para saber mais, confira Componentes Razor do ASP.NET Core. Para obter informações sobre como incorporar componentes Razor em um aplicativo MVC ou Razor Pages, confira Integrar componentes Razor do ASP.NET Core em aplicativos do ASP.NET Core.

Criar um componente de exibição

Esta seção contém os requisitos de alto nível para a criação de um componente de exibição. Mais adiante neste artigo, examinaremos cada etapa em detalhes e criaremos um componente de exibição.

A classe de componente de exibição

Uma classe de componente de exibição pode ser criada por um dos seguintes:

  • Derivando de ViewComponent
  • Decoração de uma classe com o atributo [ViewComponent] ou derivação de uma classe com o atributo [ViewComponent]
  • Criando uma classe em que o nome termina com o sufixo ViewComponent

Assim como os controladores, os componentes de exibição precisam ser classes públicas, não aninhadas e não abstratas. O nome do componente de exibição é o nome da classe com o sufixo ViewComponent removido. Também pode ser especificado de forma explícita com a propriedade Name.

Uma classe de componente de exibição:

  • Tem suporte para o construtor de injeção de dependência
  • Não participa do ciclo de vida do controlador, portanto os filtros não podem ser utilizados em um componente de exibição

Para evitar que uma classe que tenha um sufixo ViewComponent que não diferencia maiúsculas de minúsculas seja tratada como um componente de exibição, decore a classe com o atributo [NonViewComponent]:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

Métodos de componente de exibição

Um componente de exibição define sua lógica em um método:

  • InvokeAsync método que retorna Task<IViewComponentResult>.
  • Invoke método síncrono que retorna um IViewComponentResult.

Os parâmetros são recebidos diretamente da invocação do componente de exibição, não do model binding. Um componente de exibição nunca manipula uma solicitação diretamente. Normalmente, um componente de exibição inicializa um modelo e passa-o para uma exibição chamando o método View. Em resumo, os métodos de componente de exibição:

  • Definem um método InvokeAsync que retorna um Task<IViewComponentResult> ou um método Invoke síncrono que retorna um IViewComponentResult.
  • Normalmente, você inicializa um modelo e o passa para uma exibição chamando o método ViewComponent.View.
  • Os parâmetros são recebidos do método de chamada, não do HTTP. Não há nenhum model binding.
  • Não podem ser acessados diretamente como pontos de extremidade HTTP. Normalmente, são invocados em uma exibição. Um componente de exibição nunca manipula uma solicitação.
  • São sobrecarregados na assinatura, em vez de nos detalhes da solicitação HTTP atual.

Caminho de pesquisa de exibição

O runtime pesquisa a exibição nos seguintes caminhos:

  • /Views/{Nome do Controlador}/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Views/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Pages/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Areas/{Nome da Área}/Views/Shared/Components/{Nome do Componente de Exibição}/{Nome de Exibição}

O caminho da pesquisa se aplica a projetos que utilizam controladores + exibições e o Razor Pages.

O nome de exibição padrão de um componente de exibição é Default, o que significa que os arquivos de exibição normalmente terão o nome Default.cshtml. Você pode especificar um nome de exibição diferente ao criar o resultado do componente de exibição ou ao chamar o método View.

Recomendamos que você nomeie o arquivo de exibição Default.cshtml e utilize o caminho Views/Shared/Components/{View Component Name}/{View Name}. O componente de exibição PriorityList usado neste exemplo utiliza Views/Shared/Components/PriorityList/Default.cshtml para a exibição do componente de exibição.

Personalizar o caminho de busca da exibição

Para personalizar o caminho de busca da exibição, modifique a coleção Razor de ViewLocationFormats. Por exemplo, para pesquisar exibições dentro do caminho /Components/{View Component Name}/{View Name}, adicione um novo item à coleção:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

No código anterior, o espaço reservado {0} representa o caminho Components/{View Component Name}/{View Name}.

Invocar um componente de exibição

Para usar o componente de exibição, chame o seguinte em uma exibição:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

Os parâmetros são passados para o método InvokeAsync. O componente de exibição PriorityList desenvolvido no artigo é chamado a partir do arquivo de exibição Views/ToDo/Index.cshtml. No código a seguir, o método InvokeAsync é chamado com dois parâmetros:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Invocar um componente de exibição como um Auxiliar de Marcas

Um Componente de exibição pode ser chamado como um Auxiliar de Marcas:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

Os parâmetros de classe e de método na formatação Pascal Case para Auxiliares de Marcas são convertidos em kebab case. Para invocar um componente de exibição, o Auxiliar de Marca usa o elemento <vc></vc>. O componente de exibição é especificado da seguinte maneira:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Para usar um componente de exibição como um Auxiliar de Marca, registre o assembly que contém o componente de exibição usando a diretiva @addTagHelper. Se o componente de exibição estiver em um assembly chamado MyWebApp, adicione a seguinte diretiva ao arquivo _ViewImports.cshtml:

@addTagHelper *, MyWebApp

Um componente de exibição pode ser registrado como um Auxiliar de Marcas em qualquer arquivo que faça referência ao componente de exibição. Consulte Gerenciando o escopo do Auxiliar de Marca para obter mais informações sobre como registrar Auxiliares de Marca.

O método InvokeAsync usado neste tutorial:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

Na marcação anterior, o componente de exibição PriorityList torna-se priority-list. Os parâmetros para o componente de exibição são passados como atributos em kebab case.

Invocar um componente de exibição diretamente de um controlador

Normalmente, os componentes de exibição são invocados a partir de uma exibição, mas podem ser invocados diretamente de um método controlador. Embora os componentes de exibição não definam pontos de extremidade como os controladores, uma ação do controlador que retorna o conteúdo de ViewComponentResult pode ser implementada.

No exemplo a seguir, o componente de exibição é chamado diretamente do controlador:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Criar um componente básico de exibição

Baixe, compile e teste o código inicial. É um projeto básico com um controlador ToDo que exibe uma lista de itens ToDo.

List of ToDos

Atualize o controlador para que você passe a prioridade e o status da conclusão

Atualize o método Index para que você utilize os parâmetros de prioridade e status da conclusão:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

Adicionar uma classe ViewComponent

Adicione uma classe ViewComponent a ViewComponents/PriorityListViewComponent.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Observações sobre o código:

  • As classes de componente de exibição podem ser contidas em qualquer pasta do projeto.

  • Como o nome da classe PriorityListViewComponent termina com o sufixo ViewComponent, o runtime utiliza a cadeia de caracteres PriorityList ao fazer referência ao componente da classe a partir de uma exibição.

  • O atributo [ViewComponent] pode alterar o nome usado para referenciar um componente de exibição. Por exemplo, a classe poderia ter sido nomeada XYZ com o seguinte atributo [ViewComponent]:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • O atributo [ViewComponent] no código anterior informa ao seletor do componente de exibição que você deve utilizar:

    • O nome PriorityList ao procurar as exibições associadas ao componente
    • A cadeia de caracteres "PriorityList" ao fazer referência ao componente de exibição da classe.
  • O componente usa a injeção de dependência para disponibilizar o contexto de dados.

  • InvokeAsync expõe um método que pode ser chamado a partir de uma exibição e pode receber um número arbitrário de argumentos.

  • O método InvokeAsync retorna o conjunto de itens ToDo que atendem aos parâmetros isDone e maxPriority.

Criar o componente de exibição Razor

  • Crie a pasta Views/Shared/Components. Essa pasta deve nomeada Components.

  • Crie a pasta Views/Shared/Components/PriorityList. Esse nome de pasta deve corresponder ao nome da classe do componente de exibição ou ao nome da classe menos o sufixo. Se você utilizar o atributo ViewComponent, o nome da classe precisará corresponder à designação do atributo.

  • Crie uma exibição Views/Shared/Components/PriorityList/Default.cshtmlRazor:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    A exibição Razor pega uma lista de TodoItem e a exibe. Se o método InvokeAsync do componente de exibição não passar o nome da exibição, Padrão será utilizado como nome da exibição por convenção. Para substituir o estilo padrão de um controlador específico, adicione uma exibição à pasta de exibição específica do controlador (por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml).

    Se o componente de exibição for específico do controlador, ele poderá ser adicionado em uma pasta específica do controlador. Por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml é específico do controlador.

  • Adicione um div contendo uma chamada para o componente da lista de prioridades na parte inferior do arquivo Views/ToDo/index.cshtml:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

A marcação @await Component.InvokeAsync mostra a sintaxe para chamar componentes de exibição. O primeiro argumento é o nome do componente que queremos invocar ou chamar. Os parâmetros seguintes são passados para o componente. InvokeAsync pode usar um número arbitrário de argumentos.

Testar o aplicativo. A seguinte imagem mostra a lista ToDo e os itens de prioridade:

todo list and priority items

O componente de exibição pode ser chamado diretamente do controlador:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

priority items from IndexVC action

Especificar o nome de um componente de exibição

Um componente de exibição complexo pode precisar especificar uma exibição não padrão em algumas condições. O código a seguir mostra como você pode especificar a exibição do "PVC" a partir do método InvokeAsync. Atualize o método InvokeAsync na classe PriorityListViewComponent.

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Copie o arquivo Views/Shared/Components/PriorityList/Default.cshtml para uma exibição chamada Views/Shared/Components/PriorityList/PVC.cshtml. Adicione um cabeçalho para indicar que a exibição PVC está sendo usada.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Execute o aplicativo e verifique a exibição PVC.

Priority View Component

Se a exibição do PVC não for renderizada, verifique se o componente de exibição com uma prioridade de 4 ou superior é chamado.

Examinar o caminho de exibição

  • Altere o parâmetro de prioridade para três ou menos para que a exibição de prioridade não seja retornada.

  • Renomeie temporariamente o Views/ToDo/Components/PriorityList/Default.cshtml para 1Default.cshtml.

  • Teste o aplicativo e ocorrerá o seguinte erro:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Copie Views/ToDo/Components/PriorityList/1Default.cshtml para Views/Shared/Components/PriorityList/Default.cshtml.

  • Adicione uma marcação ao componente de exibição ToDo Shared para indicar que a exibição foi obtida da pasta Shared.

  • Teste o componente de exibição Shared.

ToDo output with Shared component view

Evite cadeias de caracteres com código rígido

Para segurança em tempo de compilação, substitua o nome do componente de exibição codificado pelo nome da classe. Atualize o arquivo PriorityListViewComponent.cs para que você não utilize o sufixo "ViewComponent":

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

O arquivo de exibição:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Uma sobrecarga do método Component.InvokeAsync que recebe um tipo CLR utiliza o operador typeof:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Executar o trabalho síncrono

A estrutura lida com a invocação de um método Invoke síncrono se o trabalho assíncrono não for exigido. O método a seguir cria um componente de exibição Invoke síncrono:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

O arquivo Razor do componente de exibição:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

O componente de exibição é invocado em um arquivo Razor (por exemplo, Views/Home/Index.cshtml) utilizando uma das seguintes abordagens:

Para usar a abordagem IViewComponentHelper, chame Component.InvokeAsync:

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

Para usar o Auxiliar de Marca, registre o assembly que contém o Componente de exibição que usa a diretiva @addTagHelper (o componente de exibição está em um assembly chamado MyWebApp):

@addTagHelper *, MyWebApp

Utilize o Auxiliar de Marcas do componente de exibição no arquivo de marcação Razor:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

A assinatura do método de PriorityList.Invoke é síncrona, mas Razor encontra e chama o método com Component.InvokeAsync no arquivo de marcação.

Recursos adicionais

Componentes da exibição

Os componentes de exibição são semelhantes às exibições parciais, mas são muito mais eficientes. Os componentes de exibição não utilizam a associação de modelos, eles dependem dos dados passados quando você chama o componente de exibição. Este artigo foi escrito utilizando controladores e exibições, mas os componentes de exibição funcionam com o Razor Pages.

Um componente de exibição:

  • Renderiza uma parte em vez de uma resposta inteira.
  • Inclui os mesmos benefícios de capacidade de teste e separação de interesses e encontrados entre um controlador e uma exibição.
  • Pode ter parâmetros e uma lógica de negócios.
  • É geralmente invocado em uma página de layout.

Os componentes de exibição são destinados a qualquer lugar em que a lógica de renderização reutilizável seja muito complexa para uma exibição parcial, como, por exemplo:

  • Menus de navegação dinâmica
  • Nuvem de marcas, na qual você consulta o banco de dados
  • Painel de Entrada
  • Carrinho de compras
  • Artigos publicados recentemente
  • Conteúdo da barra lateral em um blog
  • Um painel de entrada que seria renderizado em todas as páginas e mostraria os links para sair ou entrar, dependendo do estado de entrada do usuário

Um componente de exibição consiste em duas partes:

  • A classe, normalmente derivada de ViewComponent
  • O resultado que ele retorna, normalmente uma exibição.

Assim como os controladores, um componente de exibição pode ser um POCO, mas a maioria dos desenvolvedores aproveita os métodos e as propriedades disponíveis ao derivar de ViewComponent.

Ao considerar se os componentes de exibição atendem às especificações de um aplicativo, considere utilizar os componentes Razor. Os componentes Razor também combinam a marcação com o código C# para produzir unidades de interfaces do usuário reutilizáveis. Os componentes Razor foram projetados para aumentar a produtividade do desenvolvedor ao fornecer a lógica e a composição da interface do usuário no lado do cliente. Para saber mais, confira Componentes Razor do ASP.NET Core. Para obter informações sobre como incorporar os componentes Razor em um aplicativo MVC ou Razor Pages, confira Pré-renderizar e integrar componentes do ASP.NET Core Razor.

Criar um componente de exibição

Esta seção contém os requisitos de alto nível para a criação de um componente de exibição. Mais adiante neste artigo, examinaremos cada etapa em detalhes e criaremos um componente de exibição.

A classe de componente de exibição

Uma classe de componente de exibição pode ser criada por um dos seguintes:

  • Derivando de ViewComponent
  • Decoração de uma classe com o atributo [ViewComponent] ou derivação de uma classe com o atributo [ViewComponent]
  • Criando uma classe em que o nome termina com o sufixo ViewComponent

Assim como os controladores, os componentes de exibição precisam ser classes públicas, não aninhadas e não abstratas. O nome do componente de exibição é o nome da classe com o sufixo ViewComponent removido. Também pode ser especificado de forma explícita com a propriedade Name.

Uma classe de componente de exibição:

  • Tem suporte para o construtor de injeção de dependência
  • Não participa do ciclo de vida do controlador, portanto os filtros não podem ser utilizados em um componente de exibição

Para evitar que uma classe que tenha um sufixo ViewComponent que não diferencia maiúsculas de minúsculas seja tratada como um componente de exibição, decore a classe com o atributo [NonViewComponent]:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

Métodos de componente de exibição

Um componente de exibição define sua lógica em um método:

  • InvokeAsync método que retorna Task<IViewComponentResult>.
  • Invoke método síncrono que retorna um IViewComponentResult.

Os parâmetros são recebidos diretamente da invocação do componente de exibição, não do model binding. Um componente de exibição nunca manipula uma solicitação diretamente. Normalmente, um componente de exibição inicializa um modelo e passa-o para uma exibição chamando o método View. Em resumo, os métodos de componente de exibição:

  • Definem um método InvokeAsync que retorna um Task<IViewComponentResult> ou um método Invoke síncrono que retorna um IViewComponentResult.
  • Normalmente, você inicializa um modelo e o passa para uma exibição chamando o método ViewComponent.View.
  • Os parâmetros são recebidos do método de chamada, não do HTTP. Não há nenhum model binding.
  • Não podem ser acessados diretamente como pontos de extremidade HTTP. Normalmente, são invocados em uma exibição. Um componente de exibição nunca manipula uma solicitação.
  • São sobrecarregados na assinatura, em vez de nos detalhes da solicitação HTTP atual.

Caminho de pesquisa de exibição

O runtime pesquisa a exibição nos seguintes caminhos:

  • /Views/{Nome do Controlador}/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Views/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Pages/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Areas/{Nome da Área}/Views/Shared/Components/{Nome do Componente de Exibição}/{Nome de Exibição}

O caminho da pesquisa se aplica a projetos que utilizam controladores + exibições e o Razor Pages.

O nome de exibição padrão de um componente de exibição é Default, o que significa que os arquivos de exibição normalmente terão o nome Default.cshtml. Você pode especificar um nome de exibição diferente ao criar o resultado do componente de exibição ou ao chamar o método View.

Recomendamos que você nomeie o arquivo de exibição Default.cshtml e utilize o caminho Views/Shared/Components/{View Component Name}/{View Name}. O componente de exibição PriorityList usado neste exemplo utiliza Views/Shared/Components/PriorityList/Default.cshtml para a exibição do componente de exibição.

Personalizar o caminho de busca da exibição

Para personalizar o caminho de busca da exibição, modifique a coleção Razor de ViewLocationFormats. Por exemplo, para pesquisar exibições dentro do caminho /Components/{View Component Name}/{View Name}, adicione um novo item à coleção:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

No código anterior, o espaço reservado {0} representa o caminho Components/{View Component Name}/{View Name}.

Invocar um componente de exibição

Para usar o componente de exibição, chame o seguinte em uma exibição:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

Os parâmetros são passados para o método InvokeAsync. O componente de exibição PriorityList desenvolvido no artigo é chamado a partir do arquivo de exibição Views/ToDo/Index.cshtml. No código a seguir, o método InvokeAsync é chamado com dois parâmetros:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Invocar um componente de exibição como um Auxiliar de Marcas

Um Componente de exibição pode ser chamado como um Auxiliar de Marcas:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

Os parâmetros de classe e de método na formatação Pascal Case para Auxiliares de Marcas são convertidos em kebab case. Para invocar um componente de exibição, o Auxiliar de Marca usa o elemento <vc></vc>. O componente de exibição é especificado da seguinte maneira:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Para usar um componente de exibição como um Auxiliar de Marca, registre o assembly que contém o componente de exibição usando a diretiva @addTagHelper. Se o componente de exibição estiver em um assembly chamado MyWebApp, adicione a seguinte diretiva ao arquivo _ViewImports.cshtml:

@addTagHelper *, MyWebApp

Um componente de exibição pode ser registrado como um Auxiliar de Marcas em qualquer arquivo que faça referência ao componente de exibição. Consulte Gerenciando o escopo do Auxiliar de Marca para obter mais informações sobre como registrar Auxiliares de Marca.

O método InvokeAsync usado neste tutorial:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

Na marcação anterior, o componente de exibição PriorityList torna-se priority-list. Os parâmetros para o componente de exibição são passados como atributos em kebab case.

Invocar um componente de exibição diretamente de um controlador

Normalmente, os componentes de exibição são invocados a partir de uma exibição, mas podem ser invocados diretamente de um método controlador. Embora os componentes de exibição não definam pontos de extremidade como os controladores, uma ação do controlador que retorna o conteúdo de ViewComponentResult pode ser implementada.

No exemplo a seguir, o componente de exibição é chamado diretamente do controlador:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Criar um componente básico de exibição

Baixe, compile e teste o código inicial. É um projeto básico com um controlador ToDo que exibe uma lista de itens ToDo.

List of ToDos

Atualize o controlador para que você passe a prioridade e o status da conclusão

Atualize o método Index para que você utilize os parâmetros de prioridade e status da conclusão:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

Adicionar uma classe ViewComponent

Adicione uma classe ViewComponent a ViewComponents/PriorityListViewComponent.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Observações sobre o código:

  • As classes de componente de exibição podem ser contidas em qualquer pasta do projeto.

  • Como o nome da classe PriorityListViewComponent termina com o sufixo ViewComponent, o runtime utiliza a cadeia de caracteres PriorityList ao fazer referência ao componente da classe a partir de uma exibição.

  • O atributo [ViewComponent] pode alterar o nome usado para referenciar um componente de exibição. Por exemplo, a classe poderia ter sido nomeada XYZ com o seguinte atributo [ViewComponent]:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • O atributo [ViewComponent] no código anterior informa ao seletor do componente de exibição que você deve utilizar:

    • O nome PriorityList ao procurar as exibições associadas ao componente
    • A cadeia de caracteres "PriorityList" ao fazer referência ao componente de exibição da classe.
  • O componente usa a injeção de dependência para disponibilizar o contexto de dados.

  • InvokeAsync expõe um método que pode ser chamado a partir de uma exibição e pode receber um número arbitrário de argumentos.

  • O método InvokeAsync retorna o conjunto de itens ToDo que atendem aos parâmetros isDone e maxPriority.

Criar o componente de exibição Razor

  • Crie a pasta Views/Shared/Components. Essa pasta deve nomeada Components.

  • Crie a pasta Views/Shared/Components/PriorityList. Esse nome de pasta deve corresponder ao nome da classe do componente de exibição ou ao nome da classe menos o sufixo. Se você utilizar o atributo ViewComponent, o nome da classe precisará corresponder à designação do atributo.

  • Crie uma exibição Views/Shared/Components/PriorityList/Default.cshtmlRazor:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    A exibição Razor pega uma lista de TodoItem e a exibe. Se o método InvokeAsync do componente de exibição não passar o nome da exibição, Padrão será utilizado como nome da exibição por convenção. Para substituir o estilo padrão de um controlador específico, adicione uma exibição à pasta de exibição específica do controlador (por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml).

    Se o componente de exibição for específico do controlador, ele poderá ser adicionado em uma pasta específica do controlador. Por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml é específico do controlador.

  • Adicione um div contendo uma chamada para o componente da lista de prioridades na parte inferior do arquivo Views/ToDo/index.cshtml:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

A marcação @await Component.InvokeAsync mostra a sintaxe para chamar componentes de exibição. O primeiro argumento é o nome do componente que queremos invocar ou chamar. Os parâmetros seguintes são passados para o componente. InvokeAsync pode usar um número arbitrário de argumentos.

Testar o aplicativo. A seguinte imagem mostra a lista ToDo e os itens de prioridade:

todo list and priority items

O componente de exibição pode ser chamado diretamente do controlador:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

priority items from IndexVC action

Especificar o nome de um componente de exibição

Um componente de exibição complexo pode precisar especificar uma exibição não padrão em algumas condições. O código a seguir mostra como você pode especificar a exibição do "PVC" a partir do método InvokeAsync. Atualize o método InvokeAsync na classe PriorityListViewComponent.

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Copie o arquivo Views/Shared/Components/PriorityList/Default.cshtml para uma exibição chamada Views/Shared/Components/PriorityList/PVC.cshtml. Adicione um cabeçalho para indicar que a exibição PVC está sendo usada.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Execute o aplicativo e verifique a exibição PVC.

Priority View Component

Se a exibição do PVC não for renderizada, verifique se o componente de exibição com uma prioridade de 4 ou superior é chamado.

Examinar o caminho de exibição

  • Altere o parâmetro de prioridade para três ou menos para que a exibição de prioridade não seja retornada.

  • Renomeie temporariamente o Views/ToDo/Components/PriorityList/Default.cshtml para 1Default.cshtml.

  • Teste o aplicativo e ocorrerá o seguinte erro:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Copie Views/ToDo/Components/PriorityList/1Default.cshtml para Views/Shared/Components/PriorityList/Default.cshtml.

  • Adicione uma marcação ao componente de exibição ToDo Shared para indicar que a exibição foi obtida da pasta Shared.

  • Teste o componente de exibição Shared.

ToDo output with Shared component view

Evite cadeias de caracteres com código rígido

Para segurança em tempo de compilação, substitua o nome do componente de exibição codificado pelo nome da classe. Atualize o arquivo PriorityListViewComponent.cs para que você não utilize o sufixo "ViewComponent":

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

O arquivo de exibição:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Uma sobrecarga do método Component.InvokeAsync que recebe um tipo CLR utiliza o operador typeof:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Executar o trabalho síncrono

A estrutura lida com a invocação de um método Invoke síncrono se o trabalho assíncrono não for exigido. O método a seguir cria um componente de exibição Invoke síncrono:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

O arquivo Razor do componente de exibição:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

O componente de exibição é invocado em um arquivo Razor (por exemplo, Views/Home/Index.cshtml) utilizando uma das seguintes abordagens:

Para usar a abordagem IViewComponentHelper, chame Component.InvokeAsync:

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

Para usar o Auxiliar de Marca, registre o assembly que contém o Componente de exibição que usa a diretiva @addTagHelper (o componente de exibição está em um assembly chamado MyWebApp):

@addTagHelper *, MyWebApp

Utilize o Auxiliar de Marcas do componente de exibição no arquivo de marcação Razor:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

A assinatura do método de PriorityList.Invoke é síncrona, mas Razor encontra e chama o método com Component.InvokeAsync no arquivo de marcação.

Recursos adicionais

Exibir ou baixar código de exemplo (como baixar)

Componentes da exibição

Os componentes de exibição são semelhantes às exibições parciais, mas são muito mais eficientes. Os componentes de exibição não usam o model binding e dependem apenas dos dados fornecidos durante uma chamada a eles. Este artigo foi escrito utilizando controladores e exibições, mas os componentes de exibição também funcionam com o Razor Pages.

Um componente de exibição:

  • Renderiza uma parte em vez de uma resposta inteira.
  • Inclui os mesmos benefícios de capacidade de teste e separação de interesses e encontrados entre um controlador e uma exibição.
  • Pode ter parâmetros e uma lógica de negócios.
  • É geralmente invocado em uma página de layout.

Os componentes de exibição destinam-se a qualquer momento em que há uma lógica de renderização reutilizável muito complexa para uma exibição parcial, como:

  • Menus de navegação dinâmica
  • Nuvem de marcas (na qual ela consulta o banco de dados)
  • Painel de logon
  • Carrinho de compras
  • Artigos publicados recentemente
  • Conteúdo da barra lateral em um blog típico
  • Um painel de logon que é renderizado em cada página e mostra os links para logoff ou logon, dependendo do estado de logon do usuário

Um componente de exibição consiste em duas partes: a classe (normalmente derivada de ViewComponent) e o resultado que ela retorna (normalmente uma exibição). Assim como os controladores, um componente de exibição pode ser um POCO, mas a maioria dos desenvolvedores aproveita os métodos e as propriedades disponíveis ao derivar de ViewComponent.

Ao considerar se os componentes de exibição atendem às especificações de um aplicativo, considere utilizar os componentes Razor. Os componentes Razor também combinam a marcação com o código C# para produzir unidades de interfaces do usuário reutilizáveis. Os componentes Razor foram projetados para aumentar a produtividade do desenvolvedor ao fornecer a lógica e a composição da interface do usuário no lado do cliente. Para saber mais, confira Componentes Razor do ASP.NET Core. Para obter informações sobre como incorporar os componentes Razor em um aplicativo MVC ou Razor Pages, confira Pré-renderizar e integrar componentes do ASP.NET Core Razor.

Criando um componente de exibição

Esta seção contém os requisitos de alto nível para a criação de um componente de exibição. Mais adiante neste artigo, examinaremos cada etapa em detalhes e criaremos um componente de exibição.

A classe de componente de exibição

Uma classe de componente de exibição pode ser criada por um dos seguintes:

  • Derivação de ViewComponent
  • Decoração de uma classe com o atributo [ViewComponent] ou derivação de uma classe com o atributo [ViewComponent]
  • Criação de uma classe em que o nome termina com o sufixo ViewComponent

Assim como os controladores, os componentes de exibição precisam ser classes públicas, não aninhadas e não abstratas. O nome do componente de exibição é o nome da classe com o sufixo "ViewComponent" removido. Também pode ser especificado de forma explícita com a propriedade ViewComponentAttribute.Name.

Uma classe de componente de exibição:

Para impedir que uma classe que tenha um sufixo ViewComponent que não diferencia maiúsculas de minúsculas seja tratada como um componente de exibição, decore a classe com o atributo [NonViewComponent]:

[NonViewComponent]
public class ReviewComponent
{
    // ...

Métodos de componente de exibição

Um componente de exibição define sua lógica em um método InvokeAsync que retorna um Task<IViewComponentResult> ou em um método Invoke síncrono que retorna um IViewComponentResult. Os parâmetros são recebidos diretamente da invocação do componente de exibição, não do model binding. Um componente de exibição nunca manipula uma solicitação diretamente. Normalmente, um componente de exibição inicializa um modelo e passa-o para uma exibição chamando o método View. Em resumo, os métodos de componente de exibição:

  • Definem um método InvokeAsync que retorna um Task<IViewComponentResult> ou um método Invoke síncrono que retorna um IViewComponentResult.
  • Normalmente, inicializam um modelo e o passam para uma exibição chamando o método ViewComponentView.
  • Os parâmetros são recebidos do método de chamada, não do HTTP. Não há nenhum model binding.
  • Não são acessíveis diretamente como um ponto de extremidade HTTP. Eles são invocados no código (normalmente, em uma exibição). Um componente de exibição nunca manipula uma solicitação.
  • São sobrecarregados na assinatura, em vez de nos detalhes da solicitação HTTP atual.

Caminho de pesquisa de exibição

O runtime pesquisa a exibição nos seguintes caminhos:

  • /Views/{Nome do Controlador}/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Views/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Pages/Shared/Components/{Nome do Componente da Exibição}/{Nome da Exibição}
  • /Areas/{Nome da Área}/Views/Shared/Components/{Nome do Componente de Exibição}/{Nome de Exibição}

O caminho da pesquisa se aplica a projetos que utilizam controladores + exibições e o Razor Pages.

O nome de exibição padrão de um componente de exibição é Padrão, o que significa que seu arquivo de exibição normalmente terá o nome Default.cshtml. Especifique outro nome de exibição ao criar o resultado do componente de exibição ou ao chamar o método View.

Recomendamos que você nomeie o arquivo de exibição Default.cshtml e utilize o caminho Views/Shared/Components/{View Component Name}/{View Name}. O componente de exibição PriorityList usado neste exemplo utiliza Views/Shared/Components/PriorityList/Default.cshtml para a exibição do componente de exibição.

Personalizar o caminho de busca da exibição

Para personalizar o caminho de busca da exibição, modifique a coleção Razor de ViewLocationFormats. Por exemplo, para que você possa pesquisar exibições no caminho "/Components/{Nome do Componente de Exibição}/{Nome de Exibição}", adicione um novo item à coleção:

services.AddMvc()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

No código anterior, o espaço reservado "{0}" representa o caminho "Components/{Nome do Componente de Exibição}/{Nome de Exibição}".

Invocando um componente de exibição

Para usar o componente de exibição, chame o seguinte em uma exibição:

@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})

Os parâmetros serão passados para o método InvokeAsync. O componente de exibição PriorityList desenvolvido no artigo é chamado a partir do arquivo de exibição Views/ToDo/Index.cshtml. A seguir, o método InvokeAsync é chamado com dois parâmetros:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Invocando um componente de exibição como um Auxiliar de Marca

Para o ASP.NET Core 1.1 e superior, invoque um componente de exibição como um Auxiliar de Marca:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

Os parâmetros de classe e de método na formatação Pascal Case para Auxiliares de Marcas são convertidos em kebab case. Para invocar um componente de exibição, o Auxiliar de Marca usa o elemento <vc></vc>. O componente de exibição é especificado da seguinte maneira:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Para usar um componente de exibição como um Auxiliar de Marca, registre o assembly que contém o componente de exibição usando a diretiva @addTagHelper. Se o componente de exibição estiver em um assembly chamado MyWebApp, adicione a seguinte diretiva ao arquivo _ViewImports.cshtml:

@addTagHelper *, MyWebApp

É possível registrar um componente de exibição como um Auxiliar de Marca a qualquer arquivo que referencie o componente de exibição. Consulte Gerenciando o escopo do Auxiliar de Marca para obter mais informações sobre como registrar Auxiliares de Marca.

O método InvokeAsync usado neste tutorial:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Na marcação do Auxiliar de Marca:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

Na amostra acima, o componente de exibição PriorityList torna-se priority-list. Os parâmetros para o componente de exibição são passados como atributos em kebab case.

Invocando um componente de exibição diretamente em um controlador

Os componentes de exibição normalmente são invocados em uma exibição, mas você pode invocá-los diretamente em um método do controlador. Embora os componentes de exibição não definam pontos de extremidade como controladores, você pode implementar com facilidade uma ação do controlador que retorna o conteúdo de um ViewComponentResult.

Neste exemplo, o componente de exibição é chamado diretamente no controlador:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Passo a passo: criando um componente de exibição simples

Baixe, compile e teste o código inicial. É um projeto simples com um controlador ToDo que exibe uma lista de itens ToDo.

List of ToDos

Adicionar uma classe ViewComponent

Crie uma pasta ViewComponents e adicione a seguinte classe PriorityListViewComponent:

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

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

Observações sobre o código:

  • As classes de componente de exibição podem ser contidas em qualquer pasta do projeto.

  • Como o nome da classe PriorityListViewComponent termina com o sufixo ViewComponent, o runtime utiliza a cadeia de caracteres PriorityList ao fazer referência ao componente da classe a partir de uma exibição.

  • O atributo [ViewComponent] pode alterar o nome usado para referenciar um componente de exibição. Por exemplo, a classe poderia ter sido nomeada XYZ com o atributo ViewComponent:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • O atributo [ViewComponent] no código anterior informa ao seletor do componente de exibição que você deve utilizar:

    • O nome PriorityList ao procurar as exibições associadas ao componente
    • A cadeia de caracteres "PriorityList" ao fazer referência ao componente de exibição da classe.
  • O componente usa a injeção de dependência para disponibilizar o contexto de dados.

  • InvokeAsync expõe um método que pode ser chamado em uma exibição e pode usar um número arbitrário de argumentos.

  • O método InvokeAsync retorna o conjunto de itens ToDo que atendem aos parâmetros isDone e maxPriority.

Criar o componente de exibição Razor

  • Crie a pasta Views/Shared/Components. Essa pasta deve ter o nome Components.

  • Crie a pasta Views/Shared/Components/PriorityList. Esse nome de pasta deve corresponder ao nome da classe do componente de exibição ou ao nome da classe menos o sufixo (se seguimos a convenção e usamos o sufixo ViewComponent no nome da classe). Se você usou o atributo ViewComponent, o nome da classe precisa corresponder à designação de atributo.

  • Crie uma exibição Views/Shared/Components/PriorityList/Default.cshtmlRazor:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    A exibição Razor pega uma lista de TodoItem e a exibe. Se o método InvokeAsync do componente de exibição não passar o nome da exibição (como em nossa amostra), Default será usado como o nome da exibição, por convenção. Mais adiante no tutorial, mostrarei como passar o nome da exibição. Para substituir o estilo padrão de um controlador específico, adicione uma exibição à pasta de exibição específica do controlador (por exemplo, Views/ToDo/Components/PriorityList/Default.cshtml).

    Se o componente de exibição for específico do controlador, você poderá adicioná-lo à pasta específica do controlador (Views/ToDo/Components/PriorityList/Default.cshtml).

  • Adicione um div contendo uma chamada para o componente da lista de prioridades na parte inferior do arquivo Views/ToDo/index.cshtml:

    </table>
    <div>
        @await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
    </div>
    

A marcação @await Component.InvokeAsync mostra a sintaxe para chamar componentes de exibição. O primeiro argumento é o nome do componente que queremos invocar ou chamar. Os parâmetros seguintes são passados para o componente. InvokeAsync pode usar um número arbitrário de argumentos.

Testar o aplicativo. A seguinte imagem mostra a lista ToDo e os itens de prioridade:

todo list and priority items

Também chame o componente de exibição diretamente no controlador:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

priority items from IndexVC action

Especificando um nome de exibição

Um componente de exibição complexo pode precisar especificar uma exibição não padrão em algumas condições. O código a seguir mostra como você pode especificar a exibição do "PVC" a partir do método InvokeAsync. Atualize o método InvokeAsync na classe PriorityListViewComponent.

public async Task<IViewComponentResult> InvokeAsync(
    int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Copie o arquivo Views/Shared/Components/PriorityList/Default.cshtml para uma exibição chamada Views/Shared/Components/PriorityList/PVC.cshtml. Adicione um cabeçalho para indicar que a exibição PVC está sendo usada.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Atualizar Views/ToDo/Index.cshtml:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Execute o aplicativo e verifique a exibição PVC.

Priority View Component

Se a exibição PVC não é renderizada, verifique se você está chamando o componente de exibição com uma prioridade igual a 4 ou superior.

Examinar o caminho de exibição

  • Altere o parâmetro de prioridade para três ou menos para que a exibição de prioridade não seja retornada.

  • Renomeie temporariamente o Views/ToDo/Components/PriorityList/Default.cshtml para 1Default.cshtml.

  • Teste o aplicativo. Você obterá o seguinte erro:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    EnsureSuccessful
    
  • Copie Views/ToDo/Components/PriorityList/1Default.cshtml para Views/Shared/Components/PriorityList/Default.cshtml.

  • Adicione uma marcação ao componente de exibição ToDo Shared para indicar que a exibição foi obtida da pasta Shared.

  • Teste o componente de exibição Shared.

ToDo output with Shared component view

Como evitar cadeias de caracteres codificadas

Se deseja obter segurança em tempo de compilação, substitua o nome do componente de exibição embutido em código pelo nome da classe. Crie o componente de exibição sem o sufixo "ViewComponent":

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

namespace ViewComponentSample.ViewComponents
{
    public class PriorityList : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityList(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

Adicione uma instrução using no seu arquivo de exibição Razor e utilize o operador nameof:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

    <h2>ToDo nameof</h2>
    <!-- Markup removed for brevity.  -->

    <div>

        @*
            Note: 
            To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it won't compile.
            By doing so it will cause a problem to index as there will be multiple viewcomponents 
            with the same name after the compiler removes the suffix "ViewComponent"
        *@

        @*@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })*@
    </div>

Você pode utilizar uma sobrecarga do método Component.InvokeAsync que recebe um tipo de CLR. Lembre-se de utilizar o operador typeof nesse caso:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo typeof</h2>
<!-- Markup removed for brevity.  -->

<div>
    @await Component.InvokeAsync(typeof(PriorityListViewComponent), new { maxPriority = 4, isDone = true })
</div>

Executar o trabalho síncrono

A estrutura manipulará a invocação de um método Invoke síncrono se não for necessário realizar o trabalho assíncrono. O método a seguir cria um componente de exibição Invoke síncrono:

public class PriorityList : ViewComponent
{
    public IViewComponentResult Invoke(int maxPriority, bool isDone)
    {
        var items = new List<string> { $"maxPriority: {maxPriority}", $"isDone: {isDone}" };
        return View(items);
    }
}

No arquivo Razor do componente de exibição, você lista as cadeias de caracteres passadas para o método Invoke (Views/Home/Components/PriorityList/Default.cshtml):

@model List<string>

<h3>Priority Items</h3>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>

O componente de exibição é invocado em um arquivo Razor (por exemplo, Views/Home/Index.cshtml) utilizando uma das seguintes abordagens:

Para usar a abordagem IViewComponentHelper, chame Component.InvokeAsync:

@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })

Para usar o Auxiliar de Marca, registre o assembly que contém o Componente de exibição que usa a diretiva @addTagHelper (o componente de exibição está em um assembly chamado MyWebApp):

@addTagHelper *, MyWebApp

Utilize o Auxiliar de Marcas do componente de exibição no arquivo de marcação Razor:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

A assinatura do método de PriorityList.Invoke é síncrona, mas Razor encontra e chama o método com Component.InvokeAsync no arquivo de marcação.

Todos os parâmetros do componente de exibição são obrigatórios

Todo parâmetro de um componente de exibição é um atributo obrigatório. Consulte este problema do GitHub. Se algum parâmetro for omitido:

  • A assinatura do método InvokeAsync não corresponderá, portanto, o método não será executado.
  • O ViewComponent não renderizará nenhuma marcação.
  • Nenhum erro será gerado.

Recursos adicionais