Что такое код asp asperrorstontlog

Содержание

Что такое код asp asperrorstontlog

Самая актуальная документация по Visual Studio 2020: Документация по Visual Studio 2020.

Отладка исключений является важной частью разработки надежного приложения ASP.NET. Общая информация об отладке исключений находится в разделе Управление исключениями с помощью отладчика.

Для отладки необработанных исключений ASP.NET необходимо убедиться, что отладчик останавливается на них. Среда выполнения ASP.NET имеет обработчик исключений верхнего уровня. Поэтому отладчик по умолчанию никогда не прерывается при возникновении необработанных исключений. Чтобы переключиться в режим отладчика при создании исключения, необходимо выбрать параметр Прерывание выполнения при исключении: Создание для этого конкретного исключения в диалоговом окне Исключения.

Если включен «Только мой код», параметр Прерывание выполнения при исключении: Создание не вызовет немедленное прерывание отладчика в том случае, если исключение создается методом платформы .NET Framework или другим системным кодом. Вместо этого продолжится выполнение, пока отладчик не достигнет несистемного кода, после чего он останавливается. В результате этого отсутствует необходимость перебора системного кода при возникновении исключения.

С помощью режима «Только мой код» предоставляется другая, более полезная возможность: Прерывание выполнения при исключении. Не обработанное пользовательским кодом. При выборе для исключения этого параметра отладчик остановит выполнение пользовательского кода, но только в случае, если исключение не будет перехвачено и обработано пользовательским кодом. Этот параметр устраняет эффект обработчика исключений верхнего уровня ASP.NET, потому что этот обработчик содержится в непользовательском коде.

Включение отладки исключений ASP.NET с помощью режима «Только мой код»

В меню Отладка выберите пункт Исключения.

Появится диалоговое окно Исключения.

В строке Исключения среды CLR выберите Выданные или Не обработанные пользовательским кодом.

Для использования параметра Не обработанное пользовательским кодом должен быть включен режим Только мой код.

Введение в ASP.NET Core¶

В данной теме представлены новые концепции в ASP.NET Core, и здесь рассказывается, как разрабатывать современные веб приложения.

Что такое ASP.NET Core?¶

ASP.NET Core — это кроссплатформенный фреймворк с открытым исходным кодом для создания современных облачных веб приложений. Приложения ASP.NET Core могут быть запущены под`.NET Core `__ или под полной версией .NET Framework. Фреймворк состоит из модульных компонентов, что дает вам гибкость при создании решений. Вы можете разрабатывать и запускать ASP.NET Core приложения под Windows, Mac и Linux. ASP.NET Core имеет открытый исходный код на GitHub.

Почему ASP.NET Core?¶

Первый предварительный релиз ASP.NET появился почти 15 лет назад как часть .NET Framework. С тех пор миллионы разработчиков использовали его для создания и запуска веб приложений, и теперь он обладает гораздо большими возможностями.

В ASP.NET Core произошло большое число архитектурных изменений, в результате чего фреймворк стал компактным и модульным. ASP.NET Core не основывается на System.Web.dll. Он основывается на наборе пакетов NuGet. Это позволяет вам оптимизировать приложение, чтобы оно включало только те пакеты NuGet, которые вам нужны.

С ASP.NET Core вы получаете следующие фундаментальные улучшения:

  • Единую историю для сборки веб UI и веб API
  • Интеграцию современных клиентских фреймворков и рабочих процессов разработки
  • Облачную конфигурационную систему
  • Встроенное внедрение зависимостей
  • Новый легкий модульный поток HTTP запросов
  • Возможность хостинга на IIS или хостинга в самом процессе
  • Встроенный `.NET Core`_
  • Конструкцию в виде пакетов `NuGet`_
  • Новый инструментарий, который упрощает разработку
  • Возможность кроссплатформенного запуска ASP.NET приложений под Windows, Mac и Linux
  • Открытый исходный код

Анатомия приложения¶

Приложение ASP.NET Core — это просто консольное приложение, которое создает веб сервер в своем методе Main :

Microsoft.AspNetCore.Hosting.WebHostBuilder` , который следует паттерну сборки для создания хоста веб приложения. У паттерна есть методы, которые определяют веб сервер (например, UseKestrel ) и класс для запуска ( UseStartup ). В примере выше используется веб сервер Kestrel, но мы можем указать и другие серверы. В следующем разделе мы подробнее рассмотрим UseStartup . WebHostBuilder предлагает много дополнительных методов, включая UseIISIntegration для хостинга на IIS и IIS Express и UseContentRoot для указания корневой директории контента. Методы Build и Run создают IWebHost , который будет хостить приложение, и оно начнет слушать входящие HTTP запросы.

Startup¶

Метод UseStartup для WebHostBuilder указывает класс Startup для вашего приложения.

В классе Startup вы определяете поток обработки запросов, а также здесь настраиваются все сервисы, необходимые вашему приложению. Класс Startup должен быть открытым и содержать следующие методы:

  • ConfigureServices определяет, используемые вашим приложением (например, ASP.NET MVC Core, Entity Framework Core, > Configure определяет связующее ПО в потоке запросов
  • См. Запуск приложения

Сервисы¶

Сервис — это компонент для общего пользования в приложении. Сервисы доступны благодаря внедрению зависимостей. ASP.NET Core включает в себя встроенный IoC контейнер, который по умолчанию поддерживает внедрение конструктора, но вы можете легко заменить его IoC контейнером по вашему выбору. В дополнение к преимуществу слабого связывания, DI делает так, что сервисы доступны всему приложению. Например, везде доступно логирование . См. Внедрение зависимостей (Dependency Injection) .

Связующее ПО¶

В ASP.NET Core сы составляете поток запросов, используя Связующее ПО (Middleware) . Связующее ПО ASP.NET Core выполняет асинхронную логику для HttpContext , а затем либо вызывает следующее связующее ПО в цепочки, либо напрямую обрывает запрос. Обычно для связующего ПО используется “Use”, принимая зависимость для пакета NuGet и вызывая соответствующий метод расширения UseXYZ для IApplicationBuilder в методе Configure .

ASP.NET Core предлагает богатый набор связующего ПО:

С ASP.NET Core можно использовать любое связующее ПО, основанное на OWIN. См. Open Web Interface for .NET (OWIN) .

Серверы¶

Хостинговая модель ASP.NET Core напрямую не слушает запросы — она полагается на серверную реализацию HTTP, чтобы передавать запросы приложению. Переданный запрос представляется как набор интерфейсов feature, которые приложение затем компонует в HttpContext . ASP.NET Core включает в себя кроссплатформенный веб сервер, Kestrel , который обычно запускается за производственным веб сервером, таким как IIS или nginx.

Корневая директория контента¶

Корневая директория — это основной путь к любому контенту, используемому в приложении, например, его представления и веб контент. По умолчанию корневая директория контента — это то же самое, что и основной путь приложения для выполняемого хостинга; альтернативу можно указать с помощью WebHostBuilder .

Корневая директория веб¶

Корневая директория веб (web root) — это директория для открытых статических ресурсов, таких как файлов css, js и файлов изображений. Связующее ПО статических файлов по умолчанию отрабатывает файлы только из этой директории (и поддиректорий). Путем директории является /wwwroot , но вы можете указать и другой путь с помощью WebHostBuilder .

Особенности ASP.NET

С выходом первой версии .NET Framework около десяти лет назад в сфере проектирования программного обеспечения появилось совершенно новое направление. Вдохновленные наилучшими возможностями Java, COM и веб-технологий и обученные на ошибках и ограничениях прежних технологий, разработчики в Microsoft решили полностью обновить свою платформу для разработки.

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

Сегодня ASP.NET пользуется небывалой популярностью, но особо революционной технологией уже больше не является. И хотя базовые функциональные возможности, лежащие в основе ASP.NET, на удивление, выглядят точно так же, как и десять лет назад, разработчики из Microsoft добавили к ним еще кое-какие дополнительные средства и абстракции кодирования более высокого уровня.

Также появилось и еще как минимум одно новое направление, составляющее конкуренцию традиционному программированию с использованием ASP.NET, которое получило название ASP.NET MVC.

Когда платформа ASP.NET была выпущена впервые, от предыдущих продуктов Microsoft и конкурирующих платформ ее отличали семь ключевых фактов. Тем, кто перешел на ASP.NET с какой-то другой платформы для разработки веб-приложений, или еще никогда не программировал в .NET приложений для веб-сети, материал следующих разделов позволит быстро получить общее представление об ASP.NET.

ASP.NET интегрируется с .NET Framework

Платформа .NET Framework делится на практически неповторимый ряд функциональных частей с десятками тысяч типов (в .NET так называются классы, структуры, интерфейсы и другие ключевые элементы программирования). Прежде чем пытаться программировать любое приложение .NET, необходимо сначала получить хотя бы общее представление об этих частях и о том, почему они организованы именно таким, а не каким-то другим образом.

То, как организована предлагаемая в .NET Framework обширная коллекция функциональности, программистам традиционных Windows-приложений, несомненно, покажется замечательным улучшением. Каждый из тысяч доступных в .NET Framework классов размещен в логическом иерархическом контейнере, который называется пространством имен (namespace).

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

Интересно то, что способ, которым классы .NET Framework можно использовать в ASP.NET, ничем не отличается от того, которым они применяются в приложениях .NET любого другого типа (в том числе автономных Windows-приложениях, Windows-службах, утилитах командной строки и т.д.).

Хотя в .NET предлагаются ориентированные специально на Windows- и на веб-приложения классы для построения пользовательских интерфейсов, большинство возможностей .NET Framework (начиная с получения доступа к базам данных и закачивания поддержкой многопоточной обработки) допускается использовать в приложениях любого типа. Другими словами, в .NET разработчикам веб-приложений предлагаются те же самые инструменты, что и разработчикам многофункциональных клиентских приложений.

Код ASP.NET компилируется, а не интерпретируется

Подобно всем приложениям .NET, приложения ASP.NET всегда компилируются. На самом деле выполнение кода на C# или Visual Basic без предварительной компиляции просто невозможно.

Приложения ASP.NET в действительности проходят через два этапа компиляции.

На первом этапе написанный код на C# компилируется в код на промежуточном языке, который называется MSIL (Microsoft Intermediate Language — промежуточный язык Microsoft), или просто IL. Этот первый этап как раз и является одной из главных причин, по которым в .NET могут использоваться самые разные языки.

Дело в том, что все языки .NET (в том числе C#, Visual Basic и многие другие), по сути, компилируются в практически идентичный код IL. Этот первый этап компиляции может происходить как автоматически при первом запросе страницы, так и выполняться заранее (это называется предварительной компиляцией). Скомпилированный файл с кодом на IL представляет собой сборку.

Второй этап компиляции происходит непосредственно перед фактическим выполнением страницы. На этом этапе код IL компилируется в код на низкоуровневом машинном языке. Называется этот этап оперативной (Just-In-Time — JIT) компиляцией и выглядит одинаково для всех приложений .NET (включая, например, Windows-приложения).

На следующем рисунке показан этот состоящий из двух этапов процесс компиляции:

Процесс компиляции в .NET разделен на два этапа для предоставления разработчикам как можно большего удобства и мобильности. Перед тем, как создавать код на низкоуровневом машинном языке, компилятор должен знать, в какой операционной системе и на каком базовом оборудовании будет выполняться приложение (например, будет это 32- или 64-разрядная ОС Windows). Благодаря выполнению таких двух этапов компиляции, можно создавать скомпилированную сборку с кодом .NET и по-прежнему распространять ее среди более, чем одной, платформы.

Разумеется, этап оперативной компиляции, пожалуй, не был бы столь полезным, если бы его нужно было выполнять при каждом запросе той или иной страницы веб-сайта. К счастью, приложения ASP.NET не нуждаются в выполнении компиляции при каждом запрашивании веб-страницы. Вместо этого код на IL в них создается один раз и генерируется заново только в случае изменения исходного кода, а файлы, содержащие машинный код, кэшируются в системном каталоге, путь к которому выглядит примерно так:

Конкретный момент, когда код компилируется в IL, зависит от того, каким образом создается и развертывается веб-приложение. В случае создания веб-приложения в виде веб-проекта в Visual Studio код компилируется в IL при компиляции проекта. В случае его создания в виде облегченного веб-сайта без проекта, код для каждой из его страниц компилируется при первом запросе соответствующей страницы. Но и в том, и в другом случае через второй этап компиляции (из IL в машинный код) код проходит лишь при первом его запуске.

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

В ASP.NET поддерживается множество языков программирования

Хотя при разработке приложения предпочтение, скорее всего, будет отдано какому-то одному языку, выбор языка никак не влияет на то, какие задачи с помощью приложения можно будет решать. Причина в том, что какой бы язык ни использовался, код всегда будет компилироваться в IL.

Язык IL является своего рода «трамплином» для любого управляемого приложения. Под управляемым, (managed) приложением понимается любое приложение, которое пишется для .NET и выполняется внутри управляемой среды CLR.

В некотором смысле IL даже можно назвать языком .NET, потому что он является единственным языком, который распознает исполняющая среда CLR.

Чтобы лучше понять, что собой представляет IL, не помешает рассмотреть простой пример. Поэтому давайте возьмем следующий код на языке C#:

Этот код показывает, как может выглядеть самое простое из всех возможных в .NET приложений, а именно — простая утилита командной строки, которая отображает в окне консоли предсказуемое сообщение.

Теперь посмотрим на него с другой стороны. Ниже приведен код IL для его метода Main():

Просмотреть IL-код для любого скомпилированного приложения .NET довольно легко. Подробнее читайте об этом в статье — Промежуточный язык CIL.

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

Если же подобные проблемы все-таки беспокоят, рассмотрите вариант применения маскировщика (обфускатора) для запутывания кода и превращения его в более трудный для понимания. (Маскировщик, например, может переименовать все переменные и назначить им имена вроде f__а__234). В состав Visual Studio входит ограниченная версия популярного маскировщика Dotfuscator.

Важно отметить, что IL был недавно признан стандартом Ecma и ISO. Это может послужить толчком к скорому признанию таковым и других общеязыковых платформ, предназначенных для использования на других платформах. Наилучшим примером является проект Mono.

ASP.NET обслуживается средой CLR

Пожалуй, наиболее важным аспектом механизма ASP.NET является то, что функционирует он внутри исполняющей среды CLR. Все части .NET Framework — т.е. все пространства имен, приложения и классы — называются управляемым кодом. Ниже перечислены некоторые из предоставляемых ею преимуществ:

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

При каждом создании в приложении экземпляра объекта ссылочного типа CLR выделяет для него в памяти соответствующее пространство в управляемой куче. Вручную очищать это пространство в памяти никогда не потребуется. При выходе ссылки на объект за пределы области видимости (или завершении работы приложения) объект сразу же становится доступным для сборщика мусора.

Сборщик мусора периодически запускается внутри CLR и автоматически восстанавливает неиспользуемую память, которую занимают более недоступные объекты. Подобная модель избавляет от необходимости иметь дело с низкоуровневыми деталями манипулирования памятью в C++ и запутанного подсчета ссылок в СОМ.

Безопасность типов

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

Расширяемые метаданные

Информация о классах и членах является только одним из типов метаданных, которые .NET может сохранять в скомпилированной сборке. Метаданные описывают код и позволяют предоставлять дополнительную информацию исполняющей среде и другим службам. Например, эти метаданные могут указывать отладчику, как следует выполнять трассировку кода, или же сообщать Visual Studio о том, как во время проектирования должен отображаться какой-то специальный элемент управления. Они также могут использоваться для активизации других служб во время выполнения, например, запуска транзакций или пула объектов.

Структурированная обработка ошибок

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

Многопоточностъ

Среда CLR предоставляет пул потоков, которые различные классы могут использовать. Например, можно вызывать методы, считывать файлы и взаимодействовать с веб-службами асинхронным образом, не создавая явно никаких новых потоков.

ASP.NET является объектно-ориентированной технологией

Технология ASP предоставляет довольно слабую объектную модель. Она поддерживает весьма небольшой набор объектов, которые являются просто тонким уровнем, скрывающим детали HTTP и HTML.

Напротив, ASP.NET является самой настоящей объектно-ориентированной технологией. Она не только предоставляет коду полный доступ ко всем объектам .NET Framework, но и позволяет использовать все концепции объектно-ориентированного программирования (ООП). Например, она позволяет создавать пригодные для повторного использования классы, стандартизировать код с помощью интерфейсов, расширять существующие классы за счет наследования и объединять полезные функциональные возможности в распространяемый скомпилированный компонент.

Одним из наилучших примеров поддержки объектно-ориентированного поведения в ASP.NET являются серверные элементы управления. Эти элементы управления представляют собой инкапсуляцию в миниатюре. Разработчики могут манипулировать объектами элементов управления программно с использованием кода для настройки их внешнего вида, предоставления отображаемых данных и даже реагирования на события.

Вся низкоуровневая HTML-разметка, которую визуализируют эти элементы управления, скрывается из вида. Вместо того чтобы вынуждать разработчика писать низкоуровневый код HTML-разметки вручную, объекты этих элементов управления сами преобразуются в соответствующие HTML-элементы непосредственно перед отправкой веб-сервером страницы клиенту.

Таким образом, серверные элементы управления в ASP.NET позволяют абстрагироваться от низкоуровневых деталей программирования HTML и HTTP.

Ниже приведен небольшой пример со стандартным текстовым полем HTML, которое можно определить в веб-странице ASP.NET:

После добавления атрибута runat=»server» этот статический фрагмент HTML превращается в полностью функциональный серверный элемент управления, которым можно манипулировать в коде. После этого можно работать с генерируемыми им событиями, устанавливать атрибуты и привязывать его к источнику данных.

Например, установить текст для этого поля при первой загрузке страницы можно с помощью следующего кода C#:

В этом коде устанавливается свойство Value объекта HtmlInputText. В конечном итоге указанная строка появится в текстовом поле на визуализируемой и отправляемой клиенту HTML-странице.

HTML- и веб-элементы управления

На момент создания ASP.NET существовало два типа мышления. Одних разработчиков ASP.NET больше интересовали серверные элементы управления, которые бы в точности соответствовали существующему набору элементов управления HTML. Такой подход позволяет создавать интерфейсы для веб-страниц ASP.NET в редакторах HTML и предоставляет быстрый путь для миграции существующих страниц ASP.

Однако другие разработчики ASP.NET видели будущее в другом — в многофункциональных серверных элементах управления, которые бы не просто имитировали отдельные дескрипторы HTML. Эти элементы управления могли бы визуализировать свой интерфейс из десятков отдельных элементов HTML и при этом все равно предоставлять программистам простой объектный интерфейс для работы и позволять им использовать программируемые меню, календари, списки данных, средства проверки данных и т.д.

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

В большинстве случаев серверные элементы управления HTML применяются для обеспечения обратной совместимости и выполнения быстрой миграции, а веб-элементы управления — для создания новых проектов.

Дескрипторы веб-элементов управления в ASP.NET всегда начинаются с префикса asp:, за которым следует имя класса. Ниже показан пример создания текстового поля и флажка:

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

Обратите внимание, что свойство Value, которое использовалось в случае элемента управления HTML, здесь заменено свойством Text. Свойство HtmlInputText.Value было переименовано в соответствии со значением базового атрибута в HTML-дескрипторе . В веб-элементах управления акцент на преемственности с синтаксисом HTML не делается, поэтому для свойства вместо этого применяется такое более описательное имя, как Text.

В состав семейства веб-элементов управления в ASP.NET входят как сложные визуализируемые элементы управления (вроде Calendar и TreeView), так и более упрощенные элементы управления (наподобие TextBox, Label и Button), которые довольно близко отображаются на существующие дескрипторы HTML. В последнем случае серверные HTML-элементы управления и их веб-варианты в ASP.NET предоставляют похожую функциональность, но веб-элементы управления делают это через более стандартизированный и упрощенный интерфейс.

В результате веб-элементы управления проще в освоении, а также более естественно подходят разработчикам Windows-приложений, которые переходят на разработку веб-приложений, потому что многие из имен их свойств похожи на имена соответствующих элементов управления Windows.

ASP.NET поддерживает все браузеры

Одним из самых больших испытаний для разработчиков веб-приложений является необходимость в обеспечении поддержки множества различных браузеров. В разных браузерах, версиях и конфигурациях технологии HTML, CSS и JavaScript поддерживаются по-разному. Разработчики веб-приложений должны выбирать, визуализировать содержимое в соответствии с наименьшими общими характеристиками или добавлять фрагменты для принятия во внимание специфических особенностей популярных браузеров.

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

Эти серверные элементы управления генерируют разметку адаптивным образом, принимая во внимание все возможности клиента. Примером могут служить предлагаемые в ASP.NET элементы управления верификацией, которые используют JavaScript и DHTML (динамический HTML) для улучшения своего поведения в случае, если они поддерживается клиентом.

Другим примером является набор оснащенных Аjах элементов управления, которые применяют сложные процедуры JavaScript для проверки версий браузеров и используют тщательно протестированные обходные пути для обеспечения согласованного поведения. Эта функциональность является необязательной, но демонстрирует то, как интеллектуальные элементы управления могут извлекать максимум выгоды из современных браузеров без отгораживания от других клиентов. Лучше всего то, что никакого дополнительного кода для обеспечения поддержки обоих типов клиента писать не требуется.

ASP.NET позволяет легко выполнять развертывание и конфигурирование

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

Каждая установленная копия .NET Framework обеспечивает те же самые ключевые классы. Благодаря этому процедура развертывания приложения ASP.NET выполняется относительно просто. Часто достаточно всего лишь скопировать все файлы в виртуальный каталог на производственном сервере (с помощью программы FTP или даже утилиты командной строки вроде XCOPY).

Если на целевой машине имеется копия .NET Framework, то предпринимать какие-то трудоемкие действия по регистрации не понадобится.

Распространение компонентов, которые используются в приложении, столь же просто. Для этого при развертывании веб-приложения нужно скопировать сборки компонентов вместе с файлами веб-сайта. Благодаря тому, что вся информация о компонентах сохраняется прямо в метаданных внутри файлов сборок, запускать программу регистрации и вносить изменения в системный реестр Windows не требуется.

При условии размещения всех этих компонентов в правильном месте (подкаталог Bin в каталоге веб-приложения), механизм ASP NET автоматически обнаруживает их и делает доступными для кода веб-страницы. С традиционными компонентами СОМ подобное было невозможно.

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

В ASP NET решение этой задачи тоже упрощено за счет минимизации зависимости от настроек IIS (Internet Information Services — информационные службы Интернета).

Что такое код asp asperrorstontlog

Этот текст предназначен для тех, кто никогда не имел дела с ASP и вообще смутно себе представляет возможности программирования на стороне сервера. Я ставил себе задачу создать у читателя общее представление о предмете. Отдельные неточности при этом менее важны — пожалуйста, громко не ругайтесь.

Общие сведения

ASP (Active Server Pages) – это мощная технология от Microsoft, позволяющая легко разрабатывать приложения для WWW. ASP работает на платформе Windows NT и IIS (Internet Information Server), начиная с версии 3, хотя вроде есть реализации на других платформах. ASP – это не язык программирования, это внутренняя технология, позволяющая подключать программы к Web-страницам. Основа успеха ASP – простой скриптовый язык (Visual Basic Script или Java Script) и возможность использования внешних COM-компонент.

Как это все происходит?

Вы пишете программу и складываете в файл на сервере. Браузер клиента запрашивает файл. Файл сначала интерпретируется сервером, на выходе производится HTML-код. Этот HTML посылается клиенту. Файлы с программами имеют расширение .asp. Файлы asp – это обычные текстовые файлы, содержащие исходные тексты программ. Файлы делаются с помощью любого текстового редактора. Каталог, в котором размещены файлы asp должен иметь права на выполнение, так как сервер исполняет эти файлы, когда браузер их запрашивает. Собственно программы пишутся на любом скриптовом языке, который установлен в системе. По умолчанию поддерживаются VBScript и JavaScript. Можно доустановить другие (например, Perl). Если ничего специально не указывать используется VBScript. В дальнейшем будем ссылаться только на него. Программные фрагменты заключаются в скобки . Можно ставить открывающую скобку в начале файла, закрывающую – в конце, все что между ними – программа на Visual Basic’е.

Какие средства есть для программирования?

Web – нормальная среда программирования, если правильно понять, что есть что. В VBScript есть все нормальные конструкции структурного программирования (if, while, case, etc). Есть переменные (описывать не обязательно, тип явно не задается). Поддерживаются объекты. Работа с ними обычная – Object.Property, Object.Method. Есть ряд встроенных объектов (Request, Response, Session, Server, Connection, Recordset). Можно доустанавливать другие компоненты (скачивать, покупать, программировать), например для работы с электронной почтой.

Вывод

Понятия «экран», куда можно выводить данные нет. Все, что надо показать пользователю, выбрасывается в выходной поток на языке HTML. Браузер пользователя интерпретирует этот HTML. Для упрощения вывода существует объект Response . Вывод осуществляется с помощью метода Write .

Илон Маск рекомендует:  Маршрутизация в AngularJS

Так производится запись во внутренний буфер объекта Response. Когда скрипт заканчивает работу, весь буфер выдается клиенту. Надо заметить, что клиент получает «чистый» HTML, таким образом программы на ASP не зависят от клиентского ПО, что очень важно. Если внутри выводимой строки нужно использовать кавычку, кавычка удваивается. Другие методы и свойства Response позволяют управлять выводом. Так Response.Buffer регулирует, получает ли клиент данные по мере из записи в Response, или все сразу по завершении исполнения страницы. Метод Response.Redirect перенаправляет браузер на другую страницу. Чтобы им пользоваться, нельзя до него на странице использовать Response.Write.

Программа на ASP не может явно спросить пользователя о чем-то. Она получает данные из других страниц, либо через URL. Передаваемые параметры помещаются во входной поток и доступны через объект Request . Чтобы передать переменную var в программу test.asp , надо написать:

Чтобы из программы получить значение этой переменной, надо написать:

Несколько переменных разделяется знаком &:

Кроме того, чтобы задавать параметры в URL, можно воспользоваться формами HTML. В вызывающей странице пишем так:

Так это выглядит:

При этом пользователь увидит форму из одного поля ввода (var1), в нем будет значение по умолчанию «default». Второе поле (var2) будет невидимо и будет передавать всегда фиксированное значение «var2value». Кнопка «Submit Form» завершает заполнение формы и передает все переменные на test.asp (action). Если method=»get», переменные передаются через URL (test.asp?var1=default&var2=var2value). Если method=»post», передаются вместе с запросом так, что внешне передача переменных не заметна. В вызываемой программе безразлично, какой метод изпользовался (почти). Если у вас нет специальных аргументов за метод GET, используйте метод POST.

Формы

Формы HTML используются для организации диалога с пользователем. Поддерживаются стандартные элементы управления. Все многообразие задается немногими тэгами:

  • INPUT (с параметром TYPE=)
  • SELECT
  • TEXTAREA

Описание – в документации по HTML.

Взаимосвязь между отдельными страницами

Обычно сервер WWW не хранит состояние приложения, т.е. все запросы взаимонезависимы, и нет стандартного способа понять, что несколько запросов пришли от одного и того же пользователя. Но это очень нужно для разработки полноценных приложений и является одной из главных проблем разработки Web-приложений.

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

ASP, используя cookies, предоставляет программисту более простое средство — объект Session (сессия). Сессия стартует, когда новый пользователь обращается к любому asp-файлу приложения. Сессия заканчивается при отсутствии активности пользователя в течение 20 минут, либо по явной команде. Специальный объект Session хранит состояние сессии. Туда можно записывать переменные, которые доступны из любой страницы в этой сессии. Записать данные в этот объект можно просто:

Считать потом еще проще:

Сессия, таким образом, – это еще один метод передачи данных между страницами. Одна страница пишет данные в сессию, другая – берет потом оттуда.

Наряду с объектом Session существует объект Application . Если сессия создается для каждого нового пользователя, до Application существует в единственном экземпляре, и может использоваться всеми страницами приложения.

Управление приложением

Программисту предоставляется возможность реагировать на 4 события: старт/стоп приложения и старт/стоп каждой сессии. Для реализации этих событий предназначен файл global.asa , который должен располагаться в корневом каталоге приложения. Вот его примерный скелет:

Нужно «просто» вписать Ваш код на соответствующее место. Нужно заметить, что отлаживать код для global.asa довольно непросто, так как он выполняется при очень специфических обстоятельствах (к примеру при старте или остановке сервера).

Использование внешних компонент

Если на сервере установлены дополнительные компоненты, их можно использовать из ASP. Стандартные объекты (например из библиотек ADO (Connection и Recordset) и Scripting (Dictionary, FileSystemObject)) доступны всегда. Установка новой компоненты обычно состоит в копировании dll-файла в каталог на сервере и ее регистрации с помощью программы regsvr32.exe. [В COM+ используется своя процедура инсталляции объектов, это однако не влияет на использования объектов.]

Создать экземпляр объекта можно так:

Class.Object указываются в документации на компоненту. В переменной var запоминается ссылка на созданный экземпляр объекта. Когда объект не нужен, ссылку нужно обнулить с помощью команды:

Пожалуйста всегда обнуляйте все ссылки на объекты, когда они больше не нужны. Теоретически это должно происходить автоматически при завершении процедуры/страницы, однако в стандартной сборке мусора есть определенные «проблемы».

В остальном использование компоненты зависит от самой этой компоненты.

Работа с базами данных

Из ASP можно легко и просто работать с любыми базами данных. Это делается через две промежуточные технологии: ODBC и ADO.

ODBC позволяет организовать доступ к любым базам данных через унифицированный интерфейс с помощью языка SQL. Специфика конкретных СУБД учитывается при помощи специальных драйверов БД. Такие драйверы существуют для всевозможных СУБД (в частности SQL Server, Oracle, Access, FoxPro). Поддержка ODBC обеспечивается на уровне операционной системы Windows (NT). Настройка – через Control Panel/ODBC. Базовым понятием является источник данных или data source. Источник данных – это совокупность сведений о базе данных, включая ее драйвер, имя компьютера и файла, параметры. Чтобы пользоваться базой надо создать источник данных для нее. Важно, чтобы источник данных был «системным», в отличии от «пользовательского». После этого надо лишь знать имя источника данных. [В настоящее время ODBC отступает перед натиском технологии OLE DB. На практике это однако практически ничего не изменяет. Вместо имени источника данных нужно использовать Connection String, в которой указывается имя ODBC-драйвера и все его параметры.]

ADO – это совокупность объектов, доступных из ASP, позволяющих обращаться к источнику данных ODBC [или OLE DB]. Фактически нужны лишь 2 объекта – Connection , представляющий соединение с базой данных и Recordset , представляющий набор записей, полученный от источника. Сначала необходимо открыть соединение, потом к нему привязать Recordset, потом, пользуясь методами Recordset’а, обрабатывать данные. Вот пример:

Если команда SQL не возвращает данных, recordset не нужен, надо пользоваться методом Conn. Execute (SQL_COMMAND).

Если Вы хотите вызывать хранимые процедуры сервера БД с параметрами, нужно воспользоваться объектом Command , который в свою очеред содержит объекты Parameter .

Методики программирования, советы


Описание переменных

VBScript — очень нетребовательный к программисту язык. Так он не требует описывать переменные и не содержит явных типов данных. Все переменные принадлежат одному типу Variant . Из-за отсутствия описаний могут произойти очень трудно обнаруживаемые ошибки. Одна опечатка может стоить полдня поисков.

Однако, есть возможность явно потребовать описания переменных. Для этого первой строкой в ASP-файле нужно написать Option Explicit . После этого обращение к переменной, которая не была объявлена с помощью Dim , вызывает ошибку с указанием номера строки.

Кстати, где расположены описания Dim в процедуре — совершенно не важно. Они могут стоять как до использования переменной, так и после, и даже в цикле. Видимо они отрабатываются препроцессором. Явно задать тип переменной с помощью Dim Var as Typ , как в Visual Basic, все равно нельзя.

Чередование ASP/HTML

Если нужно выдать большой кусок HTML, можно не пользоваться Response.Write. Если в asp-файле встречается кусок текста вне скобок , он трактуется просто как HTML, который надо вывести. Пример:

Обработка ошибок

Для отслеживания ошибок используется специальный объект Err . Он устанавливается в ненулевое значение, если предыдущая команда породила ошибку. Ее можно проверять с помощью If, и таким образом реагировать на ошибки. Чтобы из-за ошибки не прерывалось выполнение программы, в начале нужно включить команду

Включение других файлов

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

Важно: все includes в тексте отрабатываются до исполнения файла. Т.е. даже если include стоит внутри if, то сначала будут включены все includes во всех ветках, и только потом, во время исполнения, будет принятно решение, какую ветку выполнять. Т.е. следующий код не дает условного включения файлов:

Обработка форм

Если надо что-то спросить у пользователя и на основании этого что-то сделать, в простейшем случае создается два файла: один с формой, второй – с ее обработчиком. Обработчик выполняет все действия. Пример:

Рекурсивная обработка форм

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

Переменные HTTP

Запрос от браузера, кроме запрашиваемой страницы несет еще некоторые данные. Эти данные, например, IP-адрес клиента, доступны через специальные переменные объекта Request. IP-адрес – Request(«REMOTE_ADDR»). Другие — см.документацию (ASPSamp\Samples\srvvar.asp).

Переадресация

Очень легко написать на ASP скрипт, который будет производить некоторые расчеты, и в зависимости от результатов переадресовывать браузер на разные URL (например, подставлять нужный баннер). Делается это так:

Только надо следить, чтобы до выполнения команды redirect ничего не было записано в Response (даже коментарии HTML).

Электронная почта

Одна из часто встречающихся задач – отправить электронную почту с Web-страницы. На первый взгляд, можно просто написать

Но это приводит к тому, что при отправке формы делается попытка на клиентской машине запустить почтовую программу и создать новое сообщение с данными формы. Если это не получается (почтовая программа не настроена, пользователь не отправил почту, и т.д.) — письмо и не будет отправлено. Гораздо надежнее работает серверное решение.

Для этого существуют внешние компоненты, есть и бесплатные. Например, компонента Jmail от Dimac. Все, что для нее нужно – это адрес SMTP-сервера. Вот пример ее использования:

Почему ASP.NET не используют в крупных компаниях?

В настоящий момент С# (asp.net) имеет целый ряд преимуществ над тем, что нам дает Java (в плане удобства и синтаксиса языка). Это правда!

Проблема заключается в том, что Java появился раньше, Java был открытым для использования на Linux. В итоге под сервера на Java написали огромный список уникальных решений, которые сейчас используют топовые команды (посмотри, что такое хадуп, например). В итоге, компании либо не видели смысла переписывать все на C#, так как уже имели билды на Java, либо не хотели тратить время на разработку того, что уже есть на Java.

Именно по этой причине Microsoft сейчас активно начинает спариваться с Linux и везде кричит, что они его любят.

ASP.NET Core — Написание ясного кода в ASP.NET Core с использованием встраивания зависимостей

Эта статья основана на ASP.NET Core 1.0 предварительной версии RC1. Некоторая информация может быть изменена при выпуске версии RC2.

Продукты и технологии:

ASP.NET Core 1.0

В статье рассматриваются:

  • примеры жесткого связывания;
  • написание «честных» классов;
  • встраивание зависимостей в ASP.NET Core;
  • код с возможностью модульного тестирования;
  • тестирование обязанностей контроллера.

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

Жесткое связывание

Жесткое связывание (tight coupling) хорошо подходит для демонстрационного программного обеспечения. Если вы посмотрите на типичное приложение-пример, показывающее, как создавать сайты на основе ASP.NET MVC (версий 3–5), то скорее всего найдете код, подобный следующему (взят из класса DinnersController приложения-примера NerdDinner, использующего MVC 4):

Глядя на код, чтобы оценить степень его связывания, помните фразу «new является связующим».

Этот тип кода очень сложен в модульном тестировании, потому что NerdDinnerContext создается в процессе конструирования класса и требует подключения к базе данных. Неудивительно, что такие демонстрационные приложения нечасто включают какие-либо модульные тесты. Однако ваше приложение может выиграть от нескольких модульных тестов, даже если вы не занимаетесь разработкой на основе тестов, так что было бы лучше писать код, который можно было бы протестировать. Более того, этот код нарушает принцип Don’t Repeat Yourself (DRY) (принцип «не повторяйся»), поскольку каждый класс контроллера, так или иначе обращающийся к данным, содержит один и тот же код для создания EF-контекста базы данных (Entity Framework). Это делает внесение будущих изменений более дорогостоящим и подверженным ошибкам, особенно по мере развития приложения в течение длительного времени.

Глядя на код, чтобы оценить степень его связывания, помните фразу «new является связующим». То есть везде, где экземпляр класса создается с помощью ключевого слова new, ваша реализация связывается конкретно с этим кодом реализации. Dependency Inversion Principle (принцип инверсии зависимостей) (bit.ly/DI-Principle) утверждает: «Абстракции не должны зависеть от деталей — детали должны зависеть от абстракций». В этом примере детали того, как контроллер извлекает данные для передачи представлению, зависят от деталей того, как эти данные извлекаются, а именно от EF.

Вдобавок к ключевому слову new «статическое сцепление» является еще одним источником жесткого связывания, которое затрудняет тестирование и сопровождение приложений. В предыдущем примере имеется зависимость от системных часов компьютера в виде вызова DateTime.Now. Это связывание усложнило бы создание набора тестовых Dinners для использования в некоторых модульных тестах, так как их свойства EventDate пришлось бы устанавливать относительно текущему показанию часов. Это связывание можно было бы удалить из данного метода несколькими способами, самый простой из которых — переложить заботу об этом на какую-то новую абстракцию, возвращающую Dinners, чтобы это больше не было частью метода. В качестве альтернативы я мог бы сделать значение параметром, чтобы метод возвращал все Dinners после предоставленного параметра DateTime вместо использования только DateTime.Now. Наконец, можно было бы создать абстракцию для текущего времени и ссылаться на текущее время через эту абстракцию. Это может оказаться хорошим подходом, если приложение часто ссылается на DateTime.Now. (Кроме того, заметьте, что, поскольку эти обеды [dinners] предположительно происходят в разных часовых поясах, тип DateTimeOffset может быть более эффективным выбором в реальном приложении.)

Будьте честны

Другая проблема с сопровождением кода вроде этого — он нечестен со взаимодействующими с ним объектами. Вы должны избегать написания классов, экземпляры которых можно создавать в недопустимых состояниях, так как это является частым источником ошибок. Таким образом, все, что необходимо вашему классу для выполнения своих задач, должно предоставляться через его конструктор. Как утверждает Explicit Dependencies Principle (принцип явных зависимостей) (bit.ly/ED-Principle), «методы и классы должны явным образом требовать любые взаимодействующие объекты (collaborating objects), нужные им для корректной работы». Класс DinnersController имеет лишь конструктор по умолчанию, а это подразумевает, что ему не надо взаимодействовать с какими-либо объектами для корректной работы. Но что будет, если вы подвергнете его тесту? Что сделает этот код, если вы запустите его из нового консольного приложения, которое ссылается на MVC-проект?

Первое, что не удастся в этом случае, — попытка создать экземпляр EF-контекста. Код сгенерирует исключение InvalidOperationException: «No connection string named ‘NerdDinnerContext’ could be found in the application config file.» (в конфигурационном файле приложения не найдена строка подключения с именем ‘NerdDinnerContext’). Меня обманули! Для работы этому классу нужно больше, чем заявляет его конструктор! Если классу необходим какой-то способ доступа к наборам экземпляров Dinner, он должен запрашивать их через свой конструктор (или как параметры в своих методах).

Встраивание зависимостей

Встраивание зависимостей (dependency injection, DI) относится к передаче зависимостей класса или метода в виде параметров вместо «зашивания» в код этих связей через new или статические вызовы. Это набирающий популярность метод в .NET-разработке из-за обеспечения им разъединения приложений, в которых он применяется. В ранних версиях ASP.NET преимущества DI не использовались, и, хотя в ASP.NET MVC и Web API наблюдается прогресс в отношении его поддержки, ни одна из инфраструктур до сих пор не предоставляет полной поддержки, в том числе контейнер для управления зависимостями и жизненными циклами их объектов. В ASP.NET Core 1.0 DI не просто полностью поддерживается — оно повсеместно применяется в самом продукте.

В ASP.NET Core 1.0 DI не просто полностью поддерживается — оно повсеместно применяется в самом продукте.

ASP.NET Core не только поддерживает DI, но и включает DI-контейнер, также называемый контейнером Inversion of Control (IoC) или контейнером сервисов. Каждое приложение ASP.NET Core конфигурирует свои зависимости, используя этот контейнер, в методе ConfigureServices класса Startup. Данный контейнер обеспечивает необходимую базовую поддержку, но может быть заменен пользовательской реализацией, если в этом есть потребность. Более того, EF Core также имеет встроенную поддержку DI, поэтому ее конфигурирование в приложении ASP.NET Core сводится к простому вызову метода расширения. Для этой статьи я создал ответвление NerdDinner с именем GeekDinner. EF Core конфигурируется, как показано ниже:

После этого довольно легко запросить через DI экземпляр GeekDinnerDbContext от класса контроллера вроде DinnersController:

Заметьте, что нет ни одного экземпляра ключевого слова new; все зависимости, нужные контроллеру, передаются через его контроллер, и заботится об этом за меня DI-контейнер ASP.NET. В процессе написания приложения мне незачем беспокоиться об инфраструктуре, участвующей в разрешении зависимостей, запрашиваемых моими классами через свои конструкторы. Конечно, при желании можно изменить это поведение, даже заменить контейнер по умолчанию другой реализацией. Поскольку теперь мой класс контроллера следует принципу явных зависимостей, я знаю, что для его работы нужно предоставить экземпляр GeekDinnerDbContext. Выполнив небольшую подготовку для DbContext, я могу создать экземпляр контроллера сам по себе, как демонстрирует это консольное приложение:

При конструировании DbContext в EF Core требуется несколько больше работы, чем в EF6, где просто принималась строка подключения. Дело в том, что, как и ASP.NET Core, EF Core спроектирован в расчете на большую модульность. Обычно вам не понадобится иметь дело напрямую с DbContextOptionsBuilder, так как он используется «за кулисами», когда вы конфигурируете EF через методы расширения вроде AddEntityFramework и AddSqlServer.

А можно ли это протестировать?

Тестирование приложения вручную является важным — вам нужна возможность запустить его и убедиться, что оно действительно запускается и дает ожидаемый вывод. Но поступать так всякий раз, когда вносится какое-то изменение, — пустая трата времени. Одно из значимых преимуществ свободно связанных приложений в том, что они, как правило, лучше поддаются модульному тестированию, чем жестко связанные. Еще важнее, что ASP.NET Core и EF Core гораздо проще в тестировании, чем их предшественники. Для начала я напишу простой тест, который напрямую передает в контроллер какой-то DbContext, который был сконфигурирован на использование хранилища в памяти. Я настрою GeekDinnerDbContext, используя параметр DbContextOptions, который предоставляется этим контекстом через конструктор:

Сконфигурировав это в тестовом классе, легко написать тест, показывающий, что Model в ViewResult возвращает корректные данные:

Конечно, здесь пока что мало логики для тестирования, поэтому данный тест ничего особенного и не проверяет. Критики возразили бы, что этот тест малозначим, и я согласился бы с ними. Однако это отправная точка для будущего теста, когда появится больше логики, что и будет сделано в самом ближайшем будущем. Но сначала, хоть EF Core и поддерживает модульное тестирование в памяти, я все же позабочусь о предотвращении прямого связывания с EF в своем контроллере. Нет никаких причин для связывания обязанностей UI с обязанностями инфраструктуры доступа к данным — по сути, это нарушило бы другой принцип, Separation of Concerns (принцип разделения обязанностей).

Избегайте зависимости от того, что не используется

Принцип отделения интерфейса (Interface Segregation Principle) (bit.ly/LS-Principle) утверждает, что классы должны зависеть только от той функциональности, которую они действительно используют. В случае нового DinnersController с поддержкой DI тот по-прежнему зависит от всего DbContext. Вместо склеивания реализации контроллера с EF можно было бы задействовать некую абстракцию, которая предоставляла бы необходимую функциональность.

Что на самом деле нужно этому методу действия (action method) для должного функционирования? Определенно не весь DbContext. Ему даже не требуется доступ к полному свойству Dinners контекста. Ему достаточно возможности отображать экземпляры Dinner соответствующей страницы. Простейшая .NET-абстракция, представляющая это, — IEnumerable . Поэтому я определю интерфейс, который просто возвращает IEnumerable , и это удовлетворит (большую часть) требований метода Index:

Я называю это репозитарием, поскольку он следует такому шаблону: он абстрагирует доступ к данным интерфейсом, подобным набору. Если по какой-то причине вам не нравится шаблон репозитария или имя, вы можете назвать его IGetDinners, IDinnerService или как угодно иначе (мой рецензент предложил ICanHasDinner). Независимо от того, как вы назовете этот тип, он будет служить той же цели.

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

Покончив с этим, теперь подстроим DinnersController для приема IDinnerRepository в качестве параметра конструктора вместо GeekDinnerDbContext и вызова метода List вместо прямого обращения к Dinners DbSet:

К этому моменту можно скомпилировать и запустить ваше веб-приложение, но вы столкнетесь с исключением, если перейдете к /Dinners: InvalidOperationException («Unable to resolve service for type ‘GeekDinner.Core.Interfaces.IdinnerRepository’ while attempting to activate GeekDinner.Controllers.DinnersController.») («Не удалось разрешить сервис для типа ‘GeekDinner.Core.Interfaces.IdinnerRepository’ при попытке активировать GeekDinner.Controllers.DinnersController.»). Я пока что не реализовал интерфейс, и, как только это будет сделано, мне также понадобится сконфигурировать свою реализацию для использования, когда DI будет выполнять запросы к IDinnerRepository. Реализация этого интерфейса тривиальна:

Заметьте, что прямое связывание реализации репозитария с EF совершенно нормально. Если мне понадобится заменить EF, я просто создам новую реализацию интерфейса. Этот класс реализации является частью инфраструктуры моего приложения, которая представляет собой одно из мест в приложении, где мои классы зависят от конкретных реализаций.

Чтобы сконфигурировать ASP.NET Core на встраивание правильной реализации, когда классы запрашивают IDinnerRepository, нужно добавить следующую строку кода в конец ранее показанного метода ConfigureServices:

Это выражение инструктирует DI-контейнер ASP.NET Core использовать экземпляр DinnerRepository всякий раз, когда требуется разрешить тип, зависимый от экземпляра IDinnerRepository. Scoped означает, что для каждого веб-запроса, обрабатываемого ASP.NET, будет использоваться один экземпляр. Также можно добавлять сервисы, указывая жизненные циклы Transient или Singleton. В данном случае подходит Scoped, поскольку мой DinnerRepository зависит от DbContext, который тоже использует жизненный цикл Scoped. Вот краткое описание доступных сроков жизни объектов.

  • Transient Используется новый экземпляр типа всякий раз, когда запрашивается этот тип.
  • Scoped При первом запросе в рамках данного HTTP-запроса создается новый экземпляр типа, а затем он повторно используется для всех последующих типов, разрешаемых при этом HTTP-запросе.
  • Singleton Единственный экземпляр типа создается один раз и используется всеми последующими запросами для этого типа.

Встроенный контейнер поддерживает несколько способов конструирования типов, которые он будет предоставлять. Самый типичный случай — простая передача типа контейнеру, после чего тот попытается создать экземпляр этого типа, предоставляя ему любые зависимости, которые тип запрашивает по мере выполнения. Вы также можете указать лямбда-выражение для конструирования типа или — в случае жизненного цикла Singleton — передать полностью сконструированный экземпляр в ConfigureServices при его регистрации.

После подключения DI приложение выполняется, как и раньше. Теперь, как показано на рис. 1, я могу тестировать его с применением новой абстракции, используя имитацию или заглушку реализации интерфейса IDinnerRepository вместо того, чтобы напрямую полагаться на EF в коде теста.

Рис. 1. Тестирование DinnersController, используя имитирующий объект

Этот тест работает независимо от того, откуда берется список экземпляров Dinner. Вы могли бы переписать код для доступа к данным, чтобы использовать другую базу данных, Azure Table Storage или XML-файлы, а контроллер все равно работал бы точно так же. Конечно, в данном случае он не делает ничего особенного, поэтому вам, возможно, интересно…

А как насчет реальной логики?

До сих пор я не реализовал никакой реальной бизнес-логики — у меня были лишь простые методы, возвращающие несложные наборы данных. Истинная ценность тестирования проявляется только при наличии логики и особых случаев, в которых вы должны убедиться, что приложение будет вести себя ожидаемым образом. Чтобы продемонстрировать это, я добавлю некоторые требования к своему сайту GeekDinner. Сайт будет предоставлять API, который позволит кому угодно отвечать на приглашение на обед. Однако для числа приглашаемых будет дополнительно задан максимум, и количество ответов на приглашение (RSVP) не должно превышать этот максимум. Пользователи, запрашивающие RSVP, когда максимум уже достигнут, должны добавляться в список ожидания. Наконец, желающие пообедать могут указывать крайний срок относительно их начального времени, по истечении которого они не станут принимать приглашения.

Я мог бы закодировать всю эту логику в метод действия (action), но считаю, что тогда на один метод было бы возложено слишком много обязанностей, особенно на UI-метод, который должен концентрироваться на задачах, связанных с UI, а не на бизнес-логике. Контроллер должен проверять, что ему передаются допустимые входные аргументы, и гарантировать возврат ответов, подходящих для клиента. Решения, выходящие за рамки этих обязанностей, и особенно бизнес-логика не относятся к компетенции контроллеров.

Лучшее место для размещения бизнес-логики — модель предметной области приложения, которая не должна зависеть от обязанностей инфраструктуры (вроде работы с базами данных или UI). Класс Dinner подходит для управления RSVP, описанными в требованиях, поскольку он будет хранить максимальное количество участников мероприятия и знать, сколько RSVP было выдано на данный момент. Однако часть логики также зависит от того, когда появляется RSVP (до или после крайнего срока), поэтому методу понадобится доступ к текущему времени.

Я мог бы просто использовать DateTime.Now, но это затруднило бы тестирование моей логики и связало бы модель предметной области с системными часами. Другой вариант — задействовать абстракцию IDateTime и встроить ее в сущность Dinner. Однако, как показывает мой опыт, лучше всего сохранять сущности вроде Dinner свободными от зависимостей, особенно если вы планируете применять какое-то O/RM-средство наподобие EF для извлечения этих сущностей с уровня хранения. В связи с этим я не хочу заполнять сущности зависимостями, да и EF определенно не смогла бы иметь с этим дело без дополнительного кода с моей стороны. Распространенный подход — изъятие логики из сущности Dinner и перенос ее в какой-либо сервис (вроде DinnerService или RsvpService), в который можно легко встраивать зависимости. Однако это обычно приводит к антишаблону анемичной модели предметной области (anemic domain model antipattern) (bit.ly/anemic-model), в которой сущности имеют очень мало логики (или вообще не имеют ее) и являются просто контейнерами состояния. Нет, в данном случае решение прямолинейное: метод может просто принимать текущее время как параметр и позволять вызывающему коду передавать его.

При таком подходе логика добавления RSVP проста (рис. 2). Для этого метода есть ряд тестов, которые демонстрируют, что он ведет себя, как ожидалось; тесты доступны в проекте-примере, сопутствующем этой статье.

Рис. 2. Бизнес-логика в модели предметной области

Переместив эту логику в модель предметной области, я добился того, что API-метод моего контроллера останется компактным и сконцентрированным на собственных обязанностях. В итоге можно легко протестировать, что контроллер делает то, что ему положено, поскольку через этот метод проходит сравнительно немного путей выполнения.

Обязанности контроллера

Часть обязанностей контроллера — проверка ModelState и обеспечение его допустимости. Для ясности я делаю это в методе действия, но в более крупном приложении я исключил бы этот повторяющийся код в каждом методе действия, используя Action Filter:

Предполагая, что ModelState допустим, метод действия должен извлечь соответствующий экземпляр Dinner по идентификатору, переданному в запросе. Если метод не может найти экземпляр Dinner с совпадающим идентификатором, он должен вернуть результат Not Found:

По завершении этих проверок метод действия может делегировать бизнес-операцию, представленную запросом, модели предметной области, вызвав метод AddRsvp класса Dinner, который вы видели ранее, и сохранив обновленное состояние модели предметной области (в данном случае экземпляр dinner и его набор RSVP); после этого он возвращает ответ OK:

Вспомните: я решил, что у класса Dinner не должно быть зависимости от системных часов, и предпочел передавать текущее время в его метод. В контроллере я передаю _systemClock.Now как параметр currentDateTime. Это локальное поле, заполняемое через DI, что избавляет и контроллер от жесткого связывания с системными часами. Использовать DI в контроллере вполне разумно в противоположность сущности предметной области, так как контроллеры всегда создаются контейнерами сервисов ASP.NET; это подключает любые зависимости, объявленные контроллером в его конструкторе. Поле _systemClock имеет тип IDateTime, что определяется и реализуется всего несколькими строками кода:

Конечно, мне также нужно сконфигурировать контейнер ASP.NET так, чтобы он использовал MachineClockDateTime всякий раз, когда классу требуется экземпляр IDateTime. Это делается в ConfigureServices класса Startup, и, хотя подойдет любой жизненный цикл объекта, в данном случае я предпочел использовать Singleton, так как один экземпляр MachineClockDateTime будет обслуживать все приложение:

Включив эту простую абстракцию, я могу тестировать поведение контроллера в зависимости от того, передан ли крайний срок для RSVP, и гарантировать возврат корректного результата. Поскольку у меня уже есть тесты для метода Dinner.AddRsvp, проверяющие корректность его поведения, мне не понадобится очень много тестов для проверки того же поведения через контроллер, чтобы быть уверенным, что при совместной работе контроллер и модель предметной области функционируют правильно.

Следующие шаги

Скачайте сопутствующий этой статье проект-пример и посмотрите модульные тесты для Dinner и DinnersController. Помните, что свободно связанный код, как правило, гораздо проще в модульном тестировании, чем жестко связанный код, напичканный ключевыми словами new или вызовами статических методов, зависящих от обязанностей инфраструктуры. «New является связующим», так что ключевое слово new следует использовать в приложении осознанно, а не по воле случая. Узнать больше о ASP.NET Core и ее поддержке встраивания зависимостей можно на docs.asp.net.

Стив Смит (Steve Smith) — независимый тренер, преподаватель и консультант, а также обладатель звания ASP.NET MVP. Написал десятки статей для официальной документации ASP.NET Core (docs.asp.net) и работает с группами, осваивающими эту технологию. С ним можно связаться через сайт ardalis.com, также следите за его заметками в Twitter (@ardalis).

Выражаю благодарность за рецензирование статьи эксперту Microsoft Дугу Бантингу (Doug Bunting).

Что такое код asp asperrorstontlog

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

StatusCodeResult

StatusCodeResult позволяет отправить любой статусный код клиенту:

Для создания этого результата используется метод StatusCode() , в который передается отправляемый код статуса.

Подобным образом мы можем послать браузеру любой другой статусный код. Но для отдельных кодов статуса предназначены свои отдельные классы.

HttpNotFoundResult и HttpNotFoundObjectResult

NotFoundResult и NotFoundObjectResult посылает код 404, уведомляя браузер о том, что ресурс не найден. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.

Объекты обоих классов создаются методом NotFound . Для первого класса — это метод без параметров, для второго класса — метод, который в качестве параметра принимает отправляемую информацию. Например, используем NotFoundObjectResult:

UnauthorizedResult

UnauthorizedResult посылает код 401, уведомляя пользователя, что он не автризован для доступа к ресурсу:

Для создания ответа используется метод Unauthorized() .

BadResult и BadObjectResult

BadResult и BadObjectResult посылают код 400, что говорит о том, что запрос некорректный. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.

Эти классы можно применять, например, если в запросе нет каких-то параметров или данные представляют совсем не те типы, которые мы ожидаем получить, и т.д.

Объекты обоих классов создаются методом BadRequest . Для первого класса — это метод без параметров, для второго класса — метод, который в качестве параметра принимает отправляемую информацию:

OkResult и OkObjectResult

OkResult и OkObjectResult посылают код 200, уведомляя об успешном выполнении запроса. Второй класс в дополнении к статусному коду позволяет отправить доплнительную информацию, которая потом отобразится в браузере.

Объекты обоих классов создаются методом Ok() . Для первого класса — это метод без параметров, для второго класса — метод, который в качестве параметра принимает отправляемую информацию:

Пишем свой первый RESTful веб-сервис на ASP.NET

Что такое RESTful веб-сервис?

REST используется для создания легковесных, поддерживаемых и масштабируемых веб-сервисов. Сервис, построенный на REST архитектуре, называется RESTful-сервисом. REST использует HTTP — базовый сетевой протокол.

Ключевые составляющие RESTful

Веб-сервисы прошли долгий путь с момента их появления. В 2002 году W3C выпустил определения WSDL и SOAP веб-сервисов. Это сформировало стандарт по созданию веб-сервисов.

В 2004 году W3C выпустил определение ещё одного стандарта под названием RESTful. В последние годы этот стандарт стал довольно популярным. На данный момент он используется многими известными сайтами по всему миру, в число которых входят Facebook и Twitter.

20 ноября в 18:30, Москва, беcплатно

REST — это способ получить доступ к ресурсам, которые находятся в определённой среде. Например, у вас может быть сервер с важными документами или фотографиями. Всё это — ресурсы. Если клиенту, скажем, веб-браузеру, нужны какие-то из этих ресурсов, ему необходимо отправить запрос на сервер для получения доступа к ним. REST определяет, как может осуществляться доступ к этим ресурсам.

Ключевые составляющие реализации RESTful:

  1. Ресурсы. Допустим, у нас есть сервер с записями о сотрудниках, а адрес веб-приложения — http://server.com. Чтобы получить доступ к записи сотрудника, мы можем выполнить команду http://server.com/employee/1, которая говорит серверу предоставить запись сотрудника под номером 1.
  2. Методы запросов. Они говорят, что вы хотите сделать с ресурсом. Браузер использует метод GET, чтобы проинформировать удалённую сторону о том, что он хочет получить данные. Кроме GET есть много других методов вроде POST, PUT и DELETE. В примере с http://server.com/employee/1 выше браузер на самом деле использует метод GET, поскольку он хочет получить данные о сотруднике.
  3. Заголовки запроса. Это дополнительные инструкции, посылаемые вместе с запросом. Они могут определять тип необходимого ресурса или подробности авторизации.
  4. Тело запроса. Это данные, отправляемые вместе с запросом. Данные обычно отправляются, когда выполняется POST-запрос к REST веб-сервису. Зачастую в POST-запросе клиент говорит серверу, что он хочет добавить на него ресурс. Следовательно, тело запроса содержит подробную информацию о ресурсе, который необходимо добавить на сервер.
  5. Тело ответа. Это основная часть ответа. В нашем примере на запрос http://server.com/employee/1 сервер мог бы прислать XML-документ с данными о сотруднике в теле ответа.
  6. Коды ответа. Эти коды возвращаются сервером вместе с ответом. Например, код 200 обычно означает, что при отправке ответа не произошло никакой ошибки.

Методы RESTful

Представим, что у нас есть RESTful веб-сервис по адресу http://server.com/employee/. Когда клиент делает запрос к нему, он может указать любой из обычных HTTP-методов вроде GET, POST, DELETE и PUT. Ниже указано, что могло бы произойти при использовании соответствующего метода:

  • POST — с его помощью можно создать новую запись сотрудника;
  • GET — с его помощью можно запросить список сотрудников;
  • PUT — с его помощью можно обновить данные сотрудников;
  • DELETE — с его помощью можно удалять записи сотрудников.

Посмотрим на это с точки зрения одной записи. Допустим у нас есть запись сотрудника под номером 1. Вот какое значение могли бы иметь следующие действия:

  • POST — этот метод нельзя применить, так как сотрудник с номером 1 уже существует;
  • GET — этот метод можно использовать для получения данных о сотруднике под номером 1;
  • PUT — этот метод можно использовать для обновления данных сотрудника под номером 1;
  • DELETE — этот метод можно использовать для удаления записи сотрудника под номером 1.

Почему RESTful

В основном популярность RESTful обусловлена следующими причинами:

1. Разнородные языки и среды — это одна из основных причин:

  • У веб-приложений, написанных на разных языках, есть возможность взаимодействовать друг с другом;
  • Благодаря RESTful эти приложения могут находиться в разных средах, будь то Windows или Linux.

Представим, что для работы с такими сайтами как Facebook, Twitter, Google и т. д. клиентскому приложению нужно знать, на каких языках и на какой платформе они написаны. Основываясь на этих знаниях, мы могли бы написать код для взаимодействия с ними, однако это превратилось бы в сущий ад.

Facebook, Twitter и Google дают доступ к их функциональности посредством RESTful веб-сервисов. Это даёт возможность любому клиентскому приложению взаимодействовать с этими сервисами с помощью REST.

2. Технологический бум – сегодня всё должно работать на разнообразных устройствах, будь то смартфон, ноутбук или кофеварка. Представляете, каких бы усилий стоило наладить взаимодействие этих устройств с помощью обычных веб-приложений? RESTful API делают эту задачу гораздо проще, поскольку, как было упомянуто выше, вам не нужно знать, что у устройства «под капотом».

3. Появление облачных сервисов — всё переезжает в облако. Приложения медленно перемещаются в облачные системы вроде Azure или Amazon, которые предоставляют большое количество API на основе RESTful архитектуры. Следовательно, приложения должны разрабатываться таким образом, чтобы они были совместимы с облаком. Так как все облачные архитектуры работают на основе REST, логично разрабатывать веб-сервисы тоже на REST-архитектуре, чтобы извлечь максимум пользы из облачных сервисов.

RESTful архитектура

Приложение или архитектура считается RESTful, если ей присущи следующие характеристики:

  1. Состояние и функциональность представлены в виде ресурсов — это значит, что каждый ресурс должен быть доступен через обычные HTTP-запросы GET, POST, PUT или DELETE. Так, если кто-то хочет получить файл на сервере, у них должна быть возможность отправить GET-запрос и получить файл. Если он хочет загрузить файл на сервер, то у него должна быть возможность использовать POST или PUT-запрос. Наконец, если он хочет удалить файл, должна быть возможность отправить запрос DELETE.
  2. Архитектура клиент-сервер, отсутствие состояния (stateless) и поддержка кеширования:
    • Клиент-сервер — обычная архитектура, где сервером может быть веб-сервер, на котором, на котором размещено приложение, а клиентом — обычный веб-браузер;
    • Архитектура без сохранения состояния означает, что состояние приложения не сохраняется в REST. Например, если вы удалили ресурс с сервера командой DELETE, то даже при получении положительного кода ответа нет гарантий, что он действительно был удалён. Чтобы убедиться, что ресурс удалён, необходимо отправить GET-запрос. С его помощью можно запросить ресурсы, чтобы посмотреть, присутствует ли там удалённый.

Принципы и ограничения RESTful

Архитектура REST основывается на нескольких характеристиках, которые описаны ниже. Любой RESTful веб-сервис должен им соответствовать, чтобы называться таковым. Эти характеристики также известны как принципы проектирования, которым нужно следовать при работе с RESTful-сервисами.

RESTful клиент-сервер

Это самое важное требование REST-архитектуры. Оно означает, что на сервере располагается RESTful веб-сервис, который предоставляет необходимую функциональность клиенту. При отправке клиентом запроса к веб-сервису сервер должен либо отклонить его, либо принять и предоставить соответствующий ответ.

Отсутствие состояния

Эта концепция означает, что задачей именно клиента является убедиться, что серверу передаются все необходимые данные. Это нужно для того, чтобы сервер мог составить ответ должным образом. Это простая независимая последовательность вопросов-ответов. Клиент задаёт вопрос, сервер отвечает соответствующим образом. Затем клиент задаёт другой вопрос, однако сервер не помнит, что было до этого, поэтому отвечает на него независимо.

Концепция кеша помогает нивелировать проблему отсутствия состояния. Так как каждый запрос клиента независим по своей природе, порой клиент может повторно отправить какой-нибудь запрос. Запрос придёт на сервер, и сервер отправит ответ. Это увеличивает сетевой трафик. Кеш позволяет клиенту хранить прежде отправленные запросы и ответы. Поэтому при повторной отправке запроса он не будет отправлен серверу; вместо этого необходимые данные будут взяты из кеша.

Многослойная система

Суть этой концепции заключается в том, что любой дополнительный слой вроде промежуточного (слой, в котором создаётся бизнес-логика; это может быть дополнительный сервис, с которым клиент взаимодействует до сервера) можно поместить между клиентом и сервером, на котором располагается RESTful веб-сервис. Однако этот слой должен быть внедрён прозрачно, чтобы он не нарушил взаимодействия клиента и сервера.

Единообразие интерфейса

Это фундаментальное требование дизайна RESTful-сервисов. RESTful работает на уровне HTTP и использует нижеприведённые методы для работы с ресурсами на сервере:

  • POST — для создания ресурса;
  • GET — для его получения;
  • PUT — для его обновления;
  • DELETE — для его удаления.

Создаём свой первый RESTful веб-сервис с ASP.NET

Веб-сервисы можно создавать на множестве языков. Многие IDE можно использовать для создания REST-сервисов.

Мы напишем REST-приложение на .NET, используя Visual Studio.

Наш сервис будет работать со следующим набором данных «туториалов»:

TutorialId TutorialName
Arrays
1 Queues
2 Stacks

Мы реализуем следующие RESTful методы:

  • GET Tutorial — при его вызове клиент получает все доступные TutorialName;
  • GET Tutorial/TutorialId — при его вызове клиент получает TutorialName, соответствующее переданному TutorialId;
  • POST Tutorial/TutorialName — при его вызове клиент отправляет запрос на добавление туториала с переданным TutorialName;
  • DELETE Tutorial/TutorialId — при его вызове клиент отправляет запрос на удаление туториала с TutorialName, соответствующему переданному TutorialId.

Теперь создадим шаг за шагом наш веб-сервис.

Шаг первый

Нам нужно создать пустое ASP.NET веб-приложение. Для этого откройте Visual Studio и создайте новый проект:

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

Шаг второй

В открывшемся окне перейдите по вкладкам C# → Веб. Выберите опцию «Веб-приложение ASP.NET (.NET Framework)» и введите необходимые данные проекта вроде названия и каталога:

Если далее у вас появилось следующее окно, выбирайте вариант «Пустой»:

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

Шаг третий

Теперь нужно создать файл нашего RESTful веб-сервиса. Для этого сначала нажмите Ctrl+Shift+A, либо кликните правой кнопкой по файлу проекта Webservice.REST и выберите опции Добавить → Создать элемент…:

В открывшемся окне найдите опцию «Служба WCF (с поддержкой технологии AJAX)» и дайте ей имя TutorialSevice.svc:

Прим. перев. Если вы не можете найти эту опцию, то попробуйте открыть Visual Studio Installer и загрузить часть среды, ответственную за работу с ASP.NET:

После выбора опции «Служба WCF (с поддержкой технологии AJAX)» Visual Studio создаст код, который будет основой для реализации веб-сервиса. WCF (Windows Communication Foundation) — библиотека, необходимая для налаживания взаимодействия между приложениями с помощью разных протоколов вроде TCP, HTTP и HTTPS. AJAX позволяет асинхронно обновлять веб-страницы, обмениваясь небольшими объёмами информации с сервером.

Шаг четвёртый

Теперь нам нужно внести изменения в конфигурационный файл Web.config. Он содержит настройки, необходимые для правильной работы приложения. Наше изменение позволит приложению отправлять и принимать данные как RESTful веб-сервис.

Откройте конфигурационный файл:

В открывшемся файле найдите строку и замените её на .

Шаг пятый

Пора приниматься за код. Откройте файл TutorialService.svc. Сначала добавим код для отображения наших данных. Создадим список со строками «Arrays», «Queues» и «Stacks». Они будут отражать имена доступных туториалов:

Шаг шестой

Теперь напишем код для нашего метода GET в том же файле. Этот метод будет запускаться при каждом вызове сервиса из браузера. Он будет использоваться для получения доступных туториалов:

Строка [WebGet(UriTemplate=»/Tutorial»)] — самая важная. Она нужна для определения того, как мы будем вызывать этот метод по URL. Если наш сервис расположен по адресу http://localhost:52645/TutorialService.svc и в его конец мы добавим «/Tutorial» и получим http://localhost:52645/TutorialService.svc/Tutorial, то будет вызван вышеприведённый код. Атрибут WebGet является параметром, который позволяет GetAllTutorials() быть RESTful-методом, который можно вызвать GET-запросом.

В самом методе GetAllTutorials() находится код, который собирает все названия туториалов и возвращает их в одной строке.

Шаг седьмой

Код, показанный ниже, нужен для того, чтобы вернуть соответствующий TutorialName при получении GET-запроса с TutorialId :

Как и в предыдущем примере, первая строка — самая важная, так как определяет то, как мы будем вызывать этот метод. Если мы сделаем запрос http://localhost:52645/TutorialService.svc/Tutorial/1, то веб-сервис должен вернуть TutorialName , соответствующий TutorialId с индексом 1.

Метод GetTutorialByID() реализует описанную логику. Обратите внимание на то, что мы приводим TutorialId к типу Integer . Это связано с тем, что всё передаваемое в адресную строку браузера является строкой. А поскольку индексом списка не может быть строка, мы добавляем код, необходимый для преобразования в число.

Шаг восьмой

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

На первой строке находится атрибут WebInvoke , прикреплённый к нашему методу, что позволяет вызывать его с помощью POST-запроса. Для атрибутов RequestFormat и ResponseFormat мы указываем JSON, так как именно с этим форматом работает RESTful веб-сервис.

Шаг девятый

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

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

В самом методе DeleteTutorial() мы приводим переданный TutorialId к типу Integer и удаляем из списка соответствующий элемент.

В итоге код должен выглядеть так (не учитывая элементов, которые были там изначально):

Запускаем наш веб-сервис

Мы создали наш веб-сервис, пора его запустить.

Сначала кликните правой кнопкой по файлу проекта Webservice.REST и выберите опцию «Назначить автозагружаемым проектом», чтобы Visual Studio запустила этот проект при запуске всего решения:

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

После запуска должно открыться окно браузера. Перейдите по адресу http://localhost:51056/TutorialService.svc/Tutorial и в зависимости от выбранного браузера вы увидите что-то такое:

Прим. перев. В вашем случае сервис может запуститься на localhost с другим портом. Далее в статье мы будем использовать значение 51056, однако не забывайте заменять его на своё, когда будете пытаться запускать примеры.

В этом примере браузер делает GET-запрос и тем самым вызывает написанный нами метод GetAllTutorials() , который возвращает список со всеми туториалами.

Тестируем веб-сервис

Выше мы увидели, как браузер делает GET-запрос для вызова GetAllTutorials() . Давайте проверим другие сценарии.

1. GET Tutorial/TutorialId — при вызове этого RESTful API клиент должен получить TutorialName , соответствующий переданному TutorialId .

Для вызова просто добавьте строку «/1» в конце URL, чтобы получить http://localhost:51056/TutorialService.svc/Tutorial/1. После перехода по этой ссылке вы должны увидеть следующее:

В этот раз был вызван метод GetTutorialByID() , который вернул туториал с индексом 1 — «Queues».

2. POST Tutorial/TutorialName — при вызове этого API клиент отправляет запрос на добавление переданного TutorialName , который сервер должен добавить в список. В этот раз нам понадобится инструмент Fiddler, который можно бесплатно скачать с официального сайта.

Запустите Fiddler и выполните следующие действия:

  1. Переключитесь на вкладку Composer. Она используется для создания запросов, которые можно отравить любому веб-приложению;
  2. Установите тип запроса равным «POST», а в URL вставьте адрес сервиса, в нашем случае это http://localhost:51056/TutorialService.svc/Tutorial;
  3. В окне, где уже есть строка «User-Agent: Fiddler» добавьте строку «Content-Type: application/json». Наш сервис работает только с данными в формате JSON, помните?
  4. Осталось ввести данные в поле «Request Body». Наш метод для POST-запросов принимает параметр str . Передавая строку <"str": "Trees">, мы указываем, что хотим добавить в список значение «Trees».

Нажмите на кнопку «Execute». После этого нашему сервису будет отправлен запрос на добавление «Trees».

Чтобы убедиться, что всё прошло как надо, получим список всех туториалов, перейдя по ссылке http://localhost:51056/TutorialService.svc/Tutorial. Вы должны увидеть следующее:

3. DELETE Tutorial/TutorialId — при вызове этого API клиент отправит запрос на удаление из списка TutorialName , которое соответствует переданному TutorialId .

Запустите Fiddler и выполните следующие действия:

  1. Переключитесь на вкладку Composer;
  2. Установите тип запроса равным «DELETE», а в URL вставьте адрес сервиса вместе с id элемента, который хотите удалить. Если мы хотим удалить второй элемент, то адрес будет http://localhost:51056/TutorialService.svc/Tutorial/1.

Нажмите на кнопку «Execute», чтобы отправить DELETE-запрос на удаление элемента «Queues».

Если мы опять запросим список всех туториалов, мы увидим, что их стало меньше на один:

Корректный ASP.NET Core

Специально для любителей книг из серии “С++ за 24 часа” решил написать статью про ASP.NET Core.

Если вы раньше не разрабатывали под .NET или под какую-то аналогичную платформу, то смысла заходить под кат для вас нет. А вот если вам интересно узнать что такое IoC, DI, DIP, Interseptors, Middleware, Filters (то есть все то, чем отличается Core от классического .NET), то вам определенно есть смысл прочитать данную статью, так как заниматься разработкой без понимания всего этого явно не корректно.

IoC, DI, DIP

Если театр начинается с вешалки, то ASP.NET Core начинается с Dependency Injection. Для того, чтобы разобраться с DI нужно понять, что такое IoC.

Говоря о IoC очень часто вспоминают голливудский принцип “Don’t call us, we’ll call you”. Что означает “Не нужно звонить нам мы позвоним вам сами”.

Различные источники приводят различные паттерны, к которым может быть применен IoC. И скорее всего они все правы и просто дополняют друг друга. Вот некоторые их этих паттернов: factory, service locator, template method, observer, strategy.

Давайте разберем IoC на примере простого консольного приложения.

Допустим у нас есть два простых класса, реализующих интерфейс с одним методом:

Они оба зависят от абстракции (в данном случае в виде абстракции выступает интерфейс).

И, допустим, у нас есть объект более высокого уровня, использующий эти классы:

В зависимости от параметра конструктора переменная _instance инициализируется определенным классом. Ну и далее при вызове Write будет совершен вывод на консоль или в Debug. Все вроде бы неплохо и даже, казалось бы, соответствует первой части принципа Dependency Inversion

Объекты более высокого уровня не зависят от объектов более низкого уровня. И те, и те зависят от абстракций.

В качестве абстракции в нашем случае выступает ILayer.

Но у нас должен быть еще и объект еще более высокого уровня. Тот, который использует класс Logging.

Инициализируя Logging с помощью 1 мы получаем в классе Logging экземпляр класса, выводящего данные на консоль. Если мы инициализируем Logging любым другим числом, то log.Write будет выводить данные в Debug. Все, казалось бы, работает, но работает плохо.

Наш объект более высокого уровня Main зависит от деталей кода объекта более низкого уровня — класса Logging. Если мы в этом классе что-то изменим, то нам необходимо будет изменять и код класса Main. Чтобы это не происходило мы сделаем инверсию контроля — Inversion of Control.

Сделаем так чтобы класс Main контролировал то, что происходит в классе Logging. Класс Logging будет получать в виде параметра конструктора экземпляр класса, реализующего интерфейс интерфейс ILayer.

И теперь нас класс Main будет выглядеть таким образом:

Фактически мы декорируем наш объект Logging с помощью необходимого для нас объекта.

Теперь наше приложение соответствует и второй части принципа Dependency Inversion:

Абстракции не зависят от деталей. Детали зависят от абстракций. Т.е. мы не знаем деталей того, что происходит в классе Logging, мы просто передаем туда класс, реализующий необходимую абстракцию.

Есть такой термин tight coupling — тесная связь. Чем слабее связи между компонентами в приложении, тем лучше. Хотелось бы заметить, что данный пример простого приложения немного не дотягивает до идеала. Почему? Да потому что в классе самого высокого уровня в Main у нас дважды используется создание экземпляров класса с помощью new. А есть такая мнемоническая фраза «New is a clue» — что означает чем меньше вы используется new, тем меньше тесных связей компонентов в приложении и тем лучше. В идеале мы не должны были использовать new DebugLayer, а должны были получить DebugLayer каким-нибудь другим способом. Каким? Например, из IoC контейнера или с помощью рефлексии из параметра передаваемого Main.

Теперь мы разобрались с тем, что такое Inversion of Control (IoC) и что такое принцип Dependency Inversion (DIP). Осталось разобраться с тем, что такое Dependency Injection (DI). IoC представляет собой парадигму дизайна. Dependency Injection это паттерн. Это то, что у нас теперь происходит в конструкторе класса Logging. Мы получаем экземпляр определенной зависимости (dependency). Класс Logging зависит от экземпляра класса, реализующего ILayer. И это экземпляр внедряется (injected) через конструктор.

IoC container

IoC контейнер это такой объект, который содержит в себе множество каких-то определенных зависимостей (dependency). Зависимость можно иначе назвать сервисом — как правило это класс с определенным функционалом. При необходимости из контейнера можно получить зависимость необходимого типа. Внедрение dependency в контейнер — это Inject. Извлечение — Resolve. Приведу пример самого простого самостоятельно написанного IoC контейнера:

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

Зарегистрировать зависимость (допустим, ConsoleLayer или DebugLayer которые мы использовали в прошлом примере) можно так:

Внедрение зависимостей в ASP.NET Core Dependency injection in ASP.NET Core

ASP.NET Core поддерживает проектирование программного обеспечения с возможностью внедрения зависимостей. При таком подходе достигается инверсия управления между классами и их зависимостями. ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

Дополнительные сведения о внедрении зависимостей в контроллерах MVC см. в статье Внедрение зависимостей в контроллеры в ASP.NET Core. For more information specific to dependency injection within MVC controllers, see Внедрение зависимостей в контроллеры в ASP.NET Core.

Общие сведения о внедрении зависимостей Overview of dependency injection

Зависимость — это любой объект, который требуется другому объекту. A dependency is any object that another object requires. Рассмотрим класс MyDependency с методом WriteMessage , от которого зависят другие классы в приложении. Examine the following MyDependency class with a WriteMessage method that other classes in an app depend upon:

Чтобы сделать метод WriteMessage доступным какому-то классу, можно создать экземпляр класса MyDependency . An instance of the MyDependency class can be created to make the WriteMessage method available to a class. Класс MyDependency выступает зависимостью класса IndexModel . The MyDependency class is a dependency of the IndexModel class:

Этот класс создает экземпляр MyDependency и напрямую зависит от него. The class creates and directly depends on the MyDependency instance. Зависимости в коде (как в предыдущем примере) представляют собой определенную проблему. Их следует избегать по следующим причинам: Code dependencies (such as the previous example) are problematic and should be avoided for the following reasons:

  • Чтобы заменить MyDependency другой реализацией, класс необходимо изменить. To replace MyDependency with a different implementation, the class must be modified.
  • Если у MyDependency есть зависимости, их конфигурацию должен выполнять класс. If MyDependency has dependencies, they must be configured by the class. В больших проектах, когда от MyDependency зависят многие классы, код конфигурации растягивается по всему приложению. In a large project with multiple classes depending on MyDependency , the configuration code becomes scattered across the app.
  • Такая реализация плохо подходит для модульных тестов. This implementation is difficult to unit test. В приложении нужно использовать имитацию или заглушку в виде класса MyDependency , что при таком подходе невозможно. The app should use a mock or stub MyDependency class, which isn’t possible with this approach.

Внедрение зависимостей устраняет эти проблемы следующим образом: Dependency injection addresses these problems through:

  • Используется интерфейс или базовый класс для абстрагирования реализации зависимостей. The use of an interface or base class to abstract the dependency implementation.
  • Зависимость регистрируется в контейнере служб. Registration of the dependency in a service container. ASP.NET Core предоставляет встроенный контейнер служб, IServiceProvider. ASP.NET Core provides a built-in service container, IServiceProvider. Службы регистрируются в приложении в методе Startup.ConfigureServices . Services are registered in the app’s Startup.ConfigureServices method.
  • Служба внедряется в конструктор класса там, где он используется. Injection of the service into the constructor of the class where it’s used. Платформа берет на себя создание экземпляра зависимости и его удаление, когда он больше не нужен. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it’s no longer needed.

В примере приложения интерфейс IMyDependency определяет метод, который служба предоставляет приложению. In the sample app, the IMyDependency interface defines a method that the service provides to the app:

Этот интерфейс реализуется конкретным типом, MyDependency . This interface is implemented by a concrete type, MyDependency :

MyDependency запрашивает ILogger в своем конструкторе. MyDependency requests an ILogger in its constructor. Использование цепочки внедрений зависимостей не является чем-то необычным. It’s not unusual to use dependency injection in a chained fashion. Каждая запрашиваемая зависимость запрашивает собственные зависимости. Each requested dependency in turn requests its own dependencies. Контейнер разрешает зависимости в графе и возвращает полностью разрешенную службу. The container resolves the dependencies in the graph and returns the fully resolved service. Весь набор зависимостей, которые нужно разрешить, обычно называют деревом зависимостей, графом зависимостей или графом объектов. The collective set of dependencies that must be resolved is typically referred to as a dependency tree, dependency graph, or object graph.

IMyDependency и ILogger должны быть зарегистрированы в контейнере служб. IMyDependency and ILogger must be registered in the service container. IMyDependency регистрируется в Startup.ConfigureServices . IMyDependency is registered in Startup.ConfigureServices . Службу ILogger регистрирует инфраструктура абстракций ведения журналов, поэтому такая служба является платформенной, что означает, что ее по умолчанию регистрирует платформа. ILogger is registered by the logging abstractions infrastructure, so it’s a framework-provided service registered by default by the framework.

Контейнер разрешает ILogger , используя преимущества (универсальных) открытых типов, что устраняет необходимость регистрации каждого (универсального) сконструированного типа. The container resolves ILogger by taking advantage of (generic) open types, eliminating the need to register every (generic) constructed type:

В примере приложения служба IMyDependency зарегистрирована с конкретным типом MyDependency . In the sample app, the IMyDependency service is registered with the concrete type MyDependency . Регистрация регулирует время существования службы согласно времени существования одного запроса. The registration scopes the service lifetime to the lifetime of a single request. Подробнее о времени существования служб мы поговорим далее в этой статье. Service lifetimes are described later in this topic.

Каждый метод расширения services.Add добавляет (а потенциально и настраивает) службы. Each services.Add extension method adds (and potentially configures) services. Например, services.AddMvc() добавляет службы, которые нужны для Razor Pages и MVC. For example, services.AddMvc() adds the services Razor Pages and MVC require. Рекомендуем придерживаться такого подхода в приложениях. We recommended that apps follow this convention. Поместите методы расширения в пространство имен Microsoft.Extensions.DependencyInjection, чтобы инкапсулировать группы зарегистрированных служб. Place extension methods in the Microsoft.Extensions.DependencyInjection namespace to encapsulate groups of service registrations.

Если конструктору службы требуется встроенный тип, например string , его можно внедрить с помощью конфигурации или шаблона параметров. If the service’s constructor requires a built in type, such as a string , the type can be injected by using configuration or the options pattern:

Экземпляр службы запрашивается через конструктор класса, в котором эта служба используется и назначается скрытому полю. An instance of the service is requested via the constructor of a class where the service is used and assigned to a private field. Поле используется во всем классе для доступа к службе (по мере необходимости). The field is used to access the service as necessary throughout the class.

В примере приложения запрашивается экземпляр IMyDependency , который затем используется для вызова метода службы WriteMessage . In the sample app, the IMyDependency instance is requested and used to call the service’s WriteMessage method:

Службы, внедренные в конструктор Startup Services injected into Startup

При использовании универсального узла (IHostBuilder) в конструктор Startup могут внедряться только следующие типы служб: Only the following service types can be injected into the Startup constructor when using the Generic Host (IHostBuilder):

Службы можно внедрить в Startup.Configure : Services can be injected into Startup.Configure :

Дополнительные сведения можно найти по адресу: Запуск приложения в ASP.NET Core. For more information, see Запуск приложения в ASP.NET Core.

Платформенные службы Framework-provided services

Метод Startup.ConfigureServices отвечает за определение служб, которые использует приложение, включая такие компоненты, как Entity Framework Core и ASP.NET Core MVC. The Startup.ConfigureServices method is responsible for defining the services that the app uses, including platform features, such as Entity Framework Core and ASP.NET Core MVC. Изначально коллекция IServiceCollection , предоставленная для ConfigureServices , содержит определенные платформой службы (в зависимости от настройки узла). Initially, the IServiceCollection provided to ConfigureServices has services defined by the framework depending on how the host was configured. Приложение, основанное на шаблоне ASP.NET Core, часто содержит сотни служб, зарегистрированных платформой. It’s not uncommon for an app based on an ASP.NET Core template to have hundreds of services registered by the framework. В следующей таблице перечислены некоторые примеры зарегистрированных платформой служб. A small sample of framework-registered services is listed in the following table.

Тип службы Service Type Время существования Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Временный Transient
IHostApplicationLifetime Одноэлементный Singleton
IWebHostEnvironment Одноэлементный Singleton
Microsoft.AspNetCore.Hosting.IStartup Одноэлементный Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Временный Transient
Microsoft.AspNetCore.Hosting.Server.IServer Одноэлементный Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Временный Transient
Microsoft.Extensions.Logging.ILogger Одноэлементный Singleton
Microsoft.Extensions.Logging.ILoggerFactory Одноэлементный Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Одноэлементный Singleton
Microsoft.Extensions.Options.IConfigureOptions Временный Transient
Microsoft.Extensions.Options.IOptions Одноэлементный Singleton
System.Diagnostics.DiagnosticSource Одноэлементный Singleton
System.Diagnostics.DiagnosticListener Одноэлементный Singleton
Тип службы Service Type Время существования Lifetime
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Временный Transient
Microsoft.AspNetCore.Hosting.IApplicationLifetime Одноэлементный Singleton
Microsoft.AspNetCore.Hosting.IHostingEnvironment Одноэлементный Singleton
Microsoft.AspNetCore.Hosting.IStartup Одноэлементный Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Временный Transient
Microsoft.AspNetCore.Hosting.Server.IServer Одноэлементный Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Временный Transient
Microsoft.Extensions.Logging.ILogger Одноэлементный Singleton
Microsoft.Extensions.Logging.ILoggerFactory Одноэлементный Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Одноэлементный Singleton
Microsoft.Extensions.Options.IConfigureOptions Временный Transient
Microsoft.Extensions.Options.IOptions Одноэлементный Singleton
System.Diagnostics.DiagnosticSource Одноэлементный Singleton
System.Diagnostics.DiagnosticListener Одноэлементный Singleton

Регистрация дополнительных служб с помощью методов расширения Register additional services with extension methods

Если зарегистрировать службу (включая ее зависимые службы, если нужно) можно, используя метод расширения коллекции служб, для регистрации всех служб, необходимых этой службе, рекомендуется использовать один метод расширения Add . When a service collection extension method is available to register a service (and its dependent services, if required), the convention is to use a single Add extension method to register all of the services required by that service. Ниже приведен пример того, как добавить дополнительные службы в контейнер с помощью методов расширения AddDbContext и AddIdentityCore: The following code is an example of how to add additional services to the container using the extension methods AddDbContext and AddIdentityCore:

Дополнительные сведения см. в разделе о классе ServiceCollection в документации по API-интерфейсам. For more information, see the ServiceCollection class in the API documentation.

Время существования служб Service lifetimes

Для каждой зарегистрированной службы выбирайте подходящее время существования. Choose an appropriate lifetime for each registered service. Для служб ASP.NET можно настроить следующие параметры времени существования: ASP.NET Core services can be configured with the following lifetimes:

Временный Transient

Временные службы времени существования (AddTransient) создаются при каждом их запросе из контейнера служб. Transient lifetime services (AddTransient) are created each time they’re requested from the service container. Это время существования лучше всего подходит для простых служб без отслеживания состояния. This lifetime works best for lightweight, stateless services.

Область действия Scoped

Службы времени существования с заданной областью (AddScoped) создаются один раз для каждого клиентского запроса (подключения). Scoped lifetime services (AddScoped) are created once per client request (connection).

При использовании такой службы в ПО промежуточного слоя внедрите ее в метод Invoke или InvokeAsync . When using a scoped service in a middleware, inject the service into the Invoke or InvokeAsync method. Не внедряйте службу через внедрение конструктора, поскольку в таком случае служба будет вести себя как одноэлементный объект. Don’t inject via constructor injection because it forces the service to behave like a singleton. Дополнительные сведения можно найти по адресу: Написание пользовательского ПО промежуточного слоя ASP.NET Core. For more information, see Написание пользовательского ПО промежуточного слоя ASP.NET Core.

Одноэлементный Singleton

Отдельные службы времени существования (AddSingleton) создаются при первом запросе (или при выполнении Startup.ConfigureServices , когда экземпляр указан во время регистрации службы). Singleton lifetime services (AddSingleton) are created the first time they’re requested (or when Startup.ConfigureServices is run and an instance is specified with the service registration). Созданный экземпляр используют все последующие запросы. Every subsequent request uses the same instance. Если в приложении нужно использовать одинарные службы, рекомендуется разрешить контейнеру служб управлять временем их существования. If the app requires singleton behavior, allowing the service container to manage the service’s lifetime is recommended. Реализовывать конструктивный шаблон с одинарными службами и предоставлять пользовательский код для управления временем существования объекта в классе категорически не рекомендуется. Don’t implement the singleton design pattern and provide user code to manage the object’s lifetime in the class.

Разрешать регулируемую службу из одиночной нельзя. It’s dangerous to resolve a scoped service from a singleton. При обработке последующих запросов это может вызвать неправильное состояние службы. It may cause the service to have incorrect state when processing subsequent requests.

Методы регистрации службы Service registration methods

Методы расширения регистрации службы предлагают перегрузки, которые полезны в определенных сценариях. Service registration extension methods offer overloads that are useful in specific scenarios.

Метод Method Автоматический Automatic
object object
удаление disposal
Несколько Multiple
реализации implementations
Передача аргументов Pass args
Add ()
Пример. Example:
services.AddSingleton ();
Yes Yes Да Yes Нет No
Add (sp => new )
Примеры Examples:
services.AddSingleton (sp => new MyDep());
services.AddSingleton (sp => new MyDep(«A string!»));
Yes Yes Да Yes Yes Yes
Add ()
Пример. Example:
services.AddSingleton ();
Yes Yes Нет No Нет No
AddSingleton (new )
Примеры Examples:
services.AddSingleton (new MyDep());
services.AddSingleton (new MyDep(«A string!»));
Нет No Да Yes Yes Yes
AddSingleton(new )
Примеры Examples:
services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(«A string!»));
Нет No Нет No Yes Yes

Дополнительные сведения об удалении типа см. в разделе Удаление служб. For more information on type disposal, see the Disposal of services section. Распространенный сценарий для нескольких реализаций — макеты типов для тестирования. A common scenario for multiple implementations is mocking types for testing.

Методы TryAdd регистрируют службу только в том случае, если еще не зарегистрирована реализация. TryAdd methods only register the service if there isn’t already an implementation registered.

В следующем примере первая строка регистрирует MyDependency для IMyDependency . In the following example, the first line registers MyDependency for IMyDependency . Вторая строка ничего не делает, поскольку у IMyDependency уже есть зарегистрированная реализация: The second line has no effect because IMyDependency already has a registered implementation:

Дополнительные сведения можно найти в разделе For more information, see:

Методы TryAddEnumerable(ServiceDescriptor) регистрируют службу только в том случае, если еще не существует реализации того же типа. TryAddEnumerable(ServiceDescriptor) methods only register the service if there isn’t already an implementation of the same type. Несколько служб разрешается через IEnumerable . Multiple services are resolved via IEnumerable . При регистрации служб разработчик хочет добавить экземпляр только в том случае, если экземпляр такого типа еще не был добавлен. When registering services, the developer only wants to add an instance if one of the same type hasn’t already been added. Как правило, этот метод используется авторами библиотек, чтобы избежать регистрации двух копий экземпляра в контейнере. Generally, this method is used by library authors to avoid registering two copies of an instance in the container.

В следующем примере первая строка регистрирует MyDep для IMyDep1 . In the following example, the first line registers MyDep for IMyDep1 . Вторая строка регистрирует MyDep для IMyDep2 . The second line registers MyDep for IMyDep2 . Третья строка ничего не делает, поскольку у IMyDep1 уже есть зарегистрированная реализация MyDep : The third line has no effect because IMyDep1 already has a registered implementation of MyDep :

Поведение внедрения через конструктор Constructor injection behavior

Службы можно разрешать двумя методами: Services can be resolved by two mechanisms:

  • IServiceProv >ActivatorUtilities – Позволяет создавать объекты без регистрации службы в контейнере внедрения зависимостей. ActivatorUtilities – Permits object creation without service registration in the dependency injection container. ActivatorUtilities используется с абстракциями пользовательского уровня, например со вспомогательными функциями для тегов, контроллерами MVC, и связывателями моделей. ActivatorUtilities is used with user-facing abstractions, such as Tag Helpers, MVC controllers, and model binders.

Конструкторы могут принимать аргументы, которые не предоставляются внедрением зависимостей, но эти аргументы должны назначать значения по умолчанию. Constructors can accept arguments that aren’t provided by dependency injection, but the arguments must assign default values.

Когда разрешение служб выполняется через IServiceProvider или ActivatorUtilities , для внедрения через конструктор требуется открытый конструктор. When services are resolved by IServiceProvider or ActivatorUtilities , constructor injection requires a public constructor.

Когда разрешение служб выполняется через ActivatorUtilities , для внедрения с помощью конструктора требуется наличие только одного соответствующего конструктора. When services are resolved by ActivatorUtilities , constructor injection requires that only one applicable constructor exists. Перегрузки конструктора поддерживаются, но может существовать всего одна перегрузка, все аргументы которой могут быть обработаны с помощью внедрения зависимостей. Constructor overloads are supported, but only one overload can exist whose arguments can all be fulfilled by dependency injection.

Контексты Entity Framework Entity Framework contexts

Контексты Entity Framework обычно добавляются в контейнер службы с помощью времени существования с заданной областью, поскольку операции базы данных в веб-приложении обычно относятся к области клиентского запроса. Entity Framework contexts are usually added to the service container using the scoped lifetime because web app database operations are normally scoped to the client request. Время существования по умолчанию имеет заданную область, если время существования не указано в перегрузке AddDbContext при регистрации контекста базы данных. The default lifetime is scoped if a lifetime isn’t specified by an AddDbContext overload when registering the database context. Службы данного времени существования не должны использовать контекст базы данных с временем существования короче, чем у службы. Services of a given lifetime shouldn’t use a database context with a shorter lifetime than the service.

Параметры времени существования и регистрации Lifetime and registration options

Чтобы продемонстрировать различия между указанными вариантами времени существования и регистрации, рассмотрим интерфейсы, представляющие задачи в виде операции с уникальным идентификатором OperationId . To demonstrate the difference between the lifetime and registration options, consider the following interfaces that represent tasks as an operation with a unique identifier, OperationId . В зависимости от того, как время существования службы операции настроено для этих интерфейсов, при запросе из класса контейнер предоставляет тот же или другой экземпляр службы. Depending on how the lifetime of an operations service is configured for the following interfaces, the container provides either the same or a different instance of the service when requested by a class:

Интерфейсы реализованы в классе Operation . The interfaces are implemented in the Operation class. Если идентификатор GUID не предоставлен, конструктор Operation создает его. The Operation constructor generates a GUID if one isn’t supplied:

Регистрируется служба OperationService , которая зависит от каждого из других типов Operation . An OperationService is registered that depends on each of the other Operation types. Когда служба OperationService запрашивается через внедрение зависимостей, она получает либо новый экземпляр каждой службы, либо экземпляр уже существующей службы в зависимости от времени существования зависимой службы. When OperationService is requested via dependency injection, it receives either a new instance of each service or an existing instance based on the lifetime of the dependent service.

  • Если при запросе из контейнера создаются временные службы, OperationId службы IOperationTransient отличается от OperationId службы OperationService . When transient services are created when requested from the container, the OperationId of the IOperationTransient service is different than the OperationId of the OperationService . OperationService получает новый экземпляр класса IOperationTransient . OperationService receives a new instance of the IOperationTransient class. Новый экземпляр возвращает другой идентификатор OperationId . The new instance yields a different OperationId .
  • Если при каждом клиентском запросе создаются регулируемые службы, идентификатор OperationId службы IOperationScoped будет таким же, как для службы OperationService в клиентском запросе. When scoped services are created per client request, the OperationId of the IOperationScoped service is the same as that of OperationService within a client request. В разных клиентских запросах обе службы используют разные значения OperationId . Across client requests, both services share a different OperationId value.
  • Если одинарные службы и службы с одинарным экземпляром создаются один раз и используются во всех клиентских запросах и службах, идентификатор OperationId будет одинаковым во всех запросах служб. When singleton and singleton-instance services are created once and used across all client requests and all services, the OperationId is constant across all service requests.

В Startup.ConfigureServices каждый тип добавляется в контейнер согласно его времени существования. In Startup.ConfigureServices , each type is added to the container according to its named lifetime:

Служба IOperationSingletonInstance использует определенный экземпляр с известным идентификатором Guid.Empty . The IOperationSingletonInstance service is using a specific instance with a known ID of Guid.Empty . Понятно, когда этот тип используется (его GUID — все нули). It’s clear when this type is in use (its GUID is all zeroes).

В примере приложения показано время существования объектов в пределах одного и нескольких запросов. The sample app demonstrates object lifetimes within and between individual requests. В примере приложения IndexModel запрашивает каждый вид типа IOperation , а также OperationService . The sample app’s IndexModel requests each kind of IOperation type and the OperationService . После этого на странице отображаются все значения OperationId службы и класса модели страниц в виде назначенных свойств. The page then displays all of the page model class’s and service’s OperationId values through property assignments:

Результаты двух запросов будут выглядеть так: Two following output shows the results of two requests:

Первый запрос First request:

Операции контроллера: Controller operations:

Transient: d233e165-f417-469b-a866-1cf1935d2518 Transient: d233e165-f417-469b-a866-1cf1935d2518
Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19 Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000 Instance: 00000000-0000-0000-0000-000000000000

Операции OperationService : OperationService operations:

Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64 Transient: c6b049eb-1318-4e31-90f1-eb2dd849ff64
Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19 Scoped: 5d997e2d-55f5-4a64-8388-51c4e3a1ad19
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000 Instance: 00000000-0000-0000-0000-000000000000

Второй запрос Second request:

Операции контроллера: Controller operations:

Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0 Transient: b63bd538-0a37-4ff1-90ba-081c5138dda0
Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4 Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000 Instance: 00000000-0000-0000-0000-000000000000

Операции OperationService : OperationService operations:

Transient: c4cbacb8-36a2-436d-81c8-8c1b78808aaf Transient: c4cbacb8-36a2-436d-81c8-8c1b78808aaf
Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4 Scoped: 31e820c5-4834-4d22-83fc-a60118acb9f4
Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9 Singleton: 01271bc1-9e31-48e7-8f7c-7261b040ded9
Instance: 00000000-0000-0000-0000-000000000000 Instance: 00000000-0000-0000-0000-000000000000

Проанализируйте, какие значения OperationId изменяются в пределах одного и нескольких запросов. Observe which of the OperationId values vary within a request and between requests:

  • Временные объекты всегда разные. Transient objects are always different. Временное значение OperationId отличается в первом и втором клиентских запросах, а также в обеих операциях OperationService . The transient OperationId value for both the first and second client requests are different for both OperationService operations and across client requests. Каждому запросу службы и каждому клиентскому запросу предоставляется новый экземпляр. A new instance is provided to each service request and client request.
  • Ограниченные по области объекты одинаковы в рамках клиентского запроса, но различаются для разных запросов. Scoped objects are the same within a client request but different across client requests.
  • Одинарные объекты остаются неизменными для всех объектов и запросов независимо от того, предоставляется ли в Startup.ConfigureServices экземпляр Operation . Singleton objects are the same for every object and every request regardless of whether an Operation instance is provided in Startup.ConfigureServices .

Вызов служб из функции main Call services from main

Создайте IServiceScope с IServiceScopeFactory.CreateScope для разрешения службы с заданной областью в области приложения. Create an IServiceScope with IServiceScopeFactory.CreateScope to resolve a scoped service within the app’s scope. Этот способ позволит получить доступ к службе с заданной областью при запуске для выполнения задач по инициализации. This approach is useful to access a scoped service at startup to run initialization tasks. В следующем примере показано, как получить контекст для MyScopedService в Program.Main : The following example shows how to obtain a context for the MyScopedService in Program.Main :

Проверка области Scope validation

Когда приложение выполняется в среде разработки, поставщик службы по умолчанию проверяет следующее: When the app is running in the Development environment, the default service provider performs checks to verify that:

  • Службы с заданной областью не разрешаются из корневого поставщика службы, прямо или косвенно. Scoped services aren’t directly or indirectly resolved from the root service provider.
  • Службы с заданной областью не вводятся в одноэлементные объекты, прямо или косвенно. Scoped services aren’t directly or indirectly injected into singletons.

Корневой поставщик службы создается при вызове BuildServiceProvider. The root service provider is created when BuildServiceProvider is called. Время существования корневого поставщика службы соответствует времени существования приложения или сервера — поставщик запускается с приложением и удаляется, когда приложение завершает работу. The root service provider’s lifetime corresponds to the app/server’s lifetime when the provider starts with the app and is disposed when the app shuts down.

Службы с заданной областью удаляются создавшим их контейнером. Scoped services are disposed by the container that created them. Если служба с заданной областью создается в корневом контейнере, время существования службы повышается до уровня одноэлементного объекта, поскольку она удаляется только корневым контейнером при завершении работы приложения или сервера. If a scoped service is created in the root container, the service’s lifetime is effectively promoted to singleton because it’s only disposed by the root container when app/server is shut down. Проверка областей службы перехватывает эти ситуации при вызове BuildServiceProvider . Validating service scopes catches these situations when BuildServiceProvider is called.

Дополнительные сведения можно найти по адресу: Веб-узел ASP.NET Core. For more information, see Веб-узел ASP.NET Core.

Службы запросов Request Services

Службы, доступные в пределах запроса ASP.NET Core из HttpContext , предоставляются через коллекцию HttpContext.RequestServices. The services available within an ASP.NET Core request from HttpContext are exposed through the HttpContext.RequestServices collection.

Службы запросов — это все настроенные и запрашиваемые в приложении службы. Request Services represent the services configured and requested as part of the app. Когда объекты указывают зависимости, они удовлетворяются за счет типов из RequestServices , а не из ApplicationServices . When the objects specify dependencies, these are satisfied by the types found in RequestServices , not ApplicationServices .

Как правило, использовать эти свойства в приложении напрямую не следует. Generally, the app shouldn’t use these properties directly. Вместо этого запрашивайте требуемые для классов типы через конструкторы классов и разрешите платформе внедрять зависимости. Instead, request the types that classes require via class constructors and allow the framework inject the dependencies. Этот процесс создает классы, которые легче тестировать. This yields classes that are easier to test.

Предпочтительнее запрашивать зависимости в качестве параметров конструктора, а не обращаться к коллекции RequestServices . Prefer requesting dependencies as constructor parameters to accessing the RequestServices collection.

Проектирование служб для внедрения зависимостей Design services for dependency injection

Придерживайтесь следующих рекомендаций: Best practices are to:

  • Проектируйте службы так, чтобы для получения зависимостей они использовали внедрение зависимостей. Design services to use dependency injection to obtain their dependencies.
  • Избегайте статических классов и членов с отслеживанием состояния. Avoid stateful, static classes and members. Вместо этого следует проектировать приложения для использования отдельных служб, что позволяет избежать создания глобального состояния. Design apps to use singleton services instead, which avoid creating global state.
  • Избегайте прямого создания экземпляров зависимых классов внутри служб. Avoid direct instantiation of dependent classes within services. Прямое создание экземпляров обязывает использовать в коде определенную реализацию. Direct instantiation couples the code to a particular implementation.
  • Сделайте классы приложения небольшими, хорошо организованными и удобными в тестировании. Make app classes small, well-factored, and easily tested.

Если класс имеет слишком много внедренных зависимостей, обычно это указывает на то, что у класса слишком много задач и он не соответствует принципу единственной обязанности. If a class seems to have too many injected dependencies, this is generally a sign that the class has too many responsibilities and is violating the Single Responsibility Principle (SRP). Попробуйте выполнить рефакторинг класса и перенести часть его обязанностей в новый класс. Attempt to refactor the class by moving some of its responsibilities into a new class. Помните, что в классах модели страниц Razor Pages и классах контроллера MVC должны преимущественно выполняться задачи, связанные с пользовательским интерфейсом. Keep in mind that Razor Pages page model classes and MVC controller classes should focus on UI concerns. Бизнес-правила и реализация доступа к данным должны обрабатываться в классах, которые предназначены для этих целей. Business rules and data access implementation details should be kept in classes appropriate to these separate concerns.

Удаление служб Disposal of services

Контейнер вызывает Dispose для создаваемых им типов IDisposable. The container calls Dispose for the IDisposable types it creates. Если экземпляр добавлен в контейнер с помощью пользовательского кода, он не будет удален автоматически. If an instance is added to the container by user code, it isn’t disposed automatically.

Замена стандартного контейнера служб Default service container replacement

Встроенный контейнер служб предназначен для удовлетворения потребностей платформы и большинства клиентских приложений. The built-in service container is designed to serve the needs of the framework and most consumer apps. Мы рекомендуем использовать встроенный контейнер, если только не требуется конкретная функциональная возможность, которую он не поддерживает, например: We recommend using the built-in container unless you need a specific feature that the built-in container doesn’t support, such as:

  • Внедрение свойств Property injection
  • Внедрение по имени Injection based on name
  • Дочерние контейнеры Child containers
  • Настраиваемое управление временем существования Custom lifetime management
  • Func поддерживает отложенную инициализацию Func support for lazy initialization

С приложениями ASP.NET Core можно использовать следующие сторонние контейнеры: The following 3rd party containers can be used with ASP.NET Core apps:

Потокобезопасность Thread safety

Создавайте потокобезопасные одноэлементные службы. Create thread-safe singleton services. Если одноэлементная служба имеет зависимость от временной службы, с учетом характера использования одноэлементной службой к этой временной службе также может предъявляться требование потокобезопасности. If a singleton service has a dependency on a transient service, the transient service may also require thread safety depending how it’s used by the singleton.

Фабричный метод одной службы, например второй аргумент для AddSingleton (IServiceCollection, Func ), не обязательно должен быть потокобезопасным. The factory method of single service, such as the second argument to AddSingleton (IServiceCollection, Func ), doesn’t need to be thread-safe. Как и конструктор типов ( static ), он гарантированно будет вызываться один раз одним потоком. Like a type ( static ) constructor, it’s guaranteed to be called once by a single thread.

Рекомендации Recommendations

Разрешение служб на основе async/await и Task не поддерживается. async/await and Task based service resolution is not supported. C# не поддерживает асинхронные конструкторы, поэтому рекомендуем использовать асинхронные методы после асинхронного разрешения службы. C# does not support asynchronous constructors; therefore, the recommended pattern is to use asynchronous methods after synchronously resolving the service.

Не храните данные и конфигурацию непосредственно в контейнере служб. Avoid storing data and configuration directly in the service container. Например, обычно не следует добавлять корзину пользователя в контейнер служб. For example, a user’s shopping cart shouldn’t typically be added to the service container. Конфигурация должна использовать шаблон параметров. Configuration should use the options pattern. Аналогичным образом, избегайте объектов «хранения данных», которые служат лишь для доступа к некоторому другому объекту. Similarly, avoid «data holder» objects that only exist to allow access to some other object. Лучше запросить фактический элемент через внедрение зависимостей. It’s better to request the actual item via DI.

Не используйте статический доступ к службам (например, не используйте везде IApplicationBuilder.ApplicationServices). Avoid static access to services (for example, statically-typing IApplicationBuilder.ApplicationServices for use elsewhere).

Старайтесь не использовать схему указателя служб. Avoid using the service locator pattern. Например, не вызывайте GetService для получения экземпляра службы, когда можно использовать внедрение зависимостей: For example, don’t invoke GetService to obtain a service instance when you can use DI instead:

Неправильно: Incorrect:

Правильно: Correct:

Другой вариант указателя службы, позволяющий избежать этого, — внедрение фабрики, которая разрешает зависимости во время выполнения. Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. Оба метода смешивают стратегии инверсии управления. Both of these practices mix Inversion of Control strategies.

Не используйте статический доступ к HttpContext (например, IHttpContextAccessor.HttpContext). Avoid static access to HttpContext (for example, IHttpContextAccessor.HttpContext).

Как и с любыми рекомендациями, у вас могут возникнуть ситуации, когда нужно отступить от одного из правил. Like all sets of recommendations, you may encounter situations where ignoring a recommendation is required. Такие исключения случаются редко. Главным образом они связаны с особенностями самой платформы. Exceptions are rare—mostly special cases within the framework itself.

Внедрение зависимостей является альтернативой для шаблонов доступа к статическим или глобальным объектам. DI is an alternative to static/global object access patterns. Вы не сможете воспользоваться преимуществами внедрения зависимостей, если будете сочетать его с доступом к статическим объектам. You may not be able to realize the benefits of DI if you mix it with static object access.

Илон Маск рекомендует:  Что такое код drop database
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL