/// <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); } }