Страница 5 из 6 Теперь мы создаём базовый класс, определяющий общие свойства и методы для всех атрибутов нормализации. Под нормализацией я понимаю приведение значения свойства к одному из возможных валидных(корректных) значений. Свойство NormalizationOrder будет объяснено чуть позже, а виртуальный метод Normalize это тот самый метод атрибута, который и будет выполнять основную работу. Он должен быть переопределен в дочерних классах. /// <summary>
/// Базовый класс-атрибут для создания атрибутов нормализации
/// </summary>
[AttributeUsage(AttributeTargets.Property)] public abstract class NormalizationBaseAttribute : Attribute { int m_normalizationOrder = 0; /// <summary>
/// Порядковый номер при нормализации
/// </summary>
public virtual int NormalizationOrder { get {return m_normalizationOrder;} }
public NormalizationBaseAttribute(int normalizationOrder) { m_normalizationOrder = normalizationOrder; }
/// <summary>
/// Нормализовать указанное значение
/// </summary>
/// <param name="value">Значение для нормализации</param>
/// <returns>Нормализованный объект</returns>
public virtual object Normalize(object value) { return "#NormalizationBase - Normalize method not overrided!"; } } А теперь определим несколько атрибутов, которые мы будем практически использовать. Атрибут, выполняющий обрезание определенных символов с начала и/или конца строки. Обрезаемый символ по умолчанию - пробел. /// <summary>
/// Атрибут нормализации - отсечение символов строки слева и/или справа
/// </summary>
public class NormalizationStringTrimAttribute : NormalizationBaseAttribute {
#region members
char m_trimChar = ' '; /// <summary>
/// Отсекаемый символ
/// </summary>
public virtual char TrimChar { get {return m_trimChar;} set {m_trimChar = value;} }
bool m_trimLeft = true; /// <summary>
/// Отсекать слева (с начала строки)
/// </summary>
public virtual bool TrimLeft { get {return m_trimLeft;} set {m_trimLeft = value;} }
bool m_trimRight = true; /// <summary>
/// Отсекать справа (с конца строки)
/// </summary>
public virtual bool TrimRight { get {return m_trimRight;} set {m_trimRight = value;} }
#endregion
public NormalizationStringTrimAttribute(int normalizationOrder) : base(normalizationOrder) {}
public override object Normalize(object value) { if (m_trimChar == ' ') { if (m_trimLeft & m_trimRight) return value.ToString().Trim(); else if (m_trimLeft) return value.ToString().TrimStart(); else if (m_trimRight) return value.ToString().TrimEnd(); else return value; } else { if (m_trimLeft & m_trimRight) return value.ToString().Trim(m_trimChar); else if (m_trimLeft) return value.ToString().TrimStart(m_trimChar); else if (m_trimRight) return value.ToString().TrimEnd(m_trimChar); else return value; } } }
Ещё один атрибут - сведения нескольких пробелов к одному. /// <summary>
/// Атрибут нормализации строки - сведение нескольких пробелов подряд к одному \ /// (максимально 24->1)
/// </summary>
public class NormalizationStringWhiteSpaceReduceAttribute : NormalizationBaseAttribute {
public NormalizationStringWhiteSpaceReduceAttribute(int normalizationOrder): base(normalizationOrder) {}
public override object Normalize(object value) { return value.ToString().Replace(" ", " ").Replace(" ", " ").Replace(" ", " "); } } Итак у нас есть 2 атрибута и с ними уже можно что-то сделать. Например написать такой код [DisplayName("ФИО")] [NormalizationStringTrim()] [NormalizationStringWhiteSpaceReduce()] public string Name { get { return m_Name;} set { m_Name = value;} } то есть мы предполагаем что написанная нами функция получит все атрибуты нормализации для данного свойства и последовательно перебирая их обработает свойство. Однако возникает вопрос в какой последовательности они будут возвращены отражением (а это может быть очень важно)? В той ли в которой были объявлены в коде? К сожалению, ответ - 'Нет'. Приходится извращаться и добавлять параметр, который определял порядок(последовательность) применения атрибутов. Для этого и предназначено виртуальное свойство NormalizationOrder. То есть наш сценарий действий таков: 1 получаем для свойства массив атрибутов основанных на NormalizationBaseAttribute с помощью метода Attribute.GetCustomAttributes. 2 Сортируем его в требуемом порядке. 3 Последовательно применяем к объекту нормализацию. Для второго пункта нам понадобится класс NormalizationComparer, предназначенный для сравнения двух атрибутов, наследующихся от NormalizationBaseAttribute. Сравнение производится по свойству NormalizationOrder. /// <summary>
/// Класс-сравнитель для NormalizationBaseAttribute
/// (сравнивает по NormalizationOrder)
/// </summary>
public class NormalizationComparer : IComparer {
#region IComparer Members
public int Compare(object x, object y) { return ((NormalizationBaseAttribute)x).NormalizationOrder.CompareTo( ((NormalizationBaseAttribute)y).NormalizationOrder ); }
#endregion
} основная функция нормализации /// <summary>
/// Нормализует поля объекта согласно установленным правилам
/// </summary>
public void Normalize() {
PropertyInfo[] pInfo = this.GetType().GetProperties();
for (int j=0; j<pInfo.Length; j++) { Attribute[] atts = Attribute.GetCustomAttributes(pInfo[j], typeof(NormalizationBaseAttribute)); Array.Sort(atts, new NormalizationComparer()); for (int k=0; k<atts.Length; k++) { NormalizationBaseAttribute att = (NormalizationBaseAttribute)atts[k]; if (att != null) pInfo[j].SetValue(this, att.Normalize(pInfo[j].GetValue(this, null)), null); } } } и небольшой тестовый код /// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] static void Main(string[] args) { Person p = new Person(-1); p.ShowNames(); Console.ReadLine();
p.Name = " Иванов Иван Иванович "; Console.WriteLine("'{0}'", p.Name); <b>p.Normalize();</b> Console.WriteLine("'{0}'", p.Name); Console.ReadLine(); ...
Как вы видите, оба атрибута отработали и привели наше свойство к требуемому виду. Отлично! Но нормализацией следует пользоваться с осторожностью, ведь пользователь уверен, что ввел одно значение, а перед записью в БД произошла нормализация, и значение могло измениться... Поэтому возможно 3 варианта: - проверять введенные пользователем данные и если они не соответствуют правильным предупреждать пользователя об этом и не позволять продолжать, пока они не будут исправлены; - проверять введенные пользователем данные и если они не соответствуют правильным нормализовывать и извещать пользователя об этом; - нормализовывать данные, а пользователю ничего не сообщать :) Я предпочитаю второй вариант, если ошибок нет данные сохраняются и актуальные значения отображаются пользователю. |