Pages

Advertisement

Saturday, September 1, 2007

11 Techniques to Increase Page Views on Your Blog

 

Of course more page views may or may not be what you want from your blog. At least one commenter on the previous post noted that they are happy with a low page view count because it could mean people are leaving their blog by clicking on an advertisement and thereby earning them money. While there could be some truth in this observation and I’m not adverse to this happening on my blogs - I’m also interested in building blogs that people find interesting and useful and one of the many measures of this can be page views. Of course to get back to the money thing again - those of you running impression based ads will be interested in increased page views also.

Having said that - IF you’re interested in increasing the number of pages that your average reader reads, here are a few suggestions that might help:

1. Highlight Related Posts - one of the more common practices of bloggers to encourage readers to read multiple pages on their blogs is to to highlight related posts at the end of your article. You’ll notice that i presently have a list of 5 posts at the end of each individual page that suggests other posts that readers might find useful This list is generated by a WordPress PlugIn. Those of you using other blog platforms might find similar plugins for your own system or might like to manually suggest related articles at the end of your posts.

2. Interlink within Posts - a similar but perhaps more effective technique is to highlight relevant posts within the content of your posts. If you’re writing a post that mentions something similar to what you’ve written before simply link to your previous post from within your article. For example I’ve written about this technique previously in a post on increasing the longevity of key posts.

3. Highlight Key Posts and Categories in your Blog’s Hotspots - I’ve often mentioned that the hottest posts on this blog are those highlighted in my top three menus. Specifically it is those in the top left hand box at the top of this page that are always at the top of my most read post statistics. Depending upon the goals of your blog - you may wish to fill your blog’s hotspots with ads or affiliate programs - or you may want to highlight key posts that are central to your blog and which will hook readers into what your blog is about (thereby increasing page views). Highlighting your category pages is also another similarly useful technique to encourage your readers to find more posts on the same topic. To explicitly name what your category is can also be useful. ie rather than just having the category name at the end of the post - try something like ‘read more posts like this in our ((insert category name)) category’ or ‘filed under ((insert category name))’ etc.

4. Compilation Pages - Extending the previous idea about highlighting key posts you may wish to use posts in these positions that sneeze readers not just to one post on your blog but many. The best example of this on ProBlogger is my Top 20 Posts at ProBlogger post which is in my top left hand menu. This post, as the name suggests, suggests 20 posts on my blog that readers might like to read. I know that this is a post with immense power on this blog and that many first time readers use it to bounce into all corners of my blog. One or two new readers have fed back to me that this page and the pages that it linked to was the reason that they became hooked on ProBlogger. Every post they read added to the chances that they would become loyal readers.

5. Series - While you need to be a bit careful with writing series of posts over periods of time, they are a great way to keep readers coming back and once they are complete to have them surf through multiple pages on your blog. The most popular series on this blog is my Adsense for Bloggers series which leads readers through 8 posts. I know many readers progress through this series because I occasionally get a series of comments from a reader who is obviously progressing through it - 8 comments over 30 minutes or so as they comment on each post. Don’t just do a series for the sake of increasing page views of course - this can really frustrate readers but use them on longer posts or when you’re genuinely wanting to interact with a larger topic over time.

6. Excerpts on Front Pages - I know there are a segment of ProBlogger readers that detest seeing excerpts (extended entry feature) on blog front pages and are very cynical that it’s just a ploy to get more page views. While I personally like using excerpts on front pages it is not about page views for me (although I guess it is a side benefit of it). Personally using excerpts in this way is more about keeping my front page manageable and highlighting multiple posts on the front page. ie if a reader can come to my blog and see not only the last post but the title of the second and maybe even the third post then they are more likely to explore more than just the last thing you’ve written. I tend to only use the extended entry feature on longer articles and allow shorter ones of a paragraph or two go up on the main page - unless I either forget or see the post as an important one.

7. Excerpts in RSS - Once again there is always debate over this topic of full or partial RSS feeds. I know some bloggers main purpose in partial feeds is to get bloggers directly onto their blog - thereby increasing their impression/page view count. While this is certainly a benefit of partial feeds it is not my own reason for using them. Rather I use them for copyright protection and to stop people scraping my full content onto their site’s via RSS. Whatever reason you choose to use partial/excerpt feeds - you should also realize that doing so will cause some readers to unsubscribe to your blog completely. I know in going only with partial feeds that there are some other bloggers who refuse to visit my blog - this is a cost/benefit scenario that individual bloggers need to weigh up.

8. Enable links in RSS Feeds - Another way that I know a couple of bloggers use to get RSS readers to actually surf to their blogs is to enable the ability to post html/links in their RSS and then using links to previous posts in their blog, especially in the first paragraph or two of their posts. This is not a technique I’ve tried but I know of one blogger who swears by it and says it significantly impacted the number of visitors to his blog from RSS as well as the number of pages that they viewed.

9. Search Function - most blog blog platforms have the ability to use a search feature on your blog which enables users to search your blog for keywords. This feature obviously helps your readers to locate other posts on your site and as a result increases the potential for a multiple page view visit.

10. Build an Interactive Blog - one way to get readers coming back to your blog many times over a day is to have a blog that people want to interact with. I know some ProBlogger readers visit this site at least 10 times per day just so that they can engage in the conversation that happens in comments. Since I added the ’subscribe to comments’ feature on this blog I’ve noticed some readers coming even more than normal - this can only be increasing page view levels as people return throughout the day.

11. Quality Content -This should go without saying but needs to be reinforced. Obviously if you write quality content your readers will want more of the same. Useful, original and interesting content should leave your readers hungering for more. Work on the quality of your blog and you’ll find that things like traffic levels and the numbers of pages being read should look after themselves and be on the rise.

There are no doubt other techniques for increasing page views. I’ve heard bloggers who swear by writing loads of posts per day to encourage readers to come back numerous times per day as one such technique - but I’d love to hear your experiences in comments below.

Friday, August 31, 2007

Security - Lifehacker

 

Secure your laptop with Laptop Alarm

Windows only: Next time you leave your laptop unattended, turn on freeware program Laptop Alarm. Laptop Alarm sets off an alarm to alert you any time someone tries to log off, shut down, or disconnect your power supply or USB mouse without entering your password. Laptop Alarm is similar to Mac-only alarm iAlertU but—let's be honest—with much less pizazz. (iAlertU is motion-sensing, for chrissake!) Either way, neither software is foolproof by any means. Laptop Alarm won't prevent anyone from grabbing your computer and running, but at the very least it might sound off the alarm in enough time to give you a fighting chance to chase down the bad guy. (The only true method of theft prevention is never leaving your laptop alone.) Laptop Alarm is freeware, Windows only. For more advanced laptop theft fun, check out LaptopLock.

Security - Lifehacker

Thursday, August 30, 2007

ASP.NET Tip: Control the Layout of Your Input Forms

Although many of your input forms will have a fixed number of fields, in some cases you'll need to vary the number and configuration of your controls. This article demonstrates how to control the layout of your form using a Repeater control and the ItemDataBound event to set various attributes on the controls.

The example form has a simple table that includes the following Repeater control:

<asp:Repeater ID="rptFields" runat="server">
<ItemTemplate>
<tr>
<td align="right" ID="tdPrompt" runat="server"
nowrap><%# Eval("Prompt") %>:</td>
<td><asp:TextBox ID="txtValue" runat="server" />
<input type="hidden" id="hdnFieldID"
value='<%# Eval("pkFieldID") %>'
runat="server" /></td>
</tr>
</ItemTemplate>
</asp:Repeater>

Behind the scenes, a table called FormFields (or something appropriate) has these fields:

pkFieldID
Primary Key, integer

Prompt
String, contains the prompt to prefix the field with

MaxLength
Integer, indicates maximum number of characters for the field

IsRequired
Bit, indicates whether field is required

In the initial Load event for the page, I retrieve a DataTable containing the fields from the database, and then I bind it to the Repeater like this:

rptFields.DataSource = fieldsDataTable;
rptFields.DataBind();
The key event handler to make this work is the ItemDataBound event handler. It looks something like this:

void rptParameters_ItemDataBound(object sender,
RepeaterItemEventArgs e)
{
if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType
!= ListItemType.AlternatingItem)
return;

DataRow dr = ((DataRowView)e.Item.DataItem).Row;
HtmlTableCell td = (HtmlTableCell)e.Item.FindControl("tdPrompt");

if (Convert.ToBoolean(dr["IsRequired"]))
td.Attributes["class"] = "popupreq";
else
td.Attributes["class"] = "popuptext";

TextBox txt = (TextBox)e.Item.FindControl("txtValue");
txt.MaxLength = Convert.ToInt32(dr["MaxLength"]);

}

The first thing you do is make sure that you are looking at an "Item" or "Alternating Item" template. This is because the ItemDataBound event handler is called for the Header and Footer templates, which you don't care about for this code. You then get the DataRow for the current item being bound. Next, you find the table cell that contains the prompt text and change the style to either 'popupreq' or 'popuptext'. In this example, the popupreq style uses bold face, while popuptext uses a non-bold font.

The next step is to find the TextBox control and configure the maximum length of the field. The hidden field in the HTML portion of the page will be populated automatically with the primary key of the field record, which will be used in the back-end code to properly store the data to the database.

When the user hits the Submit button, ASP.NET keeps the values that the user enters into each field and makes them available to the PostBack code. Reading the values is similar to setting them in ItemDataBound, as this snippet shows:

foreach (RepeaterItem ri in rptParameters.Items)
{
hdn = (HtmlInputHidden)ri.FindControl("hdnFieldID");
txt = (TextBox)ri.FindControl("txtValue");
// Read hdn.Value or txt.Text for persistence code
}

The reason you stored the field ID is because this data will generally need to be put into a table related to the data being edited. The easiest way I've found to do this is to fill the hidden field with the field's number, which you can use later in a stored procedure or other method to save the field's data to the database.

ASP.NET Tip: Exporting Data to Excel

A common request of users is to be able to download data for use in Microsoft Excel. This is actually fairly easy to accomplish without a lot of extra work. I do most of my database work with DataTable objects, as I don't need the overhead of a DataSet. I also have a Database wrapper class that is able to easily retrieve data from a SQL Server database. Once I use my function to get my DataTable, I can start exporting the data. For this example, however, I'll use the traditional SqlDataAdapter method to retrieve the data initially.

There are a few tricks to making this work. First, you have to create an ASPX page with no HTML content in it. The only thing that should be in the ASPX page is the Page directive at the top. The second trick is to clear the existing content and send down a new content type. For Excel, you use a content type of application/vnd.ms-excel. This tells your Web browser to expect something that can be handled within Excel. The third trick is to send down the data tab-delimited per column and a newline at the end of each row. After that, Excel does the rest of the work for you.

Here's the code:

protected void Page_Load(object sender, EventArgs e)
{
SqlConnection cn = new SqlConnection("yourconnectionstring");
cn.Open();
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", cn);
DataTable dt = new DataTable();
da.Fill(dt);
cn.Close();

Response.Clear();
Response.ContentType = "application/vnd.ms-excel";
string sep = "";
foreach (DataColumn dc in dt.Columns)
{
Response.Write(sep + dc.ColumnName);
sep = "\t";
}
Response.Write("\n");

int i;
foreach (DataRow dr in dt.Rows)
{
sep = "";
for (i = 0; i < dt.Columns.Count; i++)
{
Response.Write(sep + dr[i].ToString());
sep = "\t";
}
Response.Write("\n");
}

}

After I open my connection and retrieve my data, I clear the output buffer and send down a new content type, since the default type of text/HTML isn't appropriate in this case. I then loop through the columns and write out the column names, separated by tabs. At the end of the field list, I send down a newline character.

I then loop through the rows of the table and, within each row, loop through the fields. Each field value is printed out in its default format under the appropriate column header. If you have data types, such as Booleans, that should be printed in a different format, you can look at the dt.Columns collection to help determine which data type each field is and use an appropriate conversion function.

The result of running this page is a prompt to open or save the Excel spreadsheet. You can use this code with any query and any database connection. Once you've got the content cleared and the content type set, Excel does the hard work of formatting for you.

Redirecting Configuration with a Custom Provider

A common issue with Web applications is that applications are managed in various environments. Aside from development environments, you may have test servers, staging servers, live production servers, and potentially warm backup servers. If you store selected Web configuration sections in a central location (such as a central file share or a central configuration database), you have a more manageable solution and, depending on how you implement this, a more secure solution as well.

With ASP.NET 2.0, you can write a custom protected configuration provider that determines information about the current server and the currently running application. A custom provider can reach out to a central repository of configuration information and return the appropriate configuration information. When you have a group of individuals who manage the configuration data for live production servers, it is probably much easier to have such a group manage updates to a single database as opposed to synchronizing configuration files across multiple machines.

Implementing a custom protected configuration provider requires you to derive from the System.Configuration.ProtectedConfigurationProvider class. As you can see, the class signature is very basic:

    public abstract class ProtectedConfigurationProvider : ProviderBase
{
public abstract XmlNode Encrypt(XmlNode node);
public abstract XmlNode Decrypt(XmlNode encryptedNode);
}

For a sample provider that demonstrates redirecting configuration to a database, you implement only the Decrypt method because this is the method used at runtime to return configuration data to the caller. If you store more complex data inside your protected configuration format, implementing the Encrypt method will make life easier when storing configuration sections in a custom data store.

First look at what a "protected" configuration section in a web.config file will look like using the custom provider:

<membership configProtectionProvider="CustomDatabaseProvider">

<EncryptedData>
<sectionInfo name="membership" />
</EncryptedData>
</membership>

The <membership /> section references a protected configuration provider. Instead of the actual definition of the <membership /> section though, the <EncryptedData /> element is common to all protected configuration sections. However, what is enclosed within this element is determined by each protected configuration provider. In this case, to keep the sample provider very simple, the protected data consists of only a single element: a <sectionInfo /> element.

Unlike protected configuration providers that blindly encrypt and decrypt data, this sample provider needs to know the actual configuration section that is being requested. The custom provider needs to know what section is really being requested because its purpose is to store configuration data in a database for any arbitrary configuration section. The name attribute within the <sectionInfo /> element gives the custom provider the necessary information. Although this is just a basic example of what you can place with <EncryptedData />, you can encapsulate any kind of complex data your configuration provider may need within the XML.

The custom provider will store configuration sections in a database, keying off of a combination of the application's virtual path and the configuration section. The database schema that follows shows the table structure for storing this:

create table ConfigurationData (
ApplicationName nvarchar(256) NOT NULL,
SectionName nvarchar(150) NOT NULL,
SectionData ntext
)
go
alter table ConfigurationData
add constraint PKConfigurationData
PRIMARY KEY (ApplicationName,SectionName)
Go

Retrieving this information will similarly be very basic with just a single stored procedure pulling back the SectionData column that contains the raw text of the requested configuration section:

create procedure RetrieveConfigurationSection
@pApplicationName nvarchar(256),
@pSectionName nvarchar(256)
as
select SectionData
from ConfigurationData
where ApplicationName = @pApplicationName
and SectionName = @pSectionName
go

Because the custom protected configuration provider needs to connect to a database, a connection string must be included within the definition of the provider.

<configProtectedData>
<providers>
<add name="CustomDatabaseProvider"
type="CustomProviders.DatabaseProtectedConfigProvider,
CustomProviders"
connectionStringName="ConfigurationDatabase"
/>
</providers>
</configProtectedData>

The sample provider configuration looks similar to the configurations for the RSA and DPAPI protected configuration providers that ship in the 2.0 Framework. In this case, however, the custom provider requires a connectionStringName element so that it knows which database and database server to connect to. The value of this attribute is simply a reference to a named connection string in the <connectionStrings /> section, as shown here:

<connectionStrings>
<add name="ConfigurationDatabase"
connectionString="server=.;Integrated _
Security=true;database=CustomProtectedConfiguration"/>
</connectionStrings>

When creating your own custom providers, you have the freedom to place any provider-specific information you deem necessary in the provider's <add /> element.

Now that you have seen the data structure and configuration related information, take a look at the code for the custom provider. Because a protected configuration provider ultimately derives from System.Configuration.Provider.ProviderBase, the custom provider can override portions of ProviderBase as well as ProtectedConfigurationProvider. The custom provider overrides ProviderBase.Initialize so that the provider can retrieve the connection string from configuration:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Configuration.Provider;
using System.Web;
using System.Web.Hosting;
using System.Web.Configuration;
using System.Xml;
namespace CustomProviders
{
public class
DatabaseProtectedConfigProvider : ProtectedConfigurationProvider
{
private string connectionString;
public DatabaseProtectedConfigProvider() { }
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
string connectionStringName = config["connectionStringName"];
if (String.IsNullOrEmpty(connectionStringName))
throw new ProviderException("You must specify " +
"connectionStringName in the provider configuration");
connectionString =
WebConfigurationManager.ConnectionStrings[connectionStringName] _
.ConnectionString;
if (String.IsNullOrEmpty(connectionString))
throw new ProviderException("The connection string " +
"could not be found in <connectionString />.");
config.Remove("connectionStringName");
base.Initialize(name, config);
}
//Remainder of provider implementation
}
}

The processing inside of the Initialize method performs a few sanity checks to ensure that the connectionStringName attribute was specified in the provider's <add /> element, and that furthermore the name actually points at a valid connection string. After the connection string is obtained from the ConnectionStrings collection, it is cached internally in a private variable.

Of course, the interesting part of the provider is its implementation of the Decrypt method:

public override XmlNode Decrypt(XmlNode encryptedNode)
{
//Application name
string applicationName = HostingEnvironment.ApplicationVirtualPath;
XmlNode xn =
encryptedNode.SelectSingleNode("/EncryptedData/sectionInfo");
//Determine the configuration section to retrieve from the database
string sectionName = xn.Attributes["name"].Value;
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand cmd =
new SqlCommand("RetrieveConfigurationSection", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p1 =
new SqlParameter("@pApplicationName", applicationName);
SqlParameter p2 = new SqlParameter("@pSectionName", sectionName);
cmd.Parameters.AddRange(new SqlParameter[] { p1, p2 });
conn.Open();
string rawConfigText = (string)cmd.ExecuteScalar();
conn.Close();
//Convert the string information from the database into an XmlNode
XmlDocument xd = new XmlDocument();
xd.LoadXml(rawConfigText);
return xd.DocumentElement;
}
}

The Decrypt method's purpose is take information about the current application and information available from the <sectionInfo /> element and use it to retrieve the correct configuration data from the database.

The provider determines the correct application name by using the System.Web.Hosting.HostingEnvironment class to determine the current application's virtual path. The name of the configuration section to retrieve is determined by parsing the <EncryptedData /> section to get to the name attribute of the custom <sectionInfo /> element. With these pieces of data the provider connects to the database using the connection string supplied by the provider's configuration section.

The configuration data stored in the database is just the raw XML fragment for a given configuration section. For this example, which stores a <membership /> section in the database, the database table just contains the text of the section's definition taken from machine.config stored in an ntext field in SQL Server (i.e. just copy the <membership /> section from machine.config and insert it into the sample database table's SectionData column). Because protected configuration providers work in terms of XmlNode instances, and not raw strings, the provider converts the raw text in the database back into an XmlDocument, which can then be subsequently returned as an XmlNode instance. Because the data in the database is well-formed XML, the provider can just return the DocumentElement for the XmlDocument.

What is really powerful about custom protected configuration providers is that the Framework's configuration system is oblivious to how the configuration information is obtained. For example the following configuration code works unchanged even though the <membership /> section is now being retrieved from a database.

MembershipSection ms =
(MembershipSection)ConfigurationManager.GetSection(
"system.web/membership");

This is exactly what you would want from protected configuration. Nothing in the application code changes despite the fact that now the configuration section is stored remotely in a database as opposed to locally on the file system.

One caveat to keep in mind with custom protected configuration providers is that after the data is physically stored outside of a configuration file, ASP.NET is no longer able to automatically trigger an app-domain restart whenever the configuration data changes. With the built-in RSA and DPAPI configuration providers, this isn't an issue because the encrypted text is still stored in web.config and machine.config files. ASP.NET listens for change notifications and triggers an app-domain restart in the event the encrypted sections in any of these files change.

However, ASP.NET does not have a facility to trigger changes based on protected configuration data stored in other locations. For this reason, if you do write a custom provider along the lines of the sample provider, you need to incorporate operational procedures that force app-domains to recycle whenever you update configuration data stored in locations other than the standard file-based configuration files.