How to add Woopra to your blog

14. December 2011 19:30 by rtur.net in Blogging  //  Tags: , , ,   //   Comments (5)

woopraLots of people use Google Analytics to track user statistics on the blog. If you one of them, there is another tool you might be interesting in – something called “Woopra”. Although Analytics are cool, Woopra excels in real-time tracking – it literally shows what is going on your blog right now. Just take a look at the picture below – you can see how many people are browsing through your blog, what pages they on, searches used to bring them in, referring sites and more. And it is all real-time, you can see people coming and leaving. Pretty fun stuff.

woopra-dbrd

This will probably work with any blogging software, definitely works nicely with BlogEngine. Simply create account with Woopra, they will give you a tracking script similar to how Analytics does. It is recommended you add this script to your header section (admin/settings/custom code/html head section) but I suspect it will work if added to “tracking script” section just as well. There is size limitation for free version, 30,000 “actions” per month, but that should fit any casual blog easily. You can also buy premium if your blog suddenly goes into the stratosphere.

Comments (5) -

Dave @ Blogger Gadgets
Dave @ Blogger Gadgets
12/27/2011 8:40:28 PM #

Wow, this looks l;ike it has a lot of information. I currently use Who.amung.us, but they don't have as many features as this. Have you ever tried using the "Live" feature in Google Analytics? You should check it out, I think it is part of their latest beta.

rtur.net
rtur.net
12/29/2011 12:52:12 PM #

Yep, I use both. It doesn't seem to conflict with each other or slow things down, and it nice to have plan "b" :) Google offering more robust overall, but niche projects always try harder in their area and if real-time stats is your thing, you can actually prefer Woopra.

Nigel
Nigel
2/23/2012 4:05:45 AM #

Just some feed-back for you chap. "Just tale a look..." I think you meant to type "take" ;-)

Tutorial - Building NivoSlider Extension (Part 1)

Tutorial - Building NivoSlider Extension (Part 1)

9. September 2011 21:24 by rtur.net in Tutorials  //  Tags: ,   //   Comments (1)

Getting Started - Pure HTML Code

When I built theme for this site I used excellent Boldy theme as a template, and original theme has nice jQuery slider for a front page. I did not need it at the time, but later used it for another project and liked how simple and light-weight this slider is. In this tutorial we’ll transform NivoSlider into full-featured extension for BlogEngine.NET 2.5 and learn few tips and tricks along the way that will help you add useful functionality to your blog with no sweat.

To integrate component that uses another technology into your web application, first thing you need to understand is how it works client-side only so you can isolate pieces required by your project without PHP or whatever server-side code that component uses. Luckily, NivoSlider can be downloaded as stand-alone jQuery plugin (free under MIT license) with working plain HTML demo, so we don’t even have to do any investigative work figuring out what HTML/JavaScrip/CSS code we need to extract. Striped down for readability version looks something like this:

<div id="slider" class="nivoSlider">
	<a href="#"><img src="images/toystory.jpg" alt="" /></a>
	<a href="#"><img src="images/up.jpg" alt="" title="This is a caption" /></a>
</div>

<script type="text/javascript" src="scripts/jquery-1.6.1.min.js"></script>
<script type="text/javascript" src="../jquery.nivo.slider.pack.js"></script>
<script type="text/javascript">
$(window).load(function() {
	$('#slider').nivoSlider();
});
</script>

As you can see, on window load jQuery will attach nivoSlider() function to element with ID “slider”, and using this handler jQuery (add-on to be precise) will run the show.

Moving To Server Side

BlogEngine comes with jQuery pre-installed so we only need nivo.slider.pack add-on plus miscellanies  CSS/image files, nothing unusual. Here how we move them to BlogEngine application:

  • All sample images go to new folder ~/app_data/files/slider. BlogEngine protects files, including images, by restricting access and allowing only known types and locations. App_data folder is one of locations you can access from application with image handler without issues. Also app_data/files is default location to store blog files and images you normally upload through admin panel. And file manager added after 2.5 release also let you manage files in app_data/files. So it makes lots of sense to put our custom images there.
  • jquery.nivo.slider.pack.js go to ~/scripts folder. Scripts is a "special" folder in BlogEngine - any JavaScript you drop in there will be automatically loaded at runtime and added to posts and pages. We might make it more efficient later and only add script to the page that actually has a slider on it, but lets make it work first.
  • Style sheet nivo-slider.css and nivo-slider images folder with background images used in CSS all go under ~/styles. This is another special folder and it does the same thing for styles as "scripts" folder for JS.
  • Hard-coded HTML markup and JavaScript call from HTML page go to ~/themes/standard/site.master. Because we moved images, all image paths need to be updated.
  • Image source tags in HTML markup added to site.master now need to point to ~/app_data/files/slider. Because now we in server-side world, easiest way is to use "~" with "runat=server" to let path be resolved dynamically. Here what markup looks like now:
<div id="slider" class="nivoSlider">
	<a href="#">
		<img runat="server" src="~/image.axd?picture=Slider/sample1.png" alt="" />
	</a>
	<a href="">
		<img runat="server" src="~/image.axd?picture=Slider/sample2.png" title="sample two" alt="" />
	</a>
</div>
<script type="text/javascript">
	$(window).load(function () {
		$('#slider').nivoSlider();
	});
</script>

  
Backgrounds in CSS need to point to ~/styles/nivo-slider. Because ~/themes/standard/site.master is the base page, all images can be accessed from styles using relative URL reaching two steps up: "../../styles/nivo-slider"

#slider {
	position:relative;
	background:url(../../Styles/nivo-slider/loading.gif) no-repeat 50% 40%;
}

Here are files we used so far:

Scripts
	jquery.nivo.slider.pack.js
Styles
	nivo-slider
		*.*
	nivo-slider.css
themes
	Standard
		site.master

If you run plain vanilla BlogEngine.NET 2.5 with these changes in place, you'll get slider loaded and working as good as in HTML demo that comes with NivoSlider proper. Sure this is not very useful yet - you would need manually change markup in site.master each time you want to add or remove image from slide-show. But in the next step we'll build extension, user control and Razor helper that will make things lot more interesting and dynamic.

Comments (1) -

AnabelBr
AnabelBr
1/15/2012 7:36:58 PM #

Extension was updated with all features to the current version of 2.0

Please reference the first post in this thread to download the latest version...

Comments are closed
Optimizing ASP.NET Page Load Time

Optimizing ASP.NET Page Load Time

21. January 2012 14:42 by rtur.net in asp.net  //  Tags: , , , ,   //   Comments (0)

Let's start by creating new empty ASP.NET website and adding Default.aspx with minimal “hello world” markup. When you access your site and check it with profiler, you’ll see single get request for default page.

opt1

So far so good, right? Now let's push it a little further by adding couple images and references to 2 styles and 2 scripts. Just enough to make reasonably minimalistic test case. Let's check our new site again.

opt2

Now it looks more interesting. Here what is going on. Browser requests Default.aspx page, IIS constructs it with a help of ASP.NET and passes back HTML markup. Browser parses markup and, as it finds references to external resources, it issues additional requests to grab them. In total in this example we ended up having 7 requests: 1 for page itself, 2 for style sheets, 2 for JavaScripts and 2 for images. Out of the box, it’ll give us miserable 44 out of 100 points with Google speed test. Ouch.

opt5

So we clearly have a problem. Typical modern site often use lots of JavaScripts and style sheets. Number of requests can drag down performance significantly, and we want combine related resources whenever possible. We need a way to combine all styles together and likewise have a single JavaScript file, no matter how many scripts our application really uses.

Here is a plan: we intercept that first Default.aspx request, parse prepared HTML output before sending it to browser and replace all references to JS and CSS with reference to combine resource. So instead of:

<link rel="stylesheet" href="css1.css" type="text/css" />
<link rel="stylesheet" href="css2.css" type="text/css" />
<script src="js1.js" type="text/javascript"></script>
<script src="js2.js" type="text/javascript"></script>

We’ll get this:

<link rel="stylesheet" href="combined.css" type="text/css" />
<script src="combined.js" type="text/javascript"></script>

HTTP module can intercept requests using BeginRequest event handler. Below, code in "Application_BeginRequest" will be triggered when client requests any resource from your application, including .aspx pages. If it is a page, we want stream it back to the browser using our own custom filter.

using System;
using System.Web;

public class OptimizationModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        application.BeginRequest += (new EventHandler(Application_BeginRequest));
    }

    private void Application_BeginRequest(Object source, EventArgs e)
    {
        HttpApplication application = (HttpApplication)source;
        HttpContext context = application.Context;
        string fileExtension = VirtualPathUtility.GetExtension(context.Request.FilePath);

        if (fileExtension.Equals(".aspx"))
        {
            context.Response.Filter = new WebResourceFilter(context.Response.Filter);
        }
    }

    public void Dispose() { }
}

The concept of Response.Filter might be a little hard to grasp, good overview you can find here. Idea is to provide custom implementation of stream that will be passed down to browser instead of one built by ASP.NET engine. The only interesting part there is Write method, where we can get a hold on HTML about to be sent to client and modify it. In our case, we parse HTML looking for any JavaScript and CSS references, save them all into cache and replace them with reference to combined resources we’ll build on the fly later. Combined style reference we stick where we found first CSS style and script reference just before the "</body>" tag.

public override void Write(byte[] buffer, int offset, int count)
{
	var html = Encoding.UTF8.GetString(buffer, offset, count);

	var scriptMatches = Regex.Matches(html, @"\<script.+src=.+(\.js|\.axd).+(</script>|>)");
	var styleMatches = Regex.Matches(html, @"\<link[^>]+href=[^>]+(\.css)[^>]+>");

	if (scriptMatches.Count > 0)
	{
		foreach (Match match in scriptMatches)
		{
			html = html.Replace(match.Value, "");
			Cache.AddScript(match.Value);
		}
	}

	if (html.Contains("</body>"))
	{
		html = html.Insert(html.IndexOf("</body>"),
			"<script src=\"combined.js\" type=\"text/javascript\" defer=\"defer\" async=\"async\"></script>" +
			Environment.NewLine);
	}

	if (styleMatches.Count > 0)
	{
		int idx = 0;
		foreach (Match match in styleMatches)
		{
			idx = idx > 0 ? idx : html.IndexOf(match.Value);
			html = html.Replace(match.Value, "");
			Cache.AddStyle(match.Value);
		}

		html = html.Insert(idx, 
			"<link rel=\"stylesheet\" href=\"combined.css\" type=\"text/css\" />" +
			Environment.NewLine);
	}

	var outdata = Encoding.UTF8.GetBytes(html);
	this.sink.Write(outdata, 0, outdata.GetLength(0));
}

The cache implementation is dead simple, it only has two lists to keep scripts and styles removed from HTML markup.

using System;
using System.Collections.Generic;

public class Cache
{
    public static List<String> Scripts { get; set; }
    public static List<String> Styles { get; set; }

    public static void AddScript(string s)
    {
        if (Scripts == null)
            Scripts = new List<string>();

        if (!Scripts.Contains(s))
            Scripts.Add(s);
    }

    public static void AddStyle(string s)
    {
        if (Styles == null)
            Styles = new List<string>();

        if (!Styles.Contains(s))
            Styles.Add(s);
    }
}

When modified HTML will be sent to browser, it'll find "combined" references and issue requests to get them. Obviously, there are no physical files for IIS to send. But we can take care of it by plugging in HttpHandler that will listen for requests made to get .js and .css files and handle them appropriately. Here is JavaScript handler.

using System;
using System.Web;
using System.IO.Compression;

public class ScriptHandler : IHttpHandler
{
    public bool IsReusable { get { return false; } }

    public void ProcessRequest(HttpContext context)
    {
        if (Cache.Scripts != null && Cache.Scripts.Count > 0)
        {
            string s = "";
            foreach (var src in Cache.Scripts)
            {
                s += ScriptResolver.GetLocalScript(GetFileName(src));
            }
            s = Compressor.Minify(s);
            Compressor.Compress(context);
            context.Response.Write(s);
        }
    }

    string GetFileName(string src)
    {
        int start = src.IndexOf("src=") + 5;
        int end = src.IndexOf(".js") + 3;
        return src.Substring(start, end - start);
    }
}

As you can see, it looks up that cached list of removed .js references and goes through them, reading each .js file and combining all scripts into one big string. ScriptResolver just opens and reads file from disk, nothing interesting. Then using Response.Write handler will stream resulting string to the client instead of passing back not-existing "combined.js" file that browser asked for. Before sending, it will use Compressor to minify string and compress response. Our compressor is not too complicated:

using System;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.IO.Compression;

public class Compressor
{
    public static void Compress(HttpContext context)
    {
        if (IsEncodingAccepted("gzip"))
        {
            context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
            SetEncoding("gzip");
        }
        else if (IsEncodingAccepted("deflate"))
        {
            context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
            SetEncoding("deflate");
        }
    }

    public static string Minify(string body)
    {
        string[] lines = body.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
        StringBuilder emptyLines = new StringBuilder();
        foreach (string line in lines)
        {
            string s = line.Trim();
            if (s.Length > 0 && !s.StartsWith("//"))
                emptyLines.AppendLine(s.Trim());
        }

        body = emptyLines.ToString();

        // remove C styles comments
        body = Regex.Replace(body, "/\\*.*?\\*/", String.Empty, RegexOptions.Compiled | RegexOptions.Singleline);
        //// trim left
        body = Regex.Replace(body, "^\\s*", String.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
        //// trim right
        body = Regex.Replace(body, "\\s*[\\r\\n]", "\r\n", RegexOptions.Compiled | RegexOptions.ECMAScript);
        // remove whitespace beside of left curly braced
        body = Regex.Replace(body, "\\s*{\\s*", "{", RegexOptions.Compiled | RegexOptions.ECMAScript);
        // remove whitespace beside of coma
        body = Regex.Replace(body, "\\s*,\\s*", ",", RegexOptions.Compiled | RegexOptions.ECMAScript);
        // remove whitespace beside of semicolon
        body = Regex.Replace(body, "\\s*;\\s*", ";", RegexOptions.Compiled | RegexOptions.ECMAScript);
        // remove newline after keywords
        body = Regex.Replace(body, "\\r\\n(?<=\\b(abstract|boolean|break|byte|case|catch|char|class|const|continue|default|delete|do|double|else|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|var|void|while|with)\\r\\n)", " ", RegexOptions.Compiled | RegexOptions.ECMAScript);

        return body;
    }

    private static bool IsEncodingAccepted(string encoding)
    {
        return HttpContext.Current.Request.Headers["Accept-encoding"] != null && 
            HttpContext.Current.Request.Headers["Accept-encoding"].Contains(encoding);
    }

    private static void SetEncoding(string encoding)
    {
        HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
    }
}

It has home-brewed Minify function (demo replacement for Ajax.Minifier) to make scripts meaner and leaner and Compress method to "gzip" response that should shrink styles even more to save bandwidth. With all that taken care of, our end result should look something like this:

profiler-compressed

Here we are, going from 134.4KB to 51.2KB in size and saving browser two round-trips. No, this sure won’t score 100 as we didn't take care of image optimization, browser caching, setting appropriate HTTP headers etc - but that's ok and by the way even this bare-boned solution took me from 44 up to 86 points at page speed test. This code is intentionally simplistic and doesn’t take into account age cases, error handling etc. This is for clarity and to better represent concepts of combining, minifying and compressing in general. You can download project from link below and run it in Visual Web Developer, WebMatrix or as IIS application. It is very little code that is easy to follow.

JUST DON'T USE IT AS PRODUCTION-READY CODE!

Because it is not :) At least not yet, I'm working on possibly utilizing it for BlogEngine.NET and will publish more solid version later. Current code is for demo purposes only, it lacks tons of things you would absolutely require in your real-world application and makes way too many bold assumptions. But with all those things taking most of the space it would be a lot harder to understand workflow I really wanted to focus on.

Demo1.zip (57.82 kb)

Add comment

biuquoteimg
  • Comment
  • Preview
Loading

Laying out nested DIVs with CSS

Laying out nested DIVs with CSS

16. December 2011 12:49 by rtur.net in CSS  //  Tags: ,   //   Comments (1)

Tell me what you want, but CSS is twisted. Some simple basic tasks that should be no-brainer sometimes make you throw things and say words you later deeply regret. Usually people use IE6 as lightning rod, sadly even if you don't care about IE6 anymore CSS still will find ways to hurt you. Consider this simple scenario - I want DIV with some text and 3 little ones inside it alined right.

In a sane world, you would create a DIV, text element inside it, then 3 more DIVs with appropriate size and alignment. In CSS world, you should first "wrap your head around it" - meaning, make your brain just as twisted so it is in sync with framework. Then you understand that this is not how things "float". You need to do it all backwards! Is it hard? No. Intuitive? Hell no! May be, we need jQuery for CSS to make things to make sense. I think I even saw one somewhere on the internet, may be I'll go look around. But first I need to turn few pages in my old good "VB for dummies" book to calm down.

<html>
<head>
  <style>
	#widget { width: 300px; display: inline-table; background: #dedede; padding: 5px; }
	#widget div { float: right; width: 20px; margin-left: 2px; border: 1px solid #ccc; text-align: center; }
	#widget h2 { padding: 0; margin: 0; font-size: 16px; }
  </style>
</head>
<body>
  <div id="widget">	
	<div>3</div>
	<div>2</div>
	<div>1</div>
	<h2>Some pretty long title splitting on second line goes here.</h2>
  </div>
</body>
</html>

Comments (1) -

code geek
code geek
1/21/2012 4:05:43 PM #

"wrap your head around it" lol thats so true

Add comment

biuquoteimg
  • Comment
  • Preview
Loading

Using ELMAH with BlogEngine.NET

Using ELMAH with BlogEngine.NET

24. October 2009 14:16 by rtur.net in BlogEngine  //  Tags: ,   //   Comments (5)

elmah2

Few days ago I’ve noticed that “error.aspx” becomes quite popular destination on my site. What’s going on? I never run into errors, how do I know what others do to break in? Elmah to the rescue! This little utility specifically designed to run in the background and record any ASP.NET errors so you can review them later at your convenience. If you interested how it all works, check out this excellent article on MSDN, it goes in depth explaining technical details. I’ll focus here on how to set it up with BlogEngine (or any ASP.NET web forms application to that matter).

First, go get latest binaries from the project site. Unzip and move Elmah.dll to “bin” folder on your site. There are several versions included with download, for BE 1.5 I’m using Elmah\bin\net-2.0\Release\Elmah.dll.

Once you have DLL in the “bin” folder, you need to set up your application to use it. With download comes sample site, it has web.config with examples on how to use Elmah with different configurations (Elmah\samples\web.config). You can set it up to record errors to XML files or number of databases, including SQL server, VistaDB etc. I’m using XML data provider, so I’ll set it up to dump all errors to ~/app_data/elmah.

<configuration>
 
    <configSections>
        <sectionGroup name="elmah">
            <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
            <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
            <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
            <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
            <section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
        </sectionGroup>
    </configSections>
 
    <elmah>  
        <security allowRemoteAccess="yes" />
        <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="C:\inetpub\wwwroot\blog\app_data\elmah" />
    </elmah>
 
    <system.web>
        <httpModules>   
            <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
        </httpModules>
        <httpHandlers>
            <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
        </httpHandlers>
    </system.web>
 
    <!-- IIS 7 -->
    <system.webServer>
        <validation validateIntegratedModeConfiguration="false" />
        <modules>
            <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
        </modules>
        <handlers>
            <add name="Elmah" path="elmah.axd" verb="POST,GET,HEAD" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
        </handlers>
    </system.webServer>
 
    <location path="elmah.axd">
        <system.web>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </location>
 
</configuration>

As you can see, there are just few things you need to add to configuration to get Elmah started. With this in place, you’ll be able navigate to elmah.axd and see list of errors happened within your application and recorded by Elmah. This includes yellow screen of death and all server variables at the moment error occurred. Very helpful! In my case, it turned out that empty user profile caused Foaf handler to throw an error. Added a null check to condition, and 99% errors went away, no more worries.

elmah

If you find it useful, you can extend Elmah to be much smarter, save errors to database, purge after period of time, send email notifications and even twit your errors to the world (no, I’m not trying to be funny – it really has twitter provider).

Comments (5) -

Ben Amada
Ben Amada
10/27/2009 2:15:16 AM #

Good tip.  I think it's good we now have error.aspx in BE as a graceful way to handle/present unhandled exceptions.  I should check my logs to see how often "error.aspx" might be popping up.

Nicholas
Nicholas
11/27/2009 8:38:45 AM #

Great, I don't like having errors on my sites, this is very helpful

Bedding Duvet Covers
Bedding Duvet Covers
12/3/2009 6:26:53 AM #

Thanks for the article, i will try to implement Elmah with my blogengine site.

Hyomin
Hyomin
1/29/2010 6:35:04 AM #

Great fixed! I was looking for this too.
Thanks for saving my time a lot!

:)

Pravesh
Pravesh
3/2/2010 11:43:25 PM #

Great, Thanks for sharing. Helped me to fix errors on my blog.

Pingbacks and trackbacks (2)+

Comments are closed