Валидацию входящих данных делаешь с помощью автовалидации "дто-аргументов".
Всю специфичную валидацию выносишь в другое место и валидируешь там.
Чуть подробнее:
В dto ставишь ассерт на то, что тип, длина, формат, значение то, которое контроллер ожидает.
Дальше собираешь отдельную дто, подоходящую под твой "запрос" от приложения: создать сущность, модифицировать, вызвать цепочку действий или прочее.
Непосредственно перед вызовом логики делаешь валидацию.
Пример:
Экшен добавляет IP адрес в БД, если он не существует.
В итоге имеем:
Перед вызовом экшена контроллера проверяется (делает фреймворк), что значение в поле является IP-адресом (например).
Перед вызовом use-case'а проверяем (делаешь ты), что этот IP адрес уже добавлен в БД.