Trabalhar com o modelo de aplicativo no ASP.NET Core

Por Steve Smith

O ASP.NET Core MVC define um modelo de aplicativo que representa os componentes de um aplicativo MVC. Leia e manipule esse modelo para modificar como os elementos MVC se comportam. Por padrão, o MVC segue algumas convenções para determinar quais classes são consideradas controladores, quais métodos nessas classes são ações e como parâmetros e roteamentos se comportam. Personalize esse comportamento de acordo com as necessidades do aplicativo criando suas próprias convenções e aplicando-as globalmente ou como atributos.

Modelos e provedores (IApplicationModelProvider)

O modelo de aplicativo do ASP.NET Core MVC inclui interfaces abstratas e classes de implementação concreta que descrevem um aplicativo MVC. Esse modelo é o resultado da descoberta do MVC de controladores, ações, parâmetros de ação, rotas e filtros do aplicativo de acordo com as convenções padrão. Trabalhando com o modelo de aplicativo, você pode modificar o aplicativo para seguir convenções diferentes do comportamento padrão do MVC. Os parâmetros, os nomes, as rotas e os filtros são todos usados como dados de configuração para ações e controladores.

O Modelo de Aplicativo ASP.NET Core MVC tem a seguinte estrutura:

  • ApplicationModel
    • Controladores (ControllerModel)
      • Ações (ActionModel)
        • Parâmetros (ParameterModel)

Cada nível do modelo tem acesso a uma coleção Properties comum e níveis inferiores podem acessar e substituir valores de propriedade definidos por níveis mais altos na hierarquia. As propriedades são persistidas nas ActionDescriptor.Properties quando as ações são criadas. Em seguida, quando uma solicitação está sendo manipulada, as propriedades que uma convenção adicionou ou modificou podem ser acessadas por meio de ActionContext.ActionDescriptor. O uso de propriedades é uma ótima maneira de configurar filtros, associadores de modelos e outros aspectos dos modelos de aplicativo por ação.

Observação

A coleção ActionDescriptor.Properties não é thread-safe (para gravações) após a inicialização do aplicativo. Convenções são a melhor maneira de adicionar dados com segurança a essa coleção.

O ASP.NET Core MVC carrega o modelo de aplicativo usando um padrão de provedor, definido pela interface IApplicationModelProvider. Esta seção aborda alguns dos detalhes de implementação interna de como funciona esse provedor. O uso do padrão de provedor é um assunto avançado, principalmente para uso de estrutura. A maioria dos aplicativos deve usar convenções, não o padrão do provedor.

Implementações da interface IApplicationModelProvider "encapsulam" umas às outras, com cada implementação chamando OnProvidersExecuting em ordem crescente com base em sua propriedade Order. O método OnProvidersExecuted é então chamado em ordem inversa. A estrutura define vários provedores:

Primeiro (Order=-1000):

  • DefaultApplicationModelProvider

Em seguida (Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

Observação

A ordem na qual dois provedores com o mesmo valor para Order são chamados não é definida e, portanto, não se deve confiar nela.

Observação

IApplicationModelProvider é um conceito avançado a ser estendido pelos autores da estrutura. Em geral, aplicativos devem usar convenções e estruturas devem usar provedores. A principal diferença é que os provedores sempre são executados antes das convenções.

O DefaultApplicationModelProvider estabelece muitos dos comportamentos padrão usados pelo ASP.NET Core MVC. Suas responsabilidades incluem:

  • Adicionar filtros globais ao contexto
  • Adicionar controladores ao contexto
  • Adicionar métodos de controlador públicos como ações
  • Adicionar parâmetros de método de ação ao contexto
  • Aplicar a rota e outros atributos

Alguns comportamentos internos são implementados pelo DefaultApplicationModelProvider. Esse provedor é responsável pela construção do ControllerModel, que, por sua vez, referencia as instâncias ActionModel, PropertyModel e ParameterModel. A classe DefaultApplicationModelProvider é um detalhe de implementação de estrutura interna que pode e será alterado no futuro.

O AuthorizationApplicationModelProvider é responsável por aplicar o comportamento associado aos atributos AuthorizeFilter e AllowAnonymousFilter. Para obter mais informações, consulte Autorização simples no ASP.NET Core.

O CorsApplicationModelProvider implementa o comportamento associado ao IEnableCorsAttribute e ao IDisableCorsAttribute. Para obter mais informações, consulte Habilitar solicitações entre origens (CORS, na sigla em inglês) no ASP.NET Core.

As informações sobre os provedores internos da estrutura descritas nesta seção não estão disponíveis por meio do navegador de API do .NET. No entanto, os provedores podem ser inspecionados na fonte de referência do ASP.NET Core (repositório GitHub dotnet/aspnetcore). Use a pesquisa do GitHub para localizar os provedores pelo nome e selecione a versão da origem na lista suspensa Alternar branches/marcas .

Convenções

O modelo de aplicativo define abstrações de convenção que fornecem uma maneira simples de personalizar o comportamento dos modelos em vez de substituir o modelo ou o provedor inteiro. Essas abstrações são a maneira recomendada para modificar o comportamento do aplicativo. As convenções fornecem uma maneira de escrever código que aplicará personalizações de forma dinâmica. Enquanto os filtros fornecem um meio de modificar o comportamento da estrutura, as personalizações permitem que você controle o aplicativo todo funciona em conjunto.

As seguintes convenções estão disponíveis:

As convenções são aplicadas adicionando-as às opções do MVC ou implementando atributos e aplicando-os a controladores, ações ou parâmetros de ação (semelhantes a filtros). Ao contrário dos filtros, as convenções só são executadas quando o aplicativo está sendo iniciado, não como parte de cada solicitação.

Observação

Para obter informações sobre convenções do provedor de rotas e de modelo de aplicativo do Razor Pages , consulte Convenções de rota e de aplicativo do Razor Pages no ASP.NET Core.

Modificar o ApplicationModel

A convenção a seguir é usada para adicionar uma propriedade ao modelo de aplicativo:

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

As convenções de modelo de aplicativo são aplicadas como opções quando o MVC é adicionado em Startup.ConfigureServices:

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

As propriedades são acessíveis na coleçãoActionDescriptor.Properties nas ações do controlador:

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

Modificar a descrição ControllerModel

O modelo do controlador também pode incluir propriedades personalizadas. Elas substituirão as propriedades existentes com o mesmo nome especificado no modelo de aplicativo. O seguinte atributo de convenção adiciona uma descrição no nível do 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;
        }
    }
}

Essa convenção é aplicada como um atributo em um controlador:

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

Modificar a descrição ActionModel

Uma convenção de atributo separada pode ser aplicada a ações individuais, substituindo o comportamento já aplicado no nível do aplicativo ou do 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;
        }
    }
}

A aplicação disso a uma ação no controlador do exemplo anterior demonstra como ela substitui a convenção no nível do 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 o ParameterModel

A convenção a seguir pode ser aplicada a parâmetros de ação para modificar seu BindingInfo. A convenção a seguir exige que o parâmetro seja um parâmetro de rota. Outras fontes de associação potenciais, como valores de cadeia de caracteres de consulta, são ignoradas:

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

O atributo pode ser aplicado a qualquer parâmetro de ação:

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 a convenção a todos os parâmetros de ação, adicione a MustBeInRouteParameterModelConvention a MvcOptions em Startup.ConfigureServices:

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

Modificar o nome de ActionModel

A convenção a seguir modifica o ActionModel para atualizar o nome da ação ao qual ele é aplicado. O novo nome é fornecido como um parâmetro para o atributo. Esse novo nome é usado pelo roteamento e, portanto, isso afetará a rota usada para acessar esse método de ação:

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

Esse atributo é aplicado a um método de ação no HomeController:

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

Mesmo que o nome do método seja SomeName, o atributo substitui a convenção do MVC de uso do nome do método e substitui o nome da ação por MyCoolAction. Portanto, a rota usada para acessar essa ação é /Home/MyCoolAction.

Observação

Esse exemplo é basicamente o mesmo que usar o ActionNameAttribute interno.

Convenção de roteamento personalizada

Use uma IApplicationModelConvention para personalizar como funciona o roteamento. Por exemplo, a seguinte convenção incorporará namespaces dos controladores em suas rotas, substituindo . no namespace por / na rota:

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

A convenção é adicionada como uma opção em Startup.ConfigureServices:

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

Dica

Adicione convenções ao middleware usando MvcOptions a abordagem a seguir. O espaço reservado {CONVENTION} é a convenção a ser adicionada:

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

O exemplo a seguir aplica essa convenção às rotas que não estão usando o roteamento de atributo, nas quais o controlador tem Namespace em seu nome:

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 do modelo de aplicativo em WebApiCompatShim

O ASP.NET Core MVC usa um conjunto diferente de convenções do ASP.NET Web API 2. Usando convenções personalizadas, você pode modificar o comportamento de um aplicativo ASP.NET Core MVC para que ele seja consistente com o comportamento de um aplicativo de API Web. A Microsoft fornece o pacote NuGet WebApiCompatShim especificamente para essa finalidade.

Observação

Para obter mais informações sobre a migração do ASP.NET Web API, consulte Migrar do ASP.NET Web API para o ASP.NET Core.

Para usar o Shim de compatibilidade de API Web :

  • Adicione o pacote Microsoft.AspNetCore.Mvc.WebApiCompatShim ao projeto.
  • Adicione as convenções ao MVC chamando AddWebApiConventions em Startup.ConfigureServices:
services.AddMvc().AddWebApiConventions();

As convenções fornecidas pelo shim são aplicadas apenas às partes do aplicativo que tiveram determinados atributos aplicados a elas. Os seguintes quatro atributos são usados para controlar quais controladores devem ter suas convenções modificadas pelas convenções do shim:

Convenções de ação

O UseWebApiActionConventionsAttribute é usado para mapear o método HTTP para ações com base em seu nome (por exemplo, Get será mapeado para HttpGet). Ele se aplica somente a ações que não usam o roteamento de atributo.

Sobrecarga

O UseWebApiOverloadingAttribute é usado para aplicar a convenção WebApiOverloadingApplicationModelConvention. Essa convenção adiciona uma OverloadActionConstraint ao processo de seleção de ação, o que limita as ações de candidato àquelas para as quais a solicitação atende a todos os parâmetros não opcionais.

Convenções de parâmetro

O UseWebApiParameterConventionsAttribute é usado para aplicar a convenção de ação WebApiParameterConventionsApplicationModelConvention. Essa convenção especifica que tipos simples usados como parâmetros de ação são associados por meio do URI por padrão, enquanto tipos complexos são associados por meio do corpo da solicitação.

Rotas

O UseWebApiRoutesAttribute controla se a convenção de controlador WebApiApplicationModelConvention é aplicada. Quando habilitada, essa convenção é usada para adicionar suporte para áreas à rota e indica que o controlador está na área api.

Além de um conjunto de convenções, o pacote de compatibilidade inclui uma classe base System.Web.Http.ApiController que substitui aquela fornecida pela API Web. Isso permite que os controladores escritos para a API Web e que herdam de seu ApiController funcionem como foram criados, enquanto são executados ASP.NET Core MVC. Todos os atributos UseWebApi* listados anteriormente são aplicados à classe de controlador base. O ApiController expõe propriedades, métodos e tipos de resultado compatíveis com aqueles encontrados na API Web.

Usar ApiExplorer para documentar um aplicativo

O modelo de aplicativo expõe uma propriedade ApiExplorerModel em cada nível, que pode ser usada para percorrer a estrutura do aplicativo. Isso pode ser usado para gerar páginas da ajuda para as APIs Web usando ferramentas como o Swagger. A propriedade ApiExplorer expõe uma propriedade IsVisible que pode ser definida para especificar quais partes do modelo do aplicativo devem ser expostas. Defina essa configuração usando uma convenção:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

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

Usando essa abordagem (e convenções adicionais, se necessário), você pode habilitar ou desabilitar a visibilidade da API em qualquer nível no aplicativo.