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

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

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

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

Модель приложения ASP.NET Core MVC включает как абстрактные интерфейсы, так и конкретные классы реализации, описывающие приложение 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 — это внутренняя информация о реализации платформы, которая может измениться в будущем.

Поставщик AuthorizationApplicationModelProvider занимается применением поведения, связанным с атрибутами AuthorizeFilter и AllowAnonymousFilter. Дополнительные сведения см. в разделе "Простая авторизация" в 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}";
    }
}

Чтобы применить соглашение ко всем параметрам действия, добавьте его MustBeInRouteParameterModelConventionMvcOptions в 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 Web API 2. С помощью пользовательских соглашений можно изменить поведение приложения ASP.NET Core MVC в соответствии с поведением веб-API. Корпорация Майкрософт поставляет WebApiCompatShim пакет NuGet специально для этой цели.

Примечание.

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

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

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

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

Соглашения о действиях

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

Перегрузка

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

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

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

Маршруты

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

Помимо набора соглашений пакет совместимости включает System.Web.Http.ApiController базовый класс, заменяющий один из них веб-API. Это позволяет контроллерам веб-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 включена или отключена на любом уровне в приложении.