Фильтрация в visual studio
Фильтрация без атрибутов
Обычный способ работы с фильтрами предусматривает применение атрибутов, как неоднократно демонстрировалось в предшествующих статьях. Однако существует альтернатива. Класс Controller реализует интерфейсы IAuthenticationFilter, IAuthorizationFilter, IActionFilter, IResultFilter и IExceptionFilter. Он также предоставляет пустые виртуальные реализации всех ранее показанных методов этих интерфейсов, таких как OnAuthorization() и OnException().
В примере ниже приведен код контроллера Home, обновленный для использования этого средства и создания самостоятельно профилируемого класса контроллера:
Фильтры из метода действия FilterTest() были удалены, поскольку они больше не требуются. Контроллер Home будет добавлять информацию профилирования к ответу для любого метода действия. На рисунке ниже показан результат запуска приложения и перехода на URL вида /Home/RangeTest/150, который направляет на метод действия RangeTest() без генерации исключения (которую мы специально добавили при рассмотрении фильтра HandleError):
Такой прием наиболее полезен в случае создания базового класса, от которого будет унаследовано множество контроллеров в проекте. Весь смысл фильтрации заключается в том, чтобы поместить код, требуемый повсеместно в приложении, в разделяемое местоположение, поэтому использование данных методов в классе, который не служит базовым классом для контроллеров, не имеет особого смысла.
Я предпочитаю пользоваться атрибутами. Мне нравится разделять логику контроллеров и логику фильтров. Если вы ищете способ применения фильтра ко всем контроллерам, то далее будет показано, как это делать с помощью глобальных фильтров.
Использование глобальных фильтров
Глобальные фильтры применяются ко всем методам действий во всех контроллерах в рамках приложения. Существует соглашение по настройке глобальных фильтров, которые создаются Visual Studio автоматически, когда используется шаблон проекта MVC, но в случае выбора шаблона Empty (Пустой) их придется настраивать вручную.
Конфигурация, охватывающая все приложение, задается в классах, добавляемых в папку App_Start. Именно по этой причине в статьях, посвященных маршрутизации, маршруты определялись в файле App_Start/RouteConfig.cs. Чтобы создать эквивалент для фильтров, в папку App_Start добавляется новый файл класса по имени FilterConfig.cs с содержимым, представленным в примере ниже:
Такое же содержимое среда Visual Studio создала бы для шаблона MVC. В классе определен статический метод по имени RegisterGlobalFilters(), который получает коллекцию глобальных фильтров, выраженную в виде объекта GlobalFilterCollection, куда можно добавлять новые фильтры.
В этом файле необходимо отметить два соглашения. Первое соглашение заключается в том, что класс FilterConfig определен в пространстве имен Filters, а не в Filters.App_Start, которое Visual Studio будет автоматически использовать при создании файла в подпапке приложения. Второе соглашение касается того, что фильтр HandleError, описанный ранее, всегда определяется как глобальный фильтр посредством вызова метода Add() на объекте GlobalFilterCollection.
Вы не обязаны настраивать фильтр HandleError глобально, однако он определяет стандартную политику обработки исключений MVC. Это приводит к тому, что в случае возникновения необработанного исключения будет визуализироваться представление /Views/Shared/Error.cshtml. Во время разработки такая стандартная политика обработки исключений отключена.
В примере ниже демонстрируется применение фильтра ProfileAll глобально с использованием того же самого вызова метода, который настраивает фильтр HandleError:
Обратите внимание на то, что фильтр регистрируется глобально путем создания экземпляра класса фильтра, а это означает необходимость в ссылке на имя класса, включая суффикс Attribute. Правило предусматривает возможность не указывать суффикс Attribute во время применения фильтра в качестве атрибута, но указывать его при создании экземпляра этого класса напрямую.
Следующий шаг состоит в обеспечении вызова метода FilterConfig.RegisterGlobalFilters() в файле Global.asax при запуске приложения. В примере ниже показан код, добавленный в этот файл:
для демонстрации функционирования глобального фильтра создан новый контроллер по имени Customer, код которого приведен в примере ниже. Новый контроллер был создан из-за того, что мы хотим использовать код, к которому ранее не применялся какой-либо фильтр:
Это очень простой контроллер, метод действия Index() которого возвращает строку. Запустив приложение и перейдя на URL вида /Customer, можно увидеть результат применения глобального фильтра:
Несмотря на то что непосредственно к контроллеру никакой фильтр не применялся, глобальный фильтр добавляет информацию профилирования.
Порядок выполнения фильтров
Ранее уже объяснялось, что фильтры выполняются согласно своим типам. Последовательность выглядит следующим образом: фильтры аутентификации, фильтры авторизации, фильтры действий и фильтры результатов. Инфраструктура выполняет фильтры исключений на любой стадии, если возникает необработанное исключение. Однако в рамках каждого типа можно получить контроль над тем, в каком порядке будут использоваться индивидуальные фильтры.
В примере ниже приведено содержимое файла класса по имени SimpleMessageAttribute.cs, добавленного в папку Infrastructure, в котором определен простой фильтр, предназначенный для демонстрации порядка выполнения фильтров:
Инфраструктура MVC Framework выполняет фильтр "A" перед фильтром "Б", однако может случиться и наоборот. Инфраструктура MVC Framework не гарантирует какого-то определенного порядка выполнения фильтров. В большинстве ситуаций порядок значения не имеет. Но если порядок важен, можно воспользоваться свойством Order, как доказано в примере ниже:
Параметр Order принимает значение типа int, а ASP.NET MVC Framework выполняет фильтры в порядке по возрастанию. В примере выше фильтру "Б" для Order назначено меньшее значение, поэтому инфраструктура выполнит его первым:
Обратите внимание, что методы OnActionExecuting() выполняются в указанном порядке, но методы OnActionExecuted() - в порядке, обратном указанному. Инфраструктура MVC Framework строит стек фильтров по мере их выполнения перед запуском метода действия, а затем впоследствии раскручивает его. Данное поведение раскручивания стека изменить нельзя.
Если значение для свойства Order не задано, ему присваивается стандартное значение -1. Это значит, что при смешивании фильтров, часть из которых имеет значения Order, а часть - нет, фильтры без значений Order будут выполняться первыми, поскольку они имеют наименьшее значение свойства Order.
Если множество фильтров одного и того же типа (скажем, фильтры действий) имеют одинаковое значение Order (например, 1), то MVC Framework определяет порядок выполнения на основе того, когда тот или иной фильтр применяется. Первыми выполняются глобальные фильтры, затем - фильтры, примененные к классу контроллера, а после этого - фильтры, примененные к методу действия.
Для фильтров исключений порядок выполнения меняется на противоположный. Если Фильтры исключений с одним и тем же значением Order применяются и к контроллеру, и к методу действия, фильтр на методе действия будет выполнен первым. Глобальные фильтры исключений с одним и тем же значением Order выполняются последними.
Переопределение фильтров
Возможны ситуации, когда какой-то фильтр должен применяться глобально или на уровне контроллера, а для конкретного метода действия необходимо использовать другой фильтр. Для демонстрации такого случая фильтр SimpleMessage изменяется так чтобы его можно было применять к целому контроллеру:
Такое изменение означает, что фильтр может быть применен к индивидуальным методам действий или к целому классу контроллера. В примере ниже модифицирован способ применения этого фильтра к контроллеру Customer:
Если нужно, чтобы на метод действия влияли только фильтры, которые применены к нему напрямую, можно воспользоваться средством переопределения фильтров. Это сообщает инфраструктуре MVC Framework о необходимости игнорирования любых фильтров, которые определены на более высоком уровне, таком как уровень контроллера или глобально. Переопределения фильтров представляют собой атрибуты, реализующие интерфейс IOverrideFilter, который приведен в примере ниже:
Метод FiltersToOverride() возвращает тип фильтра, который будет переопределен. В этом примере интересуют фильтры действий, поэтому в папке Infrastructure создается файл CustomOverrideActionFiltersAttribute.cs. Как показано в примере ниже, метод FiltersToOverride() реализован так, чтобы новый атрибут переопределял тип IActionFilter:
В инфраструктуре MVC Framework имеется несколько встроенных переопределений фильтров, находящихся в пространстве имен System.Web.Mvc.Filters: OverrideAuthenticationAttribute, OverrideActionFiltersAttribute и т.д. На момент написания этих строк они не работали. Причина в том, что эти классы унаследованы от Attribute, а не FilterAttribute. Проблема должна быть решена в более позднем выпуске, но пока придется создавать специальные атрибуты переопределения фильтров вроде продемонстрированного выше.
Теперь данный фильтр можно применить к методу действия, чтобы предотвратить влияние на него со стороны фильтров действий на глобальном уровне и уровне контроллера:
На рисунке ниже видно, что выполняется только атрибут SimpleMessage, который был применен непосредственно к методу OtherAction():
Крупные группы разработчиков часто совместно работают в рамках одного большого решения с множеством проектов. При этом отдельные разработчики обычно работают с небольшим подмножеством таких проектов. Для повышения производительности при открытии больших решений в Visual Studio 2019 добавлена возможность фильтрации решений. Фильтры решений позволяют открыть решение с выборочной загрузкой проектов. Загрузка подмножества проектов в решении уменьшает время загрузки, сборки и тестирования решения и позволяет проводить более сфокусированную проверку.
Список доступных возможностей:
Вы можете быстрее перейти к коду, открыв решение без загрузки проектов. Открыв решение, вы можете выбрать, какие проекты следует загрузить.
При повторном открытии решения Visual Studio запоминает, какие проекты были загружены в предыдущем сеансе, и загружает только эти проекты.
Можно создать файл фильтра решений, чтобы сохранить одну или несколько конфигураций загрузки проектов или поделиться ими с другими участниками команды.
Этот раздел относится к Visual Studio в Windows.
Открытие отфильтрованного решения
Решение можно открыть, не загружая его проекты, непосредственно в диалоговом окне Открыть проект или из командной строки.
Диалоговое окно "Открыть проект"
Чтобы открыть решения без загрузки его проектов с помощью диалогового окна Открыть проект, сделайте следующее:
В строке меню выберите Файл > Открыть > Решение или проект.
В диалоговом окне Открыть проект выберите решение, а затем выберите Не загружать проекты.
Выберите команду Открыть.
Решение открывается без загрузки проектов.
В обозревателе решений выберите проекты, которые нужно загрузить (нажмите клавишу CTRL для выбора нескольких проектов), а затем щелкните проект правой кнопкой мыши и выберите пункт Перезагрузить проект.
При следующем локальном открытии решения Visual Studio запоминает, какие проекты загружаются.
Командная строка
(новые возможности в Visual Studio 2019 версии 16.1)
Чтобы открыть решение без загрузки его проектов из командной строки, используйте параметр /donotloadprojects , как показано в следующем примере.
Переключение режима отображения незагруженных проектов
Вы можете настроить отображение всех проектов в решении или только загруженных проектов с помощью одного из следующих вариантов в обозревателе решений.
Щелкните решение правой кнопкой мыши и выберите Показать незагруженные проекты или Скрыть незагруженные проекты.
Загрузка зависимостей проектов
Если загружены только отдельные проекты решения, некоторые зависимости проектов могут не загрузиться. Убедиться в наличии всех зависимостей можно с помощью пункта меню Загрузить зависимости проекта. Щелкните правой кнопкой мыши один или несколько загруженных проектов в обозревателе решений и выберите Загрузить зависимости проекта.
Файлы фильтров решений
Если вы хотите совместно использовать конфигурацию загрузки проектов или зафиксировать ее в системе управления версиями, можно создать файл фильтра решений (он имеет расширение SLNF). При открытии файла фильтра решений решение открывается в Visual Studio с загрузкой указанных проектов (все незагруженные проекты будут скрыты). Вы можете переключать режим просмотра незагруженных проектов.
Файлы фильтров решений визуально отличаются от обычных файлов решений дополнительным воронкообразным глифом на значке рядом с решением в обозревателе решений. Имя фильтра и количество загруженных проектов также отображаются рядом с именем решения.
Если после создания файла фильтра решений в исходное решение добавляются новые проекты, они отображаются как незагруженные проекты в обозревателе решений.
Создание файла фильтра решений
В обозревателе решений щелкните решение правой кнопкой мыши и выберите пункт Сохранить как фильтр решений.
Выберите имя и расположение файла фильтра решений.
После создания фильтра файл решений он добавляется в список Последние проекты и решения для быстрого доступа:
"Последние" в Visual Studio" />
Чтобы ограничить данные профилирования, отображаемые в представлениях отчета о производительности и экспортируемые в файлы отчетов, можно применить фильтры к файлам данных профилирования. Отчет можно ограничить данными, полученными между двумя метками времени. Кроме того, можно ограничиться данными определенных процессов или потоков. Фильтры можно сохранить в файле, а затем создать фильтр в другом файле данных профилирования, импортировав в него сохраненный фильтр.
Кроме того, отчет можно ограничить определенным отрезком времени, воспользовавшись графической временной шкалой в представлении сводки. См. раздел Практическое руководство. Фильтрация представлений отчетов на сводной временной шкале.
Процедуры
Создание фильтра отчетов профилировщика
Если окно фильтра просмотра отчетов о производительности не отображается, щелкните значок Показать фильтр на панели инструментов в представлении отчета о производительности.
Фильтр представления отчета о производительности — это таблица. Каждая строка этой таблицы соответствует предложению фильтра. В фильтр можно добавить столько предложений, сколько нужно.
Для каждого предложения, которое необходимо добавить в фильтр, выберите или введите значения в указанных ниже полях строки.
Создание фильтра отчета профилировщика в представлении отчета по меткам
На панели инструментов в представлении отчета о производительности выберите в списке Текущее представление пункт Метки.
Отобразится отчет профилировщика "Метки".
Выберите событие трассировки событий Windows или событие выборки, которое необходимо использовать в качестве отправной точки отчета.
Нажав и удерживая клавишу CTRL, щелкните событие, которое вы хотите использовать в качестве конечной точки отчета.
Щелкните правой кнопкой мыши и выберите один из следующих параметров:
Добавить фильтр по меткам — создает предложения фильтра, в которых в качестве поля фильтра используется столбец "Метка".
Добавить фильтр по меткам времени — создает предложения фильтра, в которых в качестве поля фильтра используется столбец "Метка времени в миллисекундах".
Оба варианта используются для фильтрации текущего файла данных в одних и тех же начальной и конечной точках. При экспорте фильтра для использования в других отчетах лучше может подходить один из вариантов.
Загрузка существующего фильтра из файла
На панели инструментов в представлении отчета о производительности щелкните Импортировать фильтр.
Откроется диалоговое окно Загрузить фильтр.
Укажите расположение и имя файла фильтра (VSPF) для загрузки.
Выполнение фильтра
- На панели инструментов в представлении отчета о производительности щелкните Выполнить фильтр.
Остановка фильтра, выполнение которого длится слишком долго
- На панели инструментов в представлении отчета о производительности щелкните Остановить фильтр.
Удаление фильтра в представлении отчета
Удалите строки предложений в фильтре представления отчета о производительности.
На панели инструментов в представлении отчета о производительности щелкните Выполнить фильтр.
Сохранение фильтра в файле
На панели инструментов в представлении отчета о производительности щелкните Экспортировать фильтр.
В предыдущем уроке мы реализовали страницы для совершения CRUD-операций для сущностей Student. В этом уроке мы добавим сортировку, фильтрацию и разбиение по страницам, а также создадим страницу, на которой будет простая группировка.
На следующем изображении представлен окончательный вид страницы. Заголовки столбцов являются ссылками, реализующими сортировку по убыванию и возрастанию.
Добавление заголовков-сортировщиков в столбцы на странице Students Index
Для добавления сортировки вам нужно изменить метод Index контроллера Student и добавить код на представление Student Index.
Добавление сортировки в метод Index
В Controllers\StudentController.cs замените метод Index на следующий код:
При первом вызове страницы Index строки запроса нет, и студенты отображаются в порядке по возрастанию LastName, что указано как вариант по умолчанию в switch. После того, как пользователь щелкает на заголовке столбца, соответствующее значение sortOrder добавляется в строку запроса.
The two ViewBag variables are used so that the view can configure the column heading hyperlinks with the appropriate query string values:
Это тернарное утверждения. Первое утверждает, что, если sortOrder равен null или пустой, то значение ViewBag.NameSortParam устанавливается в “Name desc”, иначе устанавливается в пустую строку.
- Если сортируется по возрастанию по LastName, ссылка LastName должна указывать на сортировку по убыванию по LastName и ссылка EnrollmentDate на сортировку по возрастанию по Date соответственно.
- Если сортируется по убыванию по LastName, ссылки должны указывать на сортировку по возрастанию как по LastName так и по Date.
- Если сортируется по возрастанию по Date, ссылки должны указывать на сортировку по возрастанию по LastName и по убыванию по Date.
- Если сортируется по убыванию по Date, ссылки должны указывать на сортировку по возрастанию по LastName и по возрастанию по Date.
Добавление заголовков-ссылок в Student Index
В этом коде используется информация в свойствах ViewBag для заполнения ссылок подходящими значениями запроса. Запустите проект и щёлкните на заголовках, чтобы убедиться, что сортировка работает.
Добавление поиска
Чтобы добавить фильтрацию, вы должны добавить на представление текстовое поле и кнопку отправки и сделать соответствующие изменения в коде метода Index.
Изменение кода метода Index
В Controllers\StudentController.cs замените код метода Index
Мы добавили в метод Index параметр searchString, кляузу в LINQ-утверждение, с помощью которого выбираются только те студенты, имя или фамилия которых содержит строку поиска. Строка поиска получается из текстового поля, которое вы позже добавите на представление. Код, который добавляет кляузу where в запрос, выполняется только в том случае, если задано значение для поиска:
Добавление поиска на представление
В Views\Student\Index.cshtmlпрямо перед открывающим тегом table добавьте заголовок, текстовое поле и кнопку Search:
Запустите проект, введите что-нибудь в строку поиска и нажмите на кнопку Search чтобы убедиться в работе фильтрации.
Добавление разбиения по страницам
Для этого необходимо сначала установить NuGet-пакет PagedList, затем сделать изменения в методе Index и добавить на представление ссылки на страницы.
Установка NuGet-пакета NuGet Package
NuGet-пакет PagedList устанавливает тип коллекции PagedList. Когда вы добавляете в коллекцию этого типа результаты запроса, вам предоставляется набор свойств и методов для обеспечения разбиения результатов по страницам.
В Visual Studio выберите проект. Затем нажмите в меню Tools пунктLibrary Package Manager и потом Add Library Package Reference.
В Add Library Package Reference нажмите на вкладку Online слева и введите в строку поиска "pagedlist". Как только появится пакет PagedList нажмите Install.
Добавление функциональности разбиения по страницам в метод Index
В Controllers\StudentController.cs добавьте using PagedList:
Замените код метода Index:
Добавлен параметр page, несущий информацию о параметре, по которому в данный момент производится сортировка, и параметр в сигнатуре метода:
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
При первом вызове страницы (или если пользователь не нажал на одну из ссылок на страницы), переменная page равна null. После нажатия в эту переменную помещается номер страницы.
Свойство ViewBag передаёт в представление текущий параметр сортировки для её сохранения при переходе на другие страницы:
Второе свойство ViewBag передаёт в представление строку фильтрации для того, чтобы при переходе на другую страницу введёная строка поиска не терялась и сохранялись настройки фильтрации. Кроме этого, если строка поиска меняется в случае перехода на другую страницу результатов, номер страницы должен быть скинут в 1, поскольку новая фильтрация предоставляет новый набор данных.
В конце метода запрос конвертируется вместо List в PagedList, после чего его можно передавать в представление, поддерживающее разбиение результатов по страницам.
В метод ToPagedList передаётся значение индекса страницы, которое равно 0, в отличие от номера страницы, который равен 1. Поэтому код извлекает 1 из номера страницы, чтобы получить значения индекса страницы (два знака вопроса обозначают оператор, определяющий значение по умолчанию для типа nullable, таким образом, выражение (page ?? 1) возвращает значение page в том случае, если оно имеет значение, или 1, если page равен null. Другими словами, установите pageIndex в page — 1 если page не равен null, или установите его в 1-1 если он равен null)
Добавление ссылок на страницы на представление
В Views\Student\Index.cshtml замените исходный код на:
Утверждение @model показывает, что представление принимает на вход объект типа PagedList вместо объекта типа List.
Текстовая строка инициализируется текущей строкой поиска чтобы пользователь мог переходить со страницы на страницу не теряя строку поиска:
Find by name: Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
Ссылки в заголовках столбцов используют строку запроса для передачи текущей строки поиска в контроллер, чтобы пользователь мог сортировать возвращённые механизмом фильтра результаты:
В «подвале» страницы находится запись, демонстрирующая номер страницы и ссылки навигации:
Page [current page number] of [total number of pages] >>
Если результатов нет, показывается надпись "Page 0 of 0".
Щелкните ссылки на страницы в различных режимах сортировки и введите какую-либо строку поиска, чтобы убедиться, что всё работает в связке.
Создание страницы со статистикой
- Создать класс с моделью представления для данных, которые мы будем передавать в представление
- Изменить код метода About в контроллере Home.
- Изменить код представления About.
Создание модели представления
Создайте папку ViewModels и в ней создайте файл EnrollmentDateGroup.cs со следующим содержанием:
Изменение контроллера Home
В HomeController.cs добавьте необходимые using:
Добавьте переменную с контекстом базы данных:
private SchoolContext db = new SchoolContext();
Замените код метода About на:
LINQ-утверждения группируют сущности студентов по дате записи, затем вычисляют количество сущностей в каждой группе и сохраняют результат в EnrollmentDateGroup.
Недавно вновь пересел на Visual Studio с другой IDE и у меня возник вопрос: какой смысл в фильтрах вместо папок? В обычных IDE структура проекта представлена папками и файлами, в студии же фильтрами.
В итоге в подавляющем большинстве проектов (что я видел), все файлы лежат в одной директории, никак логически не разбиты и чтобы просто разобраться во всём этом мусоре - приходится включать Visual Studio.
Я уж не говорю про тот ахтунг что случается при включении в проект крупной библиотеки, которую даже чисто физически по всем этим фильтрам не разбросать.
Какой в этом смысл? Может я чего-то не понимаю и глаза замылились?
p.s. Я знаю, что там есть кнопка "Show all files", но прекрасно же понятно, что по умолчанию студия хочет чтобы все работали именно с фильтрами.
Оценить 2 комментария
Извиняюсь, по VS ничего хорошего сказать не могу))тк не пользуюсь поделками от мелкомягких, но. хочу спросить, Вы пробовали CLion от .JetBeains? Как по мне очень хорошая IDE, есть релизы под все платформы, правда она завязана на CMake(но разве это плохо?)) и она платная( но мы ведь таки русские люди)))
Фильтры в Visual C++ - это методика организации рабочего пространства. Это старая и проверенная методика, зарекомендовавшая себя в MSVC++ еще с очень старых времен.
Система фильтров позволяет организовать любую лапшу из файлов сторонней библиотеки, в которой все сделано по принципу: "Работает == Не трогай". Этим она чрезвычайно полезна.
Так же фильтры позволяют сделать более тонкую организацию в тех местах, где на уровне файловой структуры она бессмысленна.
Я бы не был так поспешен, ругая инструмент за то, что им пользуются явно неорганизованные или неграмотные "специалисты". В подавляющем большинстве проектов ты просто видел невежество. И ничего больше.
Да, фильтры никак не привязаны к реальным папкам, а это и не надо. Да, на диске все можно хранить в одной папке (и в этом реальный плюс фильтров). Да, фильтры позволяют организоваться только внутри пространства самой среды.
Но и ничего больше.
Многие люди не считают должным разделять заголовки и исходный код по разным папкам, пеняя на "слишком сложную" организацию Include Directories. Только это не проблема среды, это такой у людей стиль организации рабочего пространства.
Спасибо за развёрнутый ответ. Может вы подскажите, каким образом можно быстро раскидывать файлы из сторонней библиотеки по фильтрам? Например я добавил заголовки библиотеки, они заполнили всю папку Headers и теперь не видно где чужое, где моё.
Максим Черепанцев , интересный вопрос. Лично мое мнение - каждой библиотеке свой проект! Сделай для библиотеки проект статической библиотеки, да и наладь зависимости с основным проектом.
Более того, внутри решения (solution) так же можно создавать фильтры для проектов. Это дает возможность сгруппировать проекты по тематике. Third Party - в одну кучку, Static Libraries - в другую, а основные проекты можно и в корне решения оставить.
Я предпочитаю высокий уровень организации как кода, так и структуры проекта в файловой системе. Поэтому, с моей стороны, разделение большого массива файлов на проекты в своих отдельных папках с сильным делением на папки файлов проекта, заголовков и исходного кода - бесспорно удобное и качественное решение.
Читайте также: