Pull to refresh

Comments 28

Картинка немного не в тему, а так — статья полезная. Хотя было бы неплохо ознакомить людей с кастомными атрибутами, бывает полезно.
Дело в том, что большинство статей про атрибуты рассказывают про то, как можно создать пользовательские (кастомные) атрибуты. Но вот как их потом применить… Именно поэтому я и использовал только стандартные, которые идут в самом Framework. В принципе можно написать пример и на использование кстомных атрибутов, например, сделать атрибут по средством которого можно было бы проверять, что два поля заполнены одинаковыми значениями…
Это косательно форм. А допустим, контроль доступа, маршрутизация для запросов в ASP.NET через атрибуты — это было бы интереснее. К слову, я даже не знаю можно ли последнее через атрибуты задать.
делал такое через атрибуты на PostSharp.
Это сила привычки или пример на ASP.NET MVC выглядел бы попроще?
В ASP .Net MVC есть стандартная инфраструктура, для работы с атрибутами такого вида. В данной статье, я пытался привести пример того, как написать такую инфраструктуру самому.
могут быть получены при помощиотражений
Ну вот кем надо было быть, чтобы перевести в этом контексте Reflection как отражение? Кого вообще нанимали для перевода MSDN? Они хоть иногда читали книжки кроме букваря? Должно быть, нет, ибо иначе они бы знали, что есть такое слово, как рефлексия, которое хоть и является заимствованием с латыни, но уже давно и прочно укоренилось в русском языке, а смысл передаёт наиболее точно. Хочется взять и расстрелять просто.
Именно по этому оба раза эта фраза взята в кавычки.
А я как-то привык к «отражению», «рефлексия» — звучит как психологический термин )
Суть в том, что в оригинале этот психологический термин и был выбран в качестве названия неймспейса (самокопание же). Но MSDN, видимо, переводили те же люди, что обозвали overmind надмозгом.
Все же дело вкуса. Не стоит так резко.
Каждый раз дергать рефлексию — дело накладное. В подобном приложении это, конечно, несерьезно, а вот когда атрибуты используются где-то, где нужна скорость, имеет смысл предобработать их и сгенерить на лету классы-обработчики, имхо стоит этот момент отметить. Где-то я видел статью хорошую, со сравнением различных методов обработки атрибутов и скорости этих методов.
Я, допустим, использовал атрибуты в самописном движке для одной игры — ими помечались поля, подлежащие сериализации и передаче по сети. Атрибуты предобрабатывались при старте приложения и на их основе генерился индивидуальный сериалайзер.
Это давало существенный выигрыш в рантайме (ценой слегка увеличенного времени загрузки).
Хм, а это ведь идея для отдельной статьи. Напишите?
Кстати, будет забавно показать практику из генетического программирования. Когда при старте приложения, на основе атрибутов, создаются cs файлы с новыми классами, они компилируются и подгружаются в приложение.
В том-то и дело, что эту статью я уже где-то видел, вроде бы, на хабре.
Там было очень подробно и хорошо расписано, когда и как имеет смысл обращаться к рефлексии, вот ссылку на нее бы сюда добавить.
Но увы, не могу вспомнить как она называлась.
Генерить классы — дело накладное. Особенно когда на других версиях рантайма софтина начинает валиться ругаясь на невалидный IL.
Так, PEVerify'ить надо сначала. И тестировать везде, где генератор может работать.
И с чего, вдруг, накладное-то, кстати? Накладное — через CodeDom, да и то несущественно, если все делается при запуске приложения или по первому обращению. Reflection.Emit же вообще охренительно быстр.
Накладное в плане времени написания и тестирования, понятное дело, что в рантайме потом летает. Просто у меня был печальный опыт, когда писалась достаточно сложная штука через этот самый emit, а потом оказалось, что JIT от Microsoft несколько отличается от используемого в моно. Очень долго искал, что именно ему не нравится, т. к. исключение было очень малоинформативным.
А, с этим согласен. Поддержка кода, который генерирует IL — это как владение черной магией :) Поэтому, лучше без крайностей, юзать только там, где это необходимо, а не для всего подряд.
Зависит от того, что подразумевается под «дергать рефлексию». Reflection — такой же инструмент, как и остальные, имеющий свои достоинства и недостатки, и предназначен для использования в одних случаях и не предназначен для других. Расхожее мнение «Reflection — это нечто очень медленное» (в общем случае) имеет мало общего с практикой.
А я про что сказал?
Такой же инструмент. Имеет достоинства и недостатки. Один из недостатков — весьма вероятное снижение скорости выполнения приложения в отдельных случаях, к которым приведенный автором пример не относится. Но это не единственный пример применения, и во многих случаях следует не использовать рефлексию «в лоб», а воспользоваться слегка другим подходом.
Если писать про валидацию в wpf, то как можно обойтись без IDataErrorInfo и NotifyOnValidationError=True в {Binding}?
Коли ночь на дворе, а сна нет, расскажу подробнее, как это делаю я. Материал много где дублируется в сети, и, скорее всего, не будет откровением, но кому-то может пригодиться.

Во ViewModel, которую мы хотим валидировать, реализуем интерфейс IDataErrorInfo. В этом интерфейсе два метода: один позволяет указать на ошибку в конкретной проперти, другой отвечает за валидацию модели в целом. Начну с валидации одного свойства.
public virtual string this[string columnName]
{
get { return AttributesValidation.Validate(this, columnName); }
}

На вход этого… забыл слово… подаётся название свойства, которое надо валидировтаь. На выходе — либо строка с описанием ошибки, либо string.Empty.
Для валидации аттрибутами у меня есть небольшой класс-хелпер:
internal static class AttributesValidation
{
public static string Validate(IDataErrorInfo source, string columnName)
{
var type = source.GetType();
var property = type.GetProperty(columnName);
var validators = (ValidationAttribute[]) property.GetCustomAttributes(typeof (ValidationAttribute), true);
if (validators.Any())
{
var value = property.GetValue(source, null);
var errors = validators.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? "").ToArray();
return string.Join(Environment.NewLine, errors);
}
return string.Empty;
}
}

Все валидаторы, которые есть, они наследуются от System.ComponentModel.DataAnnotations.ValidationAttribute.

Ну а теперь это всё необходимо обработать в WPF:
[TextBox Text="{Binding Path=Name, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /]

Для несложных проверок мне нравится ставить UpdateSourceTrigger=PropertyChanged. В этом случае (длят екстбокса) валидация происходит после каждого нажатия клавиши, а не после потери фокуса. Это нагляднее, но больше обработок и вычислений.
Этот снипет даёт красную рамку вокруг невалидных полей и вызов обработчика ошибок в валидаторе формы. То есть, можно сделать так:
class MyForm{
int _errCount;

public MyForm()
{
Validation.AddErrorHandler(this, OnChildControlError); // и на каждое появление ошибки валидации или пропадание ошибки валидации будет вызываться OnChildControlError.
}

protected void OnChildControlError(object sender, ValidationErrorEventArgs e)
{
switch (e.Action)
{
case ValidationErrorEventAction.Added:
_errCount += 1;
break;
case ValidationErrorEventAction.Removed:
_errCount -= 1;
break;
default:
throw new ArgumentOutOfRangeException();
}
// теперь в переменной _errCount у нас записано количество ошибочных полей, и мы можем делать какое-то действие, например задизаблить кнопку OK. Я предпочитаю объявить DependencyProperty Valid, и с ним работать в xaml.
Valid = _errCount == 0;
}

Таким образом, если всё это сплести в одну кучу, то мы получаем:
1. ViewModel умеет сообщать о своих ошибках через [propertyName]
2. Специальный валидатор умеет валидировать модели через атрибуты
3. xaml умеет помечать красным невалидные филды
4. Viewшка (например, форма или UserControl) умеет считать невалидные филды внутри себя и имеет DependcyProperty Valid, к которому можно биндить Enabled-свойство кнопок.

Вывод сообщений об ошибке в xaml я не буду расписывать, так как это есть в msdn и stackoverflow, а комментарий итак получается очень большим.
ух-ты, парсер-молодец. Вечно забываю, что в теге code дублируются переносы строк. Прошу прощения за портянку.
Это слово «индексатор»?
На хабре, для оформления кода можно использовать вмето тега code — тег source, с атрибутом lang=«c#» или lang=«cs» (c# иногда глючит).
Про тег source помнить надо, а code вот тут, прямо в этой коробке на кнопке вверху сделан.
Простите за дубль, но глаза не выдерживали читать код без соответстсвующей маркировки, использовал source. Тому кто выйдет на эту статью как я, будет немного проще. Дальше оригинал:

Коли ночь на дворе, а сна нет, расскажу подробнее, как это делаю я. Материал много где дублируется в сети, и, скорее всего, не будет откровением, но кому-то может пригодиться.

Во ViewModel, которую мы хотим валидировать, реализуем интерфейс IDataErrorInfo. В этом интерфейсе два метода: один позволяет указать на ошибку в конкретной проперти, другой отвечает за валидацию модели в целом. Начну с валидации одного свойства.
public virtual string this[string columnName]
{
   get { return AttributesValidation.Validate(this, columnName); }
}

На вход этого… забыл слово… подаётся название свойства, которое надо валидировтаь. На выходе — либо строка с описанием ошибки, либо string.Empty.
Для валидации аттрибутами у меня есть небольшой класс-хелпер:
internal static class AttributesValidation
{
   public static string Validate(IDataErrorInfo source, string columnName)
   {
   var type = source.GetType();
   var property = type.GetProperty(columnName);
   var validators = (ValidationAttribute[]) property.GetCustomAttributes(typeof (ValidationAttribute), true);
   if (validators.Any())
      {
         var value = property.GetValue(source, null);
         var errors = validators.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? "").ToArray();
         return string.Join(Environment.NewLine, errors);
      }
   return string.Empty;
   }
}

Все валидаторы, которые есть, они наследуются от System.ComponentModel.DataAnnotations.ValidationAttribute.

Ну а теперь это всё необходимо обработать в WPF:
[TextBox Text="{Binding Path=Name, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" /]

Для несложных проверок мне нравится ставить UpdateSourceTrigger=PropertyChanged. В этом случае (длят екстбокса) валидация происходит после каждого нажатия клавиши, а не после потери фокуса. Это нагляднее, но больше обработок и вычислений.
Этот снипет даёт красную рамку вокруг невалидных полей и вызов обработчика ошибок в валидаторе формы. То есть, можно сделать так:
class MyForm{
int _errCount;

public MyForm()
{
   Validation.AddErrorHandler(this, OnChildControlError); // и на каждое появление ошибки валидации или пропадание ошибки валидации будет вызываться OnChildControlError.
}

protected void OnChildControlError(object sender, ValidationErrorEventArgs e)
{
   switch (e.Action)
   {
   case ValidationErrorEventAction.Added:
      _errCount += 1;
      break;
   case ValidationErrorEventAction.Removed:
      _errCount -= 1;
      break;
   default:
      throw new ArgumentOutOfRangeException();
   }
   // теперь в переменной _errCount у нас записано количество ошибочных полей, и мы можем делать какое-то действие, например задизаблить кнопку OK. Я предпочитаю объявить DependencyProperty Valid, и с ним работать в xaml. 
   Valid = _errCount == 0;
}

Таким образом, если всё это сплести в одну кучу, то мы получаем:
1. ViewModel умеет сообщать о своих ошибках через [propertyName]
2. Специальный валидатор умеет валидировать модели через атрибуты
3. xaml умеет помечать красным невалидные филды
4. Viewшка (например, форма или UserControl) умеет считать невалидные филды внутри себя и имеет DependcyProperty Valid, к которому можно биндить Enabled-свойство кнопок.

Вывод сообщений об ошибке в xaml я не буду расписывать, так как это есть в msdn и stackoverflow, а комментарий итак получается очень большим.
Sign up to leave a comment.

Articles