Поделиться через


Работа с моделью приложения в ASP.NET Core

Автор: Стив Смит (Steve Smith)

ASP.NET Core MVC определяет модель приложения , представляющую компоненты приложения MVC. Читайте и изменяйте эту модель, чтобы модифицировать поведение элементов MVC. По умолчанию MVC следует определенным соглашениям, чтобы определить, какие классы считаются контроллерами, какие методы для этих классов являются действиями, а также как параметры и маршрутизация ведут себя. Настройте это поведение в соответствии с потребностями приложения, создавая пользовательские соглашения и применяя их глобально или как атрибуты.

Модели и поставщики (IApplicationModelProvider)

Модель приложения ASP.NET Core MVC включает как абстрактные интерфейсы, так и конкретные классы реализации, описывающие приложение MVC. Эта модель является результатом обнаружения контроллеров приложения, действий, параметров действия, маршрутов и фильтров по умолчанию. Работая с моделью приложения, измените приложение, чтобы следовать различным правилам, отличным от стандартного поведения MVC. Параметры, имена, маршруты и фильтры используются в качестве данных конфигурации для действий и контроллеров.

Модель приложения ASP.NET Core MVC имеет следующую структуру:

  • ApplicationModel
    • Контроллеры (ControllerModel)
      • Действия (ActionModel)
        • Параметры (ParameterModel)

Каждый уровень модели имеет доступ к общей Properties коллекции, а более низкие уровни могут получить доступ и перезаписать значения свойств, заданные более высокими уровнями в иерархии. Свойства сохраняются в ActionDescriptor.Properties при создании действий. Затем, при обработке запроса, можно получить доступ к любым свойствам, которые были добавлены или изменены с помощью соглашения, через ActionContext.ActionDescriptor. Использование свойств — отличный способ настроить фильтры, привязыватели моделей и другие аспекты модели приложений на основе каждого действия.

Замечание

Коллекция ActionDescriptor.Properties не является потокобезопасной (для операций записи) после запуска приложения. Соглашения — лучший способ безопасного добавления данных в эту коллекцию.

ASP.NET Core MVC загружает модель приложения с помощью шаблона поставщика, определенного интерфейсом IApplicationModelProvider . В этом разделе рассматриваются некоторые внутренние механизмы работы этого поставщика. Использование шаблона поставщика — это расширенная тема, прежде всего для использования в фреймворках. Большинство приложений должны использовать соглашения, а не шаблон поставщика.

Реализации интерфейса IApplicationModelProvider обертывают друг в друге, где каждая реализация вызывается OnProvidersExecuting в порядке возрастания на основе свойства Order. Затем вызывается метод OnProvidersExecuted в обратном порядке. Платформа определяет несколько поставщиков:

Сначала (Order=-1000):

  • DefaultApplicationModelProvider

Затем (Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

Замечание

Порядок вызова двух поставщиков с одинаковым значением Order не определен и не следует полагаться.

Замечание

IApplicationModelProvider — это продвинутая концепция для авторов фреймворков для расширения. Как правило, приложения должны использовать соглашения, а платформы должны использовать поставщиков. Ключевое различие состоит в том, что поставщики запускаются раньше, чем соглашения.

Эта DefaultApplicationModelProvider функция устанавливает множество по умолчанию действий, используемых ASP.NET Core MVC. Ее обязанности включают:

  • Добавление глобальных фильтров в контекст
  • Добавление контроллеров в контекст
  • Добавление методов общедоступного контроллера в качестве действий
  • Добавление параметров метода действия в контекст
  • Применение маршрута и других атрибутов

Некоторые встроенные поведения реализуются с помощью DefaultApplicationModelProvider. Этот поставщик отвечает за создание ControllerModel, которое, в свою очередь, ссылается на экземпляры ActionModel, PropertyModel и ParameterModel. Класс DefaultApplicationModelProvider — это внутренняя информация о реализации платформы, которая может измениться в будущем.

За применение поведения, связанного с атрибутами AuthorizeFilter и AllowAnonymousFilter, отвечает AuthorizationApplicationModelProvider. Дополнительные сведения см. в разделе "Простая авторизация" в ASP.NET Core.

Элемент CorsApplicationModelProvider реализует поведение, связанное с IEnableCorsAttribute и IDisableCorsAttribute. Дополнительные сведения см. в разделе "Включение запросов между источниками" (CORS) в ASP.NET Core.

Сведения о внутренних поставщиках платформы, описанных в этом разделе, недоступны через браузер API .NET. Однако поставщики могут быть проверены в источнике ссылок ASP.NET Core (репозиторий dotnet/aspnetcore GitHub). Используйте поиск GitHub, чтобы найти поставщиков по имени и выбрать версию источника с раскрывающимся списком ветвей или тегов Switch .

Соглашения

Модель приложения определяет абстракции соглашений, которые предоставляют более простой способ настройки поведения моделей, чем переопределение всей модели или поставщика. Эти абстракции являются рекомендуемым способом изменения поведения приложения. Соглашения предоставляют способ написания кода, который динамически применяет настройки. Хотя фильтры предоставляют средства изменения поведения платформы , настройки позволяют контролировать работу всего приложения.

Доступны следующие соглашения:

Соглашения применяются путем добавления их в параметры MVC или путем реализации атрибутов и применения их к контроллерам, действиям или параметрам действия (аналогично фильтрам). В отличие от фильтров, соглашения выполняются только при запуске приложения, а не в рамках каждого запроса.

Замечание

Сведения о соглашениях о маршрутах и поставщиках моделей приложений pages см. в Razor соглашениях Razor о маршрутах страниц и приложениях в ASP.NET Core.

Измените ApplicationModel

Для добавления свойства в модель приложения используется следующее соглашение:

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

Соглашения модели приложений применяются в качестве параметров при добавлении MVC в Startup.ConfigureServices:

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

Свойства доступны из ActionDescriptor.Properties коллекции в действиях контроллера:

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

Измените ControllerModel описание

Модель контроллера также может включать пользовательские свойства. Настраиваемые свойства переопределяют существующие свойства с тем же именем, указанным в модели приложения. Следующий атрибут соглашения добавляет описание на уровне контроллера:

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

Это соглашение применяется в качестве атрибута на контроллере:

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

Измените описание ActionModel

К отдельным действиям можно применить отдельное соглашение атрибутов, переопределяющее поведение, которое уже применяется на уровне приложения или контроллера:

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

Применение этого действия в контроллере демонстрирует, как оно переопределяет соглашение на уровне контроллера:

[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"];
    }
}

Измените ParameterModel

Для изменения BindingInfoпараметров действия можно применить следующее соглашение. Для следующего соглашения требуется, чтобы параметр был параметром маршрута. Другие потенциальные источники привязки, такие как значения строки запроса, игнорируются:

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

Атрибут может применяться к любому параметру действия:

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

Чтобы применить соглашение ко всем параметрам действия, добавьте MustBeInRouteParameterModelConvention к MvcOptions в Startup.ConfigureServices:

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

Измените имя ActionModel

Следующее соглашение модифицирует ActionModel для обновления имени действия, к которому оно применяется. Новое имя предоставляется в качестве параметра атрибута. Это новое имя используется маршрутизацией, поэтому он влияет на маршрут, используемый для достижения этого метода действия:

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

Этот атрибут применяется к методу действия в :HomeController

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

Несмотря на то, что имя метода равно SomeName, атрибут переопределяет соглашение MVC об использовании имени метода и заменяет имя действия на MyCoolAction. Таким образом, маршрут, используемый для достижения этого действия, является /Home/MyCoolAction.

Замечание

Этот пример в этом разделе по сути совпадает с использованием встроенного ActionNameAttribute.

Соглашение о пользовательской маршрутизации

Используйте IApplicationModelConvention для настройки работы маршрутизации. Например, следующее соглашение включает пространства имен контроллеров в их маршруты, заменяя . в пространстве имен на / в маршруте.

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

Конвенция добавляется как параметр в Startup.ConfigureServices:

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

Подсказка

Добавьте соглашения в промежуточное ПОMvcOptions следующим образом. Плейсхолдер {CONVENTION} используется как соглашение для добавления:

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

В следующем примере применяется соглашение к маршрутам, которые не используют маршрутизацию атрибутов, когда имя контроллера содержит Namespace:

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

Использование модели приложения в WebApiCompatShim

ASP.NET Core MVC использует другой набор соглашений, отличных от ASP.NET веб-API 2. С помощью пользовательских соглашений можно изменить поведение приложения ASP.NET Core MVC в соответствии с поведением веб-API. Корпорация Майкрософт поставляет WebApiCompatShim пакет NuGet специально для этой цели.

Замечание

Дополнительные сведения о миграции из веб-API ASP.NET см. в статье "Миграция с веб-API ASP.NET на ASP.NET Core".

Чтобы использовать адаптер совместимости для веб-API, выполните следующие шаги.

  • Добавить пакет Microsoft.AspNetCore.Mvc.WebApiCompatShim в проект.
  • Добавьте соглашения в MVC путем вызоваAddWebApiConventions:Startup.ConfigureServices
services.AddMvc().AddWebApiConventions();

Соглашения, предоставляемые промежуточной программой, применяются только к частям приложения, для которых были применены определённые атрибуты. Следующие четыре атрибута используются, чтобы определить, у каких контроллеров должны быть изменены соглашения в соответствии с правилами shim:

Конвенции действий

UseWebApiActionConventionsAttribute используется для сопоставления метода HTTP с действиями на основе их имени (например, Get сопоставляется с HttpGet). Это относится только к действиям, которые не используют маршрутизацию атрибутов.

Перегрузка

UseWebApiOverloadingAttribute используется для применения WebApiOverloadingApplicationModelConvention соглашения. Это соглашение добавляет OverloadActionConstraint в процесс выбора действия, ограничивая действия кандидатов только теми, для которых запрос удовлетворяет всем обязательным параметрам.

Соглашения о параметрах

UseWebApiParameterConventionsAttribute используется для применения соглашения о действии WebApiParameterConventionsApplicationModelConvention . Это соглашение указывает, что простые типы, используемые в качестве параметров действия, привязаны к URI по умолчанию, а сложные типы привязаны из текста запроса.

Маршруты

UseWebApiRoutesAttribute определяет, применяется ли соглашение контроллера WebApiApplicationModelConvention . Если этот параметр включен, это соглашение используется для добавления поддержки областей в маршрут и указывает, что контроллер находится в api области.

Помимо набора соглашений, пакет совместимости включает System.Web.Http.ApiController базовый класс, который заменяет предоставляемый веб-API. Это позволяет контроллерам веб-API, написанным для этой системы и наследующим её ApiController, функционировать в среде ASP.NET Core MVC. UseWebApi* Все атрибуты, перечисленные ранее, применяются к базовому классу контроллера. ApiController предоставляет свойства, методы и типы результатов, которые совместимы с теми, что используются в веб-API.

Использование ApiExplorer для документирования приложения

Модель приложения предоставляет ApiExplorerModel свойство на каждом уровне, которое можно использовать для обхода структуры приложения. Это можно использовать для создания страниц справки для веб-API с помощью таких средств, как Swagger. Свойство ApiExplorer открывает IsVisible свойство, которое можно задать для указания, какие части модели приложения должны быть доступны. Настройте этот параметр в соответствии с определённой договорённостью.

using Microsoft.AspNetCore.Mvc.ApplicationModels;

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

Используя этот подход (и дополнительные соглашения при необходимости), видимость API включена или отключена на любом уровне в приложении.