Title: Community Server 2007: Building a Visits counter control
Slug: community-server-2007-building-a-visits-counter-control
Date: 2009-05-13 14:29:00
Author: Kartones
Lang: en
Tags: Development, SQL Server, C#, CSS
Description: A guide on building a visits counter control for Community Server 2007.

<p>Since not much after I launched <a href="http://Kartones.Net">Kartones.Net</a> I added an ASP.NET user control to make the typical visits counter for all bloggers that wanted it (including me :P). Apart from having no code-behind, it had the SQL access inside (using a stored procedure and getting the configuration of the DB from ConnectionStrings.config, but still not layered).</p>  <p>So one step that was much needed was converting this to a proper Community Server 2007 control, both to take advantage of CS configuration &amp; data providers, and to make the process of adding or moving a visits counter as easy as one line of ASP.NET markup code.</p>  <p>Last night I did it, among with using CSS sprites to do only one http request to obtain the images, and here it is the how-to:</p>  <p> </p>  <p>First of all, I wanted a new table, to make it as small as possible, although we could have used the existing <b>cs_weblog_Weblogs</b> table by only adding a new long/numeric field. This is my SQL to create it:</p>  <p><font color="#0000ff" face="Courier New">USE </font><font color="#000000" face="Courier New">[yourdbname]      <br></font><font face="Courier New"><font color="#0000ff">GO        <br>SET ANSI_NULLS ON         <br>GO         <br>SET QUOTED_IDENTIFIER ON         <br>GO         <br>SET </font><font color="#000000">ANSI_PADDING </font></font><font color="#0000ff" face="Courier New">ON      <br>GO       <br>      <br>CREATE TABLE </font><font face="Courier New"><font color="#000000">[dbo].[cs_VisitCounters] (        <br>    [CounterID] [</font><font color="#0000ff">int</font><font color="#000000">] </font><font color="#0000ff">NOT NULL</font></font><font face="Courier New"><font color="#000000">,        <br>    [HitsCounter] [</font><font color="#0000ff">int</font><font color="#000000">] </font><font color="#0000ff">NOT NULL</font></font><font face="Courier New"><font color="#000000">,        <br></font><font color="#0000ff">CONSTRAINT </font><font color="#000000">[PK_CounterID] </font></font><font color="#0000ff" face="Courier New">PRIMARY KEY NONCLUSTERED  <br></font><font color="#000000" face="Courier New">(      <br>    [CounterID] </font><font face="Courier New"><font color="#0000ff">ASC        <br></font><font color="#000000">)</font><font color="#0000ff">WITH </font><font color="#000000">(PAD_INDEX  </font><font color="#0000ff">= OFF</font><font color="#000000">, STATISTICS_NORECOMPUTE  </font><font color="#0000ff">= OFF</font><font color="#000000">, IGNORE_DUP_KEY </font><font color="#0000ff">= OFF</font></font><font face="Courier New"><font color="#000000">,  <br>ALLOW_ROW_LOCKS  </font><font color="#0000ff">= ON</font><font color="#000000">, ALLOW_PAGE_LOCKS  </font><font color="#0000ff">= ON</font><font color="#000000">) </font><font color="#0000ff">ON </font><font color="#000000">[</font><font color="#0000ff">PRIMARY</font></font><font face="Courier New"><font color="#000000">]        <br>) </font><font color="#0000ff">ON </font><font color="#000000">[</font><font color="#0000ff">PRIMARY</font></font><font color="#000000" face="Courier New">]      <br>      <br></font><font face="Courier New"><font color="#0000ff">GO        <br>SET </font><font color="#000000">ANSI_PADDING </font><font color="#0000ff">OFF</font></font></p>  <p> </p>  <p>Then, we need a stored procedure to obtain the current hits. As I'm using a new table, what I do is if it doesn't exists creates it (else updates with +1):</p>  <p><font color="#0000ff" face="Courier New">USE </font><font color="#000000" face="Courier New">[yourdbname]      <br></font><font color="#0000ff" face="Courier New">GO      <br>SET ANSI_NULLS ON       <br>GO       <br>SET QUOTED_IDENTIFIER ON       <br>GO       <br>      <br>CREATE PROC </font><font color="#000000" face="Courier New">[dbo].[cs_RegisterVisitHit] (      <br>    @CounterID    </font><font color="#0000ff" face="Courier New">INT      <br>    </font><font color="#000000" face="Courier New">)      <br></font><font face="Courier New"><font color="#0000ff">AS        <br>BEGIN         <br>        <br>    SET TRANSACTION </font><font color="#000000">ISOLATION LEVEL </font><font color="#0000ff">READ </font></font><font face="Courier New"><font color="#000000">UNCOMMITTED        <br>        <br>    </font><font color="#0000ff">DECLARE </font><font color="#000000">@CounterValue </font></font><font face="Courier New"><font color="#0000ff">INT        <br>    SELECT </font><font color="#000000">@CounterValue </font><font color="#0000ff">= </font></font><font color="#800000" face="Courier New">0      <br>      <br>    </font><font face="Courier New"><font color="#0000ff">SELECT  <br>        </font><font color="#000000">@CounterValue </font><font color="#0000ff">= </font></font><font color="#000000" face="Courier New">[HitsCounter]  <br>    </font><font color="#0000ff" face="Courier New">FROM  <br>        </font><font color="#000000" face="Courier New">[yourdbname].[dbo].[cs_VisitCounters]  <br>    </font><font face="Courier New"><font color="#0000ff">WHERE  <br>        </font><font color="#000000">[CounterID] </font><font color="#0000ff">= </font></font><font face="Courier New"><font color="#000000">@CounterID        <br>        <br>    </font><font color="#0000ff">IF </font><font color="#000000">@CounterValue </font><font color="#0000ff">= </font></font><font color="#800000" face="Courier New">0      <br>    </font><font color="#0000ff" face="Courier New">BEGIN      <br>        INSERT INTO  <br>            </font><font color="#000000" face="Courier New">[yourdbname].[dbo].[cs_VisitCounters]      <br>           ([CounterID], [HitsCounter])  <br>        </font><font face="Courier New"><font color="#0000ff">VALUES  <br>            </font><font color="#000000">(@CounterID, </font><font color="#800000">1</font></font><font color="#000000" face="Courier New">)      <br>    </font><font face="Courier New"><font color="#0000ff">END        <br>    ELSE         <br>    BEGIN         <br>        UPDATE  <br>            </font><font color="#000000">[yourdbname].[dbo].[cs_VisitCounters]  <br>        </font><font color="#0000ff">SET </font><font color="#000000">[HitsCounter] </font><font color="#0000ff">= </font><font color="#000000">(@CounterValue + </font><font color="#800000">1</font><font color="#000000">)  <br>        </font><font color="#0000ff">WHERE </font><font color="#000000">[CounterID] </font><font color="#0000ff">= </font></font><font color="#000000" face="Courier New">@CounterID      <br>    </font><font color="#0000ff" face="Courier New">END      <br>     <br>    SELECT </font><font color="#000000" face="Courier New">(@CounterValue+1)      <br>      <br></font><font color="#0000ff" face="Courier New">END      <br>      <br>GO</font></p>  <p> </p>  <p>The SQL Part is done, now we need to add support for this stored procedure in the CS Data Providers.</p>  <p>First, we have to add an abstract method to <font face="Courier New">CommunityServer.Components.CommonDataProvider</font>:</p>  <p><font face="Courier New"><font color="#0000ff">public abstract long </font><font color="#000000">RegisterVisitHit(</font><font color="#0000ff">int </font><font color="#000000">CounterID)</font><font color="#0000ff">;</font></font></p>  <p>   <br>And then, the actual code on the <font face="Courier New">SqlDataProvider</font> class:</p>  <p><font face="Courier New"><font color="#0000ff">public override long </font><font color="#000000">RegisterVisitHit(</font><font color="#0000ff">int </font></font><font face="Courier New"><font color="#000000">CounterID)        <br>{         <br>    </font><font color="#0000ff">long </font><font color="#000000">hits </font><font color="#0000ff">= </font><font color="#800000">0</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>    using </font><font color="#000000">(SqlConnection connection </font><font color="#0000ff">= </font></font><font face="Courier New"><font color="#000000">GetSqlConnection())        <br>    {         <br>        SqlCommand command </font><font color="#0000ff">= new </font><font color="#000000">SqlCommand(</font><font color="#0000ff">this</font><font color="#000000">.databaseOwner + </font><font color="#808080">".cs_RegisterVisitHit"</font><font color="#000000">, connection)</font></font><font face="Courier New"><font color="#0000ff">;        <br>        </font><font color="#000000">SqlDataReader reader</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>        </font><font color="#000000">command.CommandType </font><font color="#0000ff">= </font><font color="#000000">CommandType.StoredProcedure</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>        </font><font color="#000000">command.Parameters.Add(</font><font color="#808080">"@CounterID"</font><font color="#000000">, SqlDbType.Int).Value </font><font color="#0000ff">= </font><font color="#000000">CounterID</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>        </font><font color="#000000">connection.Open()</font></font><font face="Courier New"><font color="#0000ff">;        <br>        </font><font color="#000000">reader </font><font color="#0000ff">= </font><font color="#000000">command.ExecuteReader()</font></font><font color="#0000ff" face="Courier New">;      <br>      <br>        if </font><font face="Courier New"><font color="#000000">(reader.Read())        <br>            hits </font><font color="#0000ff">= </font><font color="#000000">Convert.ToInt64(reader[</font><font color="#800000">0</font><font color="#000000">])</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>        </font><font color="#000000">connection.Close()</font></font><font color="#0000ff" face="Courier New">;      <br>    </font><font face="Courier New"><font color="#000000">}        <br>        <br>    </font><font color="#0000ff">return </font><font color="#000000">hits</font></font><font face="Courier New"><font color="#0000ff">;        <br></font><font color="#000000">}</font></font></p>  <p>As we can see on the previous code, the great thing of using CS Data Providers is that we forget about creating connections to the DB, just get the instance and execute whatever we want.</p>  <p>   <br>Now we need to implement the control. As we don't want anything special, rendering as a simple <font face="Courier New">LiteralControl</font> would be enough, and we can directly inherit from CS <font face="Courier New">WrappedContentBase</font> class (one of the basic control classes). Here is the code of the full component:</p>  <p><font face="Courier New"><font color="#0000ff">using </font><font color="#000000">System</font></font><font face="Courier New"><font color="#0000ff">;        <br>using </font><font color="#000000">System.Text</font></font><font face="Courier New"><font color="#0000ff">;        <br>using </font><font color="#000000">System.Web.UI</font></font><font face="Courier New"><font color="#0000ff">;        <br>using </font><font color="#000000">CommunityServer.Controls</font></font><font face="Courier New"><font color="#0000ff">;        <br>using </font><font color="#000000">CommunityServer.Components</font></font><font color="#0000ff" face="Courier New">;      <br>      <br>namespace </font><font face="Courier New"><font color="#000000">KartonesNet.Components        <br>{         <br>    </font><font color="#0000ff">public class </font></font><font face="Courier New"><font color="#000000">VisitsCounter : WrappedContentBase        <br>    {         <br>        </font><font color="#0000ff">private int </font><font color="#000000">counterID </font><font color="#0000ff">= </font><font color="#000000">-</font><font color="#800000">1</font></font><font face="Courier New"><font color="#0000ff">;        <br>        private int </font><font color="#000000">padding </font><font color="#0000ff">= </font><font color="#800000">0</font></font><font color="#0000ff" face="Courier New">;      <br>      <br>        public string </font><font color="#000000" face="Courier New">CounterID      <br>        {       <br>            </font><font color="#0000ff" face="Courier New">get      <br>            </font><font face="Courier New"><font color="#000000">{        <br>                </font><font color="#0000ff">return </font><font color="#000000">counterID.ToString()</font></font><font color="#0000ff" face="Courier New">;      <br>            </font><font color="#000000" face="Courier New">}      <br>            </font><font color="#0000ff" face="Courier New">set      <br>            </font><font face="Courier New"><font color="#000000">{        <br>                </font><font color="#0000ff">int </font><font color="#000000">val</font></font><font face="Courier New"><font color="#0000ff">;        <br>                if </font><font color="#000000">(</font><font color="#0000ff">int</font><font color="#000000">.TryParse(</font><font color="#0000ff">value</font><font color="#000000">, </font><font color="#0000ff">out </font></font><font face="Courier New"><font color="#000000">val))        <br>                {         <br>                    </font><font color="#0000ff">if </font><font color="#000000">(val &gt; -</font><font color="#800000">1</font></font><font face="Courier New"><font color="#000000">)        <br>                    {         <br>                        counterID </font><font color="#0000ff">= </font><font color="#000000">val</font></font><font color="#0000ff" face="Courier New">;      <br>                    </font><font face="Courier New"><font color="#000000">}        <br>                }         <br>            }         <br>        }         <br>        <br>        </font><font color="#0000ff">public string </font></font><font color="#000000" face="Courier New">Padding      <br>        {       <br>            </font><font color="#0000ff" face="Courier New">get      <br>            </font><font face="Courier New"><font color="#000000">{        <br>                </font><font color="#0000ff">return </font><font color="#000000">padding.ToString()</font></font><font color="#0000ff" face="Courier New">;      <br>            </font><font color="#000000" face="Courier New">}      <br>            </font><font color="#0000ff" face="Courier New">set      <br>            </font><font face="Courier New"><font color="#000000">{        <br>                </font><font color="#0000ff">int </font><font color="#000000">val</font></font><font face="Courier New"><font color="#0000ff">;        <br>                if </font><font color="#000000">(</font><font color="#0000ff">int</font><font color="#000000">.TryParse(</font><font color="#0000ff">value</font><font color="#000000">, </font><font color="#0000ff">out </font></font><font face="Courier New"><font color="#000000">val))        <br>                {         <br>                    </font><font color="#0000ff">if </font><font color="#000000">(val &gt; </font><font color="#800000">0</font></font><font face="Courier New"><font color="#000000">)        <br>                    {         <br>                        padding </font><font color="#0000ff">= </font><font color="#000000">val</font></font><font color="#0000ff" face="Courier New">;      <br>                    </font><font face="Courier New"><font color="#000000">}        <br>                }         <br>            }         <br>        }         <br>        <br>        </font><font color="#0000ff">protected override void </font></font><font face="Courier New"><font color="#000000">BindDefaultContent(Control ParentControl, IDataItemContainer DataItemContainer)        <br>        {         <br>            StringBuilder result </font><font color="#0000ff">= new </font><font color="#000000">StringBuilder(</font><font color="#800000">350</font><font color="#000000">)</font></font><font face="Courier New"><font color="#0000ff">;        <br>            long </font><font color="#000000">hits </font><font color="#0000ff">= </font><font color="#800000">0</font></font><font face="Courier New"><font color="#0000ff">;        <br>            string </font><font color="#000000">hitsString </font><font color="#0000ff">= string</font><font color="#000000">.Empty</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>            if </font><font color="#000000">(counterID &gt; -</font><font color="#800000">1</font></font><font face="Courier New"><font color="#000000">)        <br>            {         <br>                hits </font><font color="#0000ff">= </font><font color="#000000">CommonDataProvider.Instance().RegisterVisitHit(counterID)</font></font><font face="Courier New"><font color="#0000ff">;        <br>                </font><font color="#000000">hitsString </font><font color="#0000ff">= </font><font color="#000000">hits.ToString()</font></font><font face="Courier New"><font color="#0000ff">;        <br>        <br>                if </font><font color="#000000">(padding &gt; </font><font color="#800000">0</font></font><font face="Courier New"><font color="#000000">)        <br>                {         <br>                    </font><font color="#0000ff">while </font></font><font face="Courier New"><font color="#000000">(hitsString.Length &lt; padding)        <br>                    {         <br>                        hitsString </font><font color="#0000ff">= </font><font color="#000000">hitsString.Insert(</font><font color="#800000">0</font><font color="#000000">, </font><font color="#808080">"0"</font><font color="#000000">)</font></font><font color="#0000ff" face="Courier New">;      <br>                    </font><font face="Courier New"><font color="#000000">}        <br>                }         <br>                </font><font color="#0000ff">for </font><font color="#000000">(</font><font color="#0000ff">int </font><font color="#000000">digit </font><font color="#0000ff">= </font><font color="#800000">0</font><font color="#0000ff">; </font><font color="#000000">digit &lt; hitsString.Length</font><font color="#0000ff">; </font></font><font face="Courier New"><font color="#000000">digit++)        <br>                {         <br>                    result.Append(</font><font color="#808080">"&lt;span class='counter' id='counter"</font><font color="#000000">)</font></font><font face="Courier New"><font color="#0000ff">;        <br>                    </font><font color="#000000">result.Append(hitsString[digit])</font></font><font face="Courier New"><font color="#0000ff">;        <br>                    </font><font color="#000000">result.Append(</font><font color="#808080">"'&gt;&lt;/span&gt;"</font><font color="#000000">)</font></font><font color="#0000ff" face="Courier New">;      <br>                </font><font face="Courier New"><font color="#000000">}        <br>                ParentControl.Controls.Add(</font><font color="#0000ff">new </font><font color="#000000">LiteralControl(result.ToString()))</font></font><font color="#0000ff" face="Courier New">;      <br>            </font><font color="#000000" face="Courier New">}      <br>        }       <br>    }       <br>}</font></p>  <p>Some explanations and comments about the code:</p>  <ul>   <li>As you might have noticed, the public properties are strings. This is because the CS control wrapping converts them to either objects or strings, but with a proper parsing it's not any problem. </li>    <li>I added a padding property to add zeros at the left if we want, but the only requisite is the CounterID property. </li>    <li>We output directly inside a <font face="Courier New">StringBuilder</font> (remember to avoid string concatenations, even more if you can guess an approximate size of the <font face="Courier New">StringBuilder</font> capacity), and create &lt;span&gt; tags (I use it inside a &lt;li&gt; container, but otherwise you shold add too a &lt;div&gt; or similar container to group it). </li>    <li>We finally just add a <font face="Courier New">LiteralControl</font> to the parent control, but for more complex controls we can play here adding whatever we want. </li>    <li>CSS stuff is not handled there in purpose, delegating on each blog to implement the specific CSS (but removing unneeded complexity from the code). </li> </ul>  <p>The next step is to define the CSS stuff needed to display the counter digits (thanks here to my colleage <a href="http://twitter.com/carlostallon">Carlos Tallón</a> for the cross-browser help):</p>  <p><font color="#800000">span</font><font color="#0000ff">.</font><font color="#800000">counter      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background</font><font color="#0000ff">:url('../images/counter_bulleted.gif') no-repeat;</font><font color="#ff0000">      <br>    height</font><font color="#0000ff">: 20px;</font><font color="#ff0000">      <br>    overflow</font><font color="#0000ff">: hidden;</font><font color="#ff0000">      <br>    padding</font><font color="#0000ff">: 4px 0 6px 15px;</font><font color="#ff0000">      <br>    width</font><font color="#0000ff">: 15px      <br>}</font><font color="#800000">      <br>      <br></font><font color="#0000ff">#</font><font color="#800000">counter0      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:0px 0px      <br>}</font><font color="#800000">      <br>      <br></font><font color="#0000ff">#</font><font color="#800000">counter1      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-15px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter2      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-30px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter3      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-45px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter4      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-60px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter5      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-75px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter6      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-90px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter7      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-105px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter8      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-120px 0px      <br>}</font><font color="#800000">      <br></font><font color="#0000ff">#</font><font color="#800000">counter9      <br></font><font color="#0000ff">{</font><font color="#ff0000">      <br>    background-position</font><font color="#0000ff">:-135px 0px      <br>}</font></p>  <p>The counter class for spans defines the CSS sprite file, width and height (plus stuff for IE), and each counterX id defines the position of each digit inside the sprite file. I could (and probably should) have used classes for them too, but I wanted as few HTML code as possible (and I won't be doing any JS DOM stuff with them).</p>   <p>We're almost at the end, so now that we have everything, we need to plug our custom control, which means adding a new line under the <font face="Courier New">&lt;controls&gt;</font> section of <b>Web.Config</b>:</p>  <p><font color="#0000ff">&lt;</font><font color="#800000">add</font><font color="#ff0000"> tagPrefix</font><font color="#0000ff">="KartonesNet"</font><font color="#ff0000"> namespace</font><font color="#0000ff">="KartonesNet.Components"</font><font color="#ff0000"> assembly</font><font color="#0000ff">="KartonesNet"/&gt;</font></p>  <p>   <br>Finally, we can use the control on every page we want with a simple line of code:</p>  <p><font color="#0000ff">&lt;</font><font color="#800000">KartonesNet:VisitsCounter</font><font color="#ff0000"> ID</font><font color="#0000ff">="VisitsCounter"</font><font color="#ff0000"> CounterID</font><font color="#0000ff">="0"</font><font color="#ff0000"> runat</font><font color="#0000ff">="server"</font><font color="#ff0000"> </font><font color="#0000ff">/&gt;</font></p>  <p> </p>  <p>And that's all. You can see a live sample in the left column of this blog, or in other blogs of the community. By using CSS sprites we only make one http request call to get the counter digits so it's a small cost, and being plugged in into Community Server we can do anything (like logging Event entries).</p>  <p>I have in mind creating at least two more controls which don't need SQL code, so when I'm finished I'll add them to <a href="https://kartones.net/downloads/KartonesNet_CS2007_Addon_Pack.zip">my CS2007 Addon Pack</a>.</p>
