Share via


Sample: Version Control RSS Feed

<%@ Page Language="c#" %>
<%@ OutputCache Duration="20" Location="Server" VaryByParam="state" VaryByCustom="minorversion" VaryByHeader="Accept-Language"%>

<%
// "Copyright © Microsoft Corporation.  All rights reserved.  These Samples are based in part on the Extensible Markup Language (XML) 1.0 (Third Edition) specification Copyright © 2004 W3C® (MIT, ERCIM, Keio. All rights reserved. https://www.w3.org/consortium/legal/2002/copyright-documents-20021231."
%>

<%
//This posting is provided "AS IS" with no warranties of any kind and confers no rights.  Use of samples included in this posting is subject to the terms specified at https://www.microsoft.com/info/cpyright.htm.
%>

<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="Microsoft.TeamFoundation.Client" %>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Client"%>
<%@ Import Namespace="Microsoft.TeamFoundation.VersionControl.Common" %>

<%
// Generate an RSS feed for Team Foundation Source Code Control checkins
//
// Note: Only one request per "pester interval" from each system will be honored. See
//       pesterInterval below.
//
// This feed returns information about the most recent N checkins. See maxCheckinCount
// below.
//
// Invoking this page without any parameters returns information about all Team
// Foundation checkins up to the maximum count. If a filename is supplied, information
// about the most recent checkins for that file only are returned. Specify a filename
// by adding ?serverPath=<serverPath>. Note that the filename must be expressed as a server
// pathname without the leading $/.
//
// E.g., to see the activity for a file $/teamProjectA/myFile, add this to the Url
// ?serverPath=teamProjectA/myfile
//
// This page returns no items if the file does not exist.

// ***************************************************
// (Default: off) Set this to true to throttle user requests by host/username
// ***************************************************
bool throttleRequests = false;

// Ignore requests from the same machine within this interval
int pesterInterval = 30 * 1000; // expressed in milliseconds

if (throttleRequests)
{
    // If the originating system has been serviced recently, drop the request
    object lastRequest = Context.Cache[User.Identity.Name+Request.UserHostAddress];
    if (lastRequest != null)
    {
        if ((int) lastRequest + pesterInterval < Environment.TickCount)
        {
            return;
        }
    }
}

// Default -- everything under $/
string serverPathname = VersionControlPath.RootFolder;

try
{
    // Check whether information on a specific file was requested
    string pathname = Request.Params["serverPath"];
    if (pathname != null)
    {
        // The pathname does not contain a leading '$'
        serverPathname = VersionControlPath.Combine(VersionControlPath.RootFolder, pathname);
        // The pathname is valid.
    }
}
catch
{
    Response.StatusCode = 404;
    Response.End();
}

// The maximum number of changes to return per query
int maxCheckinCount = 50;

string rssVersion = "2.0";
string rssTtl = "5";
string rssLanguage = "en-US";
string rssLink = Request.Url.ToString();
string rssTitle = "Team Foundation RSS Feed for Source Code Control Checkins";
string rssGenerator = "Sample RSS Feed Generator for Team Foundation";
string rssDescription = "<p>This feed provides information on Team Foundation Checkins. Each Team Foundation checkin is manifested as a changeset. The changesets that have been created recently are listed here.</p><p>This feed contains the most recent <i>{0}</i> checkin(s).</p>";
string rssItemOnBehalfOf = "(on behalf of {0}) ";
string rssItemTitle = "Checkin {0} [{1} items]";
string rssNoItemsAvailableDescription = "An error occurred obtaining the latest checkin information from Team Foundation. Try again later.";
string rssItemDescription = "<p><a href=\"{5}\">Changeset {0}</a> was checked in by <i>{1} {2}</i>on {3}. This checkin includes changes to {4} item(s).</p><p>You can view the details of the checkin by selecting the provided link.</p>.";
string exceptionMessage = "<p>An exception occurred while getting updated information from Team Foundation; the detailed exception message is <i>{0}</i></p>";
string itemNotFoundErrorMessage = "<p>There were no items found.</p>";
string accessDeniedErrorMessage = "<p>You do not have permission to obtain the Team Foundation checkin information.</p>";
string otherErrorMessage = "<p>An error occurred. This may be a transient error or a permanent one. Please check the event log for messages from VSTF Source Code Control</p>";

Response.ContentType = "text/xml";
XmlTextWriter xt = new XmlTextWriter(Response.OutputStream, null);
// Begin creating the XML document
xt.WriteStartElement("rss");
xt.WriteAttributeString("version", rssVersion);
xt.WriteStartElement("channel");
xt.WriteElementString("title", rssTitle);
xt.WriteElementString("ttl", rssTtl);
xt.WriteElementString("link", rssLink);
xt.WriteElementString("pubDate", DateTime.Now.ToString());
xt.WriteElementString("language", rssLanguage);
xt.WriteElementString("generator", rssGenerator);
try
{
    RecursionType recursionType = RecursionType.Full;

    // Obtain the list of changes from the mid-tier; the number of changes is reported in
    // the channel description.
    IEnumerable changesetEnum = null;
    try
    {
        TeamFoundationServer Tfs = null;
        object cacheEntry = Context.Cache["TeamFoundationServer"];
        if (cacheEntry == null)
        {
            // Note: only works on localhost
            Tfs = new TeamFoundationServer("https://localhost:8080");
            Context.Cache["TeamFoundationServer"] = Tfs;
        }
        else
        {
            Tfs = (TeamFoundationServer) cacheEntry;
        }
        VersionControlServer Vcs = (VersionControlServer) Tfs.GetService(typeof(VersionControlServer));
        // Return changes
        changesetEnum = Vcs.QueryHistory(serverPathname,  // on this item
                                         VersionSpec.Latest, // item version
                                         0,               // that are not deleted
                                         recursionType,   // at or below this item
                                         null,            // user
                                         null,            // start version
                                         null,            // stop version
                                         maxCheckinCount, // Up to this many changes
                                         true,            // include changes
                                         false);

        int changeCount = 0;
        foreach (Changeset change in changesetEnum)
        {
            changeCount++;
        }
        xt.WriteElementString("description", String.Format(rssDescription, changeCount));
    }
    catch (Exception e)
    {
        if (e is ItemNotFoundException)
        {
            rssNoItemsAvailableDescription += itemNotFoundErrorMessage;
        }
        else
        {
            rssNoItemsAvailableDescription += otherErrorMessage;
        }
        rssNoItemsAvailableDescription += String.Format(exceptionMessage, e.Message);
        xt.WriteElementString("description", rssNoItemsAvailableDescription);
    }

    xt.WriteEndElement(); // channel

    // Create an item for each returned changeset.
    foreach (Changeset change in changesetEnum)
    {
        xt.WriteStartElement("item");
        string onBehalfOf = null;

        // Include the committer if it differs from the changeset owner.
        // This occurs when a proxy agent performs the checkin.
        if (!change.Owner.Equals(change.Committer))
        {
            onBehalfOf = String.Format(rssItemOnBehalfOf, change.Owner);
        }

        xt.WriteElementString("title", String.Format(rssItemTitle,
                                                     change.ChangesetId,
                                                     change.Changes.Length));

        string csLink = HttpUtility.HtmlEncode(new ChangesetUri(
                                                     String.Format("{0}://{1}:{2}",
                                                                  Request.Url.Scheme,
                                                                  Request.Url.Host,
                                                                  Request.Url.Port),
                                                     change.ChangesetId,
                                                     UriType.Extended).ToUrl());

        xt.WriteElementString("description", String.Format(rssItemDescription,
                                                           change.ChangesetId,
                                                           change.Committer,
                                                           onBehalfOf,
                                                           change.CreationDate,
                                                           change.Changes.Length,
                                                           csLink));
        xt.WriteElementString("link", csLink);
        xt.WriteElementString("author", onBehalfOf == null ? change.Owner : change.Committer);
        xt.WriteElementString("pubDate", change.CreationDate.ToString());
        xt.WriteElementString("guid", change.ChangesetId.ToString());
        xt.WriteEndElement(); // item
    }
}
catch (Exception e)
{
    Response.StatusCode = 404;
    Response.End();
}
finally
{
    xt.Close();
    if (throttleRequests)
    {
        Context.Cache[Request.UserHostAddress] = Environment.TickCount;
    }
}
%>

Comments

  • Anonymous
    July 27, 2005
    I've got a little home-grown code that I hacked together, it runs every 30 minutes and generates an XML...

  • Anonymous
    August 01, 2005
    Eric Jarvi - VSTS Tip: Branching Source Code

    Eric discusses what to do and not do when branching source...

  • Anonymous
    August 08, 2005
    Recently, I wrote about a Team Foundation RSS feed. There may be a change required if you're seeing ASP.NET...

  • Anonymous
    August 09, 2005
    I didn't realize this yesterday when I blogged about RSS checkin notifications yesterday, but Jeff has...

  • Anonymous
    August 09, 2005
    As we go on developing the product we frequently feel strong about doing new things around&amp;nbsp;our product...

  • Anonymous
    August 09, 2005
    As we go on developing the product we frequently feel strong about doing new things around&amp;nbsp;our product...

  • Anonymous
    April 21, 2006
    &amp;lt;Update: After some complains that it is difficult to cut paste code out of the blog, I have zipped...

  • Anonymous
    May 04, 2006
    He encontrado un ejemplo muy interesante sobre como utilizar el API de Team System para construir interesantes...

  • Anonymous
    August 09, 2007
    &lt;Update: After some complains that it is difficult to cut paste code out of the blog, I have zipped

  • Anonymous
    June 24, 2008
    Любопытно, но, похоже, пока еще никто не реализовал полноценную подписку на события TFS (eventing service)