asp:Feature
LANGUAGES: C# | VB.NET
ASP.NET VERSIONS: 2.0
Google, Can You Hear Me?
How to Design URLs that Are Search-engine-friendly
By Michael C. Neel
If Pete Townsend wrote his rock opera Tommy today, we might have a very different story: Tommy is a young lad who loves pinball, runs a Web site called Pinball Wizard, and blogs all things pinball. Despite having the most pinball information on the Internet, his traffic is a trickle. After a deep soul-searching journey he finds out why: Google can t see him.
This may make for a bad soundtrack, but the lesson is important. As developers, it s easy to forget how our sites appear to search engines when we are knee-deep in business logic implementation. No matter how well we design the interface or target content to the visitor, nothing will affect site traffic more than search-engine rankings. This article will show you how to build a database-driven Web site with search-engine-friendly URLs using tools in the ASP.NET 2.0 Framework.
What Google Wants
Note: There are thick tomes written solely on cracking the DaGoogle Code; this article is not about fooling Google or tricks to raise rankings. The goal here is to correctly identify site content to a search engine.
When parsing a link, a search engine considers (among other things) the URL, the text of the anchor tag, the title tag of the document it links to, and the content of that document. The more parts that match, the better the chance the search engine will assign to the document s index the keywords found in the link, title, and content. Let s look at a poorly designed (yet all too common) link:
<a href="http://www.ddbkig.org/blog/post.aspx?id=
34">World Finals</a>
The only part of this link with relevant content is the text of the anchor tag, World Finals . Also found in the URL are blog , post , and id , but these are keywords we don t want indexed. Let s rewrite a better URL:
<a href="http://www.ddbkid.org/Articles/Tournaments/
2006/06/12/World-Finals.aspx">World Finals</a>
q c g M hb ( BE i( w w Y ynM Y V % ( rB U) ! j $ I - E 0B ` " n3 8 U b JL & Qd%kY?u F Rg% A- iJd m )* UVD ,Ah. Klf f ? dT " m ?vP9Rm 6 r n qP J )n [ ? &| |* ?d`-ì w_ Aw~ ?? / p .>Dw E? l\J X % ,* 4^ ;7 ?u u %)& ]= c0 {^ ]5a8,n5 KW v yf Z U + A Nt; }T s j & + s X_1(+G h 6o F ? 1[L CK"e ')'s(' kSyd 36 ' { bK I D ?M X?0 G #it pYG , X*"G? $8 o y M$ @Q, T K % TY m" Ip$g -0Y= ZrWN b? B+ E x? `K` x 4 Cl = dy ?\ d f e , K'e F l_SS ?R= F? b7) Qw }: `3= Y ) X M{ dM F YR * ip 20 q "I| 6e Cqe% + ) O A i 1Z1 | S l Z ?; H I1+ 9 K Z ,'a # ) 1 ` ": T z8 @{ a ). a.& ?+ ? 3%??b_ H)J 6!r 92/ ( AM? È X 3 , JHefB0\0 \ : l0 pm _s ` ?Cb1 4J(S U ¯ Ht,!,V0F 9 y `l dq ( 2U?P ? Qc a'&` \V 2: ${2 I~ M G KQPB jN1 R .T e&B @ G_ `62 AWp A d Q mh I [ ^ vJ vn ^-:& N- &1oD d#o0 cg bM?s S F?{ a b9?R [ ; /5 +N V ]*$ K2FL 6 x y 6Pp|dE ! F A M VP F lH Q = K 6 _ b? pB o Ku õ g# ?q Kz | ( 3$ U |[? + f { w > BG E U+ B W*f dR " 2 { wpUJ8*; ~M o + # _Kqg{' R?Yr % ? j!? o8 *_ T f [ ?7;w ? |P vM + t {? c ${ { f ? Q d ! A } ? ?? h/ )h K S- J7 e? f i{ S ? Z ? ts sx K& @ ?a@ ½&kU \T?U^+ ])% q Ehj U &0WS % URE{ 4U? Ah b f *7 *l L P? 5e f Z Kh >6 ?= Vh 1e b 1G |il"*- 5 : Lg SZs k X3 2 V c |? = H` 8p l . * n Mr N I' G ^ L : 3 ( P ?l 7? ?s2 ?;M ? 0 IEND B` PK-! n [Content_Types].xmlPK-! * 86_rels/.relsPK-!V f ,drs/slideMasters/_rels/slideMaster1.xml.relsPK-! br !.drs/slideMasters/slideMaster1.xmlPK- !i ff drs/media/image2.pngPK- ! D wdrs/media/image1.pngPK 0rU M h ( h ] h p 0B ? ( Title Placeholder 1" I CPK!Z f [Content_Types].xml MO & 2W R=cJ ` F 0 iK ` #? v L w 9 u Sq : w `G ^ i ½ K I )c/ $o Vj T M R c |}0 42 ?C ?M ? P ~ *k a /8 ^ DkH bL 8e i"K \ XN\ 6r co4 y @ _ ; o PK!1 _a _rels/.rels j 0 ? ?q C No ^K [ILc X&m? 0XFo; > 0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK!F s Qdrs/shapexml.xml QO 0 O ;Dy v; Z $ I 41x> 4 ?I 1 ON b}H\u Y] ; u \ m [ \ ? ` ?t ? / |?? ! E ? a? y A[ D y~ u ZYq( 5FK ?Q u) RX 8 CKF gj b& x O?a ^ w,º[ rR , L` bR0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK!??K drs/shapexml.xml V o 0~ Hh? Z I B G @ cG Ài v(m i & `sg w } j_r cJR$4 P D* B h".K iz5| ?uE q ?1U n t J e r J0 U v f? @%ow . t W ? + J vsE , (1 5F Rbu x: N\ P -0 .D ' A.a A s xw31V[ $ ?\nP T ?G6 8Z Z x bQ 63] ? a`?M > k = u _ 5 U= j ws x ? 9+ 5Mm gN & :j M+ h [ hL ? ? & " ,J PX m] Z c e @ = Y B g P b $ ( 2e vI q RwRX o}v h . \I .?3 d6a! .? m 3$ W vb z ___PPT9 * *CC I j 1 ? 4 Slide Image Placeholder 3" ÑPK!Z f [Content_Types].xml MO & 2W R=cJ ` F 0 iK ` #? v L w 9 u Sq : w `G ^ i ½ K I )c/ $o Vj T M R c |}0 42 ?C ?M ? P ~ *k a /8 ^ DkH bL 8e i"K \ XN\ 6r co4 y @ _ ; o PK!1 _a _rels/.rels j 0 ? ?q C No ^K [ILc X&m? 0XFo; > 0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK! V4 drs/shapexml.xml U N1} Z \H X*@ E PD z 6^? 4 {l RJ_ % g . ; v O> 9 Z ? ;9 5) e /W ] [g [ u X? Gc 1 [ ^ @uj4 ? :j5 Uz % [8 % q ÓK ? w l H?Q t?z L@47b {X / jG? 6_ y4 n?W [) ' >J #" f ` ? [q / 4?C J S T 4/N& = s @ & }:C 08 M/ ! H u>| hT,^Tr p*h7 !?5# ? 9 t! K I m R W % =F b s G,J/ b!B. ?C 6 `|6 [{? v a -J i q 0 I * q ? 9 g vmg~ V]r O / " @aURf m 5 ;m I l#] & .. Q Z u } ?"/U ?Y8c (g o xl 7 ?|i? ?{M 3p ?m ^N O? + m V I L H C O$ 6 B o s>?F? W PK! drs/downrev.xmlD N 0E H 5H C V xj ` 6 { @byu_: {u? KL' s y 2 >#L ` XPm Y6| f ? \ C uj{J 0 \d D: q ? kO G 8n p * Z M T 1 ?Axux 3J #B )p PK-!Z f [Content_Types].xmlPK-!1 _a /_rels/.relsPK-! V4 *drs/shapexml.xmlPK-! drs/downrev.xmlPK l X || ? ( Notes Placeholder 4" V PPK!Z f [Content_Types].xml MO & 2W R=cJ ` F 0 iK ` #? v L w 9 u Sq : w `G ^ i ½ K I )c/ $o Vj T M R c |}0 42 ?C ?M ? P ~ *k a /8 ^ DkH bL 8e i"K \ XN\ 6r co4 y @ _ ; o PK!1 _a _rels/.rels j 0 ? ?q C No ^K [ILc X&m? 0XFo; > 0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK!h5U? drs/shapexml.xml X n 6 ? [~ D$2- 8 % VM *I vV ~Z X ] {] 4[ 2 { s e "Iz ; x X A b~ 9,, isk] j?E 62Q 4zKO'V>F / B / y L l GI ] * e k-S r tQ6ND n vR qX ? "s P +U W$? , = g =t ?+ R` V 2 !p^!b 4 ? B U @ T : l M3 6 aGm ;6 h 7C C e 5 H e >__Ks ? r tL c || &\ i`v J ZFR r?: 4 ) I 1 S 0 ? \!_(=% V \ I McXg : 9T d ,}? S u yi r R a+ M)e & Z h Z ? ?& DQ@ { ? !p i !BV, S> e J |] " ``^ Q *u&(& >k l6& ó) -xb : ~ ? h { I 3 7#h { HL O &n{ R ? a?4 aE f)c M K3v?C j{ 2bN ?| ?|v ? pV 9t U?$ 1 M ? # *D8 f i" H \ ! W xh?="6 3Z l ZF ~fu-? ?h 9 `aM D /` u8 k pH v b p . H # Iy [ )V+qki ,N* k,a { ] G K:oO #K t Z ? | 1 o 1N R7 pI +a d6Y> f+W M :\YX /( ?TH edC Le! ? ^ 6 B L Z2_ ]? Y) SE ?M P}* T wj f - Y?o ?c F8+ :E L p ? $ V) Q U a ?SE 'pe h p +0 P * F hL h {Y e _ C ?a N~?HP?T$? d b l $YF DTI\ Z G? zo P n IV d Y B ?! ks |l2 Rn6 Mn BC]?7 ?? Y _) : P[|t l iG 8 AL 5m :i w ? 4v? \ Hcgvlmu z ? )S |_Z? H~ , PK! ? 'theme/theme/_rels/themeManager.xml.rels M 0 woo? &?? 5 6?$Q ,. a i c2 1h : q m @RN ;d ` o7 g K(M&$R(.1 r'J ?T 8 V " A? H u} | $ b{ P 8 g/] QAs?( # L [ PK-! [Content_Types].xmlPK-! ? 6+_rels/.relsPK-!ky theme/theme/themeManager.xmlPK-! 9 \ theme/theme/theme1.xmlPK-! ? ' theme/theme/_rels/themeManager.xml.relsPK] : ' PK!( b [Content_Types].xml n 0E Ak P }l} m $_ t I $ s vs^?K k H $ Q - G$ oo 1# ? 5 J# \ Qg0?0 o mR o?Y 8 J mi | T X+N 3 tr @ 9 > ? 4?Mf rA s ?o_p ? yjs & Xg3 PK! * 8_rels/.rels 0D n z ? X ml o 0of 5O I w AN{c? v=m 8 38yG 6 U} S h Lq `L) d=?\ @.; 3 , ;$ e ?): v q} j:z ? 'k?(f,? X A6 \ m> PK!W x6!drs/slideMasters/slideMaster1.xml ? 0 C v+N{@e 1 ZalC >K O q6 Tq k gg ?}%?iÕ_7?I .79 2%KdA , f i 3BH ? Z ;U3 0 ; ^E | j }Qt[1i b!}S Vkf L3 $ 5; p C3 MI&v¯UD? 4#b ~! r A6mE ? _ XvCp) ?, ?5?? Z #& CB$1px\? 5 k :;d 6.= i `4k%x1 B4 F $t n f' j &?mè?sRqq qaDK 5? O 1 ? t ?{o8` , d 9 A $ B \I;P Qs z O p#s ?yM x 7 x i > O c ? (w { A #9G D ! r? m . ) A ' M! 9Y: B O 3t v , Bi Bp , 8y ? % PK-!( b [Content_Types].xmlPK-! * 8*_rels/.relsPK-!W x6!drs/slideMasters/slideMaster1.xmlPK :- - \ o' ( \ \ Z ^ ? * Header Placeholder 1" S MPK!Z f [Content_Types].xml MO & 2W R=cJ ` F 0 iK ` #? v L w 9 u Sq : w `G ^ i ½ K I )c/ $o Vj T M R c |}0 42 ?C ?M ? P ~ *k a /8 ^ DkH bL 8e i"K \ XN\ 6r co4 y @ _ ; o PK!1 _a _rels/.rels j 0 ? ?q C No ^K [ILc X&m? 0XFo; > 0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK!q ^ drs/shapexml.xml TQO 0~ ` u 6 "@&U . fu? tm vB i Cz?} }w ]\n[ 6?Fc 1g \ 3 +Pe w O&w at ) { FN d X ?m ?] N O Z5 F-4?tnfn %6s° ZJy/ * k # ATY Ee P Y '&E82$`! Y1 3 kUY f_ X/m DGR\4 . GN/ u? ^ ? B. 89? ; zJDG`){ 4 ; ? n D a3s>ppH I? o u % S { hl) J =g ; g''T / x kO ?V/* ~ Gq Fe tjIk "? S ?>2 6 ] 0 , ? 62 NF \ i} 0 k j * D? 5 n: \ j ;t W 0"T? 6@ f uj X R F ? ! ? O?'e ? T \ P I j S8 ? v X5! U o R ? \ lI $ 6I u kZ n =k] Dm I # {u w ! ; p 4xN d Wg PK! ) drs/downrev.xmlD N1E & C H IJF !& . ` h? I[ q ?{sn b5 V b Y tR 4\+ AAl L .a , 4 T X RWJ % q ; ?|p r 4 Z9+ { ` 'K k ? C}( z 2 y R a "? ? t- P &C@ v chL 1QP ?i PK-!Z f [Content_Types].xmlPK-!1 _a /_rels/.relsPK-!q ^ *drs/shapexml.xmlPK-! ) @drs/downrev.xmlPK B P R l : 2 ___PPT9 R C 5 \ V @ ^ ? & Date Placeholder 2" ÷PK!Z f [Content_Types].xml MO & 2W R=cJ ` F 0 iK ` #? v L w 9 u Sq : w `G ^ i ½ K I )c/ $o Vj T M R c |}0 42 ?C ?M ? P ~ *k a /8 ^ DkH bL 8e i"K \ XN\ 6r co4 y @ _ ; o PK!1 _a _rels/.rels j 0 ? ?q C No ^K [ILc X&m? 0XFo; > 0 x M e ` |X} d I`? N4a G2 $ R KI Z ) 4 ( M 9` ctB{ m : f@ ` 3 n |O ,? r ? j x R0T , 0 @ }W B L O5v PK! \S drs/shapexml.xml VQo 0~G ?X~E I 4Z B *:~ 5q P S ! ;wv b ?^ 7 ? V9 G U V 9 p3 Y AX r E ? ae 6 l8 F4` V( U 4 pkn V(/j pEg jtJ V ? - ˜ 9S P 0lc k8e ) | $ ?~c0 .z0G\ a??C J S t2 c Sf g?2 c N/ U q &g1 u yD !{ 4 & qaE %/?s X @ ? (P?( 2 - #OWxBm W Sw~ U * i / J ] / 8 0F Q l Y A 4?# z3? 9[v O_ U[Pf - c; qD Az\ l = r? n& ?[ / ?8X N ,?oj *Yz $ J d ? e d l If allowed.Contains(l.ToString().ToLower()) Then clean += l
Else
If l.Equals(" "c) And (clean.Length > 0 And _
clean((clean.Length - 1)) <> "-"c) Then
clean += "-"
End If
End If
Next l
Return clean
End Function 'Sanitize
'Builds a URL for an article
Public Overloads Shared Function MakeURL( _
ByVal title As String, _
ByVal category As String, _
ByVal post_date As DateTime) As String
Dim url As String = ""
title = Sanitize(title)
category = Sanitize(category)
url = "~/Articles/" + category + _
post_date.ToString("/yyy/MM/dd/") + title + ".aspx"
Return url
End Function 'MakeURL
'Builds a URL for a category
Public Overloads Shared Function MakeURL( _
ByVal category As String) As String
Dim url As String = ""
category = Sanitize(category)
url = "~/Articles/" + category + "/Default.aspx"
Return url
End Function 'MakeURL
'SiteMapProvider.Initialize, called during app start
Public Overrides Sub Initialize(ByVal name As String, _
ByVal attributes _
As System.Collections.Specialized.NameValueCollection)
MyBase.Initialize(name, attributes)
Me.BuildNodes()
End Sub 'Initialize
'SiteMapProvider.FindSiteMapNode, called to find a node
'by given URL. Note: TreeView and Menu pass a Key
'and not a URL, so we need to check both
Public Overrides Function FindSiteMapNode( _
ByVal rawUrl As String) As SiteMapNode
'Ensure our URL is relative
Dim relUrl As String = rawUrl.Replace( _
HttpRuntime.AppDomainAppVirtualPath, "~").ToLower()
'Check the root
If _smnRoot.Url.ToLower() = relUrl _
Or _smnRoot.Key = rawUrl Then
Return _smnRoot
End If
'Search the categories
Dim smnCat As SiteMapNode
For Each smnCat In _smncCategory
If smnCat.Url.ToLower() = relUrl _
Or smnCat.Key = rawUrl Then
Return smnCat
End If
Next smnCat
'Search the articles
Dim smncArticle As SiteMapNodeCollection
For Each smncArticle In _dArticle.Values
Dim smnArticle As SiteMapNode
For Each smnArticle In smncArticle
If smnArticle.Url.ToLower() = relUrl _
Or smnArticle.Key = rawUrl Then
Return smnArticle
End If
Next smnArticle
Next smncArticle
'No match
Return Nothing
End Function 'FindSiteMapNode
'SiteMapProvider.GetChildNodes, note we do not assume
'the node passed in may be a node we creadted, and
'use the Key instead. This is because a call to
'BuildNodes after app start will rebuid the lists
Public Overrides Function GetChildNodes( _
ByVal node As SiteMapNode) As SiteMapNodeCollection
'If this is the root, return the catgories
If _smnRoot.Key = node.Key Then
Return _smncCategory
End If
'If this is a category, return the articles
If _dArticle.ContainsKey(node.Key) Then
Return _dArticle(node.Key)
End If
'If this is an article, there are no children
Return Nothing
End Function 'GetChildNodes
'SiteMapProvider.GetParentNode, as with GetChildNodes
'we use the Key to compare
Public Overrides Function GetParentNode( _
ByVal node As SiteMapNode) As SiteMapNode
'Root has no parent
If _smnRoot.Key = node.Key Then
Return Nothing
End If
'If this is a category, return the root
If _dArticle.ContainsKey(node.Key) Then
Return _smnRoot
End If
'If this is an article, return it's category node
Dim smncArticle As SiteMapNodeCollection
For Each smncArticle In _dArticle.Values
Dim smnArticle As SiteMapNode
For Each smnArticle In smncArticle
If smnArticle.Key = node.Key Then
Return smnArticle.ParentNode
End If
Next smnArticle
Next smncArticle
'No match
Return Nothing
End Function 'GetParentNode
'SiteMapProvider.GetRootNodeCore, return the root
Protected Overrides Function GetRootNodeCore() _
As SiteMapNode
Return _smnRoot
End Function 'GetRootNodeCore
End Class 'PinballSiteMapProvider
End Listing One
Begin Listing Two Article.aspx
<%@ Page Language="VB"
MasterPageFile="~/MasterPage.master" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As EventArgs)
'Select the article content from the database
Dim taArticle _
As New ContentTableAdapters.ArticleTableAdapter()
Dim daArticle _
As Content.ArticleDataTable = _
taArticle.ArticleByID( _
Convert.ToInt32(SiteMap.CurrentNode.Key))
'Display the article content
lBody.Text = daArticle(0).body
lDate.Text = daArticle(0).post_date.ToString("D")
'Set the article_id for the comment data source
odsComments.SelectParameters( _
"article_id").DefaultValue = SiteMap.CurrentNode.Key
End Sub
Protected Sub dvAddComment_ItemInserted( _
ByVal sender As Object, _
ByVal e As DetailsViewInsertedEventArgs)
'After a comment is inserted, referesh the comment list
rComments.DataBind()
End Sub
Protected Sub dvAddComment_ItemInserting( _
ByVal sender As Object, _
ByVal e As DetailsViewInsertEventArgs)
'Before adding the comment, set the article_id
e.Values("article_id") = SiteMap.CurrentNode.Key
End Sub
</script>
<asp:Content ID="Content1" Runat="Server"
ContentPlaceHolderID="ContentPlaceHolder1">
<asp:ObjectDataSource ID="odsComments" runat="server"
InsertMethod="AddComment"
SelectMethod="CommentsByArticle"
TypeName="ContentTableAdapters.CommentTableAdapter">
<SelectParameters>
<asp:Parameter Name="article_id" Type="Int32" />
</SelectParameters>
<InsertParameters>
<asp:Parameter Name="article_id" Type="Int32" />
<asp:Parameter Name="author" Type="String" />
<asp:Parameter Name="comment" Type="String" />
</InsertParameters>
</asp:ObjectDataSource>
<b><asp:Literal ID="lDate" runat="server" /></b>
<asp:Literal ID="lBody" runat="server" />
<asp:Repeater ID="rComments" runat="server"
DataSourceID="odsComments">
<ItemTemplate>
<b><asp:Literal ID="lAuthor" runat="server"
Text='<%# Eval("author") %>' /> said on
<asp:Literal ID="lCommentDate" runat="server"
Text='<%# Eval("comment_date") %>' />:</b>
<p><asp:Literal ID="lComment" runat="server"
Text='<%# Eval("comment") %>' /></p>
</ItemTemplate>
</asp:Repeater>
Leave a comment...
<asp:DetailsView ID="dvAddComment" runat="server"
AutoGenerateRows="False" DataKeyNames="comment_id"
DataSourceID="odsComments" DefaultMode="Insert"
OnItemInserted="dvAddComment_ItemInserted"
OnItemInserting="dvAddComment_ItemInserting">
<Fields>
<asp:BoundField DataField="author"
HeaderText="Name" />
<asp:BoundField DataField="comment"
HeaderText="Comment" />
<asp:TemplateField ShowHeader="False">
<InsertItemTemplate>
<asp:LinkButton ID="lbInsert" runat="server"
CommandName="Insert" Text="Leave Comment"
PostBackUrl='<%# SiteMap.CurrentNode.Url %>' />
</InsertItemTemplate>
</asp:TemplateField>
</Fields>
</asp:DetailsView>
</asp:Content>
End Listing Two