Inserción de dependencias en vistas de ASP.NET Core

ASP.NET Core admite la inserción de dependencias en vistas. Esto puede ser útil para servicios específicos de vistas, como la localización o los datos necesarios solamente para rellenar los elementos de vistas. La mayoría de los datos que muestran las vistas deben pasarse desde el controlador.

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

Inserción de configuración

Los valores de los archivos de configuración, como appsettings.json y appsettings.Development.json, se pueden insertar en una vista. Considere el appsettings.Development.json de la muestra de código:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "MyRoot": {
    "MyParent": {
      "MyChildName": "Joe"
    }
  }
}

La siguiente marca muestra el valor de configuración en Razor Pages:

@page
@model PrivacyModel
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
    ViewData["Title"] = "Privacy RP";
}
<h1>@ViewData["Title"]</h1>

<p>PR Privacy</p>

<h2>
   MyRoot:MyParent:MyChildName: @Configuration["MyRoot:MyParent:MyChildName"]
</h2>

El siguiente marcado muestra el valor de configuración en una vista MVC:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
    ViewData["Title"] = "Privacy MVC";
}
<h1>@ViewData["Title"]</h1>

<p>MVC Use this page to detail your site's privacy policy.</p>

<h2>
   MyRoot:MyParent:MyChildName: @Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Para más información, consulte Configuración en ASP.NET Core.

Inserción de un servicio

Un servicio puede insertarse en una vista mediante la directiva @inject.

@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
    <title>To Do Items</title>
</head>
<body>
    <div>
        <h1>To Do Items</h1>
        <ul>
            <li>Total Items: @StatsService.GetCount()</li>
            <li>Completed: @StatsService.GetCompletedCount()</li>
            <li>Avg. Priority: @StatsService.GetAveragePriority()</li>
        </ul>
        <table>
            <tr>
                <th>Name</th>
                <th>Priority</th>
                <th>Is Done?</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Priority</td>
                    <td>@item.IsDone</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

Esta vista muestra una lista de instancias ToDoItem, junto con un resumen de estadísticas generales. El resumen se rellena a partir de StatisticsService insertado. Este servicio está registrado para la inserción de dependencias en ConfigureServices en Program.cs:

using ViewInjectSample.Helpers;
using ViewInjectSample.Infrastructure;
using ViewInjectSample.Interfaces;
using ViewInjectSample.Model.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
builder.Services.AddTransient<StatisticsService>();
builder.Services.AddTransient<ProfileOptionsService>();
builder.Services.AddTransient<MyHtmlHelper>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();

app.MapDefaultControllerRoute();


app.Run();

StatisticsService realiza algunos cálculos en el conjunto de instancias de ToDoItem, al que se accede a través de un repositorio:

using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
    public class StatisticsService
    {
        private readonly IToDoItemRepository _toDoItemRepository;

        public StatisticsService(IToDoItemRepository toDoItemRepository)
        {
            _toDoItemRepository = toDoItemRepository;
        }

        public int GetCount()
        {
            return _toDoItemRepository.List().Count();
        }

        public int GetCompletedCount()
        {
            return _toDoItemRepository.List().Count(x => x.IsDone);
        }

        public double GetAveragePriority()
        {
            if (_toDoItemRepository.List().Count() == 0)
            {
                return 0.0;
            }

            return _toDoItemRepository.List().Average(x => x.Priority);
        }
    }
}

El repositorio de ejemplo usa una colección en memoria. No se debe usar una implementación en memoria para conjuntos de datos de gran tamaño a los que se accede de forma remota.

El ejemplo muestra los datos del modelo enlazado a la vista y del servicio que se inserta en la vista:

To Do view listing total items, completed items, average priority, and a list of tasks with their priority levels and boolean values indicating completion.

Rellenar datos de búsqueda

La inserción de vistas puede ser útil para rellenar opciones en elementos de interfaz de usuario, como por ejemplo, listas desplegables. Imagine un formulario de perfil de usuario que incluye opciones para especificar el sexo, el estado y otras preferencias. La representación de este tipo de formulario mediante un enfoque estándar podría requerir que el controlador o Razor Page:

  • Soliciten los servicios de acceso a los datos para cada uno de los conjuntos de opciones.
  • Rellenen un modelo o ViewBag con cada conjunto de opciones que se va a enlazar.

Un enfoque alternativo consiste en insertar servicios directamente en la vista para obtener las opciones. Esto reduce la cantidad de código necesario para el controlador o razor Page, ya que mueve esta lógica de construcción del elemento de vista a la propia vista. La acción del controlador o Razor Page para mostrar un formulario de edición de perfil solamente necesita pasar el formulario de la instancia de perfil:

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers;

public class ProfileController : Controller
{
    public IActionResult Index()
    {
        // A real app would up profile based on the user.
        var profile = new Profile()
        {
            Name = "Rick",
            FavColor = "Blue",
            Gender = "Male",
            State = new State("Ohio","OH")
        };
        return View(profile);
    }
}

El formulario HTML que se utilizó para actualizar las preferencias incluye listas desplegables para tres de las propiedades:

Update Profile view with a form allowing the entry of name, gender, state, and favorite Color.

Estas listas se rellenan mediante un servicio que se ha insertado en la vista:

@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
    <title>Update Profile</title>
</head>
<body>
<div>
    <h1>Update Profile</h1>
    Name: @Html.TextBoxFor(m => m.Name)
    <br/>
    Gender: @Html.DropDownList("Gender",
           Options.ListGenders().Select(g => 
                new SelectListItem() { Text = g, Value = g }))
    <br/>

    State: @Html.DropDownListFor(m => m.State!.Code,
           Options.ListStates().Select(s => 
                new SelectListItem() { Text = s.Name, Value = s.Code}))
    <br />

    Fav. Color: @Html.DropDownList("FavColor",
           Options.ListColors().Select(c => 
                new SelectListItem() { Text = c, Value = c }))
    </div>
</body>
</html>

ProfileOptionsService es un servicio de nivel de interfaz de usuario diseñado para proporcionar solo los datos necesarios para este formulario:

namespace ViewInjectSample.Model.Services;

public class ProfileOptionsService
{
    public List<string> ListGenders()
    {
        // Basic sample
        return new List<string>() {"Female", "Male"};
    }

    public List<State> ListStates()
    {
        // Add a few states
        return new List<State>()
        {
            new State("Alabama", "AL"),
            new State("Alaska", "AK"),
            new State("Ohio", "OH")
        };
    }

    public List<string> ListColors()
    {
        return new List<string>() { "Blue","Green","Red","Yellow" };
    }
}

Tenga en cuenta que un tipo no registrado produce una excepción en tiempo de ejecución porque el proveedor de servicios se consulta internamente a través de GetRequiredService.

Reemplazar servicios

Además de insertar nuevos servicios, esta técnica puede usarse para reemplazar servicios previamente insertados en una página. En la imagen de abajo se muestran todos los campos disponibles en la página usada en el primer ejemplo:

Intellisense contextual menu on a typed @ symbol listing Html, Component, StatsService, and Url fields

Los campos predeterminados incluyen Html, Component y Url. Para reemplazar los asistentes HTML predeterminados por una versión personalizada, use @inject:

@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
    <title>My Helper</title>
</head>
<body>
    <div>
        Test: @Html.Value
    </div>
</body>
</html>

Consulte también

ASP.NET Core admite la inserción de dependencias en vistas. Esto puede ser útil para servicios específicos de vistas, como la localización o los datos necesarios solamente para rellenar los elementos de vistas. Debe intentar mantener la separación de intereses entre los controladores y las vistas. La mayoría de los datos que muestran las vistas deben pasarse desde el controlador.

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

Inserción de configuración

Los valores appsettings.json se pueden insertar directamente en una vista.

Ejemplo de un archivo appsettings.json:

{
   "root": {
      "parent": {
         "child": "myvalue"
      }
   }
}

La sintaxis de @inject: @inject <type> <name>

Un ejemplo que usa @inject:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
   string myValue = Configuration["root:parent:child"];
   ...
}

Inserción de un servicio

Un servicio puede insertarse en una vista mediante la directiva @inject. Puede pensar que @inject es como si agregara una propiedad a la vista y rellenara la propiedad mediante DI.

@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
    <title>To Do Items</title>
</head>
<body>
    <div>
        <h1>To Do Items</h1>
        <ul>
            <li>Total Items: @StatsService.GetCount()</li>
            <li>Completed: @StatsService.GetCompletedCount()</li>
            <li>Avg. Priority: @StatsService.GetAveragePriority()</li>
        </ul>
        <table>
            <tr>
                <th>Name</th>
                <th>Priority</th>
                <th>Is Done?</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Priority</td>
                    <td>@item.IsDone</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

Esta vista muestra una lista de instancias ToDoItem, junto con un resumen de estadísticas generales. El resumen se rellena a partir de StatisticsService insertado. Este servicio está registrado para la inserción de dependencias en ConfigureServices en Startup.cs:

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

StatisticsService realiza algunos cálculos en el conjunto de instancias de ToDoItem, al que se accede a través de un repositorio:

using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
    public class StatisticsService
    {
        private readonly IToDoItemRepository _toDoItemRepository;

        public StatisticsService(IToDoItemRepository toDoItemRepository)
        {
            _toDoItemRepository = toDoItemRepository;
        }

        public int GetCount()
        {
            return _toDoItemRepository.List().Count();
        }

        public int GetCompletedCount()
        {
            return _toDoItemRepository.List().Count(x => x.IsDone);
        }

        public double GetAveragePriority()
        {
            if (_toDoItemRepository.List().Count() == 0)
            {
                return 0.0;
            }

            return _toDoItemRepository.List().Average(x => x.Priority);
        }
    }
}

El repositorio de ejemplo usa una colección en memoria. La implementación que se muestra arriba (que funciona en todos los datos en memoria) no se recomienda para conjuntos de datos grandes, con acceso de forma remota.

El ejemplo muestra los datos del modelo enlazado a la vista y del servicio que se inserta en la vista:

To Do view listing total items, completed items, average priority, and a list of tasks with their priority levels and boolean values indicating completion.

Rellenar datos de búsqueda

La inserción de vistas puede ser útil para rellenar opciones en elementos de interfaz de usuario, como por ejemplo, listas desplegables. Imagine un formulario de perfil de usuario que incluye opciones para especificar el sexo, el estado y otras preferencias. Para representar este tipo de formulario mediante un enfoque MVC estándar, necesitaría que el controlador solicitara servicios de acceso a datos para cada uno de estos conjuntos de opciones y, después, rellenar un modelo o ViewBag con cada conjunto de opciones para enlazar.

Un enfoque alternativo consiste en insertar servicios directamente en la vista para obtener las opciones. Esto reduce la cantidad de código necesario para el controlador, ya que mueve esta lógica de construcción del elemento de vista a la propia vista. La acción del controlador para mostrar un formulario de edición de perfil solamente necesita pasar el formulario de la instancia de perfil:

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers
{
    public class ProfileController : Controller
    {
        [Route("Profile")]
        public IActionResult Index()
        {
            // TODO: look up profile based on logged-in user
            var profile = new Profile()
            {
                Name = "Steve",
                FavColor = "Blue",
                Gender = "Male",
                State = new State("Ohio","OH")
            };
            return View(profile);
        }
    }
}

El formulario HTML que se utilizó para actualizar estas preferencias incluye listas desplegables para tres de las propiedades:

Update Profile view with a form allowing the entry of name, gender, state, and favorite Color.

Estas listas se rellenan mediante un servicio que se ha insertado en la vista:

@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
    <title>Update Profile</title>
</head>
<body>
<div>
    <h1>Update Profile</h1>
    Name: @Html.TextBoxFor(m => m.Name)
    <br/>
    Gender: @Html.DropDownList("Gender",
           Options.ListGenders().Select(g => 
                new SelectListItem() { Text = g, Value = g }))
    <br/>

    State: @Html.DropDownListFor(m => m.State.Code,
           Options.ListStates().Select(s => 
                new SelectListItem() { Text = s.Name, Value = s.Code}))
    <br />

    Fav. Color: @Html.DropDownList("FavColor",
           Options.ListColors().Select(c => 
                new SelectListItem() { Text = c, Value = c }))
    </div>
</body>
</html>

ProfileOptionsService es un servicio de nivel de interfaz de usuario diseñado para proporcionar solo los datos necesarios para este formulario:

using System.Collections.Generic;

namespace ViewInjectSample.Model.Services
{
    public class ProfileOptionsService
    {
        public List<string> ListGenders()
        {
            // keeping this simple
            return new List<string>() {"Female", "Male"};
        }

        public List<State> ListStates()
        {
            // a few states from USA
            return new List<State>()
            {
                new State("Alabama", "AL"),
                new State("Alaska", "AK"),
                new State("Ohio", "OH")
            };
        }

        public List<string> ListColors()
        {
            return new List<string>() { "Blue","Green","Red","Yellow" };
        }
    }
}

Importante

No olvide registrar los tipos que solicite a través de la inserción de dependencias en Startup.ConfigureServices. Tenga en cuenta que un tipo no registrado produce una excepción en tiempo de ejecución porque el proveedor de servicios se consulta internamente a través de GetRequiredService.

Reemplazar servicios

Además de insertar nuevos servicios, esta técnica también puede usarse para reemplazar servicios previamente insertados en una página. En la imagen de abajo se muestran todos los campos disponibles en la página usada en el primer ejemplo:

Intellisense contextual menu on a typed @ symbol listing Html, Component, StatsService, and Url fields

Como puede ver, los campos predeterminados incluyen Html, Component y Url (además de StatsService que hemos insertado). Si, por ejemplo, quisiera reemplazar los asistentes de HTML predeterminadas con las suyas propias, puede hacerlo fácilmente mediante @inject:

@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
    <title>My Helper</title>
</head>
<body>
    <div>
        Test: @Html.Value
    </div>
</body>
</html>

Si quiere ampliar los servicios existentes, simplemente puede usar esta técnica al heredar o encapsular la implementación existente con la suya propia.

Consulte también