0x49D1

0L4g0YDQsNC30YDQsNCx0L7RgtC60LUsINC00LvRjyDRgNCw0LfRgNCw0LHQvtGC0YfQuNC60L7QsiA=

Изменение поведения сериализации в клиенте ADO.NET DataServices


Проблема.

Наверное многим знаком один из методов валидации при использовании ORM (в примерах буду использовать Entity Framework), когда расширяется клиентский класс для добавления туда информации об ошибках с последующим  присвоением получившегося после манипуляций объекта как DataSource ErrorProvider-у. Для тех, кому это не знакомо-поясню примером:

namespace Namspc.SomeDataService
{
 public partial class User : IDataErrorInfo
 {
 private Dictionary<string, string> _errors = new Dictionary<string, string>();

 #region Validation

 public bool IsValid
 {
 get { return _errors.Count == 0; }
 }

 partial void OnUserGroupIDChanging(Guid? value)
 {
 Common.ComboBoxRequired(_errors, value, "UserGroupID");
 }

 partial void OnFirstnameChanging(string value)
 {
 Common.StringRequired(_errors, value, "Firstname");
 }

 partial void OnLastnameChanging(string value)
 {
 Common.StringRequired(_errors, value, "Lastname");
 }

 partial void OnUsernameChanging(string value)
 {
 Common.StringRequired(_errors,value,"Username");
 }

 #endregion

 #region IDataErrorInfo Members

 public string Error
 {
 get { return string.Empty; }
 }

 public string this[string columnName]
 {
 get
 {
 if (_errors.ContainsKey(columnName))
 return _errors[columnName];
 return string.Empty;
 }
 }

 #endregion
 }
}

То есть тут мы в partial классе, описывающем дополнительно объекты клиента сервиса, добавляем описания для методов OnChanging чтоб при изменении параметра-отслеживать значения и сразу же сообщать об ошибке, если таковая появилась. Пример механизма вывода ошибок пользователю достигается при помощи ErrorProvider, как говорилось ранее:

public bool Save()
 {
 try
 {
 errorProvider1.DataSource = UserCard;
 if (UserCard.IsValid)
 {

......// Если все прошло успешно-продолжаем сохранение
context.SaveChanges(); // Тут объект context-как раз клиент сервиса(у нас ADO.NET DataServices!).

Где UserCard-объект класса User из клиента сервиса. Так вот собственно в чем появляется проблема:
При попытке сохранения выскакивает  Exception:

Error processing request stream. The property name ‘Error’ specified for type ‘User’ is not valid.

Что означает, что в схеме на сервере не значится такое свойство, то есть сервер считает, что то, что шлет ему клиент-не то, что описалось бы классом User.

Решение.

Что есть схемы, которые сверяются на клиенте/сервере?..Это XML файл, в котором все хранится в специальном формате. Такой заполненный XML создается при сериализации данных. Таким образом при сериализации данных о классе User с клиента в схему попадает и свойство Error. Наша задача-сделать так, чтоб он туда не попадал.
В Visual Basic для этого можно было вы объявить свойство Error как Friend и все было бы отлично (модификатор Friend не сериализуется) см. пост от Бет Масси. Но в C# все по-строже и Friend модификатора доступа нет. Однако можно написать собственный аттрибут, который и не позволит свойству сериализоваться:

namespace System.Data.Services.Client
{
 using System.Data.Services.Common;
 public partial class MyContextClassName
 {
  public MyContextClassName(Uri dataServiceUri,bool enableIgnoreProperties)
   : this(dataServiceUri)
  {
   this.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(DataServiceContextEx_WritingEntity);
  }

  void DataServiceContextEx_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
  {
   // e.Data gives you the XElement for the Serialization of the Entity
   //Using XLinq  , you can  add/Remove properties to the element Payload
   XName xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
   XElement xePayload = null;
   foreach (PropertyInfo property in e.Entity.GetType().GetProperties())
   {
    object[] doNotSerializeAttributes = property.GetCustomAttributes(typeof(DoNotSerializeAttribute), false);
    if (doNotSerializeAttributes.Length > 0)
    {
     if (xePayload == null)
     {
      xePayload = e.Data.Descendants().Where<XElement>(xe => xe.Name == xnEntityProperties).First<XElement>();
     }
     //The XName of the property we are going to remove from the payload
     XName xnProperty = XName.Get(property.Name, e.Data.GetNamespaceOfPrefix("d").NamespaceName);
     //Get the Property of the entity  you don't want sent to the server
     XElement xeRemoveThisProperty = xePayload
					.Descendants()
					.Where<XElement>(xe => xe.Name == xnProperty)
					.First<XElement>();
     //Remove this property from the Payload sent to the server
     xeRemoveThisProperty.Remove();
    }
   }
  }
 }
}

namespace System.Data.Services.Common
{
 /// <summary>
 /// Properties marked with this Attribute are not serialized in the payload when sent to the server
 /// </summary>
 [AttributeUsage(AttributeTargets.Property)]
 public class DoNotSerializeAttribute : Attribute
 {
 }
}

//Sample Usage
 public class TestType
 {
  public int ID { get; set; }
  /// <summary>
  /// This Property is client-only , should not be sent to the server
  /// </summary>
  [DoNotSerialize]
  public string ClientOnlyProperty { get; set; }
 }

Все дело в том, что у класса DataServiceContext есть событие WritingEntity, которое вызывается как раз перед тем, как мы передаем данные на сервер. Таки образом если обработать его и в обработчике проверять наличие аттрибута, например, DoNotSerialize, то из конечного документа можно исключить нужные нам строки, что и написано в схематическом коде выше..
Далее в коде валидации надо добавить аттрибут DoNotSerialize к свойству Error и возможно в нужных местах заменить инициализацию контекста на аналогичную из схематичного кода выше.
*спасибо Нике за наводку на такой метод валидации

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: