예제 #1
0
 /// <summary>
 /// Initializes a new DictionaryContent instance by copying values from
 /// the specified one.
 /// </summary>
 /// <param name="other">Dictionary to copy content from.</param>
 /// <remarks>This produces a shallow copy only.</remarks>
 private DictionaryContent(DictionaryContent other)
 {
     Debug.Assert(other != null, "other != null");
     this.valueContents = other.valueContents;
     this.valueTypes    = other.valueTypes;
     this.valueNames    = other.valueNames;
 }
예제 #2
0
 /// <summary>
 /// Initializes a new DictionaryContent instance by copying values from 
 /// the specified one.
 /// </summary>
 /// <param name="other">Dictionary to copy content from.</param>
 /// <remarks>This produces a shallow copy only.</remarks>
 private DictionaryContent(DictionaryContent other)
 {
     Debug.Assert(other != null, "other != null");
     this.valueContents = other.valueContents;
     this.valueTypes = other.valueTypes;
     this.valueNames = other.valueNames;
 }
        /// <summary>Delegates to each of custom and syndication serializers for serializing content</summary>
        /// <param name="content">Content in which to write null valued properties</param>
        /// <param name="provider">Data Service provider used for rights verification.</param>
        internal void Serialize(DictionaryContent content, DataServiceProviderWrapper provider)
        {
            if (this.NeedEpmSerialization)
            {
                Debug.Assert(this.epmSyndicationSerializer != null, "ResourceType with mapping implies a valid syndication content serializer");
                this.epmSyndicationSerializer.Serialize(provider);
                Debug.Assert(this.epmCustomSerializer != null, "ResourceType with mapping implies a valid custom content serializer");
                this.epmCustomSerializer.Serialize(provider);

                this.nullValuedProperties.AddNullValuesToContent(content);
            }
            else
            {
                Debug.Assert(this.targetItem != null, "Must always have target content item");
                this.targetItem.Authors.Add(new SyndicationPerson(null, String.Empty, null));
            }
        }
            /// <summary>Adds the null valued properties to the content section of a syndication entry</summary>
            /// <param name="currentRoot">Current root node</param>
            /// <param name="currentContent">Current collection to which property is to be added</param>
            private void AddNullValuesToContent(EpmNullValuedPropertyNode currentRoot, DictionaryContent currentContent)
            {
                foreach (EpmNullValuedPropertyNode node in currentRoot.Children)
                {
                    bool found;
                    DictionaryContent c = currentContent.Lookup(node.Name, out found);

                    Debug.Assert(node.ResourceType != null, "node.ResourceType != null");
                    switch (node.ResourceType.ResourceTypeKind)
                    {
                        case ResourceTypeKind.ComplexType:
                            if (!found)
                            {
                                // If a complex property is not found in content, it is either not being projected
                                // or all of its properties are mapped and all of them have KeepInContent=false
                                Debug.Assert(c == null, "when look up not found, c should be null.");
                                if (node.Element != null)
                                {
                                    Debug.Assert(node.Children.Count > 0, "If the property represented by the current node is not null, there must be children nodes.");

                                    // The complex property is not null, but some of its descendant properties are null.
                                    // We need to serialize the type name of the complex property.
                                    c = new DictionaryContent();
                                    currentContent.Add(node.Name, node.ResourceType.FullName, c);
                                }
                                else
                                {
                                    Debug.Assert(node.Children.Count == 0, "If the property represented by the current node is not null, there must not be any children node.");

                                    // The complex property is null, we write out m:null='true'.
                                    currentContent.AddNull(node.ResourceType.FullName, node.Name);
                                }
                            }

                            if (c != null)
                            {
                                // Only add the children properties if the complex property is not null.
                                this.AddNullValuesToContent(node, c);
                            }

                            break;

                        case ResourceTypeKind.Primitive:
                            Debug.Assert(c == null, "DictionaryContent not expected for primitive properties.");
                            Debug.Assert(node.Element == null, "node.Element == null");
                            if (!found)
                            {
                                currentContent.AddNull(node.ResourceType.FullName, node.Name);
                            }

                            // if found, use the value in currentContent, we don't need to do anything here.
                            break;

                        case ResourceTypeKind.EntityType:
                            Debug.Assert(false, "We cannot map navigation properties with friendly feeds.");
                            break;
                    }
                }
            }
 /// <summary>Adds the null valued properties to the content section</summary>
 /// <param name="content">Content to which null properties are to be added</param>
 internal void AddNullValuesToContent(DictionaryContent content)
 {
     this.AddNullValuesToContent(this.root, content);
 }
        /// <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();
            }
        }
        /// <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 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>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);
     }
 }