Trabajar con el modelo de aplicación en ASP.NET Core

Por Steve Smith

ASP.NET Core MVC define un modelo de aplicación que representa los componentes de una aplicación MVC. Lea y manipule este modelo para modificar el comportamiento de los elementos MVC. De forma predeterminada, MVC sigue determinadas convenciones para determinar qué clases se consideran controladores, qué métodos de esas clases son acciones y cómo se comportan los parámetros y el enrutamiento. Personalice este comportamiento para satisfacer las necesidades de una aplicación mediante la creación de convenciones personalizadas y su aplicación global o como atributos.

Modelos y proveedores ( IApplicationModelProvider )

El ASP.NET Core de aplicación MVC incluye interfaces abstractas y clases de implementación concretas que describen una aplicación MVC. Este modelo es el resultado de la detección por parte de MVC de los controladores, las acciones, los parámetros de acción, las rutas y los filtros de la aplicación de acuerdo con las convenciones predeterminadas. Al trabajar con el modelo de aplicación, modifique una aplicación para que siga convenciones diferentes del comportamiento predeterminado de MVC. Los parámetros, nombres, rutas y filtros se usan como datos de configuración para las acciones y los controladores.

El modelo de aplicación de ASP.NET Core MVC tiene la estructura siguiente:

  • ApplicationModel
    • Controladores (ControllerModel)
      • Acciones (ActionModel)
        • Parámetros (ParameterModel)

Cada nivel del modelo tiene acceso a una colección Properties común, y los niveles inferiores pueden tener acceso a los valores de propiedad establecidos por los niveles superiores de la jerarquía y sobrescribirlos. Las propiedades se conservan en ActionDescriptor.Properties cuando se crean las acciones. Después, cuando se controla una solicitud, se puede obtener acceso a través de ActionContext.ActionDescriptor a todas las propiedades que agregue o modifique una convención. El uso de propiedades es una excelente manera de configurar filtros, enlazadores de modelos y otros aspectos del modelo de aplicación por acción.

Nota

La ActionDescriptor.Properties colección no es segura para subprocesos (para escrituras) después del inicio de la aplicación. Las convenciones son la mejor manera de agregar datos de forma segura a esta colección.

ASP.NET Core MVC carga el modelo de aplicación mediante un patrón de proveedor, definido por la IApplicationModelProvider interfaz . En esta sección se describen algunos detalles de implementación interna relacionados con el funcionamiento de este proveedor. El uso del patrón de proveedor es un asunto avanzado, principalmente para el uso del marco. La mayoría de las aplicaciones deben usar convenciones, no el patrón de proveedor.

Las implementaciones de la interfaz se "encapsulan" entre sí, donde cada implementación IApplicationModelProvider llama en orden ascendente en función de su propiedad OnProvidersExecuting Order . Después, se llama al método OnProvidersExecuted en orden inverso. El marco de trabajo define varios proveedores:

Primero, (Order=-1000):

  • DefaultApplicationModelProvider

Después, (Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

Nota

El orden en el que se llama a dos proveedores con el mismo valor para es indefinido y no se debe Order confiar en él.

Nota

IApplicationModelProvider es un concepto avanzado pensado para que los autores del marco de trabajo lo extiendan. En general, las aplicaciones deben usar convenciones y los marcos deben usar proveedores. La diferencia clave es que los proveedores siempre se ejecutan antes que las convenciones.

DefaultApplicationModelProvider establece muchos de los comportamientos predeterminados que usa ASP.NET Core MVC. Entre sus responsabilidades se incluyen las siguientes:

  • Agregar filtros globales al contexto
  • Agregar controladores al contexto
  • Agregar métodos de controlador públicos como acciones
  • Agregar parámetros de métodos de acción al contexto
  • Aplicar la ruta y otros atributos

Algunos comportamientos integrados se implementan mediante DefaultApplicationModelProvider. Este proveedor es responsable de construir , que a su ControllerModel vez hace referencia a las instancias , y ActionModel PropertyModel ParameterModel . La DefaultApplicationModelProvider clase es un detalle de implementación de marco interno que puede cambiar en el futuro.

AuthorizationApplicationModelProvider se encarga de aplicar el comportamiento asociado a los atributos AuthorizeFilter y AllowAnonymousFilter. Para obtener más información, vea Autorización simple en ASP.NET Core.

implementa CorsApplicationModelProvider el comportamiento asociado a y IEnableCorsAttribute IDisableCorsAttribute . Para obtener más información, vea Habilitar solicitudes entre orígenes (CORS) en ASP.NET Core.

La información sobre los proveedores internos del marco que se describe en esta sección no está disponible a través del explorador de API de .NET. Sin embargo, los proveedores se pueden inspeccionar en ASP.NET Core origen de referencia (dotnet/aspnetcore GitHub repositorio). Use GitHub buscar los proveedores por nombre y seleccione la versión del origen con la lista desplegable Switch branches/tags (Cambiar ramas o etiquetas).

Convenciones

El modelo de aplicación define abstracciones de convenciones que proporcionan una forma más sencilla de personalizar el comportamiento de los modelos que invalidan el modelo completo o el proveedor. Estas abstracciones son la manera recomendada de modificar el comportamiento de una aplicación. Las convenciones proporcionan una manera de escribir código que aplica dinámicamente personalizaciones. Aunque los filtros proporcionan un medio para modificar el comportamiento del marco, las personalizaciones permiten controlar el funcionamiento conjunto de toda la aplicación.

Están disponibles las convenciones siguientes:

Las convenciones se aplican agregándolos a las opciones de MVC o implementando atributos y aplícándolos a controladores, acciones o parámetros de acción (similares a los filtros). A diferencia de los filtros, las convenciones solo se ejecutan cuando se inicia la aplicación, no como parte de cada solicitud.

Nota

Para obtener información sobre las convenciones del proveedor de modelos de Razor aplicación y rutas de Pages, vea Convenciones de aplicación y de ruta de Razor Pages en ASP.NET Core .

Modificar el ApplicationModel

La convención siguiente se usa para agregar una propiedad al modelo de aplicación:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ApplicationDescription : IApplicationModelConvention
    {
        private readonly string _description;

        public ApplicationDescription(string description)
        {
            _description = description;
        }

        public void Apply(ApplicationModel application)
        {
            application.Properties["description"] = _description;
        }
    }
}

Las convenciones del modelo de aplicación se aplican como opciones cuando MVC se agrega en Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new ApplicationDescription("My Application Description"));
        options.Conventions.Add(new NamespaceRoutingConvention());
    });
}

Las propiedades son accesibles desde la ActionDescriptor.Properties colección dentro de las acciones del controlador:

public class AppModelController : Controller
{
    public string Description()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }
}

Modificación de la ControllerModel descripción

El modelo de controlador también puede incluir propiedades personalizadas. Las propiedades personalizadas invalidan las propiedades existentes con el mismo nombre especificado en el modelo de aplicación. El atributo de convención siguiente agrega una descripción en el nivel de controlador:

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
    {
        private readonly string _description;

        public ControllerDescriptionAttribute(string description)
        {
            _description = description;
        }

        public void Apply(ControllerModel controllerModel)
        {
            controllerModel.Properties["description"] = _description;
        }
    }
}

Esta convención se aplica como atributo en un controlador:

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
    public string Index()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }

Modificación de la ActionModel descripción

Se puede aplicar una convención de atributo independiente a acciones individuales, invalidando el comportamiento ya aplicado en el nivel de aplicación o controlador:

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ActionDescriptionAttribute : Attribute, IActionModelConvention
    {
        private readonly string _description;

        public ActionDescriptionAttribute(string description)
        {
            _description = description;
        }

        public void Apply(ActionModel actionModel)
        {
            actionModel.Properties["description"] = _description;
        }
    }
}

Al aplicar esto a una acción dentro del controlador se muestra cómo invalida la convención de nivel de controlador:

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
    public string Index()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }

    [ActionDescription("Action Description")]
    public string UseActionDescriptionAttribute()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }
}

Modificar el ParameterModel

La convención siguiente se puede aplicar a parámetros de acción para modificar su BindingInfo. La convención siguiente requiere que el parámetro sea un parámetro de ruta. Se omiten otros orígenes de enlace posibles, como los valores de cadena de consulta:

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AppModelSample.Conventions
{
    public class MustBeInRouteParameterModelConvention : Attribute, IParameterModelConvention
    {
        public void Apply(ParameterModel model)
        {
            if (model.BindingInfo == null)
            {
                model.BindingInfo = new BindingInfo();
            }
            model.BindingInfo.BindingSource = BindingSource.Path;
        }
    }
}

El atributo se puede aplicar a cualquier parámetro de acción:

public class ParameterModelController : Controller
{
    // Will bind:  /ParameterModel/GetById/123
    // WON'T bind: /ParameterModel/GetById?id=123
    public string GetById([MustBeInRouteParameterModelConvention]int id)
    {
        return $"Bound to id: {id}";
    }
}

Para aplicar la convención a todos los parámetros de acción, agregue MustBeInRouteParameterModelConvention a MvcOptions en Startup.ConfigureServices :

options.Conventions.Add(new MustBeInRouteParameterModelConvention());

Modificación del ActionModel nombre

La convención siguiente modifica el valor de ActionModel para actualizar el nombre de la acción a la que se aplica. El nuevo nombre se proporciona como un parámetro al atributo. El enrutamiento usa este nuevo nombre, por lo que afecta a la ruta utilizada para llegar a este método de acción:

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class CustomActionNameAttribute : Attribute, IActionModelConvention
    {
        private readonly string _actionName;

        public CustomActionNameAttribute(string actionName)
        {
            _actionName = actionName;
        }

        public void Apply(ActionModel actionModel)
        {
            // this name will be used by routing
            actionModel.ActionName = _actionName;
        }
    }
}

Este atributo se aplica a un método de acción en HomeController:

// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
    return ControllerContext.ActionDescriptor.ActionName;
}

Aunque el nombre del método es SomeName, el atributo invalida la convención de MVC de usar el nombre del método y reemplaza el nombre de la acción por MyCoolAction. De este modo, la ruta usada para llegar a esta acción es /Home/MyCoolAction.

Nota

Este ejemplo de esta sección es esencialmente el mismo que el uso del ActionNameAttribute integrado.

Convención de enrutamiento personalizado

Use para IApplicationModelConvention personalizar cómo funciona el enrutamiento. Por ejemplo, la convención siguiente incorpora los espacios de nombres de los controladores en sus rutas, reemplazando en el espacio de nombres . por en la ruta / :

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;

namespace AppModelSample.Conventions
{
    public class NamespaceRoutingConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var hasAttributeRouteModels = controller.Selectors
                    .Any(selector => selector.AttributeRouteModel != null);

                if (!hasAttributeRouteModels
                    && controller.ControllerName.Contains("Namespace")) // affect one controller in this sample
                {
                    // Replace the . in the namespace with a / to create the attribute route
                    // Ex: MySite.Admin namespace will correspond to MySite/Admin attribute route
                    // Then attach [controller], [action] and optional {id?} token.
                    // [Controller] and [action] is replaced with the controller and action
                    // name to generate the final template
                    controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
                    {
                        Template = controller.ControllerType.Namespace.Replace('.', '/') + "/[controller]/[action]/{id?}"
                    };
                }
            }

            // You can continue to put attribute route templates for the controller actions depending on the way you want them to behave
        }
    }
}

La convención se agrega como una opción en Startup.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new ApplicationDescription("My Application Description"));
        options.Conventions.Add(new NamespaceRoutingConvention());
    });
}

Sugerencia

Agregue convenciones al middleware mediante MvcOptions el siguiente enfoque. El {CONVENTION} marcador de posición es la convención que se va a agregar:

services.Configure<MvcOptions>(c => c.Conventions.Add({CONVENTION}));

En el ejemplo siguiente se aplica una convención a las rutas que no usan el enrutamiento de atributos donde el controlador Namespace tiene en su nombre:

using Microsoft.AspNetCore.Mvc;

namespace AppModelSample.Controllers
{
    public class NamespaceRoutingController : Controller
    {
        // using NamespaceRoutingConvention
        // route: /AppModelSample/Controllers/NamespaceRouting/Index
        public string Index()
        {
            return "This demonstrates namespace routing.";
        }
    }
}

Uso del modelo de aplicación en WebApiCompatShim

ASP.NET Core MVC usa un conjunto diferente de convenciones de ASP.NET Web API 2. Con las convenciones personalizadas, puede modificar ASP.NET Core comportamiento de una aplicación MVC para que sea coherente con el de una aplicación de API web. Microsoft envía el WebApiCompatShim NuGet paquete específicamente para este propósito.

Nota

Para obtener más información sobre la migración desde ASP.NET Web API, vea Migración de ASP.NET Web API a ASP.NET Core .

Para usar la corrección de compatibilidad de api web:

  • Agregue el Microsoft.AspNetCore.Mvc.WebApiCompatShim paquete al proyecto.
  • Agregue las convenciones a MVC mediante una llamada AddWebApiConventions a en Startup.ConfigureServices :
services.AddMvc().AddWebApiConventions();

Las convenciones proporcionadas por las correcciones de compatibilidad solo se aplican a las partes de la aplicación a las que se han aplicado ciertos atributos. Los cuatro atributos siguientes se usan para controlar en qué controladores se deben modificar las convenciones mediante las convenciones de las correcciones de compatibilidad:

Convenciones de acción

UseWebApiActionConventionsAttribute se usa para asignar el método HTTP a acciones en función de su nombre (por ejemplo, Get se asignaría a HttpGet ). Solo se aplica a las acciones que no usan el enrutamiento de atributos.

Sobrecarga

UseWebApiOverloadingAttribute se usa para aplicar la WebApiOverloadingApplicationModelConvention convención. Esta convención agrega OverloadActionConstraint al proceso de selección de acciones, que limita las acciones candidatas a aquellas en las que la solicitud cumple todos los parámetros no opcionales.

Convenciones de parámetros

UseWebApiParameterConventionsAttribute se usa para aplicar la convención WebApiParameterConventionsApplicationModelConvention de acción. Esta convención especifica que los tipos simples usados como parámetros de acción se enlazan desde el URI de forma predeterminada, mientras que los tipos complejos se enlazan desde el cuerpo de la solicitud.

Rutas

UseWebApiRoutesAttribute controla si se WebApiApplicationModelConvention aplica la convención del controlador. Cuando se habilita, esta convención se usa para agregar compatibilidad con áreas a la ruta e indica que el controlador está en el api área.

Además de un conjunto de convenciones, el paquete de compatibilidad incluye una clase base que reemplaza a System.Web.Http.ApiController la proporcionada por la API web. Esto permite que los controladores de API web escritos para la API web y hereden de para que funcionen mientras se ApiController ejecutan ASP.NET Core MVC. Todos los atributos UseWebApi* enumerados anteriormente se aplican a la clase de controlador base. expone ApiController propiedades, métodos y tipos de resultados que son compatibles con los que se encuentran en la API web.

Uso ApiExplorer para documentar una aplicación

El modelo de aplicación expone una propiedad ApiExplorerModel en cada nivel que se puede usar para recorrer la estructura de la aplicación. Se puede usar para generar páginas de ayuda para las API web mediante herramientas como Swagger. La propiedad expone una propiedad que se puede establecer para especificar qué partes del modelo de la aplicación se ApiExplorer IsVisible deben exponer. Configure esta opción mediante una convención:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class EnableApiExplorerApplicationConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            application.ApiExplorer.IsVisible = true;
        }
    }
}

Con este enfoque (y convenciones adicionales si es necesario), la visibilidad de la API está habilitada o deshabilitada en cualquier nivel dentro de una aplicación.