Получить ModelState вне контроллера и ActionFilter

Есть ли способ получить доступ к ModelStateDictionary из DI (вне контроллера или ActionFilter)? Я думаю, что могу создать ActionFilter, который где-то хранит ссылку на ModelStateDictionary, чтобы я мог получить к нему доступ позже в другом месте, но я хочу знать, есть ли обычный способ доступа к нему, например, есть IHttpContextAccessor для HttpContext .

У нас есть веб-приложение, которое является клиентом нашего API. Причина, по которой я хочу это сделать, заключается в том, что я хочу автоматически добавлять ошибки в ModelState веб-приложения из DelegatingHandler, используемого нашим клиентом API (типизированный http-клиент). Обработчик будет отслеживать каждый ответ от API и, если это применимо (400 ответов с пользовательскими кодами ошибок в теле, например «Имя уже занято»), добавлять сообщение в файл ModelState.

До сих пор я пытался запросить ControllerContext, но, похоже, он всегда нулевой.

var controllerContext = _serviceProvider.GetService<ControllerContext>();
controllerContext?.ModelState.AddModelError("", result.ErrorMessage);

Я также просмотрел все зарегистрированные службы с помощью отладчика VS, но не нашел ничего многообещающего.


Примечание (довольно большое) относительно комментариев о SRP и разделении ответственности: я не думаю, что это нарушает SRP. Клиент API — это универсальная клиентская реализация нашего API, которую можно использовать из любого места (в настоящее время мы используем его в Xamarin и веб-приложении ASP.NET Core MVC — упомянутом в этом самом вопросе). Однако клиент API ожидает HttpClient в своем конструкторе, что означает, что его поведение может быть изменено потребителями.

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

Что касается того, следует ли манипулировать ModelState вне контроллеров: ну, это именно то, что делают такие библиотеки, как FluentValidation (и проверка по умолчанию ASP.NET).

Что касается того, следует ли манипулировать ModelState в DelegatingHandler: я думаю, что это несколько более правильное обсуждение. Тем не менее, никто действительно не представил никаких аргументов относительно того, почему это плохо.

Что касается того, нужно ли это делать «автоматически»: я думаю, что лучше иметь код в одном месте, чем помнить об этом каждый раз в каждом действии при каждом вызове API.

Что касается того, следует ли вообще помещать эти сообщения в ModelState: ну, если я углублюсь в это здесь, это боковое примечание станет слишком большим. Кроме того, никто особо не спорил по этому поводу, так что...


person andre_ss6    schedule 02.01.2019    source источник
comment
почему вы не используете ActionFilterAttribute вместо DelegatingHandler?   -  person Manoj Choudhari    schedule 02.01.2019


Ответы (2)


Вы не можете получить ControllerContext< /a>, но вы можете получить ActionContext (это более специализированная версия контекста контроллера). Вы можете получить его с помощью IActionContextAccessor:

var actionContextAccessor = _serviceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
actionContext?.ModelState.AddModelError("", result.ErrorMessage);

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

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

person poke    schedule 02.01.2019
comment
Это выглядит многообещающе. После обеда посмотрю. О вашем редактировании разделения проблем: пожалуйста, обратитесь к моему комментарию к другому ответу. - person andre_ss6; 02.01.2019
comment
@ andre_ss6 andre_ss6 Хотя это решение, но вы не должны обрабатывать проверку модели таким образом. Сделайте это в действии контроллера, потому что проверка модели всегда должна принадлежать действию контроллера. Спасибо. - person TanvirArjel; 02.01.2019
comment
@TanvirArjel Я не уверен, согласен ли я. И FluentValidation, например, и даже реализация ASP.NET по умолчанию проверяют модель и и добавляют ошибки состояния модели в свое промежуточное ПО до того, как выполнение достигнет фактического кода действия. Класс, выполняющий эту логику (тот, который наследуется от DelegatingHandler), находится в проекте веб-приложения, а не в клиенте API. Кроме того, обработчик проверяет, является ли контекст нулевым, прежде чем пытаться что-либо сделать, поэтому, если клиент API используется вне контекста действия, ничего больше не происходит. - person andre_ss6; 02.01.2019
comment
@andre_ss6 Да! Ты прав! Я тоже так думаю, но я говорил о проверке пользовательской модели и добавлении пользовательского сообщения об ошибке, что то, что вы сделали, должно быть сделано внутри действия контроллера или во время вызова модели, наследующей IValidatableObject. - person TanvirArjel; 02.01.2019

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


Если вы не хотите нарушать SRP, одним из вариантов может быть заставить http-клиент возвращать список ошибок, которые должны быть добавлены контроллером в состояние модели (или null, если ошибок нет).

person Bruno Farias    schedule 02.01.2019
comment
Да, я не думаю, что нарушаю SRP. Клиент API — это отдельная библиотека. Однако, поскольку он использует новое соглашение типизированного http-клиента, введенное IHttpClientFactory (это просто HttpClient DI с некоторыми вспомогательными методами расширения), существуют точки расширения, которые вы можете использовать для изменения поведения клиента API. Подводя итог: клиент — это собственная библиотека с общей клиентской логикой API. Однако обработчик делегирования находится в проекте веб-приложения и расширяет функциональные возможности клиента. - person andre_ss6; 02.01.2019