Sending TrackBacks by ThinqLinq

Sending TrackBacks

Yesterday, I showed how we can receive trackbacks from other sites using the TrackBack API. Today, we'll look at the other side of this picture: sending TrackBacks to other sites based on links in the post. Sending a TrackBack entails several steps:

  1. Parsing the post to find links to other sites.
  2. Checking the target site to see if it supports TrackBacks.
  3. Formatting and sending the TrackBack to the target's service.
  4. Checking for error responses.

Thus with every post I create now, I include a method to parse the post and send the trackbacks as necessary. Parsing the post is a relatively painless process. In it, I query the raw HTML of the post finding anything that looks like href="….". Although I typically use LINQ for querying, in this case the best tool for the job is a regular expression. I admit that I'm not the best at regular expressions, so if anyone has a better alternative, let me know. Here's the method that starts the parsing process:

Public Shared Sub ParseAndSendTrackbacks(ByVal post As PostItem)
    'find references that support trackbacks
    Dim pattern As String = String.Format("href={0}[a-z0-9:\.\/_\?\-\%]*{0}", _
    Dim matches = Regex.Matches(post.Description, pattern, RegexOptions.IgnoreCase)
    For Each Link In matches.OfType(Of RegularExpressions.Match)()
            Dim startIndex = Link.Value.IndexOf(ControlChars.Quote) + 1
            Dim urlPart As String = Link.Value.Substring(startIndex, _
                                        Link.Value.Length - startIndex - 1)
            Dim svc As New TrackbackService
            svc.SendTrackback("" & _
                              post.TitleUrlRewrite & ".aspx", post, urlPart)
        Catch ex As Exception
        End Try
End Sub

As you can see, this consists mostly of the Regex match and some string parsing to get down to the url that we are referring to. We then send that url into a method which will send the trackback.

Public Sub SendTrackback(ByVal postUrl As String, ByVal post As PostItem, ByVal externalUrl As String)

    Dim server = GetTrackbackServer(externalUrl)
    If server <> "" Then
        'Send the trackback to the sever
    End If
End Sub

Here, the first step is to see if the post that we are referencing in our post supports the trackback API. The trackback API has an auto-discovery mechanism whereby the page needs to include an embedded RDF which specifies the URI that a client should ping when it wants to issue a trackback. On this site, the trackback server URI for my Receiving Trackbacks post is:

    <rdf:RDF xmlns:rdf=
      <rdf:Description rdf:about=
       dc:identifier="" dc:Title="Thinq Linq"
       trackback:ping="" />

In this case, notice that the XML is included inside of a comment block. The TrackBack Technical Specification indicates that this is required by some validators. Since we know that the site needs to include a node like this, we can issue a request to the page of the post that we are wanting to send a post to and see if it includes the appropriate rdf response information. We can't assume that the page will be XHTML compliant, so we will use another regular expression to find the <rdf:RDF></rdf:RDF> node and then use XML literals with VB 9 to parse it to get the value of the trackback:ping. Since the XML is strongly typed with namespaces, we'll start by including the appropriate imports to our class file. If we don't include these imports, our LINQ query will fail because the namespaces are required when querying XML.

Imports <xmlns:rdf="">
Imports <xmlns:dc="">
Imports <xmlns:trackback="">

With the namespaces in place, we can proceed with the rest of our code to find the TrackBack server's URI. This method will return the server's address or an empty string if the server is not found for any reason.

Private Function GetTrackbackServer(ByVal externalUrl As String) As String
        'Make sure that we have a valid external link
        Dim linkUri As Uri = Nothing
        If Not Uri.TryCreate(externalUrl, UriKind.Absolute, linkUri) Then
            Return ""
        End If
        Dim server As String = ""

        'Set up the HTTP request
        Dim req = DirectCast(WebRequest.Create(linkUri), HttpWebRequest)
        req.Referer = ""
        Dim client As New WebClient()
        client.UseDefaultCredentials = True

        'Get the page's contents
        Dim page = client.DownloadString(externalUrl)

        'Find the rdf tag
        Dim regexString = String.Format("<rdf:rdf(.*?)rdf:rdf>")
        Dim match = Text.RegularExpressions.Regex.Match(page, regexString, _
RegexOptions.IgnoreCase Or RegexOptions.Singleline).Value If Not String.IsNullOrEmpty(match) Then 'Use LINQ to XML to fet the trackback:ping attribute's value Dim rdf = XDocument.Parse(match) Dim url = rdf.Root.<rdf:Description>.FirstOrDefault.@trackback:ping If Not String.IsNullOrEmpty(url) Then Return url End If End If Catch ex As Exception Diagnostics.Trace.WriteLine(ex.ToString()) End Try 'Something didn't work right, or the site doesn't support TrackBacks. Return "" End Function

Now that we know the server, we can finish off the process of sending our request to the server that we started earlier. In this case, we create the request setting the method to "POST" and the ContentType to "application/x-www-form-urlencoded". We then build our form's values that we make sure to UrlEncode and place in the request's content stream.

Public Sub SendTrackback(ByVal postUrl As String, ByVal post As PostItem, ByVal externalUrl As String)

    Dim server = GetTrackbackServer(externalUrl)
    If server <> "" Then
        Dim req = DirectCast(WebRequest.Create(server), HttpWebRequest)
        req.Method = "POST"
        req.ContentType = "application/x-www-form-urlencoded"
        Dim content As New StringBuilder()
        content.AppendFormat("url={0}", HttpUtility.UrlEncode(postUrl))
        content.AppendFormat("&title={0}", HttpUtility.UrlEncode(post.Title))
        content.AppendFormat("&excerpt={0}", _
                             HttpUtility.UrlEncode(StripHtml(post.Description, 200)))

        Dim contentBytes() = System.Text.Encoding.ASCII.GetBytes(content.ToString())
        req.ContentLength = contentBytes.Length

        Using reqStream = req.GetRequestStream
            reqStream.Write(contentBytes, 0, contentBytes.Length)
        End Using

        Dim resp = req.GetResponse()
        Using respStream = resp.GetResponseStream()
            Dim reader As New StreamReader(respStream)
            Dim response = XDocument.Parse(reader.ReadToEnd())
            'Check for errors
            If CDbl(response...<error>.FirstOrDefault()) > 0D Then
                Throw New InvalidOperationException(response...<message>.FirstOrDefault().Value)
            End If
        End Using
    End If
End Sub

Once we send the request, we make sure to check the response to see if there were any errors. As we mentioned yesterday, the TrackBack Technical Specification requires that the response be XML in the following format:

<?xml version="1.0" encoding="utf-8"?>
   <message>The error message</message>

Since it is XML, it is easy to parse this using LINQ to XML. If an error is found, we raise an exception including the contained error message from the server.

Posted on - Comment
Categories: VB - VB Dev Center - LINQ - SEO -
comments powered by Disqus