Writing extensions for BlogEngine 1.4 (part 3)

27. July 2008 15:55 by rtur.net in Tutorials  //  Tags: , ,   //   Comments (12)

Lets say we want to write an extension to track user activities on our site. Blogger should be able to set basic settings, for example choose to track  posts, pages or both. Then every time user requests post or page, we increment corresponding counter by one. Look at the picture above. Its clear that we need two sets of settings: one for post/page options and another to list user activity.

ext14-3-1

In 1.4, extension has settings collection so you can have multiple settings per extension – lets use it. Start by defining both ExtensionSettings objects as in the code:

static protected ExtensionSettings _options = null;
static protected ExtensionSettings _locations = null;

When initialize first settings object, pass extension name (or “this” – class name will be used) to the constructor. Name second settings whatever  you like, in this case it will be “Locations”. I our example, extension object will have collection of settings, first with name Example_04 and second with name Locations. First settings set as scalar (IsScalar = true), second settings are tabular, which is default. In that second tabular settings we don’t want to show “Add” and “Edit” buttons – the data generated automatically by extension and should not be added/edited manually by blogger. This is another new feature in 1.4 – you can display only what you want, even hiding whole settings section in the admin interface.

private void InitSettings()
{
  ExtensionSettings settings = new ExtensionSettings(this);
  settings.IsScalar = true;
  ...
  ExtensionSettings locations = new ExtensionSettings("Locations");
  ...
  locations.ShowAdd = false;
  locations.ShowEdit = false;   
  ...
}

Now we need to subscribe to Post.Serving and Page.Serving events, passing name of the function to execute when event fires.

Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
Page.Serving += new EventHandler<ServingEventArgs>(Page_Serving);

As we learned in the first tutorial, event handlers get back Post/Page as “sender” object along with some event arguments. In the handler, we look up title and link in the sender and pass it to Track function which will increment counter.

void Page_Serving(object sender, ServingEventArgs e)
{
  if (bool.Parse(_options.GetSingleValue("Page").ToString()))
  {
    string title = ((Page)(sender)).Title;
    string link = ((Page)(sender)).AbsoluteLink.ToString();
    Track(title, link);
  }
}

Another neat thing in 1.4 is ability to disable extension on initialization. If your extension requires first to set some unknown ahead of time values (application key, blogger name etc.) you can disable it on first load by setting extension status to false. Then blogger can go to admin page, set this value(s) and enable extension.

private void InitSettings()
{
  ...
  _settings = ExtensionManager.InitSettings("MyExtension", settings);
  ExtensionManager.SetStatus("MyExtension", false);
}

The entire code for extension shown below, as you can see it is simple and you can build sophisticated system to track down site statistics using this kind of functionality.

using System;
using BlogEngine.Core.Web.Controls;
using BlogEngine.Core;
using System.Data;
 
[Extension("Example_04", "1.0", "<a href=\"http://me.net\">Me</a>")]
public class Example_04
{
  static protected ExtensionSettings _options = null;
  static protected ExtensionSettings _locations = null;
 
  public Example_04()
  {
    Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
    Page.Serving += new EventHandler<ServingEventArgs>(Page_Serving);
    InitSettings();
  }
 
  void Page_Serving(object sender, ServingEventArgs e)
  {
    if (bool.Parse(_options.GetSingleValue("Page").ToString()))
    {
      string title = ((Page)(sender)).Title;
      string link = ((Page)(sender)).AbsoluteLink.ToString();
      Track(title, link);
    }
  }
 
  private void Post_Serving(object sender, ServingEventArgs e)
  {
    if (bool.Parse(_options.GetSingleValue("Post").ToString())
      && e.Location == ServingLocation.SinglePost)
    {
      string title = ((Post)(sender)).Title;
      string link = ((Post)(sender)).AbsoluteLink.ToString();
      Track(title, link);
    }
  }
 
  private void Track(string title, string link)
  {
    DataTable dt = _locations.GetDataTable();
    int i = 0;
    int cnt = 0;
    foreach (DataRow row in dt.Rows)
    {
      if (row["Link"].ToString() == link)
      {
        cnt = int.Parse(_locations.Parameters[2].Values[i]) + 1;
        _locations.Parameters[2].Values[i] = cnt.ToString();
        break;
      }
      i++;
    }
 
    if (cnt == 0)
    {
      _locations.AddValues(new string[] { title, link, "1" });
    }
    ExtensionManager.SaveSettings("Locations", _locations);
  }
 
  private void InitSettings()
  {
    ExtensionSettings settings = new ExtensionSettings(this);
    settings.IsScalar = true;
    settings.AddParameter("Post", "Track post clicks", 10, false, false, ParameterType.Boolean);
    settings.AddValue("Post", true);
    settings.AddParameter("Page", "Track page clicks", 10, false, false, ParameterType.Boolean);
    settings.AddValue("Page", true);
    _options = ExtensionManager.InitSettings(this.GetType().Name, settings);
 
    ExtensionSettings locations = new ExtensionSettings("Locations");
    locations.AddParameter("Title");
    locations.AddParameter("Link", "Link", 150, true, true);
    locations.AddParameter("Count", "Count", 10, false, false, ParameterType.Integer);
    locations.ShowAdd = false;
    locations.ShowEdit = false;   
    _locations = ExtensionManager.InitSettings(this.GetType().Name, locations);
  }
}

Built-in admin functionality for extensions is a great feature in BlogEngine, but for some it still might be not enough. What if you need non-standard admin interface? No worries, you can build your own and simply plug it in the BlogEngine admin interface. We'll go through this exercise in the next tutorial. 

Comments (12) -

Cristiano
Cristiano
7/30/2008 10:19:34 AM #

Hi, Ruslan
Here's another question. :-)
If I want to enter a collection of settings and use the typing of each variable (ex: ParameterType.ListBox or ParameterType.RadioGroup, etc.), it does not work !
Any suggestion ?

rtur.net
rtur.net
7/30/2008 2:26:30 PM #

I'll check this out asap.

rtur.net
rtur.net
7/31/2008 1:57:08 AM #

I've added third settings with typed collections. Seems to work fine for me. The code is below. May be you missed something, like forgot to mark it as scalar etc.?

using System;
using BlogEngine.Core.Web.Controls;
using BlogEngine.Core;
using System.Data;
using System.Collections.Generic;
using System.Collections.Specialized;

[Extension("Example_04", "1.0", "<a href=\"http://me.net\">Me</a>")]
public class Example_04
{
  static protected ExtensionSettings _options = null;
  static protected ExtensionSettings _locations = null;
  static protected ExtensionSettings _settings = null;

  public Example_04()
  {
    Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
    Page.Serving += new EventHandler<ServingEventArgs>(Page_Serving);
    InitSettings();
  }

  void Page_Serving(object sender, ServingEventArgs e)
  {
    if (bool.Parse(_options.GetSingleValue("Page").ToString()))
    {
      string title = ((Page)(sender)).Title;
      string link = ((Page)(sender)).AbsoluteLink.ToString();
      Track(title, link);
    }
  }

  private void Post_Serving(object sender, ServingEventArgs e)
  {
    if (bool.Parse(_options.GetSingleValue("Post").ToString())
      && e.Location == ServingLocation.SinglePost)
    {
      string title = ((Post)(sender)).Title;
      string link = ((Post)(sender)).AbsoluteLink.ToString();
      Track(title, link);
    }
  }

  private void Track(string title, string link)
  {
    DataTable dt = _locations.GetDataTable();
    int i = 0;
    int cnt = 0;
    foreach (DataRow row in dt.Rows)
    {
      if (row["Link"].ToString() == link)
      {
        cnt = int.Parse(_locations.Parameters[2].Values[i]) + 1;
        _locations.Parameters[2].Values[i] = cnt.ToString();
        break;
      }
      i++;
    }

    if (cnt == 0)
    {
      _locations.AddValues(new string[] { title, link, "1" });
    }
    ExtensionManager.SaveSettings("Locations", _locations);
  }

  private void InitSettings()
  {
    ExtensionSettings settings = new ExtensionSettings(this);
    settings.IsScalar = true;
    settings.AddParameter("Post", "Track post clicks", 10, false, false, ParameterType.Boolean);
    settings.AddValue("Post", true);
    settings.AddParameter("Page", "Track page clicks", 10, false, false, ParameterType.Boolean);
    settings.AddValue("Page", true);
    _options = ExtensionManager.InitSettings(this.GetType().Name, settings);

    ExtensionSettings locations = new ExtensionSettings("Locations");
    locations.AddParameter("Title");
    locations.AddParameter("Link", "Link", 150, true, true);
    locations.AddParameter("Count", "Count", 10, false, false, ParameterType.Integer);
    locations.ShowAdd = false;
    locations.ShowEdit = false;  
    _locations = ExtensionManager.InitSettings(this.GetType().Name, locations);

    ExtensionSettings settings2 = new ExtensionSettings("Typed");
    settings2.IsScalar = true;
    // define parameters
    settings2.AddParameter("TheString");
    settings2.AddParameter("TheBoolean", "The boolean");
    settings2.AddParameter("TheInteger", "The integer", 10);
    // lists
    settings2.AddParameter("TheDropdown");
    settings2.AddParameter("TheListBox", "The ListBox", 20, false, false, ParameterType.ListBox);
    settings2.AddParameter("TheRadioGroup", "The RadioGroup", 20, false, false, ParameterType.RadioGroup);
    // set default values
    settings2.AddValue("TheString", "Test string");
    settings2.AddValue("TheBoolean", true);
    settings2.AddValue("TheInteger", 25);
    // lists
    StringCollection dd = new StringCollection();
    dd.AddRange(new string[] { "One", "Two", "Three" });
    settings2.AddValue("TheDropdown", dd, "Three");
    settings2.AddValue("TheListBox", new string[] { "List1", "List2", "List3" }, "List2");
    settings2.AddValue("TheRadioGroup", new string[] { "Radio1", "Radio2", "Radio3" }, "Radio1");

    _settings = ExtensionManager.InitSettings(this.GetType().Name, settings2);
  }
}

Cristiano
Cristiano
8/1/2008 8:05:48 AM #

May be you missed something, like forgot to mark it as scalar etc.?

The problem is this: I want to enter a list of parameters without entering a default first row of these!
In your example, if you set settings2.IsScalar = false, you automatically insert a default row of values.
I do not want to enter a first line parameters (I'm converting my extension AdsenseInjector) ...
How can I do as an alternative?

Hirashiki
Hirashiki
8/2/2008 9:34:56 PM #

Hi,

Could you help me?
I tried to run your extension in blogengine 1.4.5, but seems that checkbox dont save the changes.
Did you have any trouble with this control too?

Alexander Higgins
Alexander Higgins
8/3/2008 2:21:37 AM #

Cristiano, unforunately I just came across the same issue.  The Problem is the that when setting the extenstion to scalar, the extenstion setting class seems to return ExtensionParameters.Values(0) in a few places (for example see the getdictionary method).

Can you get way to setting the value to an empty string?  If not the values(0) call throws an exception.

rtur.net
rtur.net
8/3/2008 12:11:57 PM #

@Hirashiki: can't believe I missed that... what you need to do is - go to /admin/Extension Manager/Settings.cs and add CheckBox to the "if" statement in the button click handler. I'll submit this bug fix later tonight. It should look like this:

void btnAdd_Click(object sender, EventArgs e)
{
if (IsValidForm())
{
foreach (Control ctl in phAddForm.Controls)
{
if (ctl.GetType().Name == "TextBox")
{
TextBox txt = (TextBox)ctl;

if (_settings.IsScalar)
_settings.UpdateScalarValue(txt.ID, txt.Text);
else
_settings.AddValue(txt.ID, txt.Text);
}
else if (ctl.GetType().Name == "CheckBox")
{
CheckBox cbx = (CheckBox)ctl;
_settings.UpdateScalarValue(cbx.ID, cbx.Checked.ToString());
}
else if (ctl.GetType().Name == "DropDownList")
{
DropDownList dd = (DropDownList)ctl;
_settings.UpdateSelectedValue(dd.ID, dd.SelectedValue);
}
else if (ctl.GetType().Name == "ListBox")
{
ListBox lb = (ListBox)ctl;
_settings.UpdateSelectedValue(lb.ID, lb.SelectedValue);
}
else if (ctl.GetType().Name == "RadioButtonList")
{
RadioButtonList rbl = (RadioButtonList)ctl;
_settings.UpdateSelectedValue(rbl.ID, rbl.SelectedValue);
}
}
ExtensionManager.SaveSettings(_extensionName, _settings);
if (_settings.IsScalar)
{
InfoMsg.InnerHtml = "The values has been saved";
InfoMsg.Visible = true;
}
else
{
BindGrid();
}
}
}

Alexander Higgins
Alexander Higgins
8/3/2008 6:52:28 PM #

BTW, the same issue when writing extensions.  You can't just add scalar setting parameters.  You need to specify a value for each scalar setting parameter or you experience the same issue.

Rickard Nilsson
Rickard Nilsson
8/4/2008 6:15:17 AM #

Very interesting post! Keep the series going.
Best regards

Saifullah Baber
Saifullah Baber
8/23/2008 1:24:30 AM #

aur kya haal hay

zaidler
zaidler
11/25/2008 12:44:24 PM #

Yup,Very interesting article
Thanks for sharing
<a href="http://www.zaidler.com/stilllife/">Still Life Oil Paintings</a>

Pankaj
Pankaj
12/25/2008 5:28:59 PM #

Nice Article and explanation!!!!

Comments are closed

Recent Comments

Comment RSS