Cyotek Development Bloghttps://devblog.cyotek.com/tag/webresponse/atom.xml2010-09-26T19:24:24ZCreating a trackback handler using C#urn:uuid:787e3c16-2fe4-4e70-b211-65776f508c0a2010-09-26T19:24:24Z2010-09-22T19:50:32Z<p>Cyotek.com runs on its own custom CMS/blog engine developed in
ASP.NET MVC 1.0, which has a number of advantages and
disadvantages. One of these disadvantages is no automatic
support for some common blog features such as trackbacks and
pingbacks.</p>
<p>This article will describe how to create a trackback handler for
use with MVC and the more traditional webforms.</p>
<h2 id="what-is-a-trackback">What is a trackback?</h2>
<p>A trackback is a way to be notified when a website links to a
resource on your own site. Some blogging software supports
automatic linking, so if a post on that site links to another,
when the post is submitted, it will automatically detect the
link and attempt to send a trackback to the original author. If
successful, a link is generally created from the original author
to the new post, thus building a web of interconnected resources
(in theory). You can learn a little more about trackbacks from
<a href="http://en.wikipedia.org/wiki/Trackback" rel="external nofollow noopener">Wikipedia</a>.</p>
<p>The full trackback specification can be viewed at the <a href="http://www.sixapart.com/pronet/docs/trackback_spec" rel="external nofollow noopener">SixApart
website</a></p>
<h2 id="a-trackback-handler-in-c">A trackback handler in C#</h2>
<p>Unlike pingbacks (which we'll address in a future article),
trackbacks use standard HTTP requests and so are extremely easy
to implement.</p>
<p>Available for download at the end of this article is a sample
library which you can use to implement your trackbacks.</p>
<p>As a trackback is comprised of several pieces of information
which we'll be passing about, we'll start by defining a
structure to hold this information.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">struct</span> TrackbackInfo
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">string</span> BlogName <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Excerpt <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Id <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">string</span> Title <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> Uri Uri <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The properties of this structure mirror the required information
from the trackback specification.</p>
<p>Next, we'll define an enum for the different result codes you
can return. The specification states 0 for success and 1 for
error, but I'm uncertain if you can extend this, ie is any
non-zero is classed as an error. We'll play it safe and just use
a single error code.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">enum</span> TrackbackErrorCode
<span class="symbol">{</span>
 Success<span class="symbol">,</span>
 Error
<span class="symbol">}</span>
</pre>
</figure>
<p>I'd considered two ways of implementing this, the first being an
abstract class containing methods which must be implemented in
order to provide the functionality for saving a trackback into
your chosen data source, or using delegates. In order to make it
a simple as possible to use, I've went with the latter.
Therefore, we need two delegates, one which will resolve the
&quot;permalink&quot; for the given ID, and another to actually save the
trackback.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">delegate</span> Uri GetTrackbackUrlDelegate<span class="symbol">(</span>TrackbackInfo trackback<span class="symbol">)</span><span class="symbol">;</span>
<span class="keyword">public</span> <span class="keyword">delegate</span> <span class="keyword">void</span> SaveTrackbackDelegate<span class="symbol">(</span>TrackbackInfo trackback<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="implementing-the-handler">Implementing the handler</h2>
<p>We've created a static class named <code>TrackbackHandler</code> which
contains all the functionality we'll need. We expose a single
public method, <code>GetTrackback</code>, which will return the XML block
required to notify the sender of the result of the request.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">string</span> GetTrackback<span class="symbol">(</span>NameValueCollection form<span class="symbol">,</span> SaveTrackbackDelegate saveTrackbackDelegate<span class="symbol">,</span> GetTrackbackUrlDelegate getTrackbackUrlDelegate<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span> url<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>form <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span><span class="string">&quot;form&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>saveTrackbackDelegate <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span><span class="string">&quot;saveTrackbackDelegate&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>getTrackbackUrlDelegate <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span><span class="string">&quot;getTrackbackUrlDelegate&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 url <span class="symbol">=</span> form<span class="symbol">[</span><span class="string">&quot;url&quot;</span><span class="symbol">]</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>url<span class="symbol">)</span> <span class="symbol">&amp;&amp;</span> url<span class="symbol">.</span>Contains<span class="symbol">(</span><span class="string">&quot;,&quot;</span><span class="symbol">)</span><span class="symbol">)</span>
 url <span class="symbol">=</span> url<span class="symbol">.</span>Split<span class="symbol">(</span><span class="string">&#39;,&#39;</span><span class="symbol">)</span><span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">return</span> TrackbackHandler<span class="symbol">.</span>GetTrackback<span class="symbol">(</span>saveTrackbackDelegate<span class="symbol">,</span> getTrackbackUrlDelegate<span class="symbol">,</span> form<span class="symbol">[</span><span class="string">&quot;id&quot;</span><span class="symbol">]</span><span class="symbol">,</span> url<span class="symbol">,</span> form<span class="symbol">[</span><span class="string">&quot;title&quot;</span><span class="symbol">]</span><span class="symbol">,</span> form<span class="symbol">[</span><span class="string">&quot;excerpt&quot;</span><span class="symbol">]</span><span class="symbol">,</span> form<span class="symbol">[</span><span class="string">&quot;blog_name&quot;</span><span class="symbol">]</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>This function accepts the following arguments:</p>
<ul>
<li>A <code>NameValueCollection</code> holding the submitted trackback data -
supporting both the MVC <code>FormCollection</code> or <code>Request.Form</code> for
ASP.NET.</li>
<li>An implementation of the <code>SaveTrackbackDelegate</code> delegate for
saving the trackback to your chosen data store.</li>
<li>An implementation of the <code>GetTrackbackUrlDelegate</code> for
resolving a permalink URL of the given ID.</li>
</ul>
<p>Assuming none of these are null, the method then calls a private
overload, explicitly specifying the individual items of data.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">string</span> GetTrackback<span class="symbol">(</span>SaveTrackbackDelegate saveTrackbackDelegate<span class="symbol">,</span> GetTrackbackUrlDelegate getTrackbackUrlDelegate<span class="symbol">,</span> <span class="keyword">string</span> id<span class="symbol">,</span> <span class="keyword">string</span> url<span class="symbol">,</span> <span class="keyword">string</span> title<span class="symbol">,</span> <span class="keyword">string</span> excerpt<span class="symbol">,</span> <span class="keyword">string</span> blogName<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span> result<span class="symbol">;</span>
 <span class="keyword">try</span>
 <span class="symbol">{</span>
 HttpRequest request<span class="symbol">;</span>

 request <span class="symbol">=</span> HttpContext<span class="symbol">.</span>Current<span class="symbol">.</span>Request<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>id<span class="symbol">)</span><span class="symbol">)</span>
 result <span class="symbol">=</span> GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> <span class="string">&quot;The entry ID is missing&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span>request<span class="symbol">.</span>HttpMethod <span class="symbol">!=</span> <span class="string">&quot;POST&quot;</span><span class="symbol">)</span>
 result <span class="symbol">=</span> GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> <span class="string">&quot;An invalid request was made.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>url<span class="symbol">)</span><span class="symbol">)</span>
 result <span class="symbol">=</span> TrackbackHandler<span class="symbol">.</span>GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> <span class="string">&quot;Trackback URI not specified.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>First, we validate that the request is being made via a <code>POST</code>
and not any other HTTP request, and that both the entry ID and
the URL of the sender are specified.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 TrackbackInfo trackbackInfo<span class="symbol">;</span>
 <span class="keyword">string</span> trackbackTitle<span class="symbol">;</span>
 Uri targetUri<span class="symbol">;</span>

 trackbackInfo <span class="symbol">=</span> <span class="keyword">new</span> TrackbackInfo<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Id <span class="symbol">=</span> id<span class="symbol">,</span>
 Title <span class="symbol">=</span> title<span class="symbol">,</span>
 BlogName <span class="symbol">=</span> blogName<span class="symbol">,</span>
 Excerpt <span class="symbol">=</span> excerpt<span class="symbol">,</span>
 Uri <span class="symbol">=</span> <span class="keyword">new</span> Uri<span class="symbol">(</span>url<span class="symbol">)</span>
 <span class="symbol">}</span><span class="symbol">;</span>

 targetUri <span class="symbol">=</span> getTrackbackUrlDelegate<span class="symbol">.</span>Invoke<span class="symbol">(</span>trackbackInfo<span class="symbol">)</span><span class="symbol">;</span>

</pre>
</figure>
<p>If everything is fine, we then construct our <code>TrackbackInfo</code>
object for passing to our delegates, and then try and get the
permalink for the trackback ID.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">if</span> <span class="symbol">(</span>targetUri <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 result <span class="symbol">=</span> GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> <span class="string">&quot;The entry ID could not be matched.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span> <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>TrackbackHandler<span class="symbol">.</span>CheckSourceLinkExists<span class="symbol">(</span>targetUri<span class="symbol">,</span> trackbackInfo<span class="symbol">.</span>Uri<span class="symbol">,</span> <span class="keyword">out</span> trackbackTitle<span class="symbol">)</span><span class="symbol">)</span>
 result <span class="symbol">=</span> GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Sorry couldn&#39;t find a link for \&quot;{0}\&quot; in \&quot;{1}\&quot;&quot;</span><span class="symbol">,</span> targetUri<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> trackbackInfo<span class="symbol">.</span>Uri<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>If we don't have a URL, we return an error code to the sender.</p>
<p>If we do have a URL another method, <code>CheckSourceLinkExists</code> is
called. This method will download the HTML of the caller and
attempt to verify if the senders page does in fact contain a
link matching the permalink. If it doesn't, then we'll abort
here.</p>
<p>If the method is successful and a link is detected, the method
will return the title of the senders HTML page as an out
parameter. This will be used if the trackback information didn't
include a blog name (as this is an optional field).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>blogName<span class="symbol">)</span><span class="symbol">)</span>
 trackbackInfo<span class="symbol">.</span>BlogName <span class="symbol">=</span> trackbackTitle<span class="symbol">;</span>

 saveTrackbackDelegate<span class="symbol">.</span>Invoke<span class="symbol">(</span>trackbackInfo<span class="symbol">)</span><span class="symbol">;</span>

 result <span class="symbol">=</span> TrackbackHandler<span class="symbol">.</span>GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Success<span class="symbol">,</span> <span class="keyword">string</span><span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span> <span class="symbol">(</span>Exception ex<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">//handle the error.</span>
 result <span class="symbol">=</span> TrackbackHandler<span class="symbol">.</span>GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode<span class="symbol">.</span>Error<span class="symbol">,</span> ex<span class="symbol">.</span>Message<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Finally, if everything went to plan, we save the trackback to
our data store, and return a success code. In the event of any
part of this process failing, then we return an error result.</p>
<h2 id="downloading-the-senders-html-and-checking-if-a-link-exists">Downloading the senders html and checking if a link exists</h2>
<p>In this implementation, we won't link to the senders site unless
they have already linked to us. We do this by downloading the
HTML of the senders site and checking to see if our link is
present.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">bool</span> CheckSourceLinkExists<span class="symbol">(</span>Uri lookingFor<span class="symbol">,</span> Uri lookingIn<span class="symbol">,</span> <span class="keyword">out</span> <span class="keyword">string</span> pageTitle<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">bool</span> result<span class="symbol">;</span>

 pageTitle <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">try</span>
 <span class="symbol">{</span>
 <span class="keyword">string</span> html<span class="symbol">;</span>

 html <span class="symbol">=</span> GetPageHtml<span class="symbol">(</span>lookingIn<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>html<span class="symbol">.</span>Trim<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">|</span> html<span class="symbol">.</span>IndexOf<span class="symbol">(</span>lookingFor<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">,</span> StringComparison<span class="symbol">.</span>InvariantCultureIgnoreCase<span class="symbol">)</span> <span class="symbol">&lt;</span> <span class="number">0</span><span class="symbol">)</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 HtmlDocument document<span class="symbol">;</span>

 document <span class="symbol">=</span> <span class="keyword">new</span> HtmlDocument<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 document<span class="symbol">.</span>LoadHtml<span class="symbol">(</span>html<span class="symbol">)</span><span class="symbol">;</span>
 pageTitle <span class="symbol">=</span> document<span class="symbol">.</span>GetDocumentTitle<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 result <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span>
 <span class="symbol">{</span>
 result <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">string</span> GetPageHtml<span class="symbol">(</span>Uri uri<span class="symbol">)</span>
<span class="symbol">{</span>
 WebRequest request<span class="symbol">;</span>
 HttpWebResponse response<span class="symbol">;</span>
 <span class="keyword">string</span> encodingName<span class="symbol">;</span>
 Encoding encoding<span class="symbol">;</span>
 <span class="keyword">string</span> result<span class="symbol">;</span>

 request <span class="symbol">=</span> WebRequest<span class="symbol">.</span>Create<span class="symbol">(</span>uri<span class="symbol">)</span><span class="symbol">;</span>
 response <span class="symbol">=</span> <span class="symbol">(</span>HttpWebResponse<span class="symbol">)</span>request<span class="symbol">.</span>GetResponse<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 encodingName <span class="symbol">=</span> response<span class="symbol">.</span>ContentEncoding<span class="symbol">.</span>Trim<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>encodingName<span class="symbol">)</span><span class="symbol">)</span>
 encodingName <span class="symbol">=</span> <span class="string">&quot;utf-8&quot;</span><span class="symbol">;</span>
 encoding <span class="symbol">=</span> Encoding<span class="symbol">.</span>GetEncoding<span class="symbol">(</span>encodingName<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> response<span class="symbol">.</span>GetResponseStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>StreamReader reader <span class="symbol">=</span> <span class="keyword">new</span> StreamReader<span class="symbol">(</span>stream<span class="symbol">,</span> encoding<span class="symbol">)</span><span class="symbol">)</span>
 result <span class="symbol">=</span> reader<span class="symbol">.</span>ReadToEnd<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">string</span> GetDocumentTitle<span class="symbol">(</span><span class="keyword">this</span> HtmlDocument document<span class="symbol">)</span>
<span class="symbol">{</span>
 HtmlNode titleNode<span class="symbol">;</span>
 <span class="keyword">string</span> title<span class="symbol">;</span>

 titleNode <span class="symbol">=</span> document<span class="symbol">.</span>DocumentNode<span class="symbol">.</span>SelectSingleNode<span class="symbol">(</span><span class="string">&quot;//head/title&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>titleNode <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 title <span class="symbol">=</span> titleNode<span class="symbol">.</span>InnerText<span class="symbol">;</span>
 <span class="keyword">else</span>
 title <span class="symbol">=</span> <span class="keyword">string</span><span class="symbol">.</span>Empty<span class="symbol">;</span>

 title <span class="symbol">=</span> title<span class="symbol">.</span>Replace<span class="symbol">(</span><span class="string">&quot;\n&quot;</span><span class="symbol">,</span> <span class="string">&quot;&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 title <span class="symbol">=</span> title<span class="symbol">.</span>Replace<span class="symbol">(</span><span class="string">&quot;\r&quot;</span><span class="symbol">,</span> <span class="string">&quot;&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">while</span> <span class="symbol">(</span>title<span class="symbol">.</span>Contains<span class="symbol">(</span><span class="string">&quot; &quot;</span><span class="symbol">)</span><span class="symbol">)</span>
 title <span class="symbol">=</span> title<span class="symbol">.</span>Replace<span class="symbol">(</span><span class="string">&quot; &quot;</span><span class="symbol">,</span> <span class="string">&quot; &quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> title<span class="symbol">.</span>Trim<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The function <code>GetDocumentTitle</code> uses the <a href="http://htmlagilitypack.codeplex.com/" rel="external nofollow noopener">Html Agility Pack</a>
to parse the HTML looking for the title tag. As the
<code>CheckSourceLinkExists</code> function is only checking to see if the
link exists somewhere inside the HTML you may wish to update
this to ensure that the link is actually within an anchor tag -
the Html Agility Pack makes this extremely easy.</p>
<h2 id="returning-a-response">Returning a response</h2>
<p>In several places, the <code>GetTrackback</code> method calls
<code>GetTrackbackResponse</code>. This helper function returns a block of
XML which describes the result of the operation.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">string</span> GetTrackbackResponse<span class="symbol">(</span>TrackbackErrorCode errorCode<span class="symbol">,</span> <span class="keyword">string</span> errorText<span class="symbol">)</span>
<span class="symbol">{</span>
 StringBuilder builder<span class="symbol">;</span>

 builder <span class="symbol">=</span> <span class="keyword">new</span> StringBuilder<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>StringWriter writer <span class="symbol">=</span> <span class="keyword">new</span> StringWriter<span class="symbol">(</span>builder<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 XmlWriterSettings settings<span class="symbol">;</span>
 XmlWriter xmlWriter<span class="symbol">;</span>

 settings <span class="symbol">=</span> <span class="keyword">new</span> XmlWriterSettings<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 settings<span class="symbol">.</span>Indent <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 settings<span class="symbol">.</span>Encoding <span class="symbol">=</span> Encoding<span class="symbol">.</span>UTF<span class="number">8</span><span class="symbol">;</span>

 xmlWriter <span class="symbol">=</span> XmlWriter<span class="symbol">.</span>Create<span class="symbol">(</span>writer<span class="symbol">,</span> settings<span class="symbol">)</span><span class="symbol">;</span>

 xmlWriter<span class="symbol">.</span>WriteStartDocument<span class="symbol">(</span><span class="keyword">true</span><span class="symbol">)</span><span class="symbol">;</span>
 xmlWriter<span class="symbol">.</span>WriteStartElement<span class="symbol">(</span><span class="string">&quot;response&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 xmlWriter<span class="symbol">.</span>WriteElementString<span class="symbol">(</span><span class="string">&quot;response&quot;</span><span class="symbol">,</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>errorCode<span class="symbol">)</span><span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>errorText<span class="symbol">)</span><span class="symbol">)</span>
 xmlWriter<span class="symbol">.</span>WriteElementString<span class="symbol">(</span><span class="string">&quot;message&quot;</span><span class="symbol">,</span> errorText<span class="symbol">)</span><span class="symbol">;</span>
 xmlWriter<span class="symbol">.</span>WriteEndElement<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 xmlWriter<span class="symbol">.</span>WriteEndDocument<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 xmlWriter<span class="symbol">.</span>Close<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> builder<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="implementing-an-mvc-action-for-handling-trackbacks">Implementing an MVC Action for handling trackbacks</h2>
<p>In order to use the handler from MVC, define a new action which
returns a <code>ContentResult</code>. It should only be callable from a
<code>POST</code>, and ideally it shouldn't validate input. Even if you
don't want HTML present in your trackbacks, you should strip any
HTML yourself - if you have ASP.NET validation enabled and an
attempt is made to post data containing HTML, then ASP.NET will
return the yellow screen of death HTML to the sender, not the
nice block of XML it was expecting.</p>
<p>Simply return a new <code>ContentResult</code> containing the result of the
<code>GetTrackback</code> method and a mime type of <code>text/xml</code>, as
shown below.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>AcceptVerbs<span class="symbol">(</span>HttpVerbs<span class="symbol">.</span>Post<span class="symbol">)</span><span class="symbol">]</span>
<span class="symbol">[</span>ValidateInput<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> ContentResult Trackback<span class="symbol">(</span>FormCollection form<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span> xml<span class="symbol">;</span>

 <span class="comment">// get the ID of the article to link to from the URL query string</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>form<span class="symbol">[</span><span class="string">&quot;id&quot;</span><span class="symbol">]</span><span class="symbol">)</span><span class="symbol">)</span>
 form<span class="symbol">.</span>Add<span class="symbol">(</span><span class="string">&quot;id&quot;</span><span class="symbol">,</span> Request<span class="symbol">.</span>QueryString<span class="symbol">[</span><span class="string">&quot;id&quot;</span><span class="symbol">]</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// get the response from the trackback handler</span>
 xml <span class="symbol">=</span> TrackbackHandler<span class="symbol">.</span>GetTrackback<span class="symbol">(</span>form<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SaveTrackbackComment<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetArticleUrl<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Content<span class="symbol">(</span>xml<span class="symbol">,</span> <span class="string">&quot;text/xml&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In this case, I'm also checking the query string for the ID of
the article to link to as we use a single trackback action to
handle all resources. If your trackback submission URL is unique
for resource supporting trackbacks, then you wouldn't need to do
this.</p>
<p>The implementations of your two delegates will vary depending on
how your own website is structured and how it stores data. As an
example I have included the ones used here at Cyotek.com (Entity
Framework on SQL Server 2005 using a repository pattern):</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> Uri GetArticleUrl<span class="symbol">(</span>TrackbackInfo trackback<span class="symbol">)</span>
<span class="symbol">{</span>
 Article article<span class="symbol">;</span>
 <span class="keyword">int</span> articleId<span class="symbol">;</span>
 Uri result<span class="symbol">;</span>

 Int<span class="number">32</span><span class="symbol">.</span>TryParse<span class="symbol">(</span>trackback<span class="symbol">.</span>Id<span class="symbol">,</span> <span class="keyword">out</span> articleId<span class="symbol">)</span><span class="symbol">;</span>

 article <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ArticleService<span class="symbol">.</span>GetItem<span class="symbol">(</span>articleId<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>article <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 result <span class="symbol">=</span> <span class="keyword">new</span> Uri<span class="symbol">(</span>Url<span class="symbol">.</span>Action<span class="symbol">(</span><span class="string">&quot;display&quot;</span><span class="symbol">,</span> <span class="string">&quot;article&quot;</span><span class="symbol">,</span> <span class="keyword">new</span> <span class="symbol">{</span> id <span class="symbol">=</span> article<span class="symbol">.</span>Name <span class="symbol">}</span><span class="symbol">,</span> <span class="string">&quot;http&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">else</span>
 result <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> SaveTrackbackComment<span class="symbol">(</span>TrackbackInfo trackback<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">try</span>
 <span class="symbol">{</span>
 Comment comment<span class="symbol">;</span>
 Article article<span class="symbol">;</span>
 StringBuilder body<span class="symbol">;</span>
 <span class="keyword">string</span> blogName<span class="symbol">;</span>

 article <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ArticleService<span class="symbol">.</span>GetItem<span class="symbol">(</span>Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>trackback<span class="symbol">.</span>Id<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 blogName <span class="symbol">=</span> <span class="symbol">!</span><span class="keyword">string</span><span class="symbol">.</span>IsNullOrEmpty<span class="symbol">(</span>trackback<span class="symbol">.</span>BlogName<span class="symbol">)</span> <span class="symbol">?</span> trackback<span class="symbol">.</span>BlogName <span class="symbol">:</span> trackback<span class="symbol">.</span>Uri<span class="symbol">.</span>AbsolutePath<span class="symbol">;</span>

 body <span class="symbol">=</span> <span class="keyword">new</span> StringBuilder<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 body<span class="symbol">.</span>AppendFormat<span class="symbol">(</span><span class="string">&quot;[b]{0}[/b]\n&quot;</span><span class="symbol">,</span> trackback<span class="symbol">.</span>Title<span class="symbol">)</span><span class="symbol">;</span>
 body<span class="symbol">.</span>Append<span class="symbol">(</span>trackback<span class="symbol">.</span>Excerpt<span class="symbol">)</span><span class="symbol">;</span>
 body<span class="symbol">.</span>AppendFormat<span class="symbol">(</span><span class="string">&quot; - Trackback from {0}&quot;</span><span class="symbol">,</span> blogName<span class="symbol">)</span><span class="symbol">;</span>

 comment <span class="symbol">=</span> <span class="keyword">new</span> Comment<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 comment<span class="symbol">.</span>Article <span class="symbol">=</span> article<span class="symbol">;</span>
 comment<span class="symbol">.</span>AuthorName <span class="symbol">=</span> blogName<span class="symbol">;</span>
 comment<span class="symbol">.</span>AuthorUrl <span class="symbol">=</span> trackback<span class="symbol">.</span>Uri<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 comment<span class="symbol">.</span>DateCreated <span class="symbol">=</span> DateTime<span class="symbol">.</span>Now<span class="symbol">;</span>
 comment<span class="symbol">.</span>Body <span class="symbol">=</span> body<span class="symbol">.</span>ToString<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 comment<span class="symbol">.</span>IsPublished <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 comment<span class="symbol">.</span>AuthorEmail <span class="symbol">=</span> <span class="keyword">string</span><span class="symbol">.</span>Empty<span class="symbol">;</span>
 comment<span class="symbol">.</span>AuthorUserName <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>CommentService<span class="symbol">.</span>CreateItem<span class="symbol">(</span>comment<span class="symbol">)</span><span class="symbol">;</span>

 ModelHelpers<span class="symbol">.</span>SendCommentEmail<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">,</span> article<span class="symbol">,</span> comment<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>Url<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">catch</span> <span class="symbol">(</span>System<span class="symbol">.</span>Exception ex<span class="symbol">)</span>
 <span class="symbol">{</span>
 CyotekApplication<span class="symbol">.</span>LogException<span class="symbol">(</span>ex<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">throw</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="implementing-an-asp.net-webforms-trackback-handler">Implementing an ASP.NET Webforms trackback handler</h2>
<p>Using this library from ASP.NET webforms is almost as
straightforward. You could, as in the example below, create a
normal page containing no HTML such as trackback.aspx which will
omit the XML when called.</p>
<p>Ideally however, you would probably want to implement this as a
HTTP Handler, although this is beyond the scope of this article.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> System<span class="symbol">;</span>
<span class="keyword">using</span> System<span class="symbol">.</span>Text<span class="symbol">;</span>
<span class="keyword">using</span> Cyotek<span class="symbol">.</span>Web<span class="symbol">.</span>Trackback<span class="symbol">;</span>

<span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> TrackbackHandlerPage <span class="symbol">:</span> System<span class="symbol">.</span>Web<span class="symbol">.</span>UI<span class="symbol">.</span>Page
<span class="symbol">{</span>
 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> OnInit<span class="symbol">(</span>EventArgs e<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">base</span><span class="symbol">.</span>OnInit<span class="symbol">(</span>e<span class="symbol">)</span><span class="symbol">;</span>

 Response<span class="symbol">.</span>ContentEncoding <span class="symbol">=</span> Encoding<span class="symbol">.</span>UTF<span class="number">8</span><span class="symbol">;</span>
 Response<span class="symbol">.</span>ContentType <span class="symbol">=</span> <span class="string">&quot;text/xml&quot;</span><span class="symbol">;</span>

 Response<span class="symbol">.</span>Clear<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 Response<span class="symbol">.</span>Write<span class="symbol">(</span>TrackbackHandler<span class="symbol">.</span>GetTrackback<span class="symbol">(</span>Request<span class="symbol">.</span>Form<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>SaveTrackback<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>GetTrackbackUrl<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> Uri GetTrackbackUrl<span class="symbol">(</span>TrackbackInfo trackbackInfo<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> SaveTrackback<span class="symbol">(</span>TrackbackInfo trackbackInfo<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="providing-the-trackback-url">Providing the trackback URL</h2>
<p>Of course, having a trackback handler is of no use if third
party sites can't find it! For sites to discover your trackback
URLs, you need to embed a block of HTML inside your blog
articles containing a link to your trackback handler. This URL
should be unique for each article. For cyotek.com, we append the
ID of the article as part of the query string of the URL, then
extract this in the controller action, but this isn't the only
way to do it - choose whatever suits the needs of your site.</p>
<p>The following shows the auto discovery information for this URL:</p>
<figure class="lang-xml highlight"><figcaption><span>xml</span></figcaption><pre class="code">
<span class="symbol">&lt;</span><span class="name">rdf</span><span class="symbol">:</span><span class="name">RDF</span> <span class="name">xmlns:rdf</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://www.w3.org/1999/02/22-rdf-syntax-ns#</span><span class="symbol">&quot;</span> <span class="name">xmlns:dc</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://purl.org/dc/elements/1.1/</span><span class="symbol">&quot;</span> <span class="name">xmlns:trackback</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://madskills.com/public/xml/rss/module/trackback/</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;</span><span class="name">rdf</span><span class="symbol">:</span><span class="name">Description</span> <span class="name">rdf:about</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://cyotek.com/article/display/creating-a-trackback-handler-using-csharp</span><span class="symbol">&quot;</span> <span class="name">dc:identifier</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://cyotek.com/article/display/creating-a-trackback-handler-using-csharp</span><span class="symbol">&quot;</span> <span class="name">dc:title</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">Creating a trackback handler using C#</span><span class="symbol">&quot;</span> <span class="name">trackback:ping</span><span class="symbol">=</span><span class="symbol">&quot;</span><span class="attribute">http://cyotek.com/trackback?id=21</span><span class="symbol">&quot;</span><span class="symbol">&gt;</span><span class="symbol">&lt;/</span><span class="name">rdf</span><span class="symbol">:</span><span class="name">Description</span><span class="symbol">&gt;</span>
<span class="symbol">&lt;/</span><span class="name">rdf</span><span class="symbol">:</span><span class="name">RDF</span><span class="symbol">&gt;</span>
</pre>
</figure>
<p>It includes the trackback URL (with article ID 21) and the title
of the article, plus the permalink.</p>
<h2 id="next-steps">Next steps</h2>
<p>Cyotek.com doesn't get a huge amount of traffic, and so this
library has not been extensively tested. It has worked so far,
but I can't guarantee it to be bug free!</p>
<p>Possible enhancements would be to add some form of deny list,
so if you were getting spam requests, you could more easily
disable these. Also the link checking could be made more robust
by ensure its within a valid anchor, although there's only so
much you can do.</p>
<p>I hope you find this library useful, the download link is below.
As mentioned, this library uses the Html Agility Pack for
parsing HTML, however you can replace this if required with your
own custom solution.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2010-09-22 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/creating-a-trackback-handler-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com