Лапшекодим валидацию, или запрещаем вводить неправильные данные?
Попытался тут заюзать Range атрибут для валидации моделей в WPF. А мне сказали, что он сам не заводится, а надо реализовывать интерфейс IDataErrorInfo. Обычно все примеры с ним для веб проектов (типа ASP.NET MVC) приводятся - там типа код из коробки есть, который модель под капотом валидирует, поэтому тебе кажется, что валидация происходит автоматически - достаточно просто добавить атрибутов. При попытке перенести эту "простоту" в тот же WPF ничего из коробки не заводится так же легко, как в веб проектах.
И то верно, когда привязываешь в XAML
Path=(Validation.Errors)[0].ErrorContent}
То там ничего нет без прописывания валидации в IDataErrorInfo. Сам RangeAttribute ничего не валидирует.
Для реализации валидации через IDataErrorInfo предлагается писать тонну лапшекода. Либо через магию рефлексии, либо через совсем уж лапшу с перебором всех пропертей вручную. Я всё думал, что лыжи-то не едут. Либо я тупой, либо лыжи всё же авно? Вспомнил, что я этот вопрос для себя закрыл ещё давно, и решил, что эту лапшу писать не буду. Штука в том, что они в этих статьях как правило на вьюхе используют обычный текстбокс, из которого берут строку, пытаются парсить её в число и валидируют. Ну ладно парсить, но валидировать-то тоже вручную приходится. И я давно от этого дерьма отказался в пользу правильных контролов. Вместо того, чтобы вытаскивать той же рефлексией из модели долбаный атрибут (а более удобного способа доступа к ним в Дотнете нет), потом его свойства и проверять введённые значения по ним, я ввожу прямо в модели обычные свойства типа Value, Min, Max. А потом, ска, тупо привязываю их к правильному контролу, типа NumericUpDown, который из коробки имеет те же свойства Value, Min, Max и не даёт ввести значения вне диапазона. Вуаля - не надо лапшекодить.
А если ещё добавить, что сериализация моделей с кучей валидирующих атрибутов тот ещё квест по сравнению с сериализацией обычных свойств Value, Min, Max... Или есть всё же простые варианты?
Короче, в чём суть. Вместо того, чтобы лапшекодить долбаную валидацию, не проще добавить контролы с ограничениями для вводимых чисел, строк и тому подобному? Текстбокс с проверкой длины введённой строки и фильтру символов, текст бокс с переключателями для числе и заданием диапазона (NumericUpDown), и т.д.
О, да, мать-перемать! Городим забор из наследования базовых классов с валидацией и прочим, тонны кода с линком, словарями и делегатами по вытаскиванию атрибутов и пропертей, кастомные поведения, прогресс валидации - о да, конечно, только это бы и делал с утра до вечера! Чел поди месяц пыхтел над этой бадьёй. Статью тиснул, где-то на SO я его ответы видел, где он тоже советовал основательно реализовывать IDataErrorInfo.
Кого-то хлебом не корми - дай усложнить всё до упора. ))
Одно непонятно. Как-то в веб-проектах добились, что разрабу не надо самому эти простыни писать. Ну добавил атрибут в модель и оно само под капотом валидируется. Во вьюхе привязал контрол к сообщению об ошибке, которое туда как-то попало, но мне и пофиг, как - главное, что попало. Почему в других местах, типа того же WPF, нужно всё руками вспахивать?
Эта же проблема с долбаными атрибутами возникает, когда делаешь MVVM в WPF и вообще подобные многослойки. Прописываешь в самом нижнем слое (типа модель) валидирующие атрибуты. А теперь нужно всю эту валидацию поднять на уровень выше - скажем, модель представления. Что делать будете? Лапшекодить-рефлексировать, вытаскивая атрибуты из модели? А как в атрибуты вью модели это всё поместите? Атрибуты-то во время выполнения кода не задаются - только при написании. Ну я обычно во вью модели и делаю валидирующие ограничения (типа Min и Max) в виде обычных свойств. А если их уже во втором слое в виде обычных свойств делаешь, то почему бы сразу в первом же слое их такими не сделать? К чему возьба с атрибутами и потом с рефлексией по их вытаскиванию?
Навеяло, как один знакомый сходил в одну крутую новосибскую контору лет 12-15 назад. Там ему код показали - он сказал, что они теперь всё на атрибутах делают - валидацию, шмалидацию. Модно типа теперь так. Правда, не сказал, как они теперь с этим всем ипуца и стоило ли оно того. Но я тогда зелёный был и про атрибуты лишь мельком слышал. Подумал, ну раз у них всё на атрибутах теперь, значит контора точно крутая. Ну не будет же одна из самых крутых контор Новосиба веников вязать?
Ещё доводы за перенос валидации ввода в контролы, а не в классы бизнес-логики. Начинаете новый класс модели. Если реализовывать валидацию в базовом классе, то каждый раз надо унаследоваться от него. Не задолбает ли? А вот если валидация в контролах, то ничего дополнительного сверх обычного делать не надо.
Подход же у вышеописанных товарищей с простынями рефлексии какой-то странный - мы во вьюхе получаем ввод, спускаем его на слой модели, в модели валидируем и поднимаем отчёт о валидации (ошибки и отвалидированное значение) обратно во вьюху. Все эти путешествия данных туда-сюда при простом вводе (даже не сохранении введённых значений, а просто валидации "на лету") во-первых дико связывают слои, а во-вторых - усложняют код.
Естественно, это для простых проектов - модель плюс вью модель. Если у вас там десятки слоёв, и на каждом своя валидация, то там другой подход может быть. Но суть та же - вместо гоняния данных из верхних слоёв в самые нижние и обратно, лучше всё делать на как можно более верхнем слое, где вводятся данные.
Ещё доводы за
Не нужно искать универсальное решение. Для каждого проекта будет свое наиболее подходящее
https://docs.devexpress.com/WPF/7076/controls-and-librarie...
https://docs.devexpress.com/WPF/6945/controls-and-librarie...
https://docs.devexpress.com/WPF/6933/controls-and-librarie...
Вот этого дядьку с его лапшевалидацией
https://github.com/EmmanuelDURIN/wpf-attribute-validation/...
я тоже встречал на SO по этой теме.
Хорошо, конечно, иметь кучу умных контролов вместо стандартной куцей поставки, в которой всё надо из текстбоксов брать.
Как будете сериализовывать и десериализовывать объект с атрибутами валидации? Или атрибуты не сериализуются?
Вот этого дядьку с его лапшевалидацией
А чем не нравится то?
public class Contact : ValidatorBase { [Required(ErrorMessage = " Name is required.")] [StringLength(50, ErrorMessage = "No more than 50 characters")] [Display(Name = "Name")] public string Name { get; set; } }
Вот если бы это так и работало, без той лапши, на которую я ссылку дал.
А теперь скажите, зачем нужен валидатор StringLength, если я могу использовать контрол с ограничением по длине строки?
По мне, луче иметь такую модель (проперти для примера)
Name
NameMaxLength
DisplayName
Age
AgeMin
AgeMax
чем такую
[StringLength]
[Display]
Name
[Range]
Age
Первая и привязывается к чему угодно без проблем, и сериализуется тоже без проблем. И безо всякого лапшекода. Имя привязывается к контролу со встроенным ограничителем на длину строки, и к этому же ограничителю привязывается NameMaxLength. Когда вводишь больше ограничителя, то просто прекращается ввод - не надо никаких сообщений об ошибках. То же с возрастом - привязываем его не к голому текстбоксу, а к нормальному NumericUpDown, в котором есть проверка диапазона, границы которого тоже легко привязываются к свойствам модели.
Всё! Не надо лапши, рефлексии, обязательного базового класса с реализацией допотопных неудобных интерфейсов, и трахания с сериализацией. Вам только кажется, что атрибуты это красиво и удобно. Оно красиво толкьо когда их один-два и они коротко объявлены. А когда каша из атрибутов с кучей установок их свойств (типа притаранить локализованное сообщение об ошибке - укажи словарь, укажи ключ, укажи собственно свойства атрибута), то вся красота и лаконичность быстро исчезают. Вот это что, читаемо (смотри ниже)? А я тут всего два атрибута написал. Некоторые навешивают по пять-шесть. Можно и в одну строку написать - будете скроллить экран по горизонтали. Особенно если у вас ноутбук с маленьким экраном. Писал, писал я подобную фигню, вытаскивал данные из атрибутов рефлексией... а потом подумал - а нафига оно надо? И стал всё в обычных пропертях писать.
[NameDescription( resourceType: typeof(Namespace1.Namespace2.Namespace3.Namespace4.Strings), name: "AAAName", description: "AAADescription")] [Range( 1.0, 20.0, ErrorMessageResourceType: typeof(Namespace1.Namespace2.Namespace3.Namespace4.Strings), ErrorMessageResourceName : "AAAErrorMessage")] public double AAA { get; set; } = 1.6;
Чем мне не нравятся атрибуты и прочие данные, запиханные в рефлексию - они скрытые. Вот приходит к тебе объект класса, у него свойтсва - вот его данные. А нифига - у него ещё глобальные данные класса есть. Просканируй через рефлексию на наличие атрибутов. Т.е. ты должен изучить исходные код класса, чтобы знать, как с ним работать, чтобы увидеть, что там в глобальных данных куча инфы запрятана, которую ты тоже должен знать и как-то её достать.
Это, лять, как с ссылками на файлы с кодом.
Какого хрена мне талдычат, что всё должно быть прозрачно и меньше связано, а сами пихают данные в скрытые хранилища и вводят неявные связи между проектами?
А если пользователь захочет сделать кастомный интерфейс и на нескольких языках? У нас для гостиницы и ресторанов можно было визуально весь интерфейс настраивать - формы, меню, горячие клавиши, для этого сделали специальный инструмент, спомощью которого можно было создавать кастомный формы и отчёты, как это было в Visual FoxPro, и в Microsoft Access.
А какая разница? Для разных культур так же вытаскиваете из словарей по ключам нужное значение и присваиваете свойству. Просто атрибут это делает за вас - само присваивание. Но прописываете всё (класс со словарём, ключ) вы всё равно сами. В конечном счёте с атрибутом даже больше возни - сам атрибут ещё надо оформить.
К настраиваемому виду интерфейса атрибуты вообще не относятся, по-моему. Разве что вы через них как-то эти настройки реализуете? Ну так это явно не обязательный и не единственный путь.
Какого хрена мне талдычат, что всё должно быть прозрачно и меньше связано, а сами пихают данные в скрытые хранилища и вводят неявные связи между проектами?
я не сишарпист и даже не программист, но могу предположить два варианта:
1. Кто программировал усложнял не специально, он просто по другому не мог, так научили работать с "паттернами",
Гдето читал, китайцы так программируют, заучивают код целыми страницами, и потом решают проблему - подходящим куском.
2. Кто программировал усложнял СПЕЦИАЛЬНО.
Вот ты сколько раз уже залетаешь с шашкой наголо - все дураки, ты Д'Артаньян? а потом разбираешься и признаёшь свою ошибку.
Теперь представь, тебе доверили доработать и обновить систему, ты лезешь и лепишь код простой как ситцевые трусы, документация, комментарии...
Что произойдёт? Если каждый школьник может понять и сопровождать - то спрашивается, зачем платить больше? Тебя выпнут и посадят практиканта,
А умные семизнаки как делают? Правильно, наваяют хитроумно, сам шеф не понимает, а семизнак такой - эта конструкция повысит производительность на 30 процентов!
И пофиг что функция вызывается раз в месяц, а код перелопачивать и понимать никто не будет, так семизнак остаётся при деле, и ещё идёт на повышение, незаменим ценный сотрудник.

Вот если бы это так и работало
Как это все реализуется вроде бы должно быть безразлично. Подключили либу и работаем.
А теперь скажите, зачем нужен валидатор StringLength, если я могу использовать контрол с ограничением по длине строки?
Ну давайте в проперти грид засуньте подобный контрол, да и контролов на все случаи не наберёте, когда нужно может и это, а может и это еще проверить, да и пользователю сообщить об ошибке.
По мне, лучше иметь такую модель
Опять таки в каком то частом случае. А если иметь хотя-бы пяток полей с мин и мах, то получаем уже 15 пропертей которые нужно все не забыть использовать правильно.
не надо никаких сообщений об ошибках
Очень даже надо. Вот помню где то было поле с 1000 символами. Так во первых, знаешь это заранее, а во вторых видно сколько еще можно дописать.
и сериализуется тоже без проблем
Это вообще клёво. Записали с ограничением в 16 лет, а после изменили на 18, и вдруг оказывается, что после чтения будет всё равно на 16
И пофиг что функция вызывается раз в месяц, а код перелопачивать и понимать никто не будет, так семизнак остаётся при деле, и ещё идёт на повышение, незаменим ценный сотрудник.
Вы правы пожалуй. Только так семь знаков и домик во Флориде и зарабатываются.
да и пользователю сообщить об ошибке.
Моя идея - пользователю не нужно будет сообщать об ошибке, если изначально делать контрол, который не даст ему сделать ошибку. Когда у тебя на все случаи жизни один тип поля ввода - текст бокс без ограничений, тогда и требуются сотни строк валидаций и прочей лапшы.
И я говорю не про контролы на все случаи жизни, а на 99% этих случаев. NumericUpDown и всякие маскированные текстбоксы с прочими ограничениями - давно уже само собой разумеющееся для любого набора контролов должно быть. У нормальных поставщиков контролов оно уже давно так и есть.
Хочется ведь сериализовывать данные с полями валидации.
Сохранили вот данные пользователя, а затем восстановили. А между этим в программе ограничения изменились.
Если такая проблема, то не сериализуйте поля валидации. Или при десериализации полей валидации ранее сериализованных объектов считайте их устаревшими относительно данных в текущей запущенной сборке.
Возможно, что для атрибутов просто свой механизм сериализации будет - не пересекающийся с механизмом для простых данных. Т.е. отдельно сохраняем модель, и отдельно - данные атрибутов этой модели.
Суть-то в том, что когда сериализовать не надо, то проблем нет в любом случае, а когда надо, то с атрибутами приходится возиться. А как их сериализовать? А обычно создаёшь модель для сериализации, куда атрибуты выводишь в виде обычных свойств, или пишешь для сериализатора всякие конвертеры, которые делают примерно то же самое - вытаскивают данные из атрибутов и сериализуют их.
И да, пачка свойств выглядит читаемее, чем пачка атрибутов с кучей параметров.