File: System\Data\Services\Serializers\SyndicationSerializer.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="SyndicationSerializer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides a serializer that creates syndication objects and
//      then formatts them.
// </summary>
//
// @owner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Serializers
{
    #region Namespaces.
 
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Services.Common;
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.ServiceModel.Syndication;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Serialization;
 
    #endregion Namespaces.
 
    /// <summary>Serializes results into System.ServiceModel.Syndication objects, which can then be formatted.</summary>
    internal sealed class SyndicationSerializer : Serializer
    {
        #region Fields.
 
        /// <summary>Namespace-qualified attribute for null value annotations.</summary>
        internal static readonly XmlQualifiedName QualifiedNullAttribute = new XmlQualifiedName(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace);
 
        /// <summary>Empty person singleton.</summary>
        private static readonly SyndicationPerson EmptyPerson = new SyndicationPerson(null, String.Empty, null);
 
        /// <summary>Namespace-qualified namespace prefix for the DataWeb namespace.</summary>
        private static readonly XmlQualifiedName QualifiedDataWebPrefix = new XmlQualifiedName(XmlConstants.DataWebNamespacePrefix, XmlConstants.XmlNamespacesNamespace);
 
        /// <summary>Namespace-qualified namespace prefix for the DataWebMetadata namespace.</summary>
        private static readonly XmlQualifiedName QualifiedDataWebMetadataPrefix = new XmlQualifiedName(XmlConstants.DataWebMetadataNamespacePrefix, XmlConstants.XmlNamespacesNamespace);
 
        /// <summary>Factory for syndication formatter implementation.</summary>
        private readonly SyndicationFormatterFactory factory;
 
        /// <summary>Last updated time for <see cref="SyndicationItem"/> elements.</summary>
        /// <remarks>
        /// While this is currently an arbitrary decision, it at least saves us from re-querying the system time
        /// every time an item is generated.
        /// </remarks>
        private readonly DateTimeOffset lastUpdatedTime = DateTimeOffset.UtcNow;
 
        /// <summary>Writer to which output is sent.</summary>
        private readonly XmlWriter writer;
 
        /// <summary>Top-level feed being built.</summary>
        private SyndicationFeed resultFeed;
 
        /// <summary>Top-level item being built.</summary>
        private SyndicationItem resultItem;
 
        #endregion Fields.
 
        /// <summary>Initializes a new SyndicationSerializer instance.</summary>
        /// <param name="requestDescription">Request description.</param>
        /// <param name="absoluteServiceUri">Absolute URI to the service entry point.</param>
        /// <param name="service">Service with configuration and provider from which metadata should be gathered.</param>
        /// <param name="output">Stream to write to.</param>
        /// <param name="encoding">Encoding for text in output stream.</param>
        /// <param name="etag">HTTP ETag header value.</param>
        /// <param name="factory">Factory for formatter objects.</param>
        internal SyndicationSerializer(
            RequestDescription requestDescription,
            Uri absoluteServiceUri,
            IDataService service,
            Stream output,
            Encoding encoding,
            string etag,
            SyndicationFormatterFactory factory)
            : base(requestDescription, absoluteServiceUri, service, etag)
        {
            Debug.Assert(service != null, "service != null");
            Debug.Assert(output != null, "output != null");
            Debug.Assert(encoding != null, "encoding != null");
            Debug.Assert(factory != null, "factory != null");
 
            this.factory = factory;
            this.writer = factory.CreateWriter(output, encoding);
        }
 
        /// <summary>Serializes exception information.</summary>
        /// <param name="args">Description of exception to serialize.</param>
        public override void WriteException(HandleExceptionArgs args)
        {
            ErrorHandler.SerializeXmlError(args, this.writer);
        }
 
        /// <summary>Writes a primitive value to the specified output.</summary>
        /// <param name="primitive">Primitive value to write.</param>
        /// <param name="propertyName">name of the property whose value needs to be written</param>
        /// <param name="expectedTypeName">Type name of the property</param>
        /// <param name="content">Content dictionary to which the value should be written.</param>
        internal static void WritePrimitiveValue(object primitive, string propertyName, string expectedTypeName, DictionaryContent content)
        {
            Debug.Assert(!String.IsNullOrEmpty(propertyName), "!String.IsNullOrEmpty(propertyName)");
            Debug.Assert(expectedTypeName != null, "expectedTypeName != null");
            if (primitive == null)
            {
                content.AddNull(expectedTypeName, propertyName);
            }
            else
            {
                string primitiveString = PlainXmlSerializer.PrimitiveToString(primitive);
                Debug.Assert(primitiveString != null, "primitiveString != null");
                content.Add(propertyName, expectedTypeName, primitiveString);
            }
        }
 
        /// <summary>Writes an Atom link element.</summary>
        /// <param name="linkRelation">relation of the link element with the parent element</param>
        /// <param name="title">title of the deferred element</param>
        /// <param name="href">uri for the deferred element</param>
        /// <param name="linkType">link type for the deferred element</param>
        /// <param name="item">Item to write link in.</param>
        internal static void WriteDeferredContentElement(string linkRelation, string title, string href, string linkType, SyndicationItem item)
        {
            Debug.Assert(linkRelation != null, "linkRelation != null");
            Debug.Assert(item != null, "item != null");
            Debug.Assert(linkType != null, "linkType != null");
 
            SyndicationLink link = new SyndicationLink();
            link.RelationshipType = linkRelation;
            link.Title = title;
            link.Uri = new Uri(href, UriKind.RelativeOrAbsolute);
            link.MediaType = linkType;
            item.Links.Add(link);
        }
 
        /// <summary>Flushes the writer to the underlying stream.</summary>
        protected override void Flush()
        {
            this.writer.Flush();
        }
 
        /// <summary>Writes a single top-level element.</summary>
        /// <param name="expanded">Expanded properties for the result.</param>
        /// <param name="element">Element to write, possibly null.</param>
        protected override void WriteTopLevelElement(IExpandedResult expanded, object element)
        {
            Debug.Assert(this.RequestDescription.IsSingleResult, "this.RequestDescription.SingleResult");
            Debug.Assert(element != null, "element != null");
 
            this.resultItem = new SyndicationItem();
            this.resultItem.BaseUri = this.AbsoluteServiceUri;
            IncludeCommonNamespaces(this.resultItem.AttributeExtensions);
 
            if (this.RequestDescription.TargetSource == RequestTargetSource.EntitySet ||
                this.RequestDescription.TargetSource == RequestTargetSource.ServiceOperation)
            {
                bool needPop = this.PushSegmentForRoot();
                this.WriteEntryElement(
                    expanded,
                    element,
                    this.RequestDescription.TargetResourceType,
                    this.RequestDescription.ResultUri,
                    this.RequestDescription.ContainerName,
                    this.resultItem);
                this.PopSegmentName(needPop);
            }
            else
            {
                Debug.Assert(
                    this.RequestDescription.TargetSource == RequestTargetSource.Property,
                    "TargetSource(" + this.RequestDescription.TargetSource + ") == Property -- otherwise control shouldn't be here.");
                ResourceType resourcePropertyType;
                if (this.RequestDescription.TargetKind == RequestTargetKind.OpenProperty)
                {
                    resourcePropertyType = (element == null) ? ResourceType.PrimitiveStringResourceType : WebUtil.GetResourceType(this.Provider, element);
                    if (resourcePropertyType == null)
                    {
                        Type propertyType = element == null ? typeof(string) : element.GetType();
                        throw new InvalidOperationException(Strings.Serializer_UnsupportedTopLevelType(propertyType));
                    }
                }
                else
                {
                    Debug.Assert(this.RequestDescription.Property != null, "this.RequestDescription.Property - otherwise Property source set with no Property specified.");
                    ResourceProperty property = this.RequestDescription.Property;
                    resourcePropertyType = property.ResourceType;
                }
 
                Debug.Assert(
                    resourcePropertyType.ResourceTypeKind == ResourceTypeKind.EntityType,
                    "Open ResourceTypeKind == EnityType -- temporarily, because ATOM is the only implemented syndication serializer and doesn't support it.");
 
                bool needPop = this.PushSegmentForRoot();
                this.WriteEntryElement(
                    expanded,                               // expanded
                    element,                                // element
                    resourcePropertyType,              // expectedType
                    this.RequestDescription.ResultUri,      // absoluteUri
                    this.RequestDescription.ContainerName,  // relativeUri
                    this.resultItem);                       // target
                this.PopSegmentName(needPop);
            }
 
            // Since the element is not equal to null, the factory should never return null
            SyndicationItemFormatter formatter = this.factory.CreateSyndicationItemFormatter(this.resultItem);
            formatter.WriteTo(this.writer);
        }
 
        /// <summary>Writes multiple top-level elements, possibly none.</summary>
        /// <param name="expanded">Expanded properties for the result.</param>
        /// <param name="elements">Enumerator for elements to write.</param>
        /// <param name="hasMoved">Whether <paramref name="elements"/> was succesfully advanced to the first element.</param>
        protected override void WriteTopLevelElements(IExpandedResult expanded, IEnumerator elements, bool hasMoved)
        {
            Debug.Assert(elements != null, "elements != null");
            Debug.Assert(!this.RequestDescription.IsSingleResult, "!this.RequestDescription.SingleResult");
 
            string title;
            if (this.RequestDescription.TargetKind != RequestTargetKind.OpenProperty &&
                this.RequestDescription.TargetSource == RequestTargetSource.Property)
            {
                title = this.RequestDescription.Property.Name;
            }
            else
            {
                title = this.RequestDescription.ContainerName;
            }
 
            this.resultFeed = new SyndicationFeed();
            IncludeCommonNamespaces(this.resultFeed.AttributeExtensions);
            this.resultFeed.BaseUri = RequestUriProcessor.AppendEscapedSegment(this.AbsoluteServiceUri, "");
            string relativeUri = this.RequestDescription.LastSegmentInfo.Identifier;
 
            // support for $count
            if (this.RequestDescription.CountOption == RequestQueryCountOption.Inline)
            {
                this.WriteRowCount();
            }
            
            bool needPop = this.PushSegmentForRoot();
 
            this.WriteFeedElements(
                expanded,
                elements,
                this.RequestDescription.TargetResourceType,
                title,                                      // title
                this.RequestDescription.ResultUri,          // absoluteUri
                relativeUri,                                // relativeUri 
                hasMoved,                                   // hasMoved
                this.resultFeed,                            // feed
                false);
 
            this.PopSegmentName(needPop);
 
            SyndicationFeedFormatter formatter = this.factory.CreateSyndicationFeedFormatter(this.resultFeed);
            formatter.WriteTo(this.writer);
        }
 
        /// <summary>
        /// Write out the entry count
        /// </summary>
        protected override void WriteRowCount()
        {
            XElement rowCountElement = new XElement(
                XName.Get(XmlConstants.RowCountElement, XmlConstants.DataWebMetadataNamespace),
                RequestDescription.CountValue);
 
            this.resultFeed.ElementExtensions.Add(rowCountElement);
        }
 
        /// <summary>
        /// Write out the uri for the given element
        /// </summary>
        /// <param name="element">element whose uri needs to be written out.</param>
        protected override void WriteLink(object element)
        {
            throw Error.NotImplemented();
        }
 
        /// <summary>
        /// Write out the uri for the given elements
        /// </summary>
        /// <param name="elements">elements whose uri need to be writtne out</param>
        /// <param name="hasMoved">the current state of the enumerator.</param>
        protected override void WriteLinkCollection(IEnumerator elements, bool hasMoved)
        {
            throw Error.NotImplemented();
        }
 
        /// <summary>Ensures that common namespaces are included in the topmost tag.</summary>
        /// <param name='attributeExtensions'>Attribute extensions to write namespaces to.</param>
        /// <remarks>
        /// This method should be called by any method that may write a 
        /// topmost element tag.
        /// </remarks>
        private static void IncludeCommonNamespaces(Dictionary<XmlQualifiedName, string> attributeExtensions)
        {
            attributeExtensions.Add(QualifiedDataWebPrefix, XmlConstants.DataWebNamespace);
            attributeExtensions.Add(QualifiedDataWebMetadataPrefix, XmlConstants.DataWebMetadataNamespace);
        }
 
        /// <summary>Sets the type name for the specified syndication entry.</summary>
        /// <param name="item">Item on which to set the type name.</param>
        /// <param name="fullName">Full type name for the entry.</param>
        private static void SetEntryTypeName(SyndicationItem item, string fullName)
        {
            Debug.Assert(item != null, "item != null");
            item.Categories.Add(new SyndicationCategory(fullName, XmlConstants.DataWebSchemeNamespace, null));
        }
 
        /// <summary>
        /// Write the link relation element
        /// </summary>
        /// <param name="title">title for the current element</param>
        /// <param name="linkRelation">link relation for the self uri</param>
        /// <param name="relativeUri">relative uri for the current element</param>
        /// <param name="item">Item to write to.</param>
        /// <param name="attributeExtensions">List of custom attributes to add to the link element</param>
        private static void WriteLinkRelations(string title, string linkRelation, string relativeUri, SyndicationItem item, params KeyValuePair<XmlQualifiedName, string>[] attributeExtensions)
        {
            Debug.Assert(item != null, "item != null");
            Debug.Assert(relativeUri != null, "relativeUri != null");
 
            // Write the link relation element
            var link = new SyndicationLink();
            link.RelationshipType = linkRelation;
            link.Title = title;
            link.Uri = new Uri(relativeUri, UriKind.Relative);
            foreach (KeyValuePair<XmlQualifiedName, string> attributeExtension in attributeExtensions)
            {
                link.AttributeExtensions.Add(attributeExtension.Key, attributeExtension.Value);
            }
 
            item.Links.Add(link);
        }
 
        /// <summary>
        /// Checks if a particular property value should be skipped from the content section due to 
        /// EntityProperty mappings for friendly feeds
        /// </summary>
        /// <param name="currentSourceRoot">Current root segment in the source tree for a resource type</param>
        /// <param name="propertyName">Name of the property being checked for</param>
        /// <returns>true if skipping of property value is needed, false otherwise</returns>
        private static bool EpmNeedToSkip(EpmSourcePathSegment currentSourceRoot, String propertyName)
        {
            if (currentSourceRoot != null)
            {
                EpmSourcePathSegment epmProperty = currentSourceRoot.SubProperties.Find(subProp => subProp.PropertyName == propertyName);
                if (epmProperty != null)
                {
                    Debug.Assert(epmProperty.SubProperties.Count == 0, "Complex type added as leaf node in EPM tree.");
                    Debug.Assert(epmProperty.EpmInfo != null, "Found a non-leaf property for which EpmInfo is not set.");
                    Debug.Assert(epmProperty.EpmInfo.Attribute != null, "Attribute should always be initialized for EpmInfo.");
                    if (epmProperty.EpmInfo.Attribute.KeepInContent == false)
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Obtains the child EPM segment corresponding to the given <paramref name="propertyName"/>
        /// </summary>
        /// <param name="currentSourceRoot">Current root segment</param>
        /// <param name="propertyName">Name of property</param>
        /// <returns>Child segment or null if there is not segment corresponding to the given <paramref name="propertyName"/></returns>
        private static EpmSourcePathSegment EpmGetComplexPropertySegment(EpmSourcePathSegment currentSourceRoot, String propertyName)
        {
            if (currentSourceRoot != null)
            {
                return currentSourceRoot.SubProperties.Find(subProp => subProp.PropertyName == propertyName);
            }
            else
            {
                return null;
            }
        }
 
        /// <summary>Writes the value of a complex object.</summary>
        /// <param name="element">Element to write.</param>
        /// <param name="propertyName">name of the property whose value needs to be written</param>
        /// <param name="expectedType">expected type of the property</param>
        /// <param name="relativeUri">relative uri for the complex type element</param>
        /// <param name="content">Content to write to.</param>
        /// <param name="currentSourceRoot">Epm source sub-tree corresponding to <paramref name="element"/></param>
        private void WriteComplexObjectValue(object element, string propertyName, ResourceType expectedType, string relativeUri, DictionaryContent content, EpmSourcePathSegment currentSourceRoot)
        {
            Debug.Assert(!String.IsNullOrEmpty(propertyName), "!String.IsNullOrEmpty(propertyName)");
            Debug.Assert(expectedType != null, "expectedType != null");
            Debug.Assert(!String.IsNullOrEmpty(relativeUri), "!String.IsNullOrEmpty(relativeUri)");
            Debug.Assert(expectedType.ResourceTypeKind == ResourceTypeKind.ComplexType, "Must be complex type");
            Debug.Assert(content != null, "content != null");
 
            // Non-value complex types may form a cycle.
            // PERF: we can keep a single element around and save the HashSet initialization
            // until we find a second complex type - this saves the allocation on trees
            // with shallow (single-level) complex types.
            Debug.Assert(!expectedType.IsMediaLinkEntry, "!expectedType.IsMediaLinkEntry");
            DictionaryContent valueProperties = new DictionaryContent(expectedType.Properties.Count);
            Debug.Assert(!expectedType.InstanceType.IsValueType, "!expectedType.Type.IsValueType -- checked in the resource type constructor.");
 
            if (element == null)
            {
                content.AddNull(expectedType.FullName, propertyName);
            }
            else
            {
                if (this.AddToComplexTypeCollection(element))
                {
                    ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(this.Provider, element);
                    this.WriteObjectProperties(null, element, resourceType, null, relativeUri, null, valueProperties, currentSourceRoot);
                    if (!valueProperties.IsEmpty)
                    {
                        content.Add(propertyName, resourceType.FullName, valueProperties);
                    }
 
                    this.RemoveFromComplexTypeCollection(element);
                }
                else
                {
                    throw new InvalidOperationException(Strings.Serializer_LoopsNotAllowedInComplexTypes(propertyName));
                }
            }
        }
 
        /// <summary>Write the entry element.</summary>
        /// <param name="expanded">Expanded result provider for the specified <paramref name="element"/>.</param>
        /// <param name="element">element representing the entry element</param>
        /// <param name="expectedType">expected type of the entry element</param>
        /// <param name="absoluteUri">absolute uri for the entry element</param>
        /// <param name="relativeUri">relative uri for the entry element</param>
        /// <param name="target">Target to write to.</param>
        private void WriteEntryElement(IExpandedResult expanded, object element, ResourceType expectedType, Uri absoluteUri, string relativeUri, SyndicationItem target)
        {
            Debug.Assert(element != null || (absoluteUri != null && !String.IsNullOrEmpty(relativeUri)), "Uri's must be specified for null values");
            Debug.Assert(target != null, "target != null");
 
            this.IncrementSegmentResultCount();
 
            string title, fullName;
            if (expectedType == null)
            {
                // If the request uri is targetting some open type properties, then we don't know the type of the resource
                // Hence we assume it to be of object type. The reason we do this is that if the value is null, there is
                // no way to know what the type of the property would be, and then we write it out as object. If the value
                // is not null, then we do get the resource type from the instance and write out the actual resource type.
                title = typeof(object).Name;
                fullName = typeof(object).FullName;
            }
            else
            {
                title = expectedType.Name;
                fullName = expectedType.FullName;
            }
 
            target.Title = new TextSyndicationContent(String.Empty);
            if (element == null)
            {
                SetEntryTypeName(target, fullName);
                target.AttributeExtensions[QualifiedNullAttribute] = XmlConstants.XmlTrueLiteral;
                this.WriteOtherElements(
                    element,
                    expectedType,
                    title,
                    absoluteUri,
                    relativeUri,
                    null,
                    target);
 
                // Don't know when we hit this code path, keeping existing behaviour in this case
                target.Authors.Add(EmptyPerson);
            }
            else
            {
                absoluteUri = Serializer.GetUri(element, this.Provider, this.CurrentContainer, this.AbsoluteServiceUri);
                Debug.Assert(absoluteUri.AbsoluteUri.StartsWith(this.AbsoluteServiceUri.AbsoluteUri, StringComparison.Ordinal), "absoluteUri.AbsoluteUri.StartsWith(this.AbsoluteServiceUri.AbsoluteUri, StringComparison.Ordinal))");
                relativeUri = absoluteUri.AbsoluteUri.Substring(this.AbsoluteServiceUri.AbsoluteUri.Length);
                ResourceType actualResourceType = WebUtil.GetNonPrimitiveResourceType(this.Provider, element);
 
                string mediaETag = null;
                Uri readStreamUri = null;
                string mediaContentType = null;
                if (actualResourceType.IsMediaLinkEntry)
                {
                    this.Service.StreamProvider.GetStreamDescription(element, this.Service.OperationContext, relativeUri, out mediaETag, out readStreamUri, out mediaContentType);
                }
 
                SetEntryTypeName(target, actualResourceType.FullName);
                this.WriteOtherElements(
                    element,
                    actualResourceType,
                    title,
                    absoluteUri,
                    relativeUri,
                    mediaETag,
                    target);
 
                // Write the etag property, if the type has etag properties
                string etag = this.GetETagValue(element);
                if (etag != null)
                {
                    target.AttributeExtensions[new XmlQualifiedName(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace)]
                        = etag;
                }
 
                DictionaryContent content = new DictionaryContent(actualResourceType.Properties.Count);
 
                using (EpmContentSerializer epmSerializer = new EpmContentSerializer(actualResourceType, element, target, this.Provider))
                {
                    this.WriteObjectProperties(expanded, element, actualResourceType, absoluteUri, relativeUri, target, content, actualResourceType.HasEntityPropertyMappings ? actualResourceType.EpmSourceTree.Root : null);
                    epmSerializer.Serialize(content, this.Provider);
                }
 
                if (actualResourceType.IsMediaLinkEntry)
                {
                    // Write <content type="..." src="..." />
                    Debug.Assert(readStreamUri != null, "readStreamUri != null");
                    Debug.Assert(!string.IsNullOrEmpty(mediaContentType), "!string.IsNullOrEmpty(mediaContentType)");
                    target.Content = new UrlSyndicationContent(readStreamUri, mediaContentType);
                    if (!content.IsEmpty)
                    {
                        // Since UrlSyndicationContent must have empty content, we write the <m:property /> node as SyndicationElementExtension.
                        target.ElementExtensions.Add(content.GetPropertyContentsReader());
                    }
                }
                else
                {
                    target.Content = content;
                }
            }
 
#if ASTORIA_FF_CALLBACKS
            this.Service.InternalOnWriteItem(target, element);
#endif                
        }
 
        /// <summary>
        /// Writes the feed element for the atom payload
        /// </summary>
        /// <param name="expanded">Expanded properties for the result.</param>
        /// <param name="elements">collection of entries in the feed element</param>
        /// <param name="expectedType">expectedType of the elements in the collection</param>
        /// <param name="title">title of the feed element</param>
        /// <param name="absoluteUri">absolute uri representing the feed element</param>
        /// <param name="relativeUri">relative uri representing the feed element</param>
        /// <param name="hasMoved">whether the enumerator has successfully moved to the first element</param>
        /// <param name='feed'>Feed to write to.</param>
        /// <param name="disposeElementsOnSuccess">If set to true the function should dispose the elements enumerator when it's done
        /// with it. Not in the case this method fails though.</param>
        private void WriteFeedElements(
            IExpandedResult expanded, 
            IEnumerator elements, 
            ResourceType expectedType, 
            string title, 
            Uri absoluteUri, 
            string relativeUri, 
            bool hasMoved, 
            SyndicationFeed feed, 
            bool disposeElementsOnSuccess)
        {
            Debug.Assert(feed != null, "feed != null");
 
            // Write the other elements for the feed
            feed.Id = absoluteUri.AbsoluteUri;
            feed.Title = new TextSyndicationContent(title);
            var uri = new Uri(relativeUri, UriKind.Relative);
            var link = new SyndicationLink(uri, XmlConstants.AtomSelfRelationAttributeValue, title, null, 0L);
            feed.Links.Add(link);
 
            if (!hasMoved)
            {
                // ATOM specification: if a feed contains no entries, then the feed should have at least one Author tag
                feed.Authors.Add(EmptyPerson);
            }
            
            // Instead of looping, create an item that will defer the production of SyndicationItem instances.
            // PERF: consider optimizing out empty collections when hasMoved is false.
            feed.Items = this.DeferredFeedItems(
                expanded, 
                elements, 
                expectedType, 
                hasMoved, 
                this.SaveSegmentNames(), 
                (o, e) => this.WriteNextPageLink(o, e, absoluteUri),
                disposeElementsOnSuccess);
#if ASTORIA_FF_CALLBACKS
            this.Service.InternalOnWriteFeed(feed);
#endif            
        }
 
        /// <summary>
        /// Writes the next page link to the current xml writer corresponding to the feed
        /// </summary>
        /// <param name="lastElement">Object that will contain the keys for skip token</param>
        /// <param name="expandedResult">The <see cref="IExpandedResult"/> of the $skiptoken property of the object being written</param>
        /// <param name="absoluteUri">Absolute URI for the result</param>
        private void WriteNextPageLink(object lastElement, IExpandedResult expandedResult, Uri absoluteUri)
        {
            this.writer.WriteStartElement("link", XmlConstants.AtomNamespace);
            this.writer.WriteAttributeString("rel", "next");
            this.writer.WriteAttributeString("href", this.GetNextLinkUri(lastElement, expandedResult, absoluteUri));
            this.writer.WriteEndElement();
        }
 
        /// <summary>Provides an enumeration of deferred feed items.</summary>
        /// <param name="expanded">Expanded properties for the result.</param>
        /// <param name="elements">Elements to enumerate.</param>
        /// <param name="expectedType">Expected type of elements.</param>
        /// <param name="hasMoved">Whether the enumerator moved to the first element.</param>
        /// <param name="activeSegmentNames">The segment names active at this point in serialization.</param>
        /// <param name="nextPageLinkWriter">Delegate that writes the next page link if necessity arises</param>
        /// <param name="disposeElements">If set to true the function should dispose the elements enumerator (always).</param>
        /// <returns>An object that can enumerate syndication items.</returns>
        private IEnumerable<SyndicationItem> DeferredFeedItems(
            IExpandedResult expanded, 
            IEnumerator elements,
            ResourceType expectedType, 
            bool hasMoved, 
            object activeSegmentNames,
            Action<object, IExpandedResult> nextPageLinkWriter,
            bool disposeElements)
        {
            try
            {
                object savedSegmentNames = this.SaveSegmentNames();
                this.RestoreSegmentNames(activeSegmentNames);
                object lastObject = null;
                IExpandedResult lastExpandedSkipToken = null;
                while (hasMoved)
                {
                    object o = elements.Current;
                    IExpandedResult skipToken = this.GetSkipToken(expanded);
                    
                    if (o != null)
                    {
                        SyndicationItem target = new SyndicationItem();
                        IExpandedResult expandedO = o as IExpandedResult;
                        if (expandedO != null)
                        {
                            expanded = expandedO;
                            o = GetExpandedElement(expanded);
                            skipToken = this.GetSkipToken(expanded);
                        }
 
                        this.WriteEntryElement(expanded, o, expectedType, null, null, target);
                        yield return target;
                    }
 
                    hasMoved = elements.MoveNext();
                    lastObject = o;
                    lastExpandedSkipToken = skipToken;
                }
 
                // After looping through the objects in the sequence, decide if we need to write the next
                // page link and if yes, write it by invoking the delegate
                if (this.NeedNextPageLink(elements))
                {
                    nextPageLinkWriter(lastObject, lastExpandedSkipToken);
                }
 
                this.RestoreSegmentNames(savedSegmentNames);
            }
            finally
            {
                if (disposeElements)
                {
                    WebUtil.Dispose(elements);
                }
            }
        }
 
        /// <summary>
        /// Write entry/feed elements, except the content element and related links
        /// </summary>
        /// <param name="element">entity instance being serialized</param>
        /// <param name="type">resource type of the entry element</param>
        /// <param name="title">title for the current element</param>
        /// <param name="absoluteUri">absolute uri for the current element</param>
        /// <param name="relativeUri">relative uri for the current element</param>
        /// <param name="mediaETag">entity tag for the Media Resource</param>
        /// <param name="item">Item to write to.</param>
        private void WriteOtherElements(object element, ResourceType type, string title, Uri absoluteUri, string relativeUri, string mediaETag, SyndicationItem item)
        {
            Debug.Assert(item != null, "item != null");
            Debug.Assert(absoluteUri != null, "absoluteUri != null");
            Debug.Assert(relativeUri != null, "relativeUri != null");
 
            // Write Id element
            item.Id = absoluteUri.AbsoluteUri;
 
            // Write Updated element
            item.LastUpdatedTime = this.lastUpdatedTime;
 
            // Write "edit-media" link
            if (type != null && type.IsMediaLinkEntry)
            {
                KeyValuePair<XmlQualifiedName, string>[] attributeExtensions = new KeyValuePair<XmlQualifiedName, string>[0];
                if (element != null && !string.IsNullOrEmpty(mediaETag))
                {
                    XmlQualifiedName mediaResourceETagKey = new XmlQualifiedName(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace);
                    attributeExtensions = new KeyValuePair<XmlQualifiedName, string>[] { new KeyValuePair<XmlQualifiedName, string>(mediaResourceETagKey, mediaETag) };
                }
 
                WriteLinkRelations(
                    title,
                    XmlConstants.AtomEditMediaRelationAttributeValue,
                    DataServiceStreamProviderWrapper.GetStreamEditMediaUri(relativeUri),
                    item,
                    attributeExtensions);
            }
 
            // Write "edit" link
            WriteLinkRelations(
                title,
                XmlConstants.AtomEditRelationAttributeValue,
                relativeUri,
                item);
        }
 
        /// <summary>Writes all the properties of the specified resource or complex object.</summary>
        /// <param name="expanded">Expanded properties for the result.</param>
        /// <param name="customObject">Resource or complex object with properties to write out.</param>
        /// <param name="resourceType">resourceType containing metadata about the current custom object</param>
        /// <param name="absoluteUri">absolute uri for the given resource</param>
        /// <param name="relativeUri">relative uri for the given resource</param>
        /// <param name="item">Item in which to place links / expansions.</param>
        /// <param name="content">Content in which to place values.</param>
        /// <param name="currentSourceRoot">Epm source sub-tree corresponding to <paramref name="customObject"/></param>
        private void WriteObjectProperties(IExpandedResult expanded, object customObject, ResourceType resourceType, Uri absoluteUri, string relativeUri, SyndicationItem item, DictionaryContent content, EpmSourcePathSegment currentSourceRoot)
        {
            Debug.Assert(customObject != null, "customObject != null");
            Debug.Assert(resourceType != null, "resourceType != null");
 
            Debug.Assert(!String.IsNullOrEmpty(relativeUri), "!String.IsNullOrEmpty(relativeUri)");
            
            if (absoluteUri == null && resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
            {
                // entity type should have an URI, complex type should not have an URI
                // If the static type of the object is "Object", we will mistreat an entity type as complex type and hit this situation
                throw new DataServiceException(500, Strings.BadProvider_InconsistentEntityOrComplexTypeUsage(resourceType.Name));
            }
 
            this.RecurseEnter();
            try
            {
                List<ResourcePropertyInfo> navProperties = null;
                IEnumerable<ProjectionNode> projectionNodes = null;
                if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
                {
                    Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null");
                    if (this.Provider.IsEntityTypeDisallowedForSet(this.CurrentContainer, resourceType))
                    {
                        throw new InvalidOperationException(Strings.BaseServiceProvider_NavigationPropertiesOnDerivedEntityTypesNotSupported(resourceType.FullName, this.CurrentContainer.Name));
                    }
 
                    navProperties = new List<ResourcePropertyInfo>(resourceType.Properties.Count);
 
                    projectionNodes = this.GetProjections();
                }
 
                if (projectionNodes == null)
                {
                    var action = resourceType.DictionarySerializerDelegate;
                    if (action == null && this.Provider.IsV1Provider)
                    {
                        Module module = typeof(SyndicationSerializer).Module;
                        Type customObjectType = customObject.GetType();
                        Type[] parameterTypes = new Type[] { typeof(object), typeof(DictionaryContent) };
                        DynamicMethod method = new DynamicMethod("content_populator", typeof(void), parameterTypes, module, false /* skipVisibility */);
                        ILGenerator generator = method.GetILGenerator();
                        MethodInfo methodWritePrimitiveValue = typeof(SyndicationSerializer).GetMethod("WritePrimitiveValue", BindingFlags.Static | BindingFlags.NonPublic);
 
                        // Downcast the argument.
                        generator.Emit(OpCodes.Ldarg_0);
                        generator.Emit(OpCodes.Castclass, customObjectType);
 
                        foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive))
                        {
                            if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name))
                            {
                                continue;
                            }
 
                            // WritePrimitiveValue(propertyValue, property.Name, property.ResourceType, content);
                            generator.Emit(OpCodes.Dup);
                            generator.Emit(OpCodes.Call, resourceType.GetPropertyInfo(property).GetGetMethod());
                            if (property.Type.IsValueType)
                            {
                                generator.Emit(OpCodes.Box, property.Type);
                            }
 
                            generator.Emit(OpCodes.Ldstr, property.Name);
                            generator.Emit(OpCodes.Ldstr, property.ResourceType.FullName);
                            generator.Emit(OpCodes.Ldarg_1);
                            generator.Emit(OpCodes.Call, methodWritePrimitiveValue);
                        }
 
                        generator.Emit(OpCodes.Pop);
                        generator.Emit(OpCodes.Ret);
                        action = (Action<object, DictionaryContent>)method.CreateDelegate(typeof(Action<object, DictionaryContent>), null);
                        resourceType.DictionarySerializerDelegate = action;
                    }
 
                    if (action != null)
                    {
                        action(customObject, content);
                    }
                    else
                    {
                        foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive))
                        {
                            object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null);
                            if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name))
                            {
                                continue;
                            }
 
                            WritePrimitiveValue(propertyValue, property.Name, property.ResourceType.FullName, content);
                        }
                    }
 
                    foreach (ResourceProperty property in this.Provider.GetResourceProperties(this.CurrentContainer, resourceType))
                    {
                        string propertyName = property.Name;
                        if (property.TypeKind == ResourceTypeKind.EntityType)
                        {
                            Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types");
 
                            object propertyValue =
                                (this.ShouldExpandSegment(property.Name)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null;
                            navProperties.Add(new ResourcePropertyInfo(property, propertyValue));
                        }
                        else
                        {
                            if (property.TypeKind == ResourceTypeKind.ComplexType)
                            {
                                object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null);
                                bool needPop = this.PushSegmentForProperty(property);
                                this.WriteComplexObjectValue(
                                        propertyValue,
                                        propertyName,
                                        property.ResourceType,
                                        relativeUri + "/" + property.Name,
                                        content,
                                        SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, property.Name));
                                this.PopSegmentName(needPop);
                            }
                        }
                    }
 
                    if (resourceType.IsOpenType)
                    {
                        IEnumerable<KeyValuePair<string, object>> properties = this.Provider.GetOpenPropertyValues(customObject);
                        foreach (KeyValuePair<string, object> property in properties)
                        {
                            string propertyName = property.Key;
 
                            if (String.IsNullOrEmpty(propertyName))
                            {
                                throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyName(resourceType.FullName));
                            }
 
                            Type valueType;
                            ResourceType propertyResourceType;
 
                            object value = property.Value;
 
                            if (value == null || value == DBNull.Value)
                            {
                                valueType = typeof(string);
                                propertyResourceType = ResourceType.PrimitiveStringResourceType;
                            }
                            else
                            {
                                valueType = value.GetType();
                                propertyResourceType = WebUtil.GetResourceType(this.Provider, value);
                            }
 
                            // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it.
                            if (propertyResourceType == null)
                            {
                                throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName));
                            }
 
                            if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
                            {
                                if (value != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName))
                                {
                                    continue;
                                }
 
                                WritePrimitiveValue(value, propertyName, propertyResourceType.FullName, content);
                            }
                            else
                            {
                                if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
                                {
                                    Debug.Assert(propertyResourceType.InstanceType == valueType, "propertyResourceType.Type == valueType");
                                    this.WriteComplexObjectValue(
                                            value,
                                            propertyName,
                                            propertyResourceType,
                                            relativeUri + "/" + propertyName,
                                            content,
                                            SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName));
                                }
                                else
                                {
                                    Debug.Assert(
                                        propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType,
                                        "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type.");
 
                                    // Open navigation properties are not supported on OpenTypes
                                    throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName));
                                }
                            }
                        }
                    }
                }
                else
                {
                    foreach (ProjectionNode projectionNode in projectionNodes)
                    {
                        string propertyName = projectionNode.PropertyName;
                        ResourceProperty property = resourceType.TryResolvePropertyName(propertyName);
 
                        // First solve the normal entity type property - turn it into a nav. property record
                        if (property != null && property.TypeKind == ResourceTypeKind.EntityType)
                        {
                            Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types");
 
                            // By calling the GetResourceProperties we will use the cached list of properties
                            //   for the given type and set. But we have to search through it.
                            // We could use the GetContainer (since that's what the GetResourceProperties does) and check
                            //   if it returns null, but result of that is only partially cached so it might be expensive
                            //   to evaluate for each item in the feed.
                            if (this.Provider.GetResourceProperties(this.CurrentContainer, resourceType).Contains(property))
                            {
                                object expandedPropertyValue =
                                    (this.ShouldExpandSegment(propertyName)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null;
                                navProperties.Add(new ResourcePropertyInfo(property, expandedPropertyValue));
                            }
 
                            continue;
                        }
 
                        // Now get the property value
                        object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, property == null ? propertyName : null);
 
                        // Determine the type of the property
                        ResourceType propertyResourceType;
                        if (property != null)
                        {
                            propertyResourceType = property.ResourceType;
                        }
                        else
                        {
                            if (propertyValue == null || propertyValue == DBNull.Value)
                            {
                                propertyResourceType = ResourceType.PrimitiveStringResourceType;
                            }
                            else
                            {
                                propertyResourceType = WebUtil.GetResourceType(this.Provider, propertyValue);
 
                                // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it.
                                if (propertyResourceType == null)
                                {
                                    throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName));
                                }
                            }
                        }
 
                        // And write out the value (depending on the type of the property)
                        if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive)
                        {
                            if (propertyValue == DBNull.Value)
                            {
                                propertyValue = null;
                            }
 
                            if (propertyValue != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName))
                            {
                                continue;
                            }
 
                            WritePrimitiveValue(propertyValue, propertyName, propertyResourceType.FullName, content);
                        }
                        else if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
                        {
                            bool needPop = false;
                            if (property != null)
                            {
                                needPop = this.PushSegmentForProperty(property);
                            }
 
                            this.WriteComplexObjectValue(
                                    propertyValue,
                                    propertyName,
                                    propertyResourceType,
                                    relativeUri + "/" + propertyName,
                                    content,
                                    SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName));
                            if (property != null)
                            {
                                this.PopSegmentName(needPop);
                            }
                        }
                        else
                        {
                            Debug.Assert(
                                propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType,
                                "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type.");
 
                            // Open navigation properties are not supported on OpenTypes
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName));
                        }
                    }
                }
 
                if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
                {
                    for (int i = 0; i < navProperties.Count; i++)
                    {
                        ResourcePropertyInfo propertyInfo = navProperties[i];
                        ResourceProperty navProperty = propertyInfo.Property;
 
                        Debug.Assert(
                            navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) ||
                            navProperty.IsOfKind(ResourcePropertyKind.ResourceSetReference),
                            "this must be nav property");
 
                        // Generate a link - see http://tools.ietf.org/html/rfc4287#section-4.2.7
                        string linkType = navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) ? XmlConstants.AtomEntryElementName : XmlConstants.AtomFeedElementName;
                        linkType = String.Format(CultureInfo.InvariantCulture, "{0};{1}={2}", XmlConstants.MimeApplicationAtom, XmlConstants.AtomTypeAttributeName, linkType);
                        string segmentIdentifier = navProperty.Name;
 
                        if (!this.ShouldExpandSegment(navProperty.Name))
                        {
                            WriteDeferredContentElement(
                                XmlConstants.DataWebRelatedNamespace + navProperty.Name,
                                navProperty.Name,
                                relativeUri + "/" + segmentIdentifier,
                                linkType,
                                item);
                        }
                        else
                        {
                            object propertyValue = propertyInfo.Value;
                            IExpandedResult expandedResultPropertyValue = propertyValue as IExpandedResult;
                            object expandedPropertyValue =
                                expandedResultPropertyValue != null ?
                                GetExpandedElement(expandedResultPropertyValue) :
                                propertyValue;
                            string propertyRelativeUri = relativeUri + "/" + segmentIdentifier;
                            Uri propertyAbsoluteUri = RequestUriProcessor.AppendUnescapedSegment(absoluteUri, segmentIdentifier);
 
                            SyndicationLink link = new SyndicationLink();
                            link.RelationshipType = XmlConstants.DataWebRelatedNamespace + navProperty.Name;
                            link.Title = navProperty.Name;
                            link.Uri = new Uri(propertyRelativeUri, UriKind.RelativeOrAbsolute);
                            link.MediaType = linkType;
                            item.Links.Add(link);
 
                            bool needPop = this.PushSegmentForProperty(navProperty);
 
                            // if this.CurrentContainer is null, the target set of the navigation property is hidden.
                            if (this.CurrentContainer != null)
                            {
                                if (navProperty.IsOfKind(ResourcePropertyKind.ResourceSetReference))
                                {
                                    IEnumerable enumerable;
                                    bool collection = WebUtil.IsElementIEnumerable(expandedPropertyValue, out enumerable);
                                    Debug.Assert(collection, "metadata loading must have ensured that navigation set properties must implement IEnumerable");
 
                                    SyndicationFeed feed = new SyndicationFeed();
                                    InlineAtomFeed inlineFeedExtension = new InlineAtomFeed(feed, this.factory);
                                    link.ElementExtensions.Add(inlineFeedExtension);
                                    IEnumerator enumerator = enumerable.GetEnumerator();
                                    try
                                    {
                                        bool hasMoved = enumerator.MoveNext();
                                        this.WriteFeedElements(
                                            propertyValue as IExpandedResult, 
                                            enumerator, 
                                            navProperty.ResourceType, 
                                            navProperty.Name, 
                                            propertyAbsoluteUri, 
                                            propertyRelativeUri, 
                                            hasMoved, 
                                            feed,
                                            true);
                                    }
                                    catch
                                    {
                                        WebUtil.Dispose(enumerator);
                                        throw;
                                    }
                                }
                                else
                                {
                                    SyndicationItem inlineItem = new SyndicationItem();
                                    this.WriteEntryElement(propertyValue as IExpandedResult, expandedPropertyValue, navProperty.ResourceType, propertyAbsoluteUri, propertyRelativeUri, inlineItem);
                                    InlineAtomItem inlineItemExtension = new InlineAtomItem(inlineItem, this.factory);
                                    link.ElementExtensions.Add(inlineItemExtension);
                                }
                            }
 
                            this.PopSegmentName(needPop);
                        }
                    }
                }
            }
            finally
            {
                // The matching call to RecurseLeave is in a try/finally block not because it's necessary in the 
                // presence of an exception (progress will halt anyway), but because it's easier to maintain in the 
                // code in the presence of multiple exit points (returns).
                this.RecurseLeave();
            }
        }
 
        #region Inner types.
 
        /// <summary>Stores the resource property, its value and a flag which indicates whether this is a open property or not.</summary>
        private struct ResourcePropertyInfo
        {
            /// <summary>refers to the property that this instance represents.</summary>
            private ResourceProperty resourceProperty;
 
            /// <summary>Value of the property.</summary>
            private object value;
 
            /// <summary>
            /// Creates a new instance of ResourcePropertyInfo.
            /// </summary>
            /// <param name="resourceProperty">resource property instance.</param>
            /// <param name="value">value for the resource property.</param>
            public ResourcePropertyInfo(ResourceProperty resourceProperty, object value)
            {
                Debug.Assert(resourceProperty != null, "resourceProperty != null");
                this.resourceProperty = resourceProperty;
                this.value = value;
            }
 
            /// <summary>Returns the resource property.</summary>
            internal ResourceProperty Property
            {
                get { return this.resourceProperty; }
            }
 
            /// <summary>Returns the value of the resource property.</summary>
            internal object Value
            {
                get { return this.value; }
            }
        }
 
        /// <summary>Wrapper for an inline item.</summary>
        [XmlRoot(ElementName = XmlConstants.AtomInlineElementName, Namespace = XmlConstants.DataWebMetadataNamespace)]
        internal class InlineAtomItem : IXmlSerializable
        {
            /// <summary>Factory for item formatter.</summary>
            private readonly SyndicationFormatterFactory factory;
 
            /// <summary>Item being serialized.</summary>
            private SyndicationItem item;
 
            /// <summary>Empty constructor.</summary>
            internal InlineAtomItem()
            {
            }
 
            /// <summary>Initializing constructor.</summary>
            /// <param name="item">Item being serialized.</param>
            /// <param name="factory">Factory for item formatter.</param>
            internal InlineAtomItem(SyndicationItem item, SyndicationFormatterFactory factory)
            {
                this.item = item;
                this.factory = factory;
            }
 
            #region IXmlSerializable Members
 
            /// <summary>Reserved method.</summary>
            /// <returns>null</returns>
            public System.Xml.Schema.XmlSchema GetSchema()
            {
                return null;
            }
 
            /// <summary>Generates an object from its XML representation.</summary>
            /// <param name='reader'>XmlReader with representation.</param>
            public void ReadXml(XmlReader reader)
            {
                throw Error.NotImplemented();
            }
 
            /// <summary>Converts an object into its XML representation.</summary>
            /// <param name='writer'>Writer to write representation into.</param>
            public void WriteXml(XmlWriter writer)
            {
                SyndicationItemFormatter formatter = this.factory.CreateSyndicationItemFormatter(this.item);
                if (formatter != null)
                {
                    formatter.WriteTo(writer);
                }
            }
 
            #endregion
        }
 
        /// <summary>Wrapper for an inline feed.</summary>
        [XmlRoot(ElementName = XmlConstants.AtomInlineElementName, Namespace = XmlConstants.DataWebMetadataNamespace)]
        internal class InlineAtomFeed : IXmlSerializable
        {
            /// <summary>Factory for item formatter.</summary>
            private readonly SyndicationFormatterFactory factory;
 
            /// <summary>Feed being serialized.</summary>
            private SyndicationFeed feed;
 
            /// <summary>Empty constructor.</summary>
            internal InlineAtomFeed()
            {
            }
 
            /// <summary>Initializing constructor.</summary>
            /// <param name="feed">Feed being serialized.</param>
            /// <param name="factory">Factory for item formatter.</param>
            internal InlineAtomFeed(SyndicationFeed feed, SyndicationFormatterFactory factory)
            {
                this.feed = feed;
                this.factory = factory;
            }
 
            #region IXmlSerializable Members
 
            /// <summary>Reserved method.</summary>
            /// <returns>null</returns>
            public System.Xml.Schema.XmlSchema GetSchema()
            {
                return null;
            }
 
            /// <summary>Generates an object from its XML representation.</summary>
            /// <param name='reader'>XmlReader with representation.</param>
            public void ReadXml(XmlReader reader)
            {
                throw Error.NotImplemented();
            }
 
            /// <summary>Converts an object into its XML representation.</summary>
            /// <param name='writer'>Writer to write representation into.</param>
            public void WriteXml(XmlWriter writer)
            {
                this.factory.CreateSyndicationFeedFormatter(this.feed).WriteTo(writer);
            }
 
            #endregion
        }
 
        #endregion Inner types.
    }
}