Creating a global token engine

One thing I liked most in former Joomla times was the possibilty to create global tokens that could be used anywhere in a Post / Article. Think of an "add to cart" token that you can place in a blog post that expands to a button that adds a predefined product to your cart!

What's the goal ?

What I actually wanted to do was integrating the download counts of my open source projects on my website http://www.bitboxx.net. And it should be possible in every HTML-Module or blog or anywhere else.

 <li>BBNews:<strong> \[ bitboxx:downloads|http://bbnews.codeplex.com/stats?ddrange=-1,https://api.github.com/repos/weggetor/BBNews/releases] </strong>downloads</li>
 <li>BBImageHandler: <strong>\[ bitboxx:downloads|http://bbimagehandler.codeplex.com/stats?ddrange=-1,https://api.github.com/repos/weggetor/BBImagehandler/releases,https://api.github.com/repos/weggetor/BBImagehandler-8/releases] </strong>downloads</li>
 <li>BBQuery: <strong>\[ bitboxx:downloads|http://bbquery.codeplex.com/stats?ddrange=-1,https://api.github.com/repos/weggetor/BBQuery/releases]</strong> downloads</li>
 <li>BBContact: <strong>\[ bitboxx:downloads|http://bbcontact.codeplex.com/stats?ddrange=-1,https://api.github.com/repos/weggetor/BBContact/releases]</strong> downloads</li>
 <li>BBBooking: <strong>\[ bitboxx:downloads|http://bbbooking.codeplex.com/stats?ddrange=-1,https://api.github.com/repos/weggetor/BBBooking/releases]</strong> downloads</li>
 <li>BBImageStory:<strong> \[ bitboxx:downloads|https://api.github.com/repos/weggetor/BBImageStory/releases] </strong>downloads</li>

and that's how should look like in non-edit-mode:

  • BBNews: 3066 downloads
  • BBImageHandler: 1764 downloads
  • BBQuery: 138 downloads
  • BBContact: 259 downloads
  • BBBooking: 108 downloads
  • BBImageStory:67 downloads

HttpHandler vs HttpModule

Looking at the mechanics how to achieve this goal I found out that there are two different approaches that ASP.NET delivers:

HttpHandler - This one is called by a specific url and delivers anything that I create in the code. Could be an image, a javascript or even html as result. The dnnImagehandler works this way - calling http://www.domain.tld/dnnImagehandler.ashx and providing in the url parameters which image we want to generate and how this image should be altered (resize, crop etc.). Not working here.

HttpModule - Integrates in the calling chain of every hit to the website and has a lot of hooks to change some of the input (Request) or output (Response). This should be the way to go! I could hook in when the whole page is generated and ready to be delivered to the browser. The only thing that is to be done then is looking at the generated html and replace my tokens with whatever I want to include in the page.

Creating the project

In my case I wanted to create something that is independent to any module, so I created a new project. But if you are a module developer and you want to deliver tokens for your specific module (like the "Add to cart"-Button in a store module), its easy to add a class to your module and code the token replace and you are done !

The first thing to do is adding a class to the project that inherits from IHttpModule like this:

 public class BBTokenReplaceHttpModule : IHttpModule

To fulfill the interface definition, you have to implement two methods:

public void Dispose()
    //clean-up code here.

public void Init(HttpApplication context)
    context.PostRequestHandlerExecute += new EventHandler(OnPostRequestHandlerExecute);

The PostRequestHandlerExecute hook method is the one we need to accomplish our task. It chimes in right before the content is send back to the browser. In this method we do the following:

public void OnPostRequestHandlerExecute(Object source, EventArgs e)
    HttpApplication app = (HttpApplication) source;
    HttpResponse response = app.Response;
    if (response.ContentType == "text/html" && !Globals.IsEditMode())
        ResponseFilterStream filter = new ResponseFilterStream(response.Filter);
        filter.TransformStream += filter_TransformStream;
        response.Filter = filter;

First we check if the Response is of mimetype "text/html" because we do not want to replace our tokens in images or something else. And by adding a reference to the dotnetnuke.dll we are able to check if DNN is in editmode or not. This is very important because in editmode we want to see the token code and not the output of the token - otherwise it would be very complicated to edit the token later!

Cahnging the output in an HttpModule should be done by adding a filter to the resonse object. Rick Strahl from Westwind WebConnections wrote a blog post about this topic and provides a class that we can use to get help with this. See Capturing and Transforming ASPNET Output with ResponseFilter.

As a result I have to add this class to the project:


This class has an TransformStream event where we could add our token replace code:

MemoryStream filter_TransformStream(MemoryStream ms)
    Encoding encoding = HttpContext.Current.Response.ContentEncoding;

    string output = encoding.GetString(ms.ToArray());

    while (output.IndexOf(""));
        string prop, format;

        if (token.IndexOf('|') > -1)
            prop = token.Split('|')[0];
            format = token.Split('|')[1];
            output = output.Replace("", ReplaceToken(prop, format));
            prop = token;
            format = "";
            output = output.Replace("", ReplaceToken(prop, format));

    ms = new MemoryStream(output.Length);

    byte[] buffer = encoding.GetBytes(output);
    ms.Write(buffer, 0, buffer.Length);

    return ms;

This is simply converting the stream to a string, replacing the tokens that start with [ bitboxx:..]. I'm using the standard DNN token notation that uses [object:Property|format]. I tried to use the token replace engine of DNN but this results in destroying a lot of JSON code in the page and so I decided to look specifically for the "[ bitboxx:..]" thing and replace only these tokens by doing it the non regex way (I booked a course for regex on Udemy but I think I'll never get the head around this - maybe you do better ^^).

Implementing the replacement code

Now we have all architectural things in place an we can begin to implement the specific replacement code:

public string ReplaceToken(string strPropertyName, string strFormat)
    string cacheKey = "bbtoken_" + strPropertyName + "_" + strFormat;
    string result = (string) DataCache.GetCache(cacheKey);

    if (result != null)
        return result;

    result = "";
    switch (strPropertyName.ToLower())
        case "downloads":
            int cnt = 0;
            foreach (string url in strFormat.Split(','))
                cnt += Download.DownloadCount(url);
            result = cnt.ToString();

    return result;

Here we get our property name and the format string into our method. First thing to do is looking at the cache if our token is inside. If yes we return this so we do not need to generate it with every call which could result in slowing your website down!

In my specific case, the format parameter contains a comma seperated list of urls which point to the different sites with my open source project releases. I'm using a list because some projects are on codeplex AND github, and so I had to sum the counts from the different pages.

To retrieve the counts from codeplex I make use of a nuget library that provides the ability to quest html-pages with Linq (see: HtmlAgilityPack). On Github this task is easier because the have an API for this that responses with JSON.

public static class Download
    public static int DownloadCount(string url)
        int downloads = 0;
        if (url.Contains("codeplex"))
            var webGet = new HtmlWeb();
            var document = webGet.Load(url);
            string dl = "-1";
            var nodes = document.DocumentNode.SelectNodes("//table[@id='DownloadsSummary']");
            var cell = (from tag in nodes.Descendants()
                        where tag.Name == "strong" && tag.InnerText.IndexOf("downloads") > -1
                        select tag).FirstOrDefault();

            dl = cell.InnerText.Replace("downloads", "").Replace("\r", "").Replace("\n", "").Trim();
            downloads = Convert.ToInt32(dl);
            using (WebClient wc = new WebClient())
                wc.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
                var jsstring = wc.DownloadString(url);
                JArray releases = (JArray)JsonConvert.DeserializeObject(jsstring);
                foreach (JObject release in releases)
                    downloads += Convert.ToInt32(release["assets"][0]["download_count"].ToString());

        return downloads;

Adding handler to the DNN installation

In order to make our TokenReplaceHttpmodule run in DNN , we have to do two more things:

  • copy the compiled DLL to the bin folder of your DNN installation
  • add the following line to web.config in the system.Webserver/modules path:
<add name="BBTokenReplace" type="Bitboxx.HttpModules.BBTokenReplaceHttpModule" preCondition="managedHandler"/>

Thanks for listening ! Comments welcome!

Total: 14 Comment(s)
First we check if the Response is of mimetype "text/html" because we do not want http://chinesebuffetnearmenow.net/">restaurants near me open now to replace our tokens in images or something else.
Saturday, February 10, 2018 · reply ·
A few applications may utilize summons, which is incorporated of course in most Compute Engine pictures. These devices naturally perceive an example's service account and important consents allowed to the service account. In particular, on the off chance that you give the right parts to the service account, you can utilize the http://www.essayempire.co.uk/essay-writing-service tools from your instances.
Monday, February 12, 2018 · reply ·
I backtracked with my desk, and sent my programmer An email expressing that those report card system might have been not working https://helpwithdissertations.co.uk, should "fix" it In this way it might print the rundown judgment page indicating tallies for the 5 classifications. Programmer originated on me and said "There is no code. You would say will include something. ".
Friday, February 23, 2018 · reply ·
WordAI Spinning Service
I appreciate your efforts in preparing this post. I really like your blog articles. http://www.contentastic.com/wordai-manual-article-spinning-12031">WordAI Spinning Service
Tuesday, April 3, 2018 · reply ·
Econometrics Homework Help
Amazing article thanks or sharing.. Econometrics Homework Help[/URL]
Tuesday, April 3, 2018 · reply ·
Econometrics Homework Help
Dissertation Guidance Provides quality Online Dissertation Help for students. https://economicskey.com/econometrics-assignment-help-9236">Econometrics Homework Help
Tuesday, April 3, 2018 · reply ·
Finance Homework and Project
This a good way to appreciate the teacher as they put their efforts to train students. UK dissertation Writers appreciates the teachers.https://www.finance-assignments.com/financial-management-an-overview-3067">Finance Homework and Project
Tuesday, April 3, 2018 · reply ·
Nested switch Statements Java Homework
The leading assignment help UK firm offers state of the art services to its clients with a promise of delivering all the required work well within the deadline. https://javahelponline.com/nested-switch-statements-3080">Nested switch Statements Java Homework
Tuesday, April 3, 2018 · reply ·
Robust Control Help
We also share some information about our business https://www.matlabhelp.com/robust-control-11131">Robust Control Help
Tuesday, April 3, 2018 · reply ·
Stress Management Homework Help
I genuinely appreciated understanding it. Sitting tight for some more incredible articles like this from you in the nearing days http://www.medassignments.com/stress-management-9088">Stress Management Homework Help
Tuesday, April 3, 2018 · reply ·
GGPlot In R Homework Help
This is really great work. Thank you for sharing such a useful information here in the blog. https://rprogramminghelp.xyz/ggplot-in-r-assignment-help-15444">GGPlot In R Homework Help
Tuesday, April 3, 2018 · reply ·
Geographic Factors Assignment Help
Those who come to read your article will find lots of helpful and informative tips https://www.sociologyassignments.com/geographic-factors-3121">Geographic Factors Assignment Help
Tuesday, April 3, 2018 · reply ·
Katie Bell
As per https://www.courseworkonline.co.uk In purchase for your component to operate, create sure IIS is designed to move all demands (not just .aspx requests) to the .NET framework
Thursday, April 12, 2018 · reply ·


Torsten WeggenMy name is Torsten Weggen and I am CEO of indisoftware GmbH in Hanover, Germany. I'm into DNN since 2008. Before this, I did a lot of desktop stuff mainly coded with Visual Foxpro (see http://www.auktionsbuddy.de). 

I'm programmer, husband, father + born in 1965.

Please feel free to contact me if you have questions.

Latest Posts

DNN module development with AngularJS (Part 6)
12/16/2016 7:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 5)
12/16/2016 6:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 4)
12/16/2016 5:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 3)
12/16/2016 4:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 2)
12/16/2016 3:00 AM | Torsten Weggen
DNN module development with AngularJS (Part 1)
12/15/2016 7:19 AM | Torsten Weggen
Blogging in DNN with Markdown Monster by Rick Strahl
11/27/2016 1:14 PM | Torsten Weggen
Creating a global token engine
11/18/2016 10:25 AM | Torsten Weggen
DnnImagehandler - Hot or not ?
2/21/2015 11:52 PM | Torsten Weggen
Rapid Module Development Part 2 - The multilanguage thing…
4/7/2014 7:32 PM | Torsten Weggen

My Twitter

Torsten Weggen 3/1/2018

Yeah! Booked my journey to #DNNConnect2018 ! Will be great to meet old friends again and have some Guiness together! #DNNCMS rulez !

Torsten Weggen 2/17/2018

Yess! My first commercial hybrid mobile App build with #ionic + #Angular and connected with #DNNCMS is online now https://t.co/61nDXYXu14

Torsten Weggen 1/13/2018

@TChailland Hi Thomas - no problem! Have a nice vacation and let me know when you're back.