X
dnn.blog.
Back

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.

<ul>
 <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>
</ul>

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:

ResponseFilterStream.cs

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));
        }
        else
        {
            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();
            break;

    }
    DataCache.SetCache(cacheKey,result);
    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);
        }
        else
        {
            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!

Back
Total: 0 Comment(s)

about.me.

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 7/22/2017

RT @CBPSC: Ventrian modules now free on Github! https://t.co/aKLeZawVHN #DNNCMS https://t.co/x6mhie382I

Torsten Weggen 5/24/2017

RT @WaldkauzFolk: ++ Waldkauz goes W:O:A 2017 ++ Der Wahnsinn - Wir sind in diesem Jahr beim großartigen Wacken Open Air mit... https://t.…

Torsten Weggen 4/25/2017

RT @WaldkauzFolk: "A magical pagan folk release getting a lot of inspiration of myth, legends and tales from countries all over... https://…