WCF Data Service Kliens Szerializáció

Van egy kis probléma. Ha EF-et használunk, akkor a WCF Data Service a kliensen minden partial class publikus tulajdonságot amit a generált osztályokhoz megadunk szerializálni fog. Az ok az, hogy EF kontextus esetén a WCF Data Service nem használ reflection-t, emiatt nincs is hozzá olyan attribútum ami ezt megkerülhetné. Miért probléma ez? Csak azért, mert a SaveChanges hanyatvágódik tőle és kissé nehézkes a szokványos felületeket, mint IEditableObject, IDataErrorInfo, stb. implementálni publikus tulajdonságok nélkül, amiket pl. WPF és Silverlight vezérlőkhöz is kötni lehet. Szerencsére lehetőség van beleavatkozni abba, hogy mi menjen ki a szerverre, csak egy kicsit meg kell hekkelni a cuccot. Ehhez csináltam egy két segédosztályt. Először kell egy attribútum amit a kliens osztály tulajdonságaira lehet aggatni:

namespace System.Data.Services.Common
{
    [AttributeUsage(AttributeTargets.Property)]
    public class NonSerializedDataAttribute : Attribute
    {
    }
}

Ez eddig okés. Itt jön a lényeg, ráakaszkodunk a data service WritingEntity eseményére:

using System.Collections.Generic;
using System.Data.Services.Client;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Xml.Linq;

namespace System.Data.Services.Common
{
    public class DataServiceContextSerialization
    {
        #region Constructors
        
        public DataServiceContextSerialization(DataServiceContext context)
        {
            Contract.Assert(context != null);
            NonSerializedPropertyCache = new Dictionary<Type, IList<string>>();
            context.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(Context_WritingEntity);
        }
        
        #endregion

        #region Properties

        private readonly Dictionary<Type, IList<string>> NonSerializedPropertyCache;

        #endregion

        #region Methods

        private IEnumerable<string> GetNonSerializedProperties(Type type)
        {
            var properties = (IList<string>)null;
            if (!NonSerializedPropertyCache.TryGetValue(type, out properties))
            {
                properties = new List<string>();
                foreach (var property in type.GetProperties())
                {
                    var attributes = property.GetCustomAttributes(typeof(NonSerializedDataAttribute), false);
                    if (attributes.Length > 0)
                    {
                        properties.Add(property.Name);
                    }
                }

                NonSerializedPropertyCache.Add(type, properties);
            }

            return properties;
        }

        #endregion

        #region Event Handlers

        private void Context_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
        {
            var xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
            var xePayload = (XElement)null;
            
            foreach (var property in GetNonSerializedProperties(e.Entity.GetType()))
            {
                if (xePayload == null)
                {
                    xePayload = e.Data.Descendants().Where<XElement>(
                        xe => xe.Name == xnEntityProperties).First<XElement>();
                }

                var xnProperty = XName.Get(property, e.Data.GetNamespaceOfPrefix("d").NamespaceName);
                var xeRemoveThisProperty = xePayload
                    .Descendants()
                    .Where<XElement>(xe => xe.Name == xnProperty)
                    .FirstOrDefault<XElement>();

                if (xeRemoveThisProperty != null)
{
xeRemoveThisProperty.Remove();
} } } #endregion } }

A használat egyszerű. Kell egy partial class a saját Data Service kliens kontextushoz egy kidekorált konstruktorral:

using System;
using System.Data.Services.Common;

namespace NetFx.Partnerinfo.ServiceClient.PartnerinfoDataService
{
    public partial class PartnerinfoContext
    {
        public PartnerinfoContext(Uri dataServiceUri, bool customSerialization)
            : this(dataServiceUri)
        {
            if (customSerialization)
            {
                this.CustomSerialization = new DataServiceContextSerialization(this);
            }
        }

        private readonly DataServiceContextSerialization CustomSerialization;
    }
}

Innentől kezdve pedig bármivel ki lehet bővíteni a kliens generált osztályokat, mint pl.:

using System.ComponentModel;
using System.Data.Services.Common;
using NetFx.Core;

namespace NetFx.Partnerinfo.ServiceClient.PartnerinfoDataService
{
    public partial class Address : IEditableObject
    {
        #region Constructors
        
        public Address()
        {
            this.EditableObjectAdapter = new EditableObjectAdapter<Address>(this);
        } 

        #endregion

        #region Editable Adapter

        [NonSerializedData]
        public EditableObjectAdapter<Address> EditableObjectAdapter { get; private set; }        

        #endregion

        #region IEditableObject

        public void BeginEdit()
        {
            EditableObjectAdapter.BeginEdit();
        }

        public void CancelEdit()
        {
            EditableObjectAdapter.CancelEdit();            
        }

        public void EndEdit()
        {
            EditableObjectAdapter.EndEdit();
        }

        #endregion
    }
}

Egy dologra azért figyelni kell így is, setter kell legyen minden tulajdonsághoz. Ha tudtok jobbat és egyszerűbbet, akkor szívesen veszem! 😀

Hozzászólás