/// <summary> /// Writes an association link. /// </summary> /// <param name="writer">The Xml writer to write to.</param> /// <param name="baseUri">The base Uri of the document or null if none was specified.</param> /// <param name="entry">The entry for which to write the association link.</param> /// <param name="associationLink">The association link to write.</param> internal static void WriteAssociationLink(XmlWriter writer, Uri baseUri, ODataEntry entry, ODataAssociationLink associationLink) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); Debug.Assert(entry != null, "entry != null"); Debug.Assert(associationLink != null, "associationLink != null"); // <atom:link ... writer.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomLinkElementName, AtomConstants.AtomNamespace); ODataAtomWriterMetadataUtils.WriteODataAssociationLinkMetadata(writer, baseUri, entry, associationLink); // /> writer.WriteEndElement(); }
/// <summary> /// Writes the link's start element and atom metadata. /// </summary> /// <param name="link">The link to write.</param> private void WriteLinkStart(ODataLink link) { ValidationUtils.ValidateLink(link); // Link must specify the Url // NOTE: we currently only require a non-null Url for ATOM payloads and non-expanded links in JSON. // There is no place in JSON to write a Url if the link is expanded. We can't change that for v1 and v2; we // might fix the protocol for v3. if (link.Url == null) { throw new ODataException(Strings.ODataWriter_LinkMustSpecifyUrl); } // <atom:link> this.writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomLinkElementName, AtomConstants.AtomNamespace); ODataAtomWriterMetadataUtils.WriteODataLinkMetadata(this.writer, this.BaseUri, link); }
/// <summary> /// Finish writing a feed. /// </summary> /// <param name="feed">The feed to write.</param> protected override void EndFeed(ODataFeed feed) { Debug.Assert(feed != null, "feed != null"); Uri nextPageLink = feed.NextPageLink; if (nextPageLink != null) { // <atom:link rel="next" href="next-page-link" /> this.writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomLinkElementName, AtomConstants.AtomNamespace); this.writer.WriteAttributeString(AtomConstants.AtomLinkRelationAttributeName, AtomConstants.AtomLinkRelationNextAttributeValue); this.writer.WriteAttributeString(AtomConstants.AtomHRefAttributeName, AtomUtils.ToUrlAttributeValue(nextPageLink, this.BaseUri)); this.writer.WriteEndElement(); } ODataAtomWriterMetadataUtils.WriteFeedMetadata(this.writer, this.BaseUri, feed); // </atom:feed> this.writer.WriteEndElement(); }
/// <summary> /// Write the ATOM metadata for an entry /// </summary> /// <param name="writer">The Xml writer to write to.</param> /// <param name="baseUri">The base Uri of the document or null if none was specified.</param> /// <param name="entry">The entry for which to write the metadata.</param> /// <param name="epmEntryMetadata">The ATOM metadata for the entry which came from EPM.</param> internal static void WriteEntryMetadata(XmlWriter writer, Uri baseUri, ODataEntry entry, AtomEntryMetadata epmEntryMetadata) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); // TODO, ckerer: implement the rule around authors (an entry has to have an author directly or in the <entry:source> unless the feed has an author). // currently we make all entries have an author. AtomEntryMetadata customEntryMetadata = entry.GetAnnotation <AtomEntryMetadata>(); AtomEntryMetadata entryMetadata = ODataAtomWriterMetadataEpmMergeUtils.MergeCustomAndEpmEntryMetadata(customEntryMetadata, epmEntryMetadata); if (entryMetadata == null) { // write all required metadata elements with default content // <atom:title></atom:title> ODataAtomWriterUtils.WriteEmptyElement(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomTitleElementName, AtomConstants.AtomNamespace); // <atom:updated>dateTimeOffset</atom:updated> // NOTE: the <updated> element is required and if not specified the best we can do is to create a default // one with the current date/time. ODataAtomWriterUtils.WriteElementWithTextContent(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomUpdatedElementName, AtomConstants.AtomNamespace, ODataAtomConvert.ToString(DateTimeOffset.UtcNow)); WriteEmptyAuthor(writer); } else { // <atom:title>text</atom:title> // NOTE: writes an empty element even if no title was specified since the title is required ODataAtomWriterMetadataUtils.WriteTextConstruct(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomTitleElementName, AtomConstants.AtomNamespace, entryMetadata.Title); AtomTextConstruct summary = entryMetadata.Summary; if (summary != null) { // <atom:summary>text</atom:summary> ODataAtomWriterMetadataUtils.WriteTextConstruct(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomSummaryElementName, AtomConstants.AtomNamespace, summary); } DateTimeOffset?published = entryMetadata.Published; if (published.HasValue) { // <atom:published>dateTimeOffset</atom:published> ODataAtomWriterUtils.WriteElementWithTextContent(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomPublishedElementName, AtomConstants.AtomNamespace, ODataAtomConvert.ToString(published.Value)); } // <atom:updated>date</atom:updated> // NOTE: the <updated> element is required and if not specified the best we can do is to create a default // one with the current date/time. DateTimeOffset updated = entryMetadata.Updated.HasValue ? entryMetadata.Updated.Value : DateTimeOffset.UtcNow; ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomUpdatedElementName, AtomConstants.AtomNamespace, ODataAtomConvert.ToString(updated)); bool wroteAuthor = false; IEnumerable <AtomPersonMetadata> authors = entryMetadata.Authors; if (authors != null) { foreach (AtomPersonMetadata author in authors) { if (author == null) { throw new ODataException(Strings.ODataAtomWriterMetadataUtils_AuthorMetadataMustNotContainNull); } // <atom:author>author data</atom:author> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomAuthorElementName, AtomConstants.AtomNamespace); WritePersonMetadata(writer, baseUri, author); writer.WriteEndElement(); wroteAuthor = true; } } if (!wroteAuthor) { // write empty authors since they are required WriteEmptyAuthor(writer); } IEnumerable <AtomPersonMetadata> contributors = entryMetadata.Contributors; if (contributors != null) { foreach (AtomPersonMetadata contributor in contributors) { if (contributor == null) { throw new ODataException(Strings.ODataAtomWriterMetadataUtils_ContributorMetadataMustNotContainNull); } // <atom:contributor>contributor data</atom:contributor> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContributorElementName, AtomConstants.AtomNamespace); WritePersonMetadata(writer, baseUri, contributor); writer.WriteEndElement(); } } IEnumerable <AtomLinkMetadata> links = entryMetadata.Links; if (links != null) { foreach (AtomLinkMetadata link in links) { if (link == null) { throw new ODataException(Strings.ODataAtomWriterMetadataUtils_LinkMetadataMustNotContainNull); } // <atom:link>...</atom:link> WriteAtomLinkMetadata(writer, baseUri, link); } } IEnumerable <AtomCategoryMetadata> categories = entryMetadata.Categories; if (categories != null) { foreach (AtomCategoryMetadata category in categories) { if (category == null) { throw new ODataException(Strings.ODataAtomWriterMetadataUtils_CategoryMetadataMustNotContainNull); } // <atom:category term="..." scheme="..." label="..."></atom:category> WriteCategory(writer, category); } } if (entryMetadata.Rights != null) { // <atom:rights>rights</atom:rights> ODataAtomWriterMetadataUtils.WriteTextConstruct(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomRightsElementName, AtomConstants.AtomNamespace, entryMetadata.Rights); } Uri icon = entryMetadata.Icon; if (icon != null) { // <atom:icon>Uri</atom:icon> ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIconElementName, AtomConstants.AtomNamespace, AtomUtils.ToUrlAttributeValue(icon, baseUri)); } AtomFeedMetadata source = entryMetadata.Source; if (source != null) { // <atom:source> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomSourceElementName, AtomConstants.AtomNamespace); WriteFeedMetadata(writer, baseUri, source, null); // </atom:source> writer.WriteEndElement(); } } }
/// <summary> /// Write the given feed metadata in atom format /// </summary> /// <param name="writer">The Xml writer to write to.</param> /// <param name="baseUri">The base Uri of the document or null if none was specified.</param> /// <param name="feedMetadata">The metadata to write.</param> /// <param name="feed">The feed for which to write the meadata or null if it is the metadata of an atom:source element.</param> private static void WriteFeedMetadata(XmlWriter writer, Uri baseUri, AtomFeedMetadata feedMetadata, ODataFeed feed) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(feedMetadata != null, "Feed metadata must not be null!"); // <atom:id>text</atom:id> // NOTE: this is the Id of the feed. For a regular feed this is stored on the feed itself; // if used in the context of an <atom:source> element it is stored in metadata Debug.Assert(feed == null || !string.IsNullOrEmpty(feed.Id), "The feed Id should have been validated by now."); string id = feed == null ? feedMetadata.SourceId : feed.Id; ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIdElementName, AtomConstants.AtomNamespace, id); // <atom:title>text</atom:title> // NOTE: write an empty element if no title is specified since the element is required ODataAtomWriterMetadataUtils.WriteTextConstruct(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomTitleElementName, AtomConstants.AtomNamespace, feedMetadata.Title); if (feedMetadata.Subtitle != null) { // <atom:subtitle>text</atom:subtitle> ODataAtomWriterMetadataUtils.WriteTextConstruct(writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomSubtitleElementName, AtomConstants.AtomNamespace, feedMetadata.Subtitle); } // <atom:updated>date</atom:updated> // NOTE: the <updated> element is required and if not specified the best we can do is to create a default // one with the current date/time. DateTimeOffset updated = feedMetadata.Updated.HasValue ? feedMetadata.Updated.Value : DateTimeOffset.UtcNow; ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomUpdatedElementName, AtomConstants.AtomNamespace, ODataAtomConvert.ToString(updated)); IEnumerable <AtomPersonMetadata> authors = feedMetadata.Authors; if (authors != null) { foreach (AtomPersonMetadata author in authors) { // <atom:author>author data</atom:author> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomAuthorElementName, AtomConstants.AtomNamespace); WritePersonMetadata(writer, baseUri, author); writer.WriteEndElement(); } } IEnumerable <AtomLinkMetadata> links = feedMetadata.Links; if (links != null) { foreach (AtomLinkMetadata link in links) { // <atom:link>...</atom:link> WriteAtomLinkMetadata(writer, baseUri, link); } } IEnumerable <AtomCategoryMetadata> categories = feedMetadata.Categories; if (categories != null) { foreach (AtomCategoryMetadata category in categories) { // <atom:category term="..." scheme="..." label="..."></atom:category> WriteCategory(writer, category); } } Uri logo = feedMetadata.Logo; if (logo != null) { // <atom:logo>Uri</atom:logo> ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomLogoElementName, AtomConstants.AtomNamespace, AtomUtils.ToUrlAttributeValue(logo, baseUri)); } if (feedMetadata.Rights != null) { // <atom:rights>rights</atom:rights> ODataAtomWriterMetadataUtils.WriteTextConstruct( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomRightsElementName, AtomConstants.AtomNamespace, feedMetadata.Rights); } IEnumerable <AtomPersonMetadata> contributors = feedMetadata.Contributors; if (contributors != null) { foreach (AtomPersonMetadata contributor in contributors) { // <atom:contributor>contributor data</atom:contributor> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContributorElementName, AtomConstants.AtomNamespace); WritePersonMetadata(writer, baseUri, contributor); writer.WriteEndElement(); } } AtomGeneratorMetadata generator = feedMetadata.Generator; if (generator != null) { // <atom:generator uri="..." version="...">name</atom:generator> writer.WriteStartElement(AtomConstants.AtomNamespacePrefix, AtomConstants.AtomGeneratorElementName, AtomConstants.AtomNamespace); if (generator.Uri != null) { writer.WriteAttributeString(AtomConstants.AtomGeneratorUriAttributeName, AtomUtils.ToUrlAttributeValue(generator.Uri, baseUri)); } if (!string.IsNullOrEmpty(generator.Version)) { writer.WriteAttributeString(AtomConstants.AtomGeneratorVersionAttributeName, generator.Version); } writer.WriteString(generator.Name); writer.WriteEndElement(); } Uri icon = feedMetadata.Icon; if (icon != null) { // <atom:icon>Uri</atom:icon> ODataAtomWriterUtils.WriteElementWithTextContent( writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIconElementName, AtomConstants.AtomNamespace, AtomUtils.ToUrlAttributeValue(icon, baseUri)); } }
/// <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> /// Finish writing an entry. /// </summary> /// <param name="entry">The entry to write.</param> protected override void EndEntry(ODataEntry entry) { Debug.Assert(entry != null, "entry != null"); string typeName = entry.TypeName; ResourceType entryType = ValidationUtils.ValidateTypeName(this.MetadataProvider, typeName, ResourceTypeKind.EntityType, false); // Initialize the property value cache and cache the entry properties. EntryPropertiesValueCache propertyValueCache = new EntryPropertiesValueCache(entry); EpmResourceTypeAnnotation epmResourceTypeAnnotation = null; if (entryType != null) { Debug.Assert(entryType.IsReadOnly, "The resource must be read-only to be applied to an entry."); entryType.EnsureEpmAvailability(); epmResourceTypeAnnotation = entryType.Epm(); } // <atom:id>idValue</atom:id> // NOTE: do not generate a relative Uri for the ID; it is independent of xml:base ODataAtomWriterUtils.WriteElementWithTextContent( this.writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIdElementName, AtomConstants.AtomNamespace, entry.Id); // <category term="type" scheme="odatascheme"/> // If no type information is provided, don't include the category element for type at all // NOTE: the validation of the type name happened at the beginning of this method. if (typeName != null) { ODataAtomWriterMetadataUtils.WriteCategory(this.writer, typeName, AtomConstants.ODataSchemeNamespace, null); } Uri editLink = entry.EditLink; if (editLink != null) { // <link rel="edit" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, editLink, AtomConstants.AtomEditRelationAttributeValue, typeName); } Uri readLink = entry.ReadLink; if (readLink != null) { // <link rel="self" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, readLink, AtomConstants.AtomSelfRelationAttributeValue, typeName); } // named streams IEnumerable <ODataMediaResource> namedStreams = entry.NamedStreams; if (namedStreams != null) { foreach (ODataMediaResource namedStream in namedStreams) { ValidationUtils.ValidateNamedStream(namedStream, this.Version); ODataAtomWriterUtils.WriteNamedStream(this.writer, this.BaseUri, namedStream); } } // association links IEnumerable <ODataAssociationLink> associationLinks = entry.AssociationLinks; if (associationLinks != null) { foreach (ODataAssociationLink associationLink in associationLinks) { ValidationUtils.ValidateAssociationLink(associationLink, this.Version); ODataAtomWriterUtils.WriteAssociationLink(this.writer, this.BaseUri, entry, associationLink); } } // write entry metadata including syndication EPM AtomEntryMetadata epmEntryMetadata = null; if (epmResourceTypeAnnotation != null) { ODataVersionChecker.CheckEntityPropertyMapping(this.Version, entryType); epmEntryMetadata = EpmSyndicationWriter.WriteEntryEpm( epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider, this.Version); } ODataAtomWriterMetadataUtils.WriteEntryMetadata(this.writer, this.BaseUri, entry, epmEntryMetadata); // write the content this.WriteEntryContent(entry, entryType, propertyValueCache, epmResourceTypeAnnotation == null ? null : epmResourceTypeAnnotation.EpmSourceTree.Root); // write custom EPM if (epmResourceTypeAnnotation != null) { EpmCustomWriter.WriteEntryEpm( this.writer, epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider); } // </entry> this.writer.WriteEndElement(); }