Componentes de vista en ASP.NET Core

Por Rick Anderson

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, dependen de los datos pasados al llamar al componente de vista. Este artículo se escribió usando 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
  • 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, que normalmente se deriva de ViewComponent
  • El resultado que devuelve, normalmente una vista.

Al igual que los controladores, un componente de vista puede ser un POCO, pero la mayoría de los desarrolladores aprovechan las ventajas que ofrecen los métodos y las propiedades disponibles al derivar de ViewComponent.

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

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:

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 Name.

Una clase de componente de vista:

  • Es compatible con la inserción de dependencias de constructor.
  • No participa en el ciclo de vida del controlador, por lo que los filtros no se pueden usar en un componente de vista.

Para evitar que una clase que tenga un sufijo que no distingue mayúsculas de minúsculas ViewComponent se trate como un componente de vista, decora la clase con el atributo [NonViewComponent]:

using Microsoft.AspNetCore.Mvc;

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

Métodos de componente de vista

Un componente de vista define su lógica en un:

  • Método InvokeAsync que devuelve Task<IViewComponentResult>.
  • Método asincrónico Invoke que devuelve un 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.
  • Por lo general, inicializa un modelo y lo pasa a una vista mediante una llamada al método ViewComponent.View.
  • 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. Normalmente se invocan 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}
  • /Areas/{Nombre de área}/Views/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}

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

El nombre de vista predeterminado para un componente de vista es Default, lo que significa que los archivos de vista normalmente se denominarán Default.cshtml. Se 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 que se utiliza en esta muestra utiliza 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 la búsqueda de la vista, modifique la colección ViewLocationFormats de Razor. Por ejemplo, para buscar vistas dentro de la ruta de acceso /Components/{View Component Name}/{View Name}, agregue un elemento nuevo a la colección:

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.

En el código anterior, el marcador de posición {0} representa la ruta de acceso Components/{View Component Name}/{View Name}.

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 han pasado al método InvokeAsync. El componente de vista PriorityList desarrollado en el artículo se invoca desde el archivo de vista Views/ToDo/Index.cshtml. En el código siguiente, se llama al método InvokeAsync con dos 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 un componente de vista como un asistente de etiquetas

Se puede invocar un componente de vista como asistente de etiquetas:

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

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 =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

En el marcado 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 una acción del controlador que devuelva el contenido de ViewComponentResult.

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

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

Creación de un componente de vista básico

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

List of ToDos

Actualización del controlador para pasar el estado de prioridad y finalización

Actualice el método Index para usar los parámetros de estado de prioridad y finalización:

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);
    }

Agregar una clase ViewComponent

Agregar una clase 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();
    }
}

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 PriorityListViewComponent acaba con el sufijo ViewComponent, el tiempo de ejecución usará la cadena PriorityList cuando haga referencia al componente de clase desde una vista.

  • El atributo [ViewComponent] puede cambiar el nombre usado para hacer referencia a un componente de vista. Por ejemplo, la clase podría haberse denominado XYZ con el atributo [ViewComponent] siguiente:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • El atributo [ViewComponent] del código anterior indica al selector de componentes de vista que use:

    • El nombre PriorityList al buscar las vistas asociadas al componente.
    • La cadena "PriorityList" al hacer referencia al componente de clase desde una vista.
  • 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.

Crear la vista de Razor del componente de vista

  • 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 del componente de vista o el nombre de la clase menos el sufijo. Si se ha usado el atributo ViewComponent, el nombre de clase debe coincidir con la designación del atributo.

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

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

    La vista de Razor toma una lista de TodoItem y muestra estos elementos. Si el método InvokeAsync del componente de vista no pasa el nombre de la vista, se usa Default para el nombre de vista, según la convención. 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, se puede agregar a la carpeta específica del controlador. Por ejemplo, Views/ToDo/Components/PriorityList/Default.cshtml es específico del controlador.

  • 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>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </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.

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

todo list and priority items

Se puede llamar al componente de vista directamente desde el controlador:

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

priority items from IndexVC action

Especificar un nombre de componente 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>

Ejecute la aplicación y compruebe la vista PVC.

Priority View Component

Si la vista PVC no se representa, compruebe que se 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, se produce 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
    
  • Copie Views/ToDo/Components/PriorityList/1Default.cshtml en Views/Shared/Components/PriorityList/Default.cshtml.

  • Agregue algún marcado a la vista de componentes de vista de la lista de tareas pendientes Shared para indicar que la vista está en la carpeta Shared.

  • Pruebe la vista de componentes Shared.

ToDo output with Shared component view

Evitar cadenas codificadas de forma rígida

Para lograr seguridad en tiempo de compilación, reemplace el nombre del componente de vista codificado de forma rígida por el nombre de clase. Actualice el archivo PriorityListViewComponent.cs para no usar el sufijo "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();
    }
}

Archivo de vista:

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

Una sobrecarga de método Component.InvokeAsync que toma un tipo CLR usa el 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>

Realizar el trabajo sincrónico

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

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);
        }
    }
}

El archivo Razor del componente de vista:

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

Se invoca el componente de vista en un archivo de Razor (por ejemplo, Views/Home/Index.cshtml) con uno de los siguientes métodos:

Para usar el método 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 archivo de marcado de Razor:

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

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

Recursos adicionales

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, dependen de los datos pasados al llamar al componente de vista. Este artículo se escribió usando 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
  • 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, que normalmente se deriva de ViewComponent
  • El resultado que devuelve, normalmente una vista.

Al igual que los controladores, un componente de vista puede ser un POCO, pero la mayoría de los desarrolladores aprovechan las ventajas que ofrecen los métodos y las propiedades disponibles al derivar de ViewComponent.

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

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:

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 Name.

Una clase de componente de vista:

  • Es compatible con la inserción de dependencias de constructor.
  • No participa en el ciclo de vida del controlador, por lo que los filtros no se pueden usar en un componente de vista.

Para evitar que una clase que tenga un sufijo que no distingue mayúsculas de minúsculas ViewComponent se trate como un componente de vista, decora la clase con el atributo [NonViewComponent]:

using Microsoft.AspNetCore.Mvc;

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

Métodos de componente de vista

Un componente de vista define su lógica en un:

  • Método InvokeAsync que devuelve Task<IViewComponentResult>.
  • Método asincrónico Invoke que devuelve un 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.
  • Por lo general, inicializa un modelo y lo pasa a una vista mediante una llamada al método ViewComponent.View.
  • 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. Normalmente se invocan 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}
  • /Areas/{Nombre de área}/Views/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}

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

El nombre de vista predeterminado para un componente de vista es Default, lo que significa que los archivos de vista normalmente se denominarán Default.cshtml. Se 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 que se utiliza en esta muestra utiliza 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 la búsqueda de la vista, modifique la colección ViewLocationFormats de Razor. Por ejemplo, para buscar vistas dentro de la ruta de acceso /Components/{View Component Name}/{View Name}, agregue un elemento nuevo a la colección:

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.

En el código anterior, el marcador de posición {0} representa la ruta de acceso Components/{View Component Name}/{View Name}.

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 han pasado al método InvokeAsync. El componente de vista PriorityList desarrollado en el artículo se invoca desde el archivo de vista Views/ToDo/Index.cshtml. En el código siguiente, se llama al método InvokeAsync con dos 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 un componente de vista como un asistente de etiquetas

Se puede invocar un componente de vista como asistente de etiquetas:

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

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 =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

En el marcado 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 una acción del controlador que devuelva el contenido de ViewComponentResult.

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

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

Creación de un componente de vista básico

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

List of ToDos

Actualización del controlador para pasar el estado de prioridad y finalización

Actualice el método Index para usar los parámetros de estado de prioridad y finalización:

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);
    }

Agregar una clase ViewComponent

Agregar una clase 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();
    }
}

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 PriorityListViewComponent acaba con el sufijo ViewComponent, el tiempo de ejecución usará la cadena PriorityList cuando haga referencia al componente de clase desde una vista.

  • El atributo [ViewComponent] puede cambiar el nombre usado para hacer referencia a un componente de vista. Por ejemplo, la clase podría haberse denominado XYZ con el atributo [ViewComponent] siguiente:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • El atributo [ViewComponent] del código anterior indica al selector de componentes de vista que use:

    • El nombre PriorityList al buscar las vistas asociadas al componente.
    • La cadena "PriorityList" al hacer referencia al componente de clase desde una vista.
  • 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.

Crear la vista de Razor del componente de vista

  • 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 del componente de vista o el nombre de la clase menos el sufijo. Si se ha usado el atributo ViewComponent, el nombre de clase debe coincidir con la designación del atributo.

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

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

    La vista de Razor toma una lista de TodoItem y muestra estos elementos. Si el método InvokeAsync del componente de vista no pasa el nombre de la vista, se usa Default para el nombre de vista, según la convención. 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, se puede agregar a la carpeta específica del controlador. Por ejemplo, Views/ToDo/Components/PriorityList/Default.cshtml es específico del controlador.

  • 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>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </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.

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

todo list and priority items

Se puede llamar al componente de vista directamente desde el controlador:

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

priority items from IndexVC action

Especificar un nombre de componente 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>

Ejecute la aplicación y compruebe la vista PVC.

Priority View Component

Si la vista PVC no se representa, compruebe que se 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, se produce 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
    
  • Copie Views/ToDo/Components/PriorityList/1Default.cshtml en Views/Shared/Components/PriorityList/Default.cshtml.

  • Agregue algún marcado a la vista de componentes de vista de la lista de tareas pendientes Shared para indicar que la vista está en la carpeta Shared.

  • Pruebe la vista de componentes Shared.

ToDo output with Shared component view

Evitar cadenas codificadas de forma rígida

Para lograr seguridad en tiempo de compilación, reemplace el nombre del componente de vista codificado de forma rígida por el nombre de clase. Actualice el archivo PriorityListViewComponent.cs para no usar el sufijo "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();
    }
}

Archivo de vista:

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

Una sobrecarga de método Component.InvokeAsync que toma un tipo CLR usa el 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>

Realizar el trabajo sincrónico

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

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);
        }
    }
}

El archivo Razor del componente de vista:

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

Se invoca el componente de vista en un archivo de Razor (por ejemplo, Views/Home/Index.cshtml) con uno de los siguientes métodos:

Para usar el método 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 archivo de marcado de Razor:

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

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

Recursos adicionales

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 escribió usando 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 estudiar si los componentes de vista cumplen las especificaciones de una aplicación, considere la posibilidad de usar los componentes de Razor en su lugar. Los componentes de Razor también combinan el marcado con código de C# para producir unidades de interfaz de usuario reutilizables. Los componentes de Razor están diseñados para ofrecer productividad a los desarrolladores mediante elementos de composición y lógica de interfaz de usuario del lado cliente. Para más información, vea Componentes Razor de ASP.NET Core. Para obtener información sobre cómo incorporar componentes Razor en una aplicación MVC o Razor Pages, consulte Integración y representación previa de componentes de ASP.NET Core Razor.

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.

Para detener una clase que tenga un sufijo ViewComponent que no distingue mayúsculas de minúsculas se trate como un componente de vista, decora la clase con el atributo [NonViewComponent]:

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

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.
  • Por lo general, inicializa un modelo y lo pasa a una vista mediante una llamada al método ViewComponentView.
  • 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}
  • /Areas/{Nombre de área}/Views/Shared/Components/{Nombre de componente de vista}/{Nombre de vista}

La ruta de búsqueda se aplica a los proyectos que utilizan 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 que se utiliza en esta muestra utiliza 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 la búsqueda de la vista, modifique la colección ViewLocationFormats de Razor. 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 PriorityList desarrollado en el artículo se invoca desde el archivo de vista 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.

List of ToDos

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 PriorityListViewComponent acaba con el sufijo ViewComponent, el tiempo de ejecución usará la cadena PriorityList cuando haga referencia al componente de clase desde una vista.

  • El atributo [ViewComponent] puede cambiar el nombre usado para hacer referencia a un componente de vista. Por ejemplo, la clase podría haberse denominado XYZ con el atributo ViewComponent:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • El atributo [ViewComponent] del código anterior indica al selector de componentes de vista que use:

    • El nombre PriorityList al buscar las vistas asociadas al componente.
    • La cadena "PriorityList" al hacer referencia al componente de clase desde una vista.
  • 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.

Crear la vista de Razor del componente de vista

  • 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.

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

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

    La vista de Razor toma una lista de TodoItem y muestra estos elementos. 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, se puede agregar 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.

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

todo list and priority items

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

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

priority items from IndexVC action

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.

Priority View Component

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 de componentes de vista de la lista de tareas pendientes Shared para indicar que la vista está en la carpeta Shared.

  • Pruebe la vista de componentes Shared.

ToDo output with Shared component view

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 instrucción using a su archivo de vista de Razor y use el 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>

Puede usar una sobrecarga del método Component.InvokeAsync que toma un tipo CLR. No olvide usar el operador typeof 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 Razor del componente de vista enumera las cadenas pasadas al 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>

Se invoca el componente de vista en un archivo de Razor (por ejemplo, Views/Home/Index.cshtml) con uno de los siguientes métodos:

Para usar el método 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 archivo de marcado de Razor:

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

La firma del método de PriorityList.Invoke es sincrónica, pero Razor busca y llama al método con Component.InvokeAsync en el archivo de 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