Componentes de vista en ASP.NET Core

Por Rick Anderson

Vea o descargue el código de ejemplo (cómo descargarlo)

Componentes de vista

Los componentes de vista son similares a las vistas parciales, pero mucho más eficaces. Los componentes de vista no usan el enlace de modelos y solo dependen de los datos proporcionados cuando se les llama. Este artículo se ha escrito con controladores y vistas, pero los componentes de vista también funcionan con Razor Pages.

Un componente de vista:

  • Representa un fragmento en lugar de una respuesta completa.
  • Incluye las mismas ventajas de separación de conceptos y capacidad de prueba que se encuentran entre un controlador y una vista.
  • Puede tener parámetros y lógica de negocios.
  • Normalmente se invoca desde una página de diseño.

Los componentes de vista están diseñados para cualquier lugar que tenga lógica de representación reutilizable demasiado compleja para una vista parcial, como:

  • Menús de navegación dinámica
  • Nube de etiquetas (donde consulta la base de datos)
  • Panel de inicio de sesión
  • Carro de la compra
  • Artículos publicados recientemente
  • Contenido de la barra lateral de un blog típico
  • Un panel de inicio de sesión que se representa en cada página y muestra los vínculos para iniciar o cerrar sesión, según el estado del usuario

Un componente de vista consta de dos partes: la clase (normalmente derivada de ViewComponent) y el resultado que devuelve (por lo general, una vista). Al igual que los controladores, un componente de vista puede ser un POCO, pero la mayoría de los desarrolladores prefieren aprovechar las ventajas que ofrecen los métodos y las propiedades disponibles al derivar de ViewComponent.

Al considerar si los componentes de vista cumplen las especificaciones de una aplicación, considere la posibilidad de Razor usar componentes en su lugar. Razor Los componentes de también combinan marcado con código de C# para generar unidades de interfaz de usuario reutilizables. Razor Los componentes están diseñados para la productividad del desarrollador al proporcionar lógica y composición de la interfaz de usuario del lado cliente. Para más información, consulte Componentes de Razor de ASP.NET Core. Para obtener información sobre cómo incorporar Razor componentes en una aplicación MVC o Razor Pages, vea Integración y representación previa de componentes Razor de ASP.NET Core .

Crear un componente de vista

Esta sección contiene los requisitos de alto nivel para crear un componente de vista. Más adelante en el artículo examinaremos cada paso en detalle y crearemos un componente de vista.

La clase de componente de vista

Es posible crear una clase de componente de vista mediante una de las siguientes acciones:

  • Derivar de ViewComponent
  • Decorar una clase con el atributo [ViewComponent] o derivar de una clase con el atributo [ViewComponent]
  • Crear una clase cuyo nombre termina con el sufijo ViewComponent

Al igual que los controladores, los componentes de vista deben ser clases públicas, no anidadas y no abstractas. El nombre del componente de vista es el nombre de la clase sin el sufijo "ViewComponent". También se puede especificar explícitamente mediante la propiedad ViewComponentAttribute.Name.

Una clase de componente de vista:

  • Es totalmente compatible con la inserción de dependencias de constructor.

  • No participa en el ciclo de vida del controlador, lo que significa que no se pueden usar filtros en un componente de vista.

Métodos de componente de vista

Un componente de vista define su lógica en un método InvokeAsync que devuelve un elemento Task<IViewComponentResult> o en un método Invoke sincrónico que devuelve un elemento IViewComponentResult. Los parámetros proceden directamente de la invocación del componente de vista, no del enlace de modelos. Un componente de vista nunca controla directamente una solicitud. Por lo general, un componente de vista inicializa un modelo y lo pasa a una vista mediante una llamada al método View. En resumen, los métodos de componente de vista:

  • Defina un método InvokeAsync que devuelva un elemento Task<IViewComponentResult> o un método Invoke sincrónico que devuelva un elemento IViewComponentResult.
  • Normalmente inicializa un modelo y lo pasa a una vista mediante una llamada al ViewComponent View método .
  • Los parámetros provienen del método que realiza la llamada y no de HTTP. No hay ningún enlace de modelos.
  • No son accesibles directamente como punto de conexión HTTP. Se invocan desde el código (normalmente en una vista). Un componente de vista nunca controla una solicitud.
  • Se sobrecargan en la firma, en lugar de los detalles de la solicitud HTTP actual.

Ruta de búsqueda de la vista

El tiempo de ejecución busca la vista en las rutas de acceso siguientes:

  • /Views/{Controller Name}/Components/{Nombre de componente de vista}/{Nombre de vista}
  • /Views/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}
  • /Pages/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}

La ruta de búsqueda se aplica a los proyectos que usan controladores y vistas y Razor Pages.

El nombre de vista predeterminado para un componente de vista es Default, lo que significa que el archivo de vista normalmente se denominará Default.cshtml. Puede especificar un nombre de vista diferente al crear el resultado del componente de vista o al llamar al método View.

Se recomienda que asigne el nombre Default.cshtml al archivo de vista y use la ruta de acceso Views/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}. El componente de vista PriorityList usado en este ejemplo usa Views/Shared/Components/PriorityList/Default.cshtml para la vista del componente de vista.

Personalización de la ruta de acceso de la búsqueda de la vista

Para personalizar la ruta de acceso de búsqueda de la vista, modifique Razor la ViewLocationFormats colección de . Por ejemplo, para buscar vistas dentro de la ruta de acceso "/Components/{Nombre del componente de la vista}/{Nombre de la vista}", agregue un elemento nuevo a la colección:

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

En el código anterior, el marcador de posición "{0}" representa la ruta de acceso "Components/{Nombre del componente de la vista}/{Nombre de la vista}".

Invocar un componente de vista

Para usar el componente de vista, llame a lo siguiente dentro de una vista:

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

Los parámetros se pasarán al método InvokeAsync. El componente de vista desarrollado en el artículo se invoca desde el archivo de vista PriorityList Views/ToDo/Index.cshtml. En la tabla siguiente, se llama al método InvokeAsync con dos parámetros:

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

Invocación de un componente de vista como un asistente de etiquetas

Para ASP.NET Core 1.1 y versiones posteriores, puede invocar un componente de vista como un asistente de etiquetas:

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

Los parámetros de clase y método con grafía Pascal para los asistentes de etiquetas se convierten a su grafía kebab. El asistente de etiquetas que va a invocar un componente de vista usa el elemento <vc></vc>. El componente de vista se especifica de la manera siguiente:

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

Para usar un componente de vista como un asistente de etiquetas, registre el ensamblado que contiene el componente de vista mediante la directiva @addTagHelper. Si el componente de vista está en un ensamblado denominado MyWebApp, agregue la directiva siguiente al archivo _ViewImports.cshtml:

@addTagHelper *, MyWebApp

Puede registrar un componente de vista como un asistente de etiquetas en cualquier archivo que haga referencia al componente de vista. Vea Administración del ámbito de los asistentes de etiquetas para más información sobre cómo registrar asistentes de etiquetas.

El método InvokeAsync usado en este tutorial:

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

En el marcado del asistente de etiquetas:

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

En el ejemplo anterior, el componente de vista PriorityList se convierte en priority-list. Los parámetros para el componente de vista se pasan como atributos en grafía kebab.

Invocar un componente de vista directamente desde un controlador

Los componentes de vista suelen invocarse desde una vista, pero se pueden invocar directamente desde un método de controlador. Aunque los componentes de vista no definen puntos de conexión como controladores, se puede implementar fácilmente una acción del controlador que devuelva el contenido de ViewComponentResult.

En este ejemplo, se llama al componente de vista directamente desde el controlador:

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

Tutorial: Creación de un componente de vista simple

Descargue, compile y pruebe el código de inicio. Se trata de un proyecto simple con un controlador ToDo que muestra una lista de tareas pendientes.

Lista de tareas pendientes

Agregar una clase ViewComponent

Cree una carpeta ViewComponents y agregue la siguiente clase 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();
        }
    }
}

Notas sobre el código:

  • Las clases de componentes de vista pueden estar contenidas en cualquier carpeta del proyecto.

  • Dado que el nombre de clase PriorityList ViewComponent acaba con el sufijo ViewComponent, el tiempo de ejecución usará la cadena "PriorityList" cuando haga referencia al componente de clase desde una vista. Más adelante explicaremos esta cuestión con más detalle.

  • El atributo [ViewComponent] puede cambiar el nombre usado para hacer referencia a un componente de vista. Por ejemplo, podríamos haber asignado a la clase el nombre XYZ y aplicado el atributo ViewComponent:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • El atributo [ViewComponent] anterior le indica al selector de componentes de vista que use el nombre PriorityList al buscar las vistas asociadas al componente y que use la cadena "PriorityList" al hacer referencia al componente de clase desde una vista. Más adelante explicaremos esta cuestión con más detalle.

  • El componente usa la inserción de dependencias para que el contexto de datos esté disponible.

  • InvokeAsync expone un método al que se puede llamar desde una vista y puede tomar un número arbitrario de argumentos.

  • El método InvokeAsync devuelve el conjunto de elementos ToDo que cumplen los parámetros isDone y maxPriority.

Creación de la vista de Razor componentes

  • Cree la carpeta Views/Shared/Components. Esta carpeta debe denominarse Components.

  • Cree la carpeta Views/Shared/Components/PriorityList. El nombre de esta carpeta debe coincidir con el nombre de la clase de componente de vista o con el nombre de la clase sin el sufijo (si se ha seguido la convención y se ha usado el sufijo ViewComponent en el nombre de clase). Si ha usado el atributo ViewComponent, el nombre de clase debe coincidir con la designación del atributo.

  • Cree una vista Views/Shared/Components/PriorityList/Default.cshtml: Razor

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

    La Razor vista toma una lista de y las TodoItem muestra. Si el método InvokeAsync del componente de vista no pasa el nombre de la vista (como en nuestro ejemplo), se usa Default para el nombre de vista, según la convención. Más adelante en el tutorial veremos cómo pasar el nombre de la vista. Para reemplazar el estilo predeterminado de un controlador concreto, agregue una vista a la carpeta de vistas específicas del controlador (por ejemplo, Views/ToDo/Components/PriorityList/Default.cshtml).

    Si el componente de vista es específico del controlador, puede agregarlo a la carpeta específica del controlador (Views/ToDo/Components/PriorityList/Default.cshtml).

  • Agregue un elemento div que contenga una llamada al componente de lista de prioridad en la parte inferior del archivo Views/ToDo/index.cshtml:

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

El marcado @await Component.InvokeAsync muestra la sintaxis para llamar a los componentes de vista. El primer argumento es el nombre del componente que se quiere invocar o llamar. Los parámetros siguientes se pasan al componente. InvokeAsync puede tomar un número arbitrario de argumentos.

Pruebe la aplicación. En la imagen siguiente se muestra la lista de tareas pendientes y los elementos de prioridad:

lista de tareas pendientes y elementos de prioridad

También puede llamar al componente de vista directamente desde el controlador:

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

elementos de prioridad de la acción IndexVC

Especificar un nombre de vista

En algunos casos, puede ser necesario que un componente de vista complejo especifique una vista no predeterminada. En el código siguiente se muestra cómo especificar la vista "PVC" desde el método InvokeAsync. Actualice el método InvokeAsync en la clase 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 el archivo Views/Shared/Components/PriorityList/Default.cshtml en una vista denominada Views/Shared/Components/PriorityList/PVC.cshtml. Agregue un encabezado para indicar que se está usando la vista PVC.

@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>

Actualice Views/ToDo/Index.cshtml:

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

Ejecute la aplicación y compruebe la vista PVC.

Componente de vista de prioridad

Si la vista PVC no se representa, compruebe que está llamando al componente de vista con prioridad cuatro o superior.

Examinar la ruta de acceso de la vista

  • Cambie el parámetro de prioridad a tres o menos para que no se devuelva la vista de prioridad.

  • Cambie temporalmente el nombre de Views/ToDo/Components/PriorityList/Default.cshtml a 1Default.cshtml.

  • Pruebe la aplicación. Obtendrá el siguiente error:

    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 en Views/Shared/Components/PriorityList/Default.cshtml.

  • Agregue algún marcado a la vista del componente de vista ToDo compartida para indicar que la vista es de la carpeta Compartido.

  • Pruebe la vista de componentes Shared.

Salida de la lista de tareas pendientes con la vista de componentes Shared

Evitar cadenas codificadas de forma rígida

Si busca seguridad en tiempo de compilación, puede reemplazar el nombre del componente de vista codificado de forma rígida por el nombre de clase. Cree el componente de vista sin el sufijo "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();
        }
    }
}

Agregue una using instrucción al archivo de vista y use el operador Razor 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>

Puede usar una sobrecarga de Component.InvokeAsync método que toma un tipo CLR. Recuerde usar el typeof operador en este 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>

Realizar el trabajo sincrónico

El marco controla la invocación de un método Invoke sincrónico si no necesita realizar un trabajo asincrónico. El método siguiente crea un componente de vista Invoke sincrónico:

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

El archivo del componente de vista enumera las cadenas que se pasan al método Razor Invoke (Views/ Home /Components/PriorityList/Default.cshtml):

@model List<string>

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

El componente de vista se invoca en un archivo Razor (por ejemplo, Views/ Home /Index.cshtml) mediante uno de los enfoques siguientes:

Para usar el método IViewComponentHelper, llame a Component.InvokeAsync:

El componente de vista se invoca en Razor un archivo (por ejemplo, Views/ Home /Index.cshtml) con IViewComponentHelper .

Llame a Component.InvokeAsync:

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

Para usar el asistente de etiquetas, registre el ensamblado que contiene el componente de vista con el uso de la directiva @addTagHelper (el componente de vista se encuentra en un ensamblado denominado MyWebApp):

@addTagHelper *, MyWebApp

Use el asistente de etiquetas del componente de vista en el Razor archivo de marcado:

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

La firma del método de PriorityList.Invoke es sincrónica, Razor pero busca y llama al método con en el archivo de Component.InvokeAsync marcado.

Todos los parámetros de componente de vista son obligatorios

Cada parámetro de un componente de vista es un atributo obligatorio. Consulte este problema de GitHub. Si se omite algún parámetro:

  • La firma del método InvokeAsync no coincidirá, por lo que el método no se ejecutará.
  • ViewComponent no representará ningún marcado.
  • No se producirá ningún error.

Recursos adicionales