.NET and Open Source: better together

RTur.net

  • Join Us on Facebook!
  • Follow Us on Twitter!
  • LinkedIn
  • Subcribe to Our RSS Feed

Tutorial - Building NivoSlider Extension (Part 3)

Data Persistence

db_1What we need next is to save metadata for each picture used by every slider, and also we need to be able to add and delete all these records. Extension settings are standard way of doing it in BlogEngine - you declare what kind of data you want to maintain, set initial values and first time extension runs it will instantiate settings object and save it on the back-end. To maintain these data, blogger goes to admin/extensions and clicks extension link in the right sidebar. This will load auto-generated form where settings can be edited. The code below would be sufficient:

namespace rtur.net.NivoSlider
{
    using BlogEngine.Core.Web.Extensions;

    public class NivoSettings
    {
        static ExtensionSettings settings;

        public static ExtensionSettings ImageData
        {
            get
            {
                if (settings == null)
                {
                    var extensionSettings = new ExtensionSettings(Constants.ExtensionName);
                    
                    extensionSettings.AddParameter(Constants.UID, "UID");
                    extensionSettings.AddParameter(Constants.Url, "Url");
                    extensionSettings.AddParameter(Constants.Title, "Title");

                    extensionSettings.AddValues(new[] { "slider1:sample1.png", "#", "" });
                    extensionSettings.AddValues(new[] { "slider1:sample2.png", "#", "sample two" });

                    settings = ExtensionManager.InitSettings(Constants.ExtensionName, extensionSettings);
                }
                return settings;
            }
        }
    }
}

But in our case, would be helpful also allow to upload images. For this, we'll go with custom admin page. Custom page is an external page provided by extension author, by convention we put it under user controls: ~/User controls/NivoSlider/Admin.aspx. To let extension manager know what page to load instead of auto-generated form, new admin page has to be registered:

get
{
	if (settings == null)
	{
		...

		ExtensionManager.SetAdminPage(Constants.ExtensionName, Constants.AdminUrl);
	}
	return settings;
}

Now that we have images handled by settings dynamically, repository code has to be adjusted to use settings instead of hard-coded values. One little perk we can provide is a link from slider straight to admin screen – if user is administrator, this link will be added in the lower-right corner of the slider.

namespace rtur.net.NivoSlider
{
    using System.Data;
    using BlogEngine.Core;

    public class Repository
    {
        public static string GetSlider(string id, string width = "960", string height = "370")
        {
            if (NivoSettings.ImageData == null || NivoSettings.ImageData.GetDataTable().Rows.Count < 1)
                return "";

            var div = string.Format("<div id=\"{0}\" style=\"width: {1}px; height: {2}px;\">", id, width, height);

            const string img = "<a href=\"{0}\"><img runat=\"server\" src=\"~/image.axd?picture=Slider/{1}\" width=\"{2}\" height=\"{3}\" title=\"{4}\" alt=\"\" /></a>";

            foreach (DataRow row in NivoSettings.ImageData.GetDataTable().Rows)
            {
                string[] uid = row[Constants.UID].ToString().Split(':');
                
                if (uid.GetUpperBound(0) < 1)
                    continue;

                if(uid[0] == id)
                    div += string.Format(img, row[Constants.Url], uid[1], width, height, row[Constants.Title]);
            }

            div += "</div>";

            if (Security.IsAdministrator)
            {
                var left = int.Parse(width) - 40;
                var url = Blog.CurrentInstance.RelativeWebRoot + Constants.AdminUrl.Replace("~/", "") + "?ctrl=" + id;
                div += string.Format("<a href=\"{0}\" runat=\"server\" class=\"nivo-edit\" style=\"left:{1}px\">Edit</a>", url, left);
            }
            
            div += "<script type=\"text/javascript\">$(window).load(function (){ LoadSlider('#" + id + "');});</script>";

            return div;
        }
    }
}

Because strings are evil, tiny helper class will turn them all into constants. This way we can avoid case-sensitivity issues, mistyping and other fun to debug things.

namespace rtur.net.NivoSlider
{
    public class Constants
    {
        public const string ExtensionName = "NivoSlider";
        public const string AdminUrl = "~/User controls/NivoSlider/Admin.aspx";
        public const string ImageFolder = "/files/Slider";
        public const string UID = "UID";
        public const string Url = "Url";
        public const string Title = "Title";
    }
}

Custom Administration

Now we can take over settings maintenance by providing our own implementation. We need to create form to add/delete records and upload image file. Because page inherits from admin.master it will have same look and feel as rest of the admin pages. ASP.NET grids are generally heavy on resources, so we hand-code images table. Here is what resulting Admin.aspx looks like:

<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/admin/admin.master" CodeFile="Admin.aspx.cs" Inherits="rtur.net.NivoSlider.SliderAdmin" %>
<%@ MasterType VirtualPath="~/admin/admin.master" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="rtur.net.NivoSlider" %>
<asp:Content ID="Content1" ContentPlaceHolderID="cphAdmin" Runat="Server">
<div class="content-box-outer">
    <div class="content-box-full">
        <div>
            <h1>Settings: Slider</h1>
            <div style="float: left; width: 500px">
                <p>
                    <span style="width: 120px; display:inline-block;">Control ID:</span>
                    <asp:TextBox ID="txtConrolId" runat="server" MaxLength="100"></asp:TextBox>
                    <asp:RequiredFieldValidator runat="server" ID="reqCtrlId" ValidationGroup="data" ControlToValidate="txtConrolId" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;">Site URL:</span>
                    <asp:TextBox ID="txtUrl" Width="300" runat="server" MaxLength="250"></asp:TextBox>
                    <asp:RequiredFieldValidator runat="server" ID="reqSiteUrl" ValidationGroup="data" ControlToValidate="txtUrl" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"><%=Resources.labels.title %>:</span>
                    <asp:TextBox ID="txtTitle" Width="300" runat="server" MaxLength="250"></asp:TextBox>
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"><%=Resources.labels.upload %>:</span>
                    <asp:FileUpload runat="server" id="txtUploadImage" />
                    <asp:RequiredFieldValidator runat="server" ID="reqUpload" ValidationGroup="data" ControlToValidate="txtUploadImage" Text="<%$ Resources:labels, required %>" />
                </p>
                <p>
                    <span style="width: 120px; display:inline-block;"> </span>
                    <asp:button runat="server" ValidationGroup="data" CssClass="btn primary" id="btnSave" Text=" Add " OnClick="SaveItem" />    
                </p>
            </div>

            <div class="clear"></div>
            
            <div>
                <table id="tblImgages" class="beTable rounded">
                  <thead>
                    <tr>
                      <th width="25"> </th>
                      <th width="120">Conrol Id</th>
                      <th width="200">Image File</th>
	                  <th width="250">Title</th>
                      <th width="auto">URL</th>
                      <th width="120"> </th>
                    </tr>
                  </thead>
                  <tbody>
                    <%
                       var i = 0;
                       foreach (DataRow row in NivoSettings.ImageData.GetDataTable().Rows)
                       {
                           var cls = i%2 == 0 ? "" : "alt";
                           string[] uid = row[Constants.UID].ToString().Split(':');
                           if (uid.GetUpperBound(0) < 1) continue;
                           var ctrl = uid[0];
                           var src = uid[1];
                    %>
                    <tr class="<%=cls %>">
                      <td><%=i + 1 %></td>
                      <td><%= ctrl %></td>
                      <td><%= src %></td>
	                  <td><%= row[Constants.Title] %></td>
                      <td><%= row[Constants.Url] %></td>
                      <td align="center" style="vertical-align:middle"><a class="deleteAction" href="?id=<%=row[Constants.UID].ToString() %>">Delete</a></td>
                    </tr>
                    <% i++; } %>
                  </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
</asp:Content>

The code behind has "Save" and “Delete” methods with added “Upload” functionality, this will let blogger not only maintain image metadata but also conveniently upload image itself along with it.

using System;
using System.IO;
using BlogEngine.Core;
using BlogEngine.Core.Web.Extensions;

namespace rtur.net.NivoSlider
{
    public partial class SliderAdmin : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            App_Code.WebUtils.CheckIfPrimaryBlog(false);

            if (!Page.IsPostBack)
            {
                if (Request.QueryString["id"] != null)
                    DeleteItem(Request.QueryString["id"]);

                if (Request.QueryString["ctrl"] != null)
                    txtConrolId.Text = Request.QueryString["ctrl"];
            }
        }

        protected void SaveItem(object sender, EventArgs e)
        {
            try
            {
                Upload();

                var src = txtUploadImage.FileName.ToLowerInvariant();

                NivoSettings.ImageData.AddValues(new[] { txtConrolId.Text + ":" + src, txtUrl.Text, txtTitle.Text });

                ExtensionManager.SaveSettings(Constants.ExtensionName, NivoSettings.ImageData);
            }
            catch (Exception ex)
            {
                Utils.Log("rtur.net.SliderAdmin.SaveItem", ex);
            }
            Response.Redirect(Request.RawUrl);
        }

        protected void DeleteItem(string id)
        {
            var table = NivoSettings.ImageData.GetDataTable();
            
            for (int i = 0; i < table.Rows.Count; i++)
            {
                if (table.Rows[i][Constants.UID].ToString() == id)
                {
                    foreach (ExtensionParameter par in NivoSettings.ImageData.Parameters)
                    {
                        par.DeleteValue(i);
                    }

                    ExtensionManager.SaveSettings(Constants.ExtensionName, NivoSettings.ImageData);
                    break;
                }
            }

            // delete image
            string[] uid = id.Split(':');
            if (uid.GetUpperBound(0) < 1) return;

            var folder = Server.MapPath(Blog.CurrentInstance.StorageLocation + Constants.ImageFolder);
            var imgPath = Path.Combine(folder, uid[1]);
            File.Delete(imgPath);
        }

        private void Upload()
        {
            var folder = Server.MapPath(Blog.CurrentInstance.StorageLocation + Constants.ImageFolder);
            
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);

            txtUploadImage.PostedFile.SaveAs(Path.Combine(folder, txtUploadImage.FileName));
        }
    }
}

sldr-adm

Looks like we are ready for prime time – in the next step we’ll package and upload extension to the gallery so that other people can download and use it.