How to manage web feed entries (XAML)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

This topic shows how to access a service document and modify the feed resources it contains using the Windows.Web.AtomPub namespace, which is the Windows Runtime implementation of the Atom Publication standard.

Prerequisites

The following examples use C# or C++ and are based on the AtomPub sample. For general help creating a Windows Runtime app using C# or Visual Basic, see Create your first Windows Runtime app using C# or Visual Basic. For general help creating a Windows Runtime app using C++, see Create your first Windows Runtime app using C++.

To ensure your Windows Runtime app is network ready, you must set any network capabilities that are needed in the project Package.appxmanifest file. If your app needs to connect as a client to remote services on the Internet, then the Internet (Client) capability is needed. If the app needs to connect as a client to remote services on a home network or work network, then the Home/Work Networking capability is needed. For more information, see How to configure network isolation capabilities.

The following examples utilize Windows.Web.AtomPub classes for feed management operations, and Windows.Web.Syndication classes to represent individual feed elements. Additionally, most web publication services will require some form of authentication, a functionality provided by the Windows.Security.Credentials namespace.

Service documents

Before we review the example code, it helps to have a basic understanding of how service documents are used to define the structure of feed content for a web service.

The service document encapsulates at least one workspace element, which represents one or more collections. In other words, web publications like personal blogs and web pages are considered workspaces, and the contained collections represent individual feeds; each containing a number of entries.

The following syntax is a short example of a service document:

<?xml version="1.0" encoding='utf-8'?>
<service xmlns="http://www.w3.org/2007/app"
         xmlns:atom="http://www.w3.org/2005/Atom">
    <workspace>
        <atom:title>Main Site</atom:title>
        <collection
            href="http://example.org/blog/main" >
            <atom:title>My Blog Entries</atom:title>
            <categories
               href="http://example.com/cats/forMain.cats" />
        </collection>
        <collection
            href="http://example.org/blog/pic" >
            <atom:title>Pictures</atom:title>
            <accept>image/png</accept>
            <accept>image/jpeg</accept>
            <accept>image/gif</accept>
        </collection>
    </workspace>
</service>

To retrieve a service document, pass the associated Uri to RetrieveServiceDocumentAsync.

To retrieve, edit, or delete specific feed entries, an app will need to parse a retrieved ServiceDocument for the absolute URIs associated with individual entries.

Namespace declarations and client initialization

First you will want to declare the namespaces that support our feed operations. To create the request and work with the feed content, we declare the Windows.Web.AtomPub and Windows.Web.Syndication. To handle errors, we include the Windows.Web namespace. Use of authentication credentials (for secure web services) and the destination Uri requires Windows.Security.Credentials and Windows.Foundation .

The example code defines a set of variables that can be used to pass credentials and specific feed addresses between operations.

using Windows.Foundation;
using Windows.Security.Credentials;
using Windows.Web;
using Windows.Web.AtomPub;
using Windows.Web.Syndication;

// The default values for the web service site
private const string baseUri = "http://<web service url>";
private const string user = "";
private const string password = "";

// The default Service Document and Edit 'URIs' for the web service
private const string editUri = "";
private const string serviceDocUri = "";
private const string feedUri = "";

private AtomPubClient client;
private SyndicationFeed currentFeed;
private int currentItemIndex;

SyndicationItem currentItem;
SyndicationItemIterator feedIterator = new SyndicationItemIterator();

These variables are used to initialize the AtomPubClient with any authentication credentials needed.

static public AtomPubClient GetClient()
{
    client = new AtomPubClient();
    client.BypassCacheOnRetrieve = true;

    if (!String.IsNullOrEmpty(user) && !String.IsNullOrEmpty(password)) {
        client.ServerCredential = new PasswordCredential();
        
        client.ServerCredential.UserName = user; 
        client.ServerCredential.Password = password;
    }
    else {
        client.ServerCredential = null;
    }

    return client;
}

Creating a new post within a collection

A new post can be added to an existing collection by creating a new SyndicationItem object and populating it with the desired content. When the SyndicationItem is ready, pass the object, a short string describing the entry, and the feed Uri to CreateResourceAsync method on the AtomPubClient.

You first need to check the URI address for the feed to make sure that the string passed contains a valid URI address. A user of your app may accidentally type a character that is not allowed in a URI. An exception is thrown when a string that is not valid for a URI is passed to the constructor for the Windows.Foundation.Uri object.

.NET Framework: The System.Uri class is used instead of the Windows.Foundation.Uri class in C# and Visual Basic.

In C# and Visual Basic, this error can be avoided by using the System.Uri class and one of the System.Uri.TryCreate methods to test the string received from the app user before the URI is constructed. In C++, there is no method to try and parse a string to a URI. To catch this exception in that case, use a try/catch block around the C++ code where the URI is constructed.

You must write code to handle exceptions when you call most asynchronous network methods. Your exception handler can retrieve more detailed information on the cause of the exception to better understand the failure and make appropriate decisions. For more information, see How to handle exceptions in network apps.

The CreateResourceAsync method throws an exception if a connection could not be established with the HTTP server or the Uri object does not point to a valid AtomPub or RSS feed. The sample code uses a try/catch block to catch any exceptions and print out more detailed information on the exception if an error occurs.

// Called when an async function generates an exception in a try/catch block
static public bool HandleException(Exception ex) {
    SyndicationErrorStatus status = SyndicationError.GetStatus(ex.HResult);
    if (status == SyndicationErrorStatus.InvalidXml) {
        NotifyUser("An invalid XML exception was thrown." +
            "Please make sure to use a URI that points to a RSS or Atom feed.");
        }

        if (status == SyndicationErrorStatus.Unknown) {
            WebErrorStatus webError = WebError.GetStatus(ex.HResult);

            if (webError == WebErrorStatus.Unknown) {
            // Neither a syndication nor a web error. Rethrow.
                throw;
            }
        }

        NotifyUser(ex.Message);
)

// Read the service document to find the URI we're supposed 
// to use when uploading content.
private async Task<Uri> FindEditUri(Uri serviceUri) {

    ServiceDocument serviceDocument = await GetClient().RetrieveServiceDocumentAsync(serviceUri);

    foreach (Workspace workspace in serviceDocument.Workspaces) {
        foreach (ResourceCollection collection in workspace.Collections)
        {
            if (string.Join(";", collection.Accepts) == "application/atom+xml;type=entry") {
                return collection.Uri;
            }
        }
    }
    return null;
}

private async void CreatePost(string uriString, string postTitle, 
    string postContent, string postSummary, string postAuthor)
{

    if (!Uri.TryCreate(uriString.Trim() + serviceDocUri, UriKind.Absolute, out serviceUri)) {
        NotifyUser("Invalid URI.");
        return;
    }

    // The title cannot be an empty string or a string with white spaces only, since it is used also
    // as the resource description (Slug header).
    if (String.IsNullOrWhiteSpace(postTitle)) {
        NotifyUser("Post title cannot be blank.");
        return;
    }

    NotifyUser("Fetching Service document");

    try {
        // The result here is usually the same as:
        // Uri resourceUri = new Uri(uriString.Trim() + editUri);
        Uri resourceUri = await FindEditUri(serviceUri);
        
        if (resourceUri == null) {
            NotifyUser("URI not found in service document.");
            return;
        }

        NotifyUser("Uploading Post");

        SyndicationItem item = new SyndicationItem();
        item.Title = new SyndicationText(postTitle, SyndicationTextType.Text);
        item.Content = new SyndicationContent(postContent, SyndicationTextType.Html);

        SyndicationItem result = await GetClient().CreateResourceAsync(resourceUri, postTitle, item);

        NotifyUser("New post created.");
    }
    catch (Exception ex) {
        HandleException(ex);
   }
}

Editing a post within a collection

To edit an existing entry in a collection, pass the associated Uri to the RetrieveFeedAsync method on the SyndicationClient. For example code to retrieve an existing entry, see the How to access a web feed.

Prepare a SyndicationItem with new values, and pass the object to UpdateResourceAsync on the AtomPubClient along with the Uri used to retrieve the entry.

private async void EditPost(Uri resourceUri, string postTitle, string postContent, 
string postSummary, string postAuthor))
{
    try {
        // Update the item
        SyndicationItem updatedItem = new SyndicationItem();
        updatedItem.Title = new SyndicationText(editTitle, SyndicationTextType.Text);
        updatedItem.Content = new SyndicationContent(postContent, SyndicationTextType.Html);

        await GetClient().UpdateResourceAsync(resourceUri, updatedItem);

        NotifyUser("Updating item completed.");
    }
    catch (Exception ex) {
        HandleException(ex);
    }
}

Deleting a post from a collection

Deleting an entry from a collection is accomplished by passing the associated EditUri from the SyndicationItem to deleteResourceItemAsync method on the AtomPubClient.

// Delete the current entry
private async void DeletePost(SyndicationItem currentItem)
{
    NotifyUser("Deleting item...");
 
    try {
        await GetClient().DeleteResourceItemAsync(currentItem.EditUri);

        NotifyUser("Deleting item completed.");

    }
    catch (Exception ex) {
        HandleException(ex);
    }
}

Summary

In this topic we retrieved a service document and introduced new, and modified existing, collection entries within that document. For a demonstration of basic feed retrieval, see How to access a web feed.

Other

Create your first Windows Runtime app using C# or Visual Basic

Create your first Windows Runtime app using C++

How to configure network isolation capabilities

How to handle exceptions in network apps

How to access a web feed

Reference

AtomPubClient

SyndicationItem

Windows.Web.AtomPub

Windows.Web.Syndication

Samples

AtomPub sample

Syndication sample