/// <summary> /// Writes the custom mapped EPM properties to an XML writer which is expected to be positioned such to write /// a child element of the entry element. /// </summary> /// <param name="writer">The XmlWriter to write to.</param> /// <param name="epmTargetTree">The EPM target tree to use.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> internal static void WriteEntryEpm( XmlWriter writer, EpmTargetTree epmTargetTree, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); Debug.Assert(epmTargetTree != null, "epmTargetTree != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available."); // If there are no custom mappings, just return null. EpmTargetPathSegment customRootSegment = epmTargetTree.NonSyndicationRoot; Debug.Assert(customRootSegment != null, "EPM Target tree must always have non-syndication root."); if (customRootSegment.SubSegments.Count == 0) { return; } foreach (EpmTargetPathSegment targetSegment in customRootSegment.SubSegments) { Debug.Assert(!targetSegment.IsAttribute, "Target segments under the custom root must be for elements only."); string alreadyDeclaredPrefix = null; WriteElementEpm(writer, targetSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix); } }
/// <summary> /// Constructor. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="odataWriterSettings">Configuration settings for the writer to create.</param> /// <param name="encoding">The encoding to use for writing.</param> /// <param name="writingResponse">True if the writer is to write a response payload; false if it's to write a request payload.</param> /// <param name="metadataProvider">The metadata provider to use.</param> /// <param name="writingFeed">True if the writer is created for writing a feed; false when it is created for writing an entry.</param> /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param> internal ODataJsonWriter( Stream stream, ODataWriterSettings odataWriterSettings, Encoding encoding, bool writingResponse, DataServiceMetadataProviderWrapper metadataProvider, bool writingFeed, bool synchronous) : base( odataWriterSettings.Version, odataWriterSettings.BaseUri, writingResponse, metadataProvider, writingFeed, synchronous) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(stream != null, "stream != null"); Debug.Assert(odataWriterSettings != null, "odataWriterSettings != null"); this.outputStream = new AsyncBufferedStream(stream); this.textWriter = new StreamWriter(this.outputStream, encoding); this.jsonWriter = new JsonWriter(this.textWriter, odataWriterSettings.Indent); }
/// <summary> /// Validates a type name to ensure that it's not an empty string. /// </summary> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="typeName">The type name to validate.</param> /// <param name="typeKind">The expected type kind for the given type name.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <returns>The resource type with the given name and kind if the metadata was available, otherwise null.</returns> internal static ResourceType ValidateTypeName(DataServiceMetadataProviderWrapper metadata, string typeName, ResourceTypeKind typeKind, bool isOpenPropertyType) { DebugUtils.CheckNoExternalCallers(); if (typeName == null) { // if we have metadata the type name of an entry or a complex value of an open property must not be null if (metadata != null && (typeKind == ResourceTypeKind.EntityType || isOpenPropertyType)) { throw new ODataException(Strings.ODataWriterCore_MissingTypeNameWithMetadata); } return null; } // we do not allow empty type names if (typeName.Length == 0) { throw new ODataException(Strings.ODataWriter_TypeNameMustNotBeEmpty); } // If we do have metadata, lookup the type and translate it to ResourceType. ResourceType resourceType = null; if (metadata != null) { resourceType = typeKind == ResourceTypeKind.MultiValue ? ValidateMultiValueTypeName(metadata, typeName) : ValidateNonMultiValueTypeName(metadata, typeName, typeKind); } return resourceType; }
/// <summary> /// Constructor. /// </summary> /// <param name="version">The version of the OData protocol to use.</param> /// <param name="metadataProvider">The metadata provider to use.</param> /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param> protected ODataCollectionWriterCore(ODataVersion version, DataServiceMetadataProviderWrapper metadataProvider, bool synchronous) { DebugUtils.CheckNoExternalCallers(); this.version = version; this.metadataProvider = metadataProvider; this.synchronous = synchronous; this.scopes.Push(new Scope(CollectionWriterState.Start, null)); }
/// <summary> /// Writes an EPM element target. /// </summary> /// <param name="writer">The writer to write to.</param> /// <param name="targetSegment">The target segment describing the element to write.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private static void WriteElementEpm( XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null && !targetSegment.IsAttribute, "Only element target segments are supported by this method."); // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string. string elementPrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteStartElement(elementPrefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri); // Write out the declaration explicitly only if the prefix is not empty (just like the WCF DS does) if (elementPrefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } // Serialize the sub segment attributes first foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments) { if (subSegment.IsAttribute) { WriteAttributeEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix); } } if (targetSegment.HasContent) { Debug.Assert(!targetSegment.SubSegments.Any(subSegment => !subSegment.IsAttribute), "If the segment has a content, it must not have any element children."); string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata); // TODO: In V3 we should check for textPropertyValue == null and write out the m:null in that case. Debug.Assert(textPropertyValue != null, "Null property value should not get here, the GetEntryPropertyValueAsText should take care of that for now."); writer.WriteString(textPropertyValue); } else { // Serialize the sub segment elements now foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments) { if (!subSegment.IsAttribute) { WriteElementEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix); } } } // Close the element writer.WriteEndElement(); }
/// <summary> /// Constructor. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="writerSettings">Configuration settings for the writer to create.</param> /// <param name="encoding">The encoding to use for writing.</param> /// <param name="metadataProvider">The metadata provider to use.</param> /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param> internal ODataAtomCollectionWriter( Stream stream, ODataWriterSettings writerSettings, Encoding encoding, DataServiceMetadataProviderWrapper metadataProvider, bool synchronous) : base(writerSettings.Version, metadataProvider, synchronous) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(stream != null, "stream != null"); Debug.Assert(writerSettings != null, "writerSettings != null"); this.outputStream = new AsyncBufferedStream(stream); this.writer = ODataAtomWriterUtils.CreateXmlWriter(this.outputStream, writerSettings, encoding); }
/// <summary> /// Constructor. /// </summary> /// <param name="version">The version of the OData protocol to use.</param> /// <param name="baseUri">The Base URI to use for all the URIs being written.</param> /// <param name="writingResponse">True if the writer is to write a response payload; false if it's to write a request payload.</param> /// <param name="metadataProvider">The metadata provider to use.</param> /// <param name="writingFeed">True if the writer is created for writing a feed; false when it is created for writing an entry.</param> /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param> protected ODataWriterCore( ODataVersion version, Uri baseUri, bool writingResponse, DataServiceMetadataProviderWrapper metadataProvider, bool writingFeed, bool synchronous) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(baseUri == null || baseUri.IsAbsoluteUri, "baseUri must be either null or absolute"); this.version = version; this.baseUri = baseUri; this.writingResponse = writingResponse; this.metadataProvider = metadataProvider; this.writingFeed = writingFeed; this.synchronous = synchronous; this.scopes.Push(Scope.Create(WriterState.Start, null)); }
/// <summary> /// Writes property names and value pairs. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="owningType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param> /// <param name="properties">The enumeration of properties to write out.</param> /// <param name="version">The protocol version used for writing.</param> internal static void WriteProperties( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ResourceType owningType, IEnumerable<ODataProperty> properties, ODataVersion version) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(jsonWriter != null, "jsonWriter != null"); if (properties == null) { return; } foreach (ODataProperty property in properties) { WriteProperty(jsonWriter, metadata, property, owningType, version); } }
/// <summary> /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM. /// </summary> /// <param name="targetSegment">The target segment to read the value for.</param> /// <param name="epmValueCache">The entry EPM value cache to use.</param> /// <param name="resourceType">The resource type of the entry being processed.</param> /// <param name="metadata">The metadata provider to use.</param> /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns> private static string GetEntryPropertyValueAsText( EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata) { Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content."); Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(resourceType != null, "resourceType != null"); bool nullOnParentProperty; object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty); return EpmWriterUtils.GetPropertyValueAsText(propertyValue); }
/// <summary> /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM. /// </summary> /// <param name="targetSegment">The target segment to read the value for.</param> /// <param name="epmValueCache">The entry EPM value cache to use.</param> /// <param name="resourceType">The resource type of the entry being processed.</param> /// <param name="metadata">The metadata provider to use.</param> /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns> private static string GetEntryPropertyValueAsText( EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata) { Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content."); Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(resourceType != null, "resourceType != null"); bool nullOnParentProperty; object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty); if (propertyValue == null) { // TODO: In V3 when we use new format for null values using the m:null attribute we need to check here // if we're mapping into an attribute, in which case we should fail (can't write null to attribute in V3) // or if we're writing into element return true null, to use the m:null attribute. // In V2 nulls are written out as empty string always (and they're written into content as well) return string.Empty; } else { return EpmWriterUtils.GetPropertyValueAsText(propertyValue); } }
/// <summary> /// Writes an EPM attribute target. /// </summary> /// <param name="writer">The writer to write to.</param> /// <param name="targetSegment">The target segment describing the attribute to write.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private static void WriteAttributeEpm( XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null && targetSegment.IsAttribute, "Only attribute target segments are supported by this method."); Debug.Assert(targetSegment.HasContent, "Attribute target segments must have content."); string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata); Debug.Assert(textPropertyValue != null, "Text value of a property mapped to attribute must not be null, the GetEntryPropertyValueAsText should take care of that."); // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string. string attributePrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteAttributeString(attributePrefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, textPropertyValue); // Write out the declaration explicitely only if the prefix is not empty (just like the WCF DS does) if (attributePrefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } }
/// <summary> /// Write an <see cref="ODataProperty" /> to the given stream. This method creates an /// async buffered stream and writes the property to it. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="property">The property to write.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="writingResponse">Flag indicating whether a request or a response is being written.</param> internal static void WriteTopLevelProperty( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ODataProperty property, ResourceType owningType, ODataVersion version, bool writingResponse) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(jsonWriter != null, "jsonWriter != null"); Debug.Assert(property != null, "property != null"); ODataJsonWriterUtils.WriteDataWrapper( jsonWriter, writingResponse, () => { jsonWriter.StartObjectScope(); WriteProperty(jsonWriter, metadata, property, owningType, version); jsonWriter.EndObjectScope(); }); }
/// <summary> /// Writes a name/value pair for a property. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="version">The protocol version used for writing.</param> private static void WriteProperty( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ODataProperty property, ResourceType owningType, ODataVersion version) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(jsonWriter != null, "jsonWriter != null"); ValidationUtils.ValidateProperty(property); ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType); bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null; jsonWriter.WriteName(property.Name); object value = property.Value; if (value == null) { // verify that MultiValue properties are not null if (resourceProperty != null && resourceProperty.Kind == ResourcePropertyKind.MultiValue) { throw new ODataException(Strings.ODataWriter_MultiValuePropertiesMustNotHaveNullValue(resourceProperty.Name)); } jsonWriter.WriteValue(null); } else { ResourceType resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType; ODataComplexValue complexValue = value as ODataComplexValue; if (complexValue != null) { WriteComplexValue(jsonWriter, metadata, complexValue, resourcePropertyType, isOpenPropertyType, version); } else { ODataMultiValue multiValue = value as ODataMultiValue; if (multiValue != null) { ODataVersionChecker.CheckMultiValueProperties(version, property.Name); WriteMultiValue(jsonWriter, metadata, multiValue, resourcePropertyType, isOpenPropertyType, version); } else { WritePrimitiveValue(jsonWriter, value, resourcePropertyType); } } } }
/// <summary> /// Resolve a type name against the provided metadata. If no type name is given we either throw (if a type name on the value is required, e.g., on entries) /// or infer the type from metadata (if available). /// </summary> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="typeFromMetadata">The type inferred from metadata or null if no metadata is available.</param> /// <param name="typeName">The type name to be resolved.</param> /// <param name="typeKind">The expected type kind of the resolved type.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <returns>A resource type for the <paramref name="typeName"/> or null if no metadata is available.</returns> internal static ResourceType ResolveTypeName(DataServiceMetadataProviderWrapper metadata, ResourceType typeFromMetadata, ref string typeName, ResourceTypeKind typeKind, bool isOpenPropertyType) { DebugUtils.CheckNoExternalCallers(); ResourceType typeFromValue = ValidationUtils.ValidateTypeName(metadata, typeName, typeKind, isOpenPropertyType); typeFromValue = ValidationUtils.ValidateMetadataType(typeFromMetadata, typeFromValue, typeKind); // derive the type name from the metadata if available if (typeName == null && typeFromValue != null) { typeName = typeFromValue.FullName; } return typeFromValue; }
/// <summary> /// Creates a new ODataMessageWriter for the given request message and writer settings. /// </summary> /// <param name="requestMessage">The request message for which to create the writer.</param> /// <param name="settings">The writer settings to use for writing the message payload.</param> /// <param name="metadataProvider">The metadata provider to use.</param> public ODataMessageWriter(IODataRequestMessage requestMessage, ODataWriterSettings settings, IDataServiceMetadataProvider metadataProvider) { ExceptionUtils.CheckArgumentNotNull(requestMessage, "requestMessage"); ExceptionUtils.CheckArgumentNotNull(settings, "settings"); ODataVersionChecker.CheckVersionSupported(settings.Version); this.writingResponse = false; this.message = new ODataRequestMessage(requestMessage); this.settings = settings; if (metadataProvider != null) { this.metadataProvider = new DataServiceMetadataProviderWrapper(metadataProvider); } }
/// <summary> /// Writes a service document in ATOM/XML format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="defaultWorkspace">The default workspace to write in the service document.</param> /// <param name="baseUri">The base Uri specified in the writer settings for writing the service document.</param> internal static void WriteServiceDocument( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataWorkspace defaultWorkspace, Uri baseUri) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); Debug.Assert(defaultWorkspace != null, "defaultWorkspace != null"); if (baseUri == null) { // We require a base Uri for writing service documents in Xml format since the Uris for resource collections/entity sets // will be relative. throw new ODataException(Strings.ODataAtomWriterUtils_BaseUriRequiredForWritingServiceDocument); } IEnumerable<ODataResourceCollectionInfo> collections = ValidationUtils.ValidateWorkspace(metadata == null ? null : metadata.ResourceSets, defaultWorkspace); // <app:service> writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingServiceElementName, AtomConstants.AtomPublishingNamespace); // xml:base=... writer.WriteAttributeString(AtomConstants.XmlBaseAttributeName, AtomConstants.XmlNamespace, baseUri.AbsoluteUri); // xmlns=http://www.w3.org/2007/app writer.WriteAttributeString(AtomConstants.XmlnsNamespacePrefix, AtomConstants.XmlNamespacesNamespace, AtomConstants.AtomPublishingNamespace); // xmlns:atom="http://www.w3.org/2005/Atom" writer.WriteAttributeString( AtomConstants.NonEmptyAtomNamespacePrefix, AtomConstants.XmlNamespacesNamespace, AtomConstants.AtomNamespace); // <app:workspace> writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingWorkspaceElementName, AtomConstants.AtomPublishingNamespace); ODataAtomWriterMetadataUtils.WriteWorkspaceMetadata(writer, defaultWorkspace); foreach (ODataResourceCollectionInfo collectionInfo in collections) { // <app:collection> writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingCollectionElementName, AtomConstants.AtomPublishingNamespace); // The name of the collection is the entity set name; The href of the <app:collection> element must be the link for the entity set. // Since we model the collection as having a 'Name' (for JSON) we require a base Uri for Atom/Xml. writer.WriteAttributeString(AtomConstants.AtomHRefAttributeName, Uri.EscapeUriString(collectionInfo.Name)); ODataAtomWriterMetadataUtils.WriteCollectionMetadata(writer, collectionInfo); // </app:collection> writer.WriteEndElement(); } // </app:workspace> writer.WriteEndElement(); // </app:service> writer.WriteEndElement(); }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isTopLevel">True if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> internal static void WriteProperty( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataProperty property, ResourceType owningType, ODataVersion version, bool isTopLevel, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); ValidationUtils.ValidateProperty(property); ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType); EpmSourcePathSegment epmSourcePathSegment = null; if (epmParentSourcePathSegment != null) { epmSourcePathSegment = epmParentSourcePathSegment.SubProperties.Where(subProperty => subProperty.PropertyName == property.Name).FirstOrDefault(); } object value = property.Value; // TODO: If we implement validation or type conversions the value needs to be converted here // since the next method call needs to know if the value is a string or not in some cases. // If EPM tells us to skip this property in content, then we're done here. if (!ShouldWritePropertyInContent(value, epmSourcePathSegment, version)) { return; } // <d:propertyname> writer.WriteStartElement( isWritingCollection ? string.Empty : AtomConstants.ODataNamespacePrefix, property.Name, AtomConstants.ODataNamespace); if (isTopLevel) { WriteDefaultNamespaceAttributes(writer, DefaultNamespaceFlags.OData | DefaultNamespaceFlags.ODataMetadata); } // Null property value. if (value == null) { // verify that MultiValue properties are not null if (resourceProperty != null && resourceProperty.Kind == ResourcePropertyKind.MultiValue) { throw new ODataException(Strings.ODataWriter_MultiValuePropertiesMustNotHaveNullValue(resourceProperty.Name)); } ODataAtomWriterUtils.WriteNullAttribute(writer); } else { ODataComplexValue complexValue = value as ODataComplexValue; ResourceType resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType; bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null; // Complex properties are written recursively. if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { ODataMultiValue multiValue = value as ODataMultiValue; if (multiValue != null) { ODataVersionChecker.CheckMultiValueProperties(version, property.Name); WriteMultiValue(writer, metadata, multiValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { WritePrimitiveValue(writer, value, resourcePropertyType); } } } // </d:propertyname> writer.WriteEndElement(); }
/// <summary> /// Reads a property value starting with the specified index to the property value path. /// </summary> /// <param name="cachedProperties">The enumeration of properties to search for the first property in the property value path.</param> /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param> /// <param name="resourceType">The resource type of the entry or complex value the <paramref name="cachedProperties"/> enumeration belongs to.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="epmValueCache">The EPM value cache to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> private object ReadPropertyValue( IEnumerable<ODataProperty> cachedProperties, int sourceSegmentIndex, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, EpmValueCache epmValueCache, out bool nullOnParentProperty) { Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index."); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); EpmSourcePathSegment sourceSegment = this.propertyValuePath[sourceSegmentIndex]; string propertyName = sourceSegment.PropertyName; bool lastSegment = this.propertyValuePath.Length == sourceSegmentIndex + 1; ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(propertyName, resourceType); if (resourceProperty != null) { // If this is the last part of the path, then it has to be a primitive or multiValue type otherwise should be a complex type if (lastSegment) { if (!resourceProperty.IsOfKind(ResourcePropertyKind.Primitive) && !resourceProperty.IsOfKind(ResourcePropertyKind.MultiValue)) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } } else { if (!resourceProperty.IsOfKind(ResourcePropertyKind.ComplexType)) { throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } } } else { Debug.Assert( resourceType.IsOpenType, "Only open types can have undeclared properties, otherwise we should have failed in the ValidatePropertyDefined method."); } ODataProperty property = null; if (cachedProperties != null) { property = cachedProperties.FirstOrDefault(p => p.Name == propertyName); } if (property == null) { throw new ODataException(Strings.EpmSourceTree_MissingPropertyOnInstance(propertyName, resourceType.FullName)); } object propertyValue = property.Value; ODataComplexValue propertyComplexValue = propertyValue as ODataComplexValue; if (lastSegment) { if (propertyValue != null && resourceProperty != null) { ValidationUtils.ValidateIsExpectedPrimitiveType(propertyValue, resourceProperty.ResourceType); } // If this property is the last one it has to be either a primitive or multivalue // TODO: Check for named streams here as well if (propertyComplexValue != null) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } nullOnParentProperty = false; return propertyValue; } else { // Otherwise it's in the middle and thus it must be a complex value if (propertyComplexValue == null) { throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } string typeName = propertyComplexValue.TypeName; ResourceType complexValueType = MetadataUtils.ResolveTypeName( metadata, resourceProperty == null ? null : resourceProperty.ResourceType, ref typeName, ResourceTypeKind.ComplexType, resourceProperty == null); return this.ReadComplexPropertyValue( propertyComplexValue, sourceSegment, epmValueCache, sourceSegmentIndex + 1, complexValueType, metadata, out nullOnParentProperty); } }
/// <summary> /// Reads a property value starting on a complex value. /// </summary> /// <param name="complexValue">The complex value to start with.</param> /// <param name="complexPropertySegment">The EPM source path segment which points to the <paramref name="complexValue"/>.</param> /// <param name="epmValueCache">The EPM value cache to use.</param> /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param> /// <param name="resourceType">The resource type of the complex value.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> private object ReadComplexPropertyValue( ODataComplexValue complexValue, EpmSourcePathSegment complexPropertySegment, EpmValueCache epmValueCache, int sourceSegmentIndex, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, out bool nullOnParentProperty) { Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(sourceSegmentIndex >= 0, "sourceSegmentIndex >= 0"); Debug.Assert(resourceType != null, "resourceType != null"); if (complexValue == null) { nullOnParentProperty = true; return null; } return this.ReadPropertyValue( EpmValueCache.GetComplexValueProperties(epmValueCache, complexPropertySegment, complexValue, false), sourceSegmentIndex, resourceType, metadata, epmValueCache, out nullOnParentProperty); }
/// <summary> /// Reads a property value starting on an entry. /// </summary> /// <param name="epmValueCache">The EPM value cache for the entry to read from.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> internal object ReadEntryPropertyValue( EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, out bool nullOnParentProperty) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > 0, "The propertyValuePath must not be empty for an entry property."); Debug.Assert(resourceType != null, "resourceType != null"); // TODO - verify that we actually need the internal property PropertyValuePath // TODO - It might be possible to avoid the "value" type checks below if we do property value validation based on the resource type return this.ReadPropertyValue( epmValueCache.EntryProperties, 0, resourceType, metadata, epmValueCache, out nullOnParentProperty); }
/// <summary> /// Writes EPM value to a person construct (author or contributor). /// </summary> /// <param name="targetSegment">The target segment which points to either author or contributor element.</param> /// <param name="epmValueCache">The EPM value cache to use to get property values.</param> /// <param name="resourceType">The resource type of the entry being processed.</param> /// <param name="metadata">The metadata provider to use.</param> /// <returns>The person metadata or null if no person metadata should be written for this mapping.</returns> private static AtomPersonMetadata WritePersonEpm( EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata) { Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert( targetSegment.SegmentName == AtomConstants.AtomAuthorElementName || targetSegment.SegmentName == AtomConstants.AtomContributorElementName, "targetSegment must be author or contributor."); AtomPersonMetadata personMetadata = null; foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments) { Debug.Assert(subSegment.HasContent, "sub segment of author segment must have content, there are no subsegments which don't have content under author."); string textPropertyValue = GetEntryPropertyValueAsText(subSegment, epmValueCache, resourceType, metadata); if (textPropertyValue == null) { // TODO: In Multi-Values or in V3 mapping nulls to author/contributor subelements is not legal since there's no way to express it. // author/contributor subelements don't allow extension attributes, so we can't add the m:null attribute. continue; } // Initialize the person element only if we actually need to write something to it. if (personMetadata == null) { personMetadata = new AtomPersonMetadata(); } Debug.Assert(subSegment.EpmInfo != null && subSegment.EpmInfo.Attribute != null, "The author subsegment must have EPM info and EPM attribute."); switch (subSegment.EpmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorName: case SyndicationItemProperty.ContributorName: personMetadata.Name = textPropertyValue; break; case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.ContributorEmail: // TODO: Validate the email value. In V3 or in multi-values the email value must not be null and it must not be empty // since the syndication API doesn't allow empty email values (see below) and it's questionable if ATOM allows it itself. // In case the value is empty the syndication API will actually omit the email element from the payload // we have to simulate that behavior here by not setting the property in that case. if (textPropertyValue.Length > 0) { personMetadata.Email = textPropertyValue; } break; case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorUri: // TODO: Validate the uri value. In V3 or in multi-values the uri value must not be null and it must not be empty // since the syndication API doesn't allow empty uri values (see below) and it's questionable if ATOM allows it itself. // In case the value is empty the syndication API will actually omit the uri element from the payload // we have to simulate that behavior here by not setting the property in that case. if (textPropertyValue.Length > 0) { personMetadata.UriFromEpm = textPropertyValue; } break; default: throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WritePersonEpm)); } } return personMetadata; }
/// <summary> /// Creates an <see cref="ODataWriter"/> for the specified message and its stream. /// </summary> /// <param name="format">The serialization format to create the writer for.</param> /// <param name="encoding">The encoding to create the writer with.</param> /// <param name="stream">The response stream to write to.</param> /// <param name="settings">Writer settings to use.</param> /// <param name="writingResponse">True if we are writing a response message; false for request messages.</param> /// <param name="metadataProvider">The metadata provider to use.</param> /// <param name="writingFeed">True when creating a writer to write a feed; false when creating a writer to write an entry.</param> /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param> /// <returns>The newly created <see cref="ODataWriter"/> instance.</returns> /// <remarks>This is used to create the writer once we've obtained the stream from the response.</remarks> private static ODataWriter CreateWriter( ODataFormat format, Encoding encoding, Stream stream, ODataWriterSettings settings, bool writingResponse, DataServiceMetadataProviderWrapper metadataProvider, bool writingFeed, bool synchronous) { if (settings.BaseUri != null && !settings.BaseUri.IsAbsoluteUri) { throw new ODataException(Strings.ODataWriter_BaseUriMustBeNullOrAbsolute(UriUtils.UriToString(settings.BaseUri))); } switch (format) { case ODataFormat.Json: return new ODataJsonWriter(stream, settings, encoding, writingResponse, metadataProvider, writingFeed, synchronous); case ODataFormat.Atom: return new ODataAtomWriter(stream, settings, encoding, writingResponse, metadataProvider, writingFeed, synchronous); case ODataFormat.Default: Debug.Assert(false, "Should never get here as content-type negotiation should not return Default format for entry or feed."); throw new ODataException(Strings.ODataWriter_CannotCreateWriterForFormat(format.ToString())); default: throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriter_CreateWriter_UnreachableCodePath)); } }
/// <summary> /// Write the given collection of properties. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="owningType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param> /// <param name="cachedProperties">Collection of cached properties for the entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param> internal static void WriteProperties( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ResourceType owningType, IEnumerable<ODataProperty> cachedProperties, ODataVersion version, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); if (cachedProperties == null) { return; } foreach (ODataProperty property in cachedProperties) { WriteProperty(writer, metadata, property, owningType, version, false, isWritingCollection, epmValueCache, epmSourcePathSegment); } }
/// <summary> /// Writes the syndication part of EPM for an entry into ATOM metadata OM. /// </summary> /// <param name="epmTargetTree">The EPM target tree to use.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="version">The version of OData protocol to use.</param> /// <returns>The ATOM metadata OM with the EPM values populated.</returns> internal static AtomEntryMetadata WriteEntryEpm( EpmTargetTree epmTargetTree, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, ODataVersion version) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmTargetTree != null, "epmTargetTree != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available."); // If there are no syndication mappings, just return null. EpmTargetPathSegment syndicationRootSegment = epmTargetTree.SyndicationRoot; Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root."); if (syndicationRootSegment.SubSegments.Count == 0) { return null; } AtomEntryMetadata entryMetadata = new AtomEntryMetadata(); foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments) { if (targetSegment.IsMultiValueProperty) { Debug.Assert( targetSegment.EpmInfo != null && targetSegment.EpmInfo.Attribute != null, "MultiValue property target segment must have EpmInfo and the Epm Attribute."); ODataVersionChecker.CheckMultiValueProperties(version, targetSegment.EpmInfo.Attribute.SourcePath); // WriteMultiValueEpm(entry, targetSegment, epmValueCache); throw new NotImplementedException(); } else if (targetSegment.HasContent) { EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo; Debug.Assert( epmInfo != null && epmInfo.Attribute != null, "If the segment has content it must have EpmInfo which in turn must have the Epm attribute"); bool nullOnParentProperty; object propertyValue = epmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty); string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue); switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.Updated: entryMetadata.Updated = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, version); break; case SyndicationItemProperty.Published: entryMetadata.Published = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, version); break; case SyndicationItemProperty.Rights: entryMetadata.Rights = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; case SyndicationItemProperty.Summary: entryMetadata.Summary = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; case SyndicationItemProperty.Title: entryMetadata.Title = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; default: throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget)); } } else if (targetSegment.SegmentName == AtomConstants.AtomAuthorElementName) { AtomPersonMetadata authorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata); Debug.Assert(entryMetadata.Authors == null, "Found two mappings to author, that is invalid."); if (authorMetadata != null) { entryMetadata.Authors = CreateSinglePersonList(authorMetadata); } } else if (targetSegment.SegmentName == AtomConstants.AtomContributorElementName) { AtomPersonMetadata contributorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata); Debug.Assert(entryMetadata.Contributors == null, "Found two mappings to contributor, that is invalid."); if (contributorMetadata != null) { entryMetadata.Contributors = CreateSinglePersonList(contributorMetadata); } } else if (targetSegment.SegmentName == AtomConstants.AtomLinkElementName) { AtomLinkMetadata linkMetadata = new AtomLinkMetadata(); //// WriteLinkEpm(entry, targetSegment, epmValueCache); Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to link must be conditional."); linkMetadata.Relation = targetSegment.CriteriaValue; List<AtomLinkMetadata> links; if (entryMetadata.Links == null) { links = new List<AtomLinkMetadata>(); entryMetadata.Links = links; } else { links = entryMetadata.Links as List<AtomLinkMetadata>; Debug.Assert(links != null, "AtomEntryMetadata.Links must be of type List<AtomLinkMetadata> since we create it like that."); } links.Add(linkMetadata); throw new NotImplementedException(); } else if (targetSegment.SegmentName == AtomConstants.AtomCategoryElementName) { AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata(); //// WriteCategoryEpm(entry, targetSegment, epmValueCache) Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to category must be conditional."); categoryMetadata.Scheme = targetSegment.CriteriaValue; List<AtomCategoryMetadata> categories; if (entryMetadata.Categories == null) { categories = new List<AtomCategoryMetadata>(); entryMetadata.Categories = categories; } else { categories = entryMetadata.Links as List<AtomCategoryMetadata>; Debug.Assert(categories != null, "AtomEntryMetadata.Categories must be of type List<AtomCategoryMetadata> since we create it like that."); } categories.Add(categoryMetadata); throw new NotImplementedException(); } else { throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_TargetSegment)); } } return entryMetadata; }
/// <summary> /// Writes out the value of a complex property. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="complexValue">The complex value to write.</param> /// <param name="metadataType">The metadata type for the complex value.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property we're writing. (can be null)</param> internal static void WriteComplexValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataComplexValue complexValue, ResourceType metadataType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); string typeName = complexValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata ResourceType complexValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } WriteProperties( writer, metadata, complexValueType, EpmValueCache.GetComplexValueProperties(epmValueCache, epmSourcePathSegment, complexValue, true), version, isWritingCollection, epmValueCache, epmSourcePathSegment); }
/// <summary> /// Writes out the value of a complex property. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="complexValue">The complex value to write.</param> /// <param name="resourcePropertyType">The metadata type for the complex value.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="version">The protocol version used for writing.</param> internal static void WriteComplexValue( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ODataComplexValue complexValue, ResourceType resourcePropertyType, bool isOpenPropertyType, ODataVersion version) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); // Start the object scope which will represent the entire complex instance jsonWriter.StartObjectScope(); // Write the "__metadata" : { "type": "typename" } // But only if we actually have a typename to write, otherwise we need the __metadata to be omitted entirely string typeName = complexValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata ResourceType complexValueType = MetadataUtils.ResolveTypeName(metadata, resourcePropertyType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType); if (typeName != null) { // Write the __metadata object jsonWriter.WriteName(JsonConstants.ODataMetadataName); jsonWriter.StartObjectScope(); // "type": "typename" jsonWriter.WriteName(JsonConstants.ODataMetadataTypeName); jsonWriter.WriteValue(typeName); // End the __metadata jsonWriter.EndObjectScope(); } // Write the properties of the complex value as usual WriteProperties(jsonWriter, metadata, complexValueType, complexValue.Properties, version); // End the object scope which represents the complex instance jsonWriter.EndObjectScope(); }
/// <summary> /// Write the items in a MultiValue in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="multiValue">The MultiValue to write.</param> /// <param name="resourcePropertyType">The resource type of the multi value (or null if not metadata is available).</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the multivalue property we're writing. (can be null)</param> private static void WriteMultiValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataMultiValue multiValue, ResourceType resourcePropertyType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { Debug.Assert(multiValue != null, "multiValue != null"); string typeName = multiValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata MultiValueResourceType multiValueType = (MultiValueResourceType)MetadataUtils.ResolveTypeName(metadata, resourcePropertyType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } ResourceType expectedItemType = multiValueType == null ? null : multiValueType.ItemType; IEnumerable items = EpmValueCache.GetMultiValueItems(epmValueCache, epmSourcePathSegment, multiValue, true); if (items != null) { foreach (object itemValue in items) { object item; EpmMultiValueItemCache epmItemCache = itemValue as EpmMultiValueItemCache; if (epmItemCache != null) { item = epmItemCache.ItemValue; } else { item = itemValue; } ValidationUtils.ValidateMultiValueItem(item); writer.WriteStartElement(AtomConstants.ODataNamespacePrefix, AtomConstants.ODataMultiValueItemElementName, AtomConstants.ODataNamespace); ODataComplexValue complexValue = item as ODataComplexValue; if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, expectedItemType, false, isWritingCollection, version, epmItemCache, epmSourcePathSegment); } else { ODataMultiValue multiValueItem = item as ODataMultiValue; if (multiValueItem != null) { throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported); } else { AtomValueUtils.WritePrimitiveValue(writer, item, expectedItemType); } } writer.WriteEndElement(); } } }
/// <summary> /// Writes a service document in JSON format. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="defaultWorkspace">The default workspace to write in the service document.</param> internal static void WriteServiceDocument( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ODataWorkspace defaultWorkspace) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(jsonWriter != null, "jsonWriter != null"); Debug.Assert(defaultWorkspace != null, "defaultWorkspace != null"); IEnumerable<ODataResourceCollectionInfo> collections = ValidationUtils.ValidateWorkspace(metadata == null ? null : metadata.ResourceSets, defaultWorkspace); Debug.Assert(collections != null, "collections != null"); WriteDataWrapper( jsonWriter, true, () => { // "{" jsonWriter.StartObjectScope(); // "EntitySets": jsonWriter.WriteName(JsonConstants.ODataServiceDocumentEntitySetsName); // "[" jsonWriter.StartArrayScope(); foreach (ODataResourceCollectionInfo collectionInfo in collections) { ValidationUtils.ValidateResourceCollectionInfo(collectionInfo); // <collection name> jsonWriter.WriteValue(collectionInfo.Name); } // "]" jsonWriter.EndArrayScope(); // "}" jsonWriter.EndObjectScope(); }); }
/// <summary> /// Constructor. /// </summary> /// <param name="metadataProvider">The metadata provider to use for binding.</param> public MetadataBinder(IDataServiceMetadataProvider metadataProvider) { ExceptionUtils.CheckArgumentNotNull(metadataProvider, "metadataProvider"); this.metadataProvider = new DataServiceMetadataProviderWrapper(metadataProvider); }
/// <summary> /// Writes out the value of a MultiValue property. /// </summary> /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="multiValue">The bag value to write.</param> /// <param name="metadataType">The metadata type for the MultiValue.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="version">The protocol version used for writing.</param> private static void WriteMultiValue( JsonWriter jsonWriter, DataServiceMetadataProviderWrapper metadata, ODataMultiValue multiValue, ResourceType metadataType, bool isOpenPropertyType, ODataVersion version) { Debug.Assert(multiValue != null, "multiValue != null"); // Start the object scope which will represent the entire MultiValue instance jsonWriter.StartObjectScope(); // "__metadata": { "type": "typename" } // If the MultiValue has type information write out the metadata and the type in it. string typeName = multiValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata ResourceType multiValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType); if (typeName != null) { // Write the __metadata object jsonWriter.WriteName(JsonConstants.ODataMetadataName); jsonWriter.StartObjectScope(); // "type": "typename" jsonWriter.WriteName(JsonConstants.ODataMetadataTypeName); jsonWriter.WriteValue(typeName); // End the __metadata jsonWriter.EndObjectScope(); } // "results": [ // This represents the array of items in the MultiValue jsonWriter.WriteDataArrayName(); jsonWriter.StartArrayScope(); // Iterate through the MultiValue items and write them out (treat null Items as an empty enumeration) IEnumerable items = multiValue.Items; if (items != null) { ResourceType expectedItemType = multiValueType == null ? null : ((MultiValueResourceType)multiValueType).ItemType; foreach (object item in items) { ValidationUtils.ValidateMultiValueItem(item); ODataComplexValue itemAsComplexValue = item as ODataComplexValue; if (itemAsComplexValue != null) { WriteComplexValue(jsonWriter, metadata, itemAsComplexValue, expectedItemType, false, version); } else { ODataMultiValue itemAsMultiValue = item as ODataMultiValue; if (itemAsMultiValue != null) { throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported); } else { WritePrimitiveValue(jsonWriter, item, expectedItemType); } } } } // End the array scope which holds the items jsonWriter.EndArrayScope(); // End the object scope which holds the entire MultiValue jsonWriter.EndObjectScope(); }