Обработчик команд Windows при запуске

Обновлено: 21.11.2024

Внедрение класса диспетчера вместо нескольких команд и запросов в конструкторе контроллера

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

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

Диспетчер команд

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

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

А теперь, что касается реализации, мы реализуем часть разрешения и вызова службы.

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

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

Что касается реализации, мы снова будем полагаться на нашу настройку внедрения зависимостей, выполненную с помощью метода расширения AddCommandQueryHandlers в классе Startup.

Прежде чем мы удалим все параметры команд и обработчиков запросов из конструктора контроллера, нам нужно зарегистрировать наш диспетчер в контейнере IOC в Startup.cs

Использование диспетчеров в контроллерах

Когда нам нужно вызвать обработчик для команды, нам просто нужно вызвать метод HandleAsync для экземпляра диспетчера команд и передать экземпляр реализации ICommand. В случае запроса нам нужно вызвать HandleAsync для экземпляра диспетчера запросов, но вместо одного универсального типа нам нужно предоставить как реализацию IQuery, так и возвращаемый результат, который мы ожидаем, чтобы диспетчер мог его разрешить.

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

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

Ссылки

Отказ от ответственности

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

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

Происходит при вызове метода Run() объекта Application.

Тип события

Примеры

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

Браузерные приложения XAML (XBAP) не могут извлекать и обрабатывать аргументы командной строки, поскольку они запускаются с развертыванием ClickOnce (см. раздел Развертывание приложения WPF (WPF)). Однако они могут извлекать и обрабатывать параметры строки запроса из URL-адресов, которые используются для их запуска.

Примечания

Типичное приложение Windows Presentation Foundation при запуске может выполнять различные задачи инициализации, в том числе:

Обработка параметров командной строки.

Открытие главного окна.

Инициализация ресурсов области приложения.

Инициализация свойств области приложения.

Вы можете декларативно указать ресурсы главного окна и области приложения с помощью XAML (StartupUri и Resources соответственно). Однако иногда ресурсы или главное окно вашего приложения могут быть определены только программно во время выполнения.Кроме того, свойства области приложения и параметры командной строки можно использовать только программно. Программную инициализацию можно выполнить, обработав событие Startup, включая следующее:

Получение и обработка параметров командной строки, которые доступны в свойстве Args класса StartupEventArgs, которое передается обработчику событий Startup.

Инициализировать ресурсы области приложения с помощью свойства Resources.

Инициализируйте свойства области приложения с помощью свойства Properties.

Создать и показать одно (или несколько) окон.

Параметры командной строки также можно получить, вызвав статический метод GetCommandLineArgs объекта Environment. Однако для выполнения GetCommandLineArgs требуется полное доверие.

Если вы задали StartupUri с помощью XAML, созданное главное окно будет недоступно ни из свойства MainWindow, ни из свойства Windows объекта Application до тех пор, пока не будет обработано событие Startup. Если вам нужен доступ к главному окну во время запуска, вам нужно вручную создать новый объект окна из обработчика событий запуска.

Если ваше приложение использует CredentialPolicy для указания политики учетных данных, вам необходимо установить CredentialPolicy после запуска Startup; в противном случае WPF устанавливает внутреннюю политику по умолчанию сразу после возникновения события Startup.

Аргументы командной строки, которые передаются обработчику событий запуска, отличаются от параметров строки запроса URL, которые передаются браузерному приложению XAML (XBAP).

Шаблон Command Query Responsibility Segregation (CQRS) — один из моих любимых способов обработки запросов веб-API по той простой причине, что чтение и запись четко разделены. Довольно часто на каком-то этапе проекта вы сталкиваетесь с узкими местами производительности, которые, скорее всего, вызваны тем, как вы читаете или записываете свои данные.

CQRS разделяет чтение и запись, чтобы вы могли независимо использовать разные платформы сохранения для чтения и записи. Например, вы можете использовать Entity Framework для чтения, но хотите записывать большие объемы данных с помощью хранимых процедур, и BulkCopy может сыграть важную роль. С другой стороны, вы можете захотеть писать асинхронно, так как в большинстве случаев вам не нужно извлекать какие-либо данные после записи данных.

Существует больше преимуществ использования шаблона CQRS при обработке запросов от Web API, но есть и некоторые недостатки использования этого шаблона, которые в какой-то момент делают ваш код длиннее и немного сложнее для понимания. Чем больше у вас строк, тем больше мест, где можно что-то упустить и потенциально вызвать ошибку.

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

Команды и обработчики команд

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

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

Еще одна команда, которая нам может понадобиться, это команда удаления продукта

Наши примеры фиктивных команд и обработчиков готовы. Давайте объявим некоторые запросы и обработчики запросов для получения данных.

Запросы и обработчики запросов

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

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

Теперь давайте составим запрос, который будет учитывать свойства id и name

А теперь давайте создадим обработчик для этого запроса, который будет его обрабатывать

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

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

У нас есть одна строка для каждой команды/запроса и их обработчиков, и на данный момент, только для простого сценария, у нас уже есть четыре строки. Этот блок будет расти только для реальных проектов, поэтому было бы неплохо, если бы мы могли сделать это с меньшим количеством строк, потому что с таким подходом легко пропустить зависимость, которую нужно внедрить, и, следовательно, увеличить время отладки. В то же время, как только мы добавим новый запрос/команду, нам нужно зарегистрировать его в контейнере IOC.Та же ситуация, когда мы используем несколько профилей AutoMapper, легко пропустить один и потратить время на отладку. Было бы неплохо, если бы приложение делало это за нас автоматически?

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

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

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

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

Все, что нам нужно сделать сейчас, это вызвать этот метод расширения из наших ConfigureServices с другим типом интерфейса

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

Ссылки

Отказ от ответственности

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

CQRS означает разделение ответственности за запросы команд. CQRS помогает нам разделить наши логические реализации на 2 категории, такие как «Команды» и «Запрос». «Команды» определяют такие операции, как создание или обновление данных в источнике данных (базе данных). «Запрос» указывает операции для получения данных.

В CQRS модели (классы запроса/ответа) независимы или принадлежат одной операции, что означает, что классы модели не могут быть разделены между разными «командами» или разными «запросами» или между «командой» и «запросом». .

Судя по диаграмме, обратите внимание на модель запроса/ответа (необязательно), потому что иногда мы будем использовать параметры запроса или возвращать простой скалярный тип, и в этих случаях мы не будем создавать модели.

Настроить контекст базы данных Entity Framework Core:

Для этой демонстрации я создал таблицу "Продукты". Итак, чтобы установить связь между нашим проектом API и базой данных, здесь я реализую технику Code First с существующей базой данных.

Давайте установим библиотеку расширений SQL Server для ядра Entity Framework. Давайте добавим сущность таблицы «Товары». Давайте создадим папку, например «Данные», внутри создадим подпапку, например «Объекты», и добавим файл, например «Products.cs»

Создать обработчик команд для сохранения нового продукта:

Итак, давайте начнем наш пример с создания обработчика команд для сохранения нового "Продукта" в таблице. Итак, мы должны создать «RequestModel» (класс полезной нагрузки). Поэтому в иерархии папок, например «RequestModels/CommandRequestModels», добавьте файл «SaveProductRequestModel.cs».

Теперь давайте создадим интерфейс с абстрактным методом для сохранения новых продуктов. Итак, иерархия папок, например «Контракты/CommandHandlers», и добавьте файл, например «ISaveProductCommandHandler.cs»

  • Из этого определения метода мы делаем вывод, что у этой команды была только "RequestModel" (SaveProductRequestMode), но не было "ResponseModel", поскольку ее тип возвращаемого значения - скалярный тип Integer.

Давайте создадим конечную точку для сохранения нашего «Продукта». Давайте создадим новый контроллер, например ProductController.cs.

Создайте QueryHandler для получения всех продуктов:

Давайте начнем создавать QueryHandler для получения всех «Продуктов». Начнем с создания ResponseModel. В иерархии папок «ResponseModels/QueryResponseModels» добавьте файл типа «AllProductsResponseModel»

Теперь давайте объявим определение абстрактного метода в интерфейсе обработчика запросов для получения всех «Продуктов». В иерархию папок, например «Контракты/QueryHandlers», добавьте файл «IAllProductsQueryHandler.cs».

  • Здесь мы можем заметить, что у нашего обработчика запросов нет RequestModel, у него есть ResponseModel (AllProductResponseModel).

Создать обработчик запросов для фильтрации товаров по ценам:

Для лучшего понимания давайте создадим еще один обработчик запросов, который выбирает "Продукты" по ценовому диапазону. Давайте создадим «ResponseModel» в иерархии папок, например «ResponeModels/QueryResponeModels», и добавим файл, например «PriceRangeProductsResponseModel.cs».

Теперь давайте определим абстрактный метод в нашем интерфейсе обработчика запросов. В иерархию папок, например «Контракты/QueryHandlers», добавьте файл, например «IPriceRangeProductsQueryHandler.cs»

Теперь давайте реализуем метод PriceRangeProductsAsync. В иерархию папок, например "Handlers/QueryHandlers", добавьте файл, например "PriceRangeProductsQueryHandler.cs".

Наконец мы завершили наш пример, реализовав несколько обработчиков запросов и команд. Таким образом, структура папок нашего проекта будет выглядеть так:

МедиатР:

MediatR — это еще один шаблон проектирования. MediatR содержит обработчики, способные работать с командами и запросами.

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

Из диаграммы мы должны понять, что MediatR должен иметь как RequestModel, так и ResponseMode. В MediatR «RequestModel» всегда является определяемым пользователем классом, тогда как «ResponseModel» — либо определяемым пользователем классом, либо скалярным типом.

Установите MediatR NuGet:

Зарегистрируйтесь и добавьте MediatR:

Обновите логику в обработчике команд, чтобы сохранить новый продукт с помощью MediatR:

В шаблоне MediatR требуется «RequestModel», на основе «RequestModel» соответствующий обработчик будет автоматически вызываться MedaitR. «RequesModel» всегда должен быть определяемым пользователем классом. В иерархии папок «RequestModels/CommandRequestModels» обновите «SaveProductRequestModel.cs», как показано ниже:
RequestModels/CommandRequestModels/SaveProductRequestModel.cs:

  • В шаблоне mediatr наши RequestModels (например, SaveProductRequestModel) должны наследовать 'IRequest'. Модель «IRequest» «T» должна быть нашим обработчиком «ResponseModel».
  • С помощью MediatR нам не нужно создавать интерфейсы для наших обработчиков команд/запросов. Таким образом, мы можем удалить все наши классы интерфейса внутри папки «Контракты» в нашем проекте.
  • Наш обработчик команд наследует MediatR.IRequestHandler. «TRequest» означает нашу «RequestModel» (только определяемый пользователем класс), «TResponse» означает нашу «ResponseModel» (либо определяемый пользователем класс, либо скалярный тип).
  • Наш обработчик команд должен реализовать 'Handle' как общедоступный метод. Таким образом, первым входным параметром «Handle» является «RequestModel». Тип возвращаемого значения метода Handle – это наша модель ResponseModel.
  • Итак, вот метод 'Send' 'IMediator', который принимает нашу модель RequestModel (например, SaveProductRequestModel) в качестве входного значения. Таким образом, метод «Отправить» вызовет соответствующий обработчик команд (например, SaveProductCommandHandler) с помощью «RequestModel» (например, SaveProductRequestModel).

Обновите логику в QueryHandler для получения всех продуктов с помощью MediatR:

В настоящее время класс AllProductsQueryHandler имеет метод GetListAsync, у которого нет RequestModel. Но мы знаем, что MediatR полностью зависит от RequestModel. Итак, мы должны создать пустую «RequestModel», чтобы она работала с «MediatR». В иерархию папок, например «RequestModel/QueryRequestModels», добавьте новый файл, например «AllProductsRequestModel.cs»

  • В шаблоне mediatr наши RequestModels (например, AllProductsRequestModel) должны наследовать IRequest. Модель «IRequest» «T» должна быть «ResponseModel» (например, List) нашего обработчика.
  • Наш обработчик запросов наследует MediatR.IRequestHandler. «TRequest» означает нашу «RequestModel» (только определяемый пользователем класс), «TResponse» означает нашу «ResponseModel» (либо определяемый пользователем класс, либо скалярный тип).
  • Наш обработчик запросов должен реализовать 'Handle' как общедоступный метод. Таким образом, первым входным параметром «Handle» является «RequestModel» (например, AllProductsRequestModel). Тип возвращаемого значения метода Handle — это наша модель ResponseModel (например, список).
  • Итак, вот метод 'Send' 'IMediator', который принимает нашу модель RequestModel (например, AllProductsRequestModel) в качестве входного значения. Таким образом, метод «Отправить» вызовет соответствующий обработчик запросов (например, AllProductsQueryHandler) с помощью «RequestModel» (например, AllProductsRequestModel).

Обновить логику в обработчике запросов для фильтрации товаров по ценам с помощью MediatR:

В настоящее время «PriceRangeProductsQueryHandler» имеет метод «PriceRangeProductsAsync», который содержит 2 входных параметра, таких как «minPrice» и «maxPrice». Теперь нам нужно создать «RequestModel» с этими входными свойствами для поддержки MediatR. В иерархию папок, например "RequestModel/QueryRequestModels", добавьте новый файл, например "PriceRangeProductsRequestModel.cs"

  • В шаблоне mediatr наши RequestModels (например, PriceRangeProductsRequestModel) должны наследовать IRequest. Модель «IRequest» «T» должна быть моделью «ResponseModel» (например, List
  • Наш обработчик запросов наследует MediatR.IRequestHandler. «TRequest» означает нашу «RequestModel» (только определяемый пользователем класс), «TResponse» означает нашу «ResponseModel» (либо определяемый пользователем класс, либо скалярный тип).
  • Наш обработчик запросов должен реализовать 'Handle' как общедоступный метод. Таким образом, первым входным параметром «Handle» является «RequestModel» (например, PriceRangeProductsRequestModel). Тип возвращаемого значения метода «Handle» — это наша «ResponseModel» (например, List
  • Итак, вот метод 'Send' 'IMediator', который принимает нашу модель RequestModel (например, PriceRangeProductsRequestModel) в качестве входного значения. Таким образом, метод «Отправить» вызовет соответствующий обработчик запросов (например, PriceRangeProductsQueryHandler) с помощью «RequestModel» (например, PriceRangeProductsRequestModel).

Видеосессия:

Подведение итогов:

Надеюсь, эта статья предоставила некоторую полезную информацию о шаблонах CQRS и MediatR. Мне приятно получать ваши отзывы, предложения и лучшие методы в разделе комментариев ниже.

Ссылка:

Следуй за мной:

  • Получить ссылку
  • Фейсбук
  • Твиттер
  • Pinterest
  • Электронная почта
  • Другие приложения

Ярлыки

  • Получить ссылку
  • Фейсбук
  • Твиттер
  • Pinterest
  • Электронная почта
  • Другие приложения

Комментарии

Оставить комментарий

Популярные записи из этого блога

Пользовательская проверка подлинности Blazor WebAssembly с нуля

  • Получить ссылку
  • Фейсбук
  • Твиттер
  • Pinterest
  • Электронная почта
  • Другие приложения
  • Получить ссылку
  • Фейсбук
  • Твиттер
  • Pinterest
  • Электронная почта
  • Другие приложения

Читайте также: