LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

The Code Factory in the News

clock July 24, 2008 08:41 by author JKealey

We're happy to see that our client The Code Factory, an Ottawa-based software co-working franchise, was recently featured on the local news. Some of us were on site while the spot was filmed and it was an interesting (yet stressful) experience to be filmed when you're trying to draft out the features to include in the next release!

ctv

Twitter + Co-working

This coincides with our recent addition of a cool little feature to The Code Factory's interactive kiosk. We're now publishing events to Twitter when people check-in and check-out of the location. Of course, members can choose to hide their activities for privacy reasons, but this serves as an interesting off-site complement to the in-store kiosk which indicates who's currently on location. If you follow TheCodeFactory on Twitter, you can see when your friends arrive and decide to head out there yourself!



jQuery Content Slider Tutorial

clock July 22, 2008 15:22 by author EtienneT

Simple Demo (Firefox, IE7, IE6, Opera, Safari) | Demo with content | Source Code

LavaBlast launched a new front page recently and we have incorporated a new jQuery slider. You can see it in action below (in the Flash animation) or you can go see it directly on our website home page.

Scroll down to find out how this little puppy was implemented!

 

 

HTML & CSS

HTML

This is the basic ASP.NET in the ASPX page:

<div class="FrontMenu">
    <div class="Bar">
        <asp:Repeater ID="Repeater" runat="server" onitemdatabound="Repeater_ItemDataBound">
            <ItemTemplate>
                <span class="item">
                    <a href="#" class="jFlowControl">
                        <asp:Label ID="lbl" runat="server" />
                    </a>
                    <div class="spike" style="z-index:999999">
                    </div>
                    <div class="right">
                    </div>
                </span>
            </ItemTemplate>
        </asp:Repeater>
    </div>
    <div class="Panel">
        <div><cms:Paragraph ID="paragraph1" runat="server" ContentName="frontMenu1" /></div>
        <div><cms:Paragraph ID="paragraph2" runat="server" ContentName="frontMenu2" /></div>
        <div><cms:Paragraph ID="paragraph3" runat="server" ContentName="frontMenu3" /></div>
        <div><cms:Paragraph ID="paragraph4" runat="server" ContentName="frontMenu4" /></div>
        <div><cms:Paragraph ID="paragraph6" runat="server" ContentName="frontMenu5" /></div>
    </div>
</div>

This is pretty simple HTML.  Everything is enclosed in the main div with the css class FrontMenu.  We have the animated bar at the top and the content panel underneath it.  The menu bar is generated by a simple repeater control bound to data in our ASP.NET page. Each menu item is a span containing a link that we’ll use to change the selected menu item.  The Panel div contains multiple dynamic paragraphs from our content management system (SubSonic CMS).  You could easily change this code to bind to data from another source.

Here is the code behind for this control.  We kept it pretty simple:

public partial class FrontPageMenu : UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            List<string> list = new List<string>();
            list.Add("How can we help?");
            list.Add("Our Products");
            list.Add("Hot Features");
            list.Add("Testimonials");
            list.Add("Read");
 
            Repeater.DataSource = list;
            Repeater.DataBind();
        }
    }
 
    protected void Repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
        {
            Label lbl = e.Item.FindControl("lbl") as Label;
            lbl.Text = (string)e.Item.DataItem;
        }
    }
}

 

image

The following picture shows how the html menu item elements are rendered.  The CSS section of this article will dive deeper in the inner workings of the menu.

image

JQuery

jFlow

We used jFlow to scroll our panels when we click on a menu item.  The code was fairly straightforward to use.  We included this script in our user control.

$(".FrontMenu").jFlow({
    slides: ".FrontMenu .Panel",
    width: "974px",
    height: "344px",
    duration: 300
});

And then we included a simple reference to our script manager.

<asp:ScriptManagerProxy ID="proxy" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/js/jquery.flow.1.0.min.js" />
    </Scripts>
</asp:ScriptManagerProxy>

 

We could have used Next and Previous buttons but decided not to.

 

IE6 Problems

This menu did require some basic jQuery.  Except for the scrolling animation, we could have built almost all of this in pure CSS thanks to the hover pseudo-class. However, Internet Explorer 6 came back from the grave to haunt us…  As we still have some visitors who are using IE6, we could not afford to let our home page break down in IE6.  Microsoft gods: please please, hear our prayers and find a way to erase IE6 from the face of earth.  But hey, we've got work to do and can't wait five years for the browser to die like IE 5 did.

We discovered that using a:hover in IE6 to change our background image will make the browser crash.  We used IETester to test the menu in IE6: it made IETester crash.  We then tried Virtual PC running Win98 and IE6: Internet Explorer crashed again when we hovered over one of the links with a:hover CSS styles.

The solution to this problem was simply to apply a style to the hovered link (.hover). Then we could easily style the children of this element to our liking without breaking IE6.

$(".FrontMenu .Bar a").mouseover(function() { $(this).addClass("hover"); });
$(".FrontMenu .Bar a").mouseout(function() { $(this).removeClass("hover"); });

To change the style of the selected item we added the css class .sel to the span.item (the parent of the clicked link).  First of all, when a link is clicked, remove the currently selected item.  Second, set the parent of the link as the current selected item.  It’s important to return false as otherwise the browser will follow the link and scroll to the top of the page.

$("div.FrontMenu div.Bar a").click(function()
{
    $("div.FrontMenu div.Bar").children("span.sel").removeClass("sel");
    $(this).parent().addClass("sel");
    return false;
});

 

CSS

Let’s take a deeper look at the menu’s CSS.  Let’s start with the FirstMenu class which is not too complicated.

.FrontMenu
{
    padding: 5px 5px 0px 5px;
    margin-left: 2px;
    margin-top: -4px;
}
 
.FrontMenu .Bar
{
    background: #F6ECA4 url(images/MainPage/bar.jpg) no-repeat top left;
    width: 980px;
    height: 48px;
    position: relative;
}
 
.FrontMenu .Bar a
{
    color: #FFFFFF;
    font-size: large;
    font-family: Tahoma;
    position: relative;
    top: 7px;
    display: block;
    text-decoration: none;
    padding-right: 6px;
    margin-right: -6px;
    cursor: pointer;
}

Now let’s take a closer look at the Bar menu.  Here are the images we used to style our menu items.

CSS Images    
.FrontMenu .Bar span.sel

selLeft.gif

selLeft

selRight.gif

selRight

spike.gif

spike

.FrontMenu .Bar a.hover span

hoverLeft.gif

hoverLeft

hoverRight.gif

 

 

hoverRight

 

Like you saw in the jQuery part, we change the class in JavaScript to bypass some IE6 issues, so you should not be surprised by the CSS.

The code for the main span for each menu item:

.FrontMenu .Bar span.item
{
    line-height: 30px;
    margin: 0px 0px;
    float: left;
    position: relative;
    display: inline;
    cursor: pointer;
    width: 188px;
    text-align: center;
    margin-left: 6px;
}

Here is the code when you hover over the link inside the menu item:

/* We have to handle hover with jQuery because :hover makes IE6 crash when we change the background image. */
.FrontMenu .Bar a.hover
{
    background: transparent url(images/MainPage/hoverRight.gif) no-repeat top right;
    height: 30px;
}
 
/* We have to handle hover with jQuery because :hover makes IE6 crash when we change the background image. */
.FrontMenu .Bar a.hover span
{
    background: transparent url(images/MainPage/hoverLeft.gif) no-repeat top left;
    height: 30px;
    display: block;
}

 

Like you can see, the link contains the hoverLeft  background image and the span inside the link contains the hoverRight.  This enables the link to have any length and the control will resize easily.  If you ever get a link that is wider than the left image, simply make the image wider...

Then we only needed the CSS to change the menu item to make it look selected.

 

 

 

 

.FrontMenu .Bar span.sel a:hover { background: none; padding-left: 0px; margin-left: 0px; }
.FrontMenu .Bar span.sel a:hover span { background: none; padding-left: 0px; margin-left: 0px; }
 
.FrontMenu .Bar span.sel
{
    background: transparent url(images/MainPage/selLeft.gif) no-repeat top left;
    height: 48px;
}
 
.FrontMenu .Bar span.item .spike, .FrontMenu .Bar span.sel .spike
{
    background: transparent url(images/MainPage/spike.gif) no-repeat top left;
    display:none;
    position: absolute;
    top: 44px;
    left: 50%;
    margin-left: -11px;
    width: 22px;
    height: 17px;
    z-index: 9999;
}
 
.FrontMenu .Bar span.sel .spike
{
    display: block;
}
 
.FrontMenu .Bar span.sel .right
{
    background: transparent url(images/MainPage/selRight.gif) no-repeat top right;
    position: absolute;
    height: 48px;
    width: 4px;
    right: 0px;
    top: 0px;
}
 
.FrontMenu .Bar span.sel a { color: #d43300; }

 

The parent span for the menu item with the .sel class contains the selLeft image and the div.right inside this span contains the selRight image.  We have to make sure too that the hover style does not get applied when the item is selected.

The spike spike is applied in absolute position in the center.  To do that in absolute position, you have to set the following:

left: 50%;
margin-left: -11px;  // <—This is the width/2
width: 22px;

Even with a higher z-index, we were not able to make the spike go on top of the content panel in IE6.  Therefore, we had to put a margin on top of content panel just to make sure the spike was not overlapping the content panel below:

.FrontMenu div.Panel
{
    height: 328px;
    width: 974px;
    margin-top: 15px;
}

 

 

Simple Demo (Firefox, IE7, IE6, Opera, Safari) | Demo with content | Source Code

kick it on DotNetKicks.com



Would you put cartoons on your software startup's website?

clock July 17, 2008 20:42 by author JKealey

We've revamped our website home page and wanted to invite you to visit it and let us know what you think.   The general template of the site hasn't changed, as our enhancements focused on five core elements:

  1. Simpler menu structure. When we first launched our website, our pages were never nested more than one level deep. We've since added new content and our site was getting harder to navigate. By going with a tree-like structure and adding markers to indicate which page you are currently viewing, we feel this solves our main usability problem.
  2. Testimonials. Ian Graham, the man behind The Code Factory, an Ottawa-based software co-working location, talks about how he enjoyed doing business with us. We feel this touch increases our credibility, and the fact that we get things done. 
  3. Concise information. We've integrated much more information on our home page and re-worked the text to make it very concise. The home page leads you to numerous inner pages which feature more detailed information about our products. We're always re-working the innards of our site and we're never "done", but we feel this new home page will help drive traffic to the appropriate locations.... only time Google Analytics will tell.
  4. Web 2.0 slider. We wanted to have a bit of fun even if it meant requiring JavaScript on our pages.
  5. Cartoons. This is the most controversial aspect of our new home page. We've integrated cartoons on our homepage.... cartoons on a franchise software corporate site? Allow us to explain.

LavaBlast Software home page

Why are we using cartoons?

Simply put, everyone we've talked to is divided in two completely distinct camps. One camp feels our cartoons makes our website unprofessional and inappropriate for the franchise industry's decision makers (one of the more vocal people in this camp is Michael Webster, Ph.D, LL.B.). Others feel it gives us a more personalized feel (a human touch) which increases their trust in our company.

There are hundreds of companies building software for the franchise industry and we want to show that we have a different philosophy from many of the old-school companies. Simply put, we (as web visitors) distrust generic consulting websites littered with stock photography and we didn't want to repeat the same mistake. We love to use pictures, but bad quality pictures or video are often worse than not having any.  After a year and a half of having a more corporate feel (without using stock photography), we decided it was time to do something wilder. We hope to impress our target market with an atypical corporate website, even if it ruffles a few feathers.  

We target small yet energetic franchise systems. These franchisors are not heads of billion dollar corporate empires, they are entrepreneurs who want to grow a concept which worked in their flagship store and scale it to the next level, via franchising. At their growth stage, these franchisors are looking for someone who can listen to their needs, build cost-effective software solutions, and help them grow. The franchises we deal with don't have large IT departments: they're looking to get outside help with technology, as they don't have the knowledge in-house. Outsourcing allows them to get more bang for their buck than hiring software engineers to build everything from scratch.

Why don't you like stock photos?

Does the following image incite you to contact a software firm for custom development?

handshake stock photo

When we shop around and find a company featuring such a picture, it reveals that they botched their web development work and they're probably going to botch any work we give them. Attention to detail is one characteristic we always want to see; however, we're not completely against stock photography but we disapprove of stock photography abuse. For example, if a company has a page talking about their team, and the team picture is actually a stock photo... they're taking it too far.

As a sidenote, Toronto-based Idée Inc. created an image search engine that not only helps identify stock photography but also people that have stolen your copyrighted images. Here's a screenshot of the results returned by TinEye for the previous image: 

TinEye Image Search

TinEye even found this modified image... very nice technology!

modified handshake

What do you think?

In summary, we decided to go with a cartoonish feel because we felt it was the best way to distinguish ourselves from our stereotypical competition. We purposefully project more youthful brand image, as we are targeting smaller franchise systems. Do you think differently? Are you an ardent defender of stock photo or do you think you've found the perfect balance of web 2.0 styling with the warm fuzzy feeling of seeing people? Do you agree with us? Let us know!

kick it on DotNetKicks.com

blast it on Franchise NewsBlast


Image Post Processing Caching

clock July 15, 2008 13:41 by author EtienneT

Complete Source Code | Small Demo

This article present a small class library that abstracts opening, modifying (applying effects, resizing, etc.), and caching images in ASP.NET.  Everything needs to be abstracted to ensure the code is easily testable (opening, modifying, and caching of the images).  You may want to resize your images or convert them to black and white and cache the result, and want to test these operations.

You want to be able to read image data from different sources:

  • An image on the local disk on the web server
  • A remote image on the Internet that you want to download and cache
  • An image in your database

You want to be able to apply any number of post processing algorithms to the resulting image:

  • Resize the image (generate thumbnails)
  • Apply an image filter such as convert to black and white
  • Do anything on the image that requires computing and where caching the result proves beneficial from a performance standpoint. 

CacheManager

First of all, let's look at a nice caching class I found on a DotNetKicks kicked story.  Zack Owens gave a nice piece of code in his blog to help you manage your ASP.NET cached objects.  The goal of this class is simply to let you have a strongly typed way to access your cached objects.  Here is the code for the class with some slight modifications:

public class CacheManager<T> where T : class
{
    private Cache cache;
    private static CacheItemRemovedCallback callback;
    private static object _lock = new object();
 
    private TimeSpan cacheTime = new TimeSpan(1, 0, 0, 0); // Default 1 day
 
    public CacheManager(Cache cache)
    {
        this.cache = cache;
        if(callback == null)
            callback = new CacheItemRemovedCallback(RemovedFromCache);
    }
 
    public T Get(string key)
    {
        try
        {
            lock (_lock)
            {
                if (cache[key] == null)
                    return default(T);
 
                T b = CastToT(cache[key]);
 
                return b;
            }
        }
        catch (ArgumentException ex) // The object was disposed by something! return null;
        {
            return null;
        }
    }
 
    public void Add(string key, T obj, TimeSpan cacheTime)
    {
        lock (_lock)
        {
            if (obj != null)
                cache.Add(key, CastFromT(obj), null, DateTime.Now.Add(cacheTime), Cache.NoSlidingExpiration, CacheItemPriority.Default, callback);
        }
    }
 
    protected void RemovedFromCache(string key, object o, CacheItemRemovedReason reason)
    {
        T obj = o as T;
        if (obj != null)
        {
            lock (_lock)
            {
                DisposeObject(obj);
            }
        }
    }
 
    protected virtual void DisposeObject(T obj) { }
 
    protected virtual T CastToT(object obj) { return obj as T; }
 
    protected virtual object CastFromT(T obj) { return obj as T; }
 
    public TimeSpan CacheTime
    {
        get { return cacheTime; }
        set { cacheTime = value; }
    }
}

As you can see, this is a pretty simple class.  We defined some virtual methods to be implemented in our child class, for example DisposeObject if you want to cache disposable objects (continue to read if you want to know why this is a really bad idea).

The constructor requires a Cache object; we can simply pass along the Page's Cache (Page.Cache) to make it happy.  We now want to derive from CacheManager to help us in our main task which is to cache modified images.

ImageCacheManager

To create our image-specific cache manager, we defined a new class called ImageCacheManager which is a subclass of CacheManager  and will cache byte arrays (our images).  We implemented this feature in the past, but did a big mistake that led to a mysterious bug.  We were caching Bitmap objects in the ASP.NET cache but this was a big, big mistake.  Bitmap objects are GDI+ managed objects and they need to get disposed.  Even if we had methods to dispose the Bitmap when they were removed from the cache, some Bitmap objects were disposed while still in the cache (because of a memory leak elsewhere in the application). This caused errors downstream when we tried to use those objects later.  The lesson: we'll only cache only byte[] of the images.

The default image format is PNG in our case, but you can specify your own in the constructor.  In our case we are using PNG because we are in a controlled environment where we know everyone is using IE7, so we can use transparent PNG.  You probably want to use a different format for general public web sites since IE6 doesn't support transparent PNG.

This class will enable to download remote images and cache them locally, as well.  We needed this feature since we have a lot of remote point of sales which synchronize their product list from a central database.  We didn't want to send product images during synchronization because it would have been too much data.  Instead we decided to store our images on a central server and since our stores always have Internet access, they download and cache the images via this image cache manager.  In our product, when a franchisor changes a product image in the main database, the cached version of the picture in the point of sale will expire within the next day and the new picture would be downloaded when used.

ImageCacheManager is an abstract class.  It implements image caching and handles the fact that you want to apply post processing to an image with options (interface IImagePostProcess) and it abstracts the way you load the image (interface IImageReader).  Here is the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Caching;
using System.Drawing;
using System.IO;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Web;
using System.Net;
using LavaBlast.ImageCaching.PostProcess;
using LavaBlast.ImageCaching.Reader;
 
namespace LavaBlast.ImageCaching
{
    /// <summary>
    /// This class specialize in caching modified images.  Images are cached as
    /// byte[].  You can apply different modifications in serial to the image
    /// before caching it.
    /// 
    /// Construct a cache key depending on the options of your image post processing.
    /// This enables to cache copies of an image with different post processing applied to it.
    /// 
    /// Control how the image will be read.  On local disk, via Internet etc.
    /// </summary>
    public abstract class ImageCacheManager : CacheManager<byte[]>
    {
        public ImageCacheManager(Cache cache) : this(cache, ImageFormat.Png) { }
 
        protected Dictionary<string, IImagePostProcess> postProcess = new Dictionary<string, IImagePostProcess>();
 
        protected ImageFormat format = ImageFormat.Png; // Default image format PNG
 
        public ImageCacheManager(Cache cache, ImageFormat format) : base(cache)
        {
            this.format = format;
 
            InitImagePostProcess();
        }
 
        /// <summary>
        /// Determine which image reader will be used to read this image.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <returns></returns>
        protected abstract IImageReader GetReader(Uri uriPath);
 
        /// <summary>
        /// Fill the variable postProcess with post processing to apply
        /// to an image each time.
        /// </summary>
        protected abstract void InitImagePostProcess();
 
        /// <summary>
        /// This method shall return a unique key depending on the path of the
        /// image plus the options of it's post processing process.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        protected abstract string ConstructKey(Uri path, Dictionary<string, object> options);
 
        /// <summary>
        /// Get an image from the following path.  Use the provided options to use in post processing.
        /// If refresh is true, don't use the cached version.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <param name="options"></param>
        /// <param name="refresh"></param>
        /// <returns></returns>
        protected byte[] GetImage(Uri uriPath, Dictionary<string, object> options, bool refresh)
        {
            string key = ConstructKey(uriPath, options);
            byte[] cached = Get(key);
 
            if (cached != null && !refresh)
                return cached;
            else
            {
                try
                {
                    byte[] image = ReadBitmap(uriPath); // Get the original data from the image
 
                    byte[] modified = PostProcess(image, options); // Do any post processing on the image (resize it or apply some effects)
 
                    Add(key, modified, CacheTime); // Add this modified version to the cache
 
                    return modified;
                }
                catch
                {
                    return null;
                }
            }
        }
 
        /// <summary>
        /// Run all post processing process on the image and return the resulting image.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        protected byte[] PostProcess(byte[] input, Dictionary<string, object> options)
        {
            byte[] result = input;
 
            foreach (string key in postProcess.Keys)
                result = postProcess[key].Process(result, options[key]);
 
            return result;
        }
 
        /// <summary>
        /// From a path, return a byte[] of the image.
        /// </summary>
        /// <param name="uriPath"></param>
        /// <returns></returns>
        protected byte[] ReadBitmap(Uri uriPath)
        {
            using (Stream stream = GetReader(uriPath).GetData(uriPath))
            {
 
                byte[] data = new byte[0];
 
                Bitmap pict = null;
 
                try
                {
                    pict = new Bitmap(stream);
                    data = ImageHelper.GetBytes(pict, format);
                }
                catch
                {
                    return null;
                }
                finally
                {
                    if (pict != null)
                        pict.Dispose();
                }
 
                stream.Close();
 
                return data;
            }
        }
    }
}

Because this is an abstract base class, we need a concrete implementation of ImageCacheManager.  We created ThumbnailCacheManager.  ThumbnailCacheManager checks the URI if it’s a local or remote file and uses the right image reader.  It has only one post processing task (resizing the image), but it could have more.  It construct the unique key for the cache from the processing task’s options.

Image Resizing

Here is a quick example of a typical image processing task: resizing it.  The class implement the simple method Process(byte[] input, object op) where op is in fact the options of the post processing process.  I could not use generics in my IImagePostProcess interface because of the way I store them later…  Here is a quick code example of how to resize an image.

namespace LavaBlast.ImageCaching.PostProcess
{
    /// <summary>
    /// Post processing that take an image and resize it
    /// </summary>
    public class ImageResizePostProcess : IImagePostProcess
    {
        public byte[] Process(byte[] input, object op)
        {
            byte[] oThumbNail;
 
            ImageResizeOptions options = (ImageResizeOptions)op;
 
            Bitmap pict = null, thumb = null;
 
            try
            {
                using (MemoryStream s = new MemoryStream(input))
                {
                    pict = new Bitmap(s); // Initial picture
 
                    s.Close();
                }
 
                thumb = new Bitmap(options.Size.Width, options.Size.Height); // Future thumb picture
 
                using (Graphics oGraphic = Graphics.FromImage(thumb))
                {
                    oGraphic.CompositingQuality = CompositingQuality.HighQuality;
                    oGraphic.SmoothingMode = SmoothingMode.HighQuality;
                    oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    oGraphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    Rectangle oRectangle = new Rectangle(0, 0, options.Size.Width, options.Size.Height);
 
                    oGraphic.DrawImage(pict, oRectangle);
 
                    oThumbNail = ImageHelper.GetBytes(thumb, options.ImageFormat);
 
                    oGraphic.Dispose();
 
                    return oThumbNail;
                }
            }
            catch
            {
                return null;
            }
            finally
            {
                if (thumb != null)
                    thumb.Dispose();
                if (pict != null)
                    pict.Dispose();
            }
        }
    }
 
    public class ImageResizeOptions
    {
        public Size Size = Size.Empty;
        public ImageFormat ImageFormat = ImageFormat.Png;
    }
}

 

Reading the picture

Reading the picture is the easy part and I have included two implementations of IImageReader: one for a local images and one for a remote images.  You could easily implement one which loads images from you database.

LocalImageReader

/// <summary>
    /// An image reader to read images on local disk.
    /// </summary>
    public class LocalImageReader : IImageReader
    {
        public Stream GetData(Uri path)
        {
            FileStream stream = new FileStream(path.LocalPath, FileMode.Open);
 
            return stream;
        }
    }

RemoteImageReader

/// <summary>
    /// Image reader to read remote image on the web.
    /// </summary>
    public class RemoteImageReader : IImageReader
    {
        public Stream GetData(Uri url)
        {
            string path = url.ToString();
            try
            {
                if (path.StartsWith("~/"))
                    path = "file://" + HttpRuntime.AppDomainAppPath + path.Substring(2, path.Length - 2);
 
                WebRequest request = (WebRequest)WebRequest.Create(new Uri(path));
 
                WebResponse response = request.GetResponse() as WebResponse;
 
                return response.GetResponseStream();
            }
            catch { return new MemoryStream(); } // Don't make the program crash just because we have a picture which failed downloading
        }
    }

HttpHandler

Finally, you want to serve those images with an HttpHandler. The code for the HttpHandler is pretty simple as you only need to parse the parameters from the QueryString and pass them to the ThumbnailCacheManager presented above.  The handler receives a parameter “p” for the path of the image (local or remote) and a parameter “refresh” which can be used to ignore the cached version of the image.  Additionally, we can pass parameters such as “width” and “height” for our image resizing.  Warning: you must adapt this code to your environment otherwise you are exposing a security hole because of the path parameter.

When debugging your image caching HttpHandler, don't forget to clear your temporary Internet files from IE or FireFox because your images will also be cached in your web browser otherwise your code will not be executed!

using System;
using System.Collections;
using System.Data;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using LavaBlast.ImageCaching;
 
namespace WebApplication
{
    /// <summary>
    /// Really simple HttpHandler to output an image.
    /// </summary>
    public class ImageCaching : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            string path = context.Request.Params["p"] ?? "";
            bool refresh = context.Request.Params["refresh"] == "true";
 
            int width, height;
            if (!int.TryParse(context.Request.QueryString["width"], out width))
                width = 300;
            if (!int.TryParse(context.Request.QueryString["height"], out height))
                height = 60;
 
            byte[] image = null;
            ThumbnailCacheManager manager = new ThumbnailCacheManager(context.Cache);
            image = manager.GetThumbnail(path, width, height);
 
            if (image != null)
            {
                context.Response.ContentType = "image/png";
                try
                {
                    context.Response.OutputStream.Write(image, 0, image.Length);
                }
                catch { }
 
                context.Response.End();
            }
        }
 
        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
    }
}

 

 

Source Code | Small Demo

kick it on DotNetKicks.com



Upgrading to SubSonic v2.1

clock July 10, 2008 16:05 by author JKealey

The timing for the release of SubSonic v2.1 could not have been better as we're between time-critical projects at the moment. As our readers know, we've used SubSonic as our business object code generator since we first launched the company. I spent a few hours this morning doing the migration of our codebase and it seems to have gone smoothly. We've posted some cool improvements we've made to SubSonic in previous posts: Improved ManyManyList Control, Object Change Tracking, and an Improved ObjectDataSource Controller. Migrating to v2.1 involved a few changes and this post will describe them briefly. As this is currently a work in progress, we'll let the dust settle before writing a more formal post.

LavaBlastManyManyList :)

Rob integrated the LavaBlastManyManyList control into SubSonic. It does strike me as uncommon for an open source project to list the contributor in the class name, but who am I to complain? :)

Changes to our SubSonicHelper and SubSonicController.

SubSonic changed the base classes for their objects. Therefore, we have to change our own SubSonicController<T, C> to extend RecordBase<T> instead of AbstractRecord<T>. In our SubSonicHelper, we changed AbstractRecord<T> and ActiveRecord<T> to RecordBase<T> but, for some reason, we also had an ActiveList<T> which we changed to AbstractList<T> to match the rest of the application.

SubSonic Collections no longer extend List<T>

Collections are now extending BindingList<T>, apparently for improved DataBinding support. However, this breaks all the code you may have which uses the fact that Collections were generic lists: Sort, Find, FindAll, FindLast, AddRange, Exists, etc. Luckily for us, we have replacement methods for Sort/Find, which are easier to use but not as powerful as custom delegates/predicates. Rewriting the 70-odd locations in our code to avoid using methods from the List<T> interface isn't what I consider fun and you may feel the same way. The code we had to rewrite was non-trivial and rewriting all these locations without being able to recompile and test (as we don't have unit tests that specifically check that the items in a Collection are sorted the right way, for example), we took the decision to go with a low-impact change.

We edited CS_ClassTemplate.aspx and CS_ViewTemplate.aspx and added the following method to both collections:

   1: public List<<%=className%>> ToList()  {
   2:     return new List<<%=className%>>(Items); // shallow copy
   3: }

BindingList<T> has a protected property named Items which is indeed a List<T>. We didn't check the implementation details, but since it doesn't make this property public, we can assume that playing with that list directly (removing items from the list for example) might screw up the original collection. Therefore, we're creating a shallow copy of the List and using that in our code when necessary. Now that everything compiles and works properly, we can rewrite code where performance is more important (and use the original SubSonic collection instead).

Found two bugs, one old, one new.

We've reported two bugs in the SubSonic's brand new issue tracker on Google Code. (Issue 3 is a rare case relating to composite keys and paging, it probably won't affect you as it has been around forever. However, Issue 4 is a bit more worrisome as it implies that most of your code that uses StoredProcedures might not work anymore without a small workaround until they release SubSonic v2.1.1.)

Conclusion

I hope this helps all of you who were trying to get our SubSonic v2.0.3 code working on SubSonic v2.1! When everything will have been tested thoroughly, we'll post more source code.

kick it on DotNetKicks.com


Franchises Can Learn From Software Startups - Part 3: Reacting

clock July 4, 2008 08:39 by author JKealey

This article is the last of a three-part series related to technology in the franchise world. It focuses on what franchisors should be doing to react to the trends presented in Part 2.

Franchisors should be doing a number of things to keep up with the Net Generation. Most of these are straightforward once franchisors realize that people expect lots of information, expect it immediately, and expect their opinion to be taken seriously.

Streamline your processes

The more people use software, the more people expect of it and become irritated if a feature requires extra effort when it could be automated. Users don't pay attention when performing a task repeatedly and introduce errors into systems. Therefore, your business processes, including the software portion of it, should be as integrated as possible. Of course, you cannot integrate all at once and have to work on the pain points for which an integrated solution would save the most time or prevent the most errors. Streamlining your business processes is an iterative process which requires constant effort and attention but is very rewarding.

The processes that are easy to streamline vary for each franchise. However, using product-based retail stores as an example, the integration which provides the most value is between the point of sale and the franchise intranet. The goal is to offer automated sales reporting and centralized product line management. Once that is done, you can greatly simplify in-store product ordering by automating recurring orders via the in-stock quantities, for example.

Franchisors should keep in mind that integration that is already built-in built into a software product line is a very valuable asset. BjEmerson covered this, and other valuable questions, in his post on Blue Mau Mau. As a franchisor, it is your responsibility to periodically bring up the subject of integration with various suppliers to ensure you have an efficient process in place. Keep in mind that integration should not intend to cover all special cases and that you should put manual processes in place to double check that all the information in your system is accurate. 

A fine-tuned franchise is much more appealing to a franchise prospect because of the simplicity of its day-to-day management. Furthermore, if you have developed your own software or processes to make everything easier, the value proposition is even clearer.

Franchise Collaboration

Franchise Collaboration The most important thing a small franchisor can do is to stop any unidirectional (waterfall) decision making. Transparency and collaboration help foster trust whereas keeping everyone in the dark before enforcing a big change is simply not a good business practice. Obviously, you won't be able to make everyone happy all the time but when franchisees feel their opinion is appreciated, everyone benefits. Obviously, this involves much more than technology but franchise collaboration software such as forums and polls can help. Thanks to open source software and online tools, you can even set this up for free. The real added-value comes when collaboration becomes a part of every day tasks, such as polling features directly integrated into the point of sale. This promotes collaboration within the franchise since the franchisees are not required to login to a separate tool when they get back home after a long day's work.

If you don't collaborate with your franchisees, you will lose them, period. If a franchisee leaves you because you never listened to their opinion, you can be sure that people reviewing your UFOC will end up hearing it. On the other end of the spectrum, a franchise which pushes strong franchisee collaboration via online tools can be a strong selling point for new prospect. A simple demo of the current issues being discussed will clear up any fears about ongoing support.

Openly discussing issues and possible solutions with your franchisees forces you to write things down in a logical fashion and think about the issues in a rational way. This simple activity often guides the decision making process and leads to the best decision.  

Give out more information

Franchisors should utilize their website and should not feel shy about posting lots of information to attract new franchise prospects. Obviously, organizing this information is very important as to avoid overwhelming the user but franchisors should post lots of information and treat their prospects intelligently. The website should include a high-level executive summary which allows interested prospects to drill down to find relevant information on separate pages.

The classic sales technique of not giving out too much information, having prospects request additional information, and having a salesman call them back to conclude a deal is no longer the best approach in today's online context: these practices must be adapted. Franchisors who don't display basic information such as franchise fees and setup costs are shooting themselves in the foot for numerous reasons.

First, today's visitors expect more information and they expect it now. Your prospects are probably thinking about starting their own business in this same field and it is your job to show them the wide breadth of problems you've already solved and how it is a better business decision to purchase a proven franchise system. You also need to show how your franchise is better than other franchise systems and your website is an ideal place to showcase your distinguishing factors. 

Second, the volume of franchise prospects on the Internet has increased, although we've mentioned franchisors are feeling  the quality has dropped. There is a growing number of people looking for low-cost franchises and if yours is not one of them, stating your requirements explicitly will help reduce low quality prospects.   If you are looking at catering to this growing niche, you might as well clearly define the lower cost options you are offering (kiosk format instead of store format, for example). Once you've formalized your offering and covered the frequently asked questions in detail on your website, you've developed a resource base that can be utilized by your salesforce.

Keep in mind that it is possible to get information about leads via your website, even if you're posting most of your information online. All you need to do is post a bit of exclusive content on your website which is only accessible after filling out a short form (email address, name and phone number). This gives you a way to contact prospects after the fact while still giving your prospects information when they want it (now!). This exclusive content can be as simple as a two-page PDF brochure or as extensive as a virtual tour of your store with pictures and videos.

Spread the word

Spread the word The first thing you should do as a small/new franchisor is to actively participate in online communities. You should remember that online participation is a give&take relationship and you need to do more than self-promotion or demolish everyone's opinions. You can start by participating online in franchise communities such as Blue Mau Mau and FranMarket and simply writing comments on other people's posts. Everyone has a different background and you can often refer to your past experiences to help clarify posts by other people. You should also look for online communities which specialize in your niche, to raise awareness about your brand but also simply to exchange ideas. If your franchise is a dog kennel, you should look for pet-related online communities.  Finally, don't overlook any local business online communities which may be appealing to you. Hooking up with a local software startup or local artist might put your franchise in a better position to take on the world.

In addition to participating in online communities, you should start your own blog either at Blue Mau Mau or at another free service. There are numerous things you can (and should) blog about:

  • A post for each new franchisee with an interview, franchisee profile, store pictures, etc.
  • What makes your franchise unique (you should be able to find dozens of cool things that distinguish your franchise from the competition)
  • New products or services
  • What the franchise is proactively working on
  • Your lessons learned as a franchisor
  • Partner announcements
  • How you or your franchisees gave back to the community
  • Trends
  • Internal reflections
  • Congratulate one of your franchisees for outstanding achievements
  • How your franchise is saving people money or saving the planet
  • You've got a particular problem and are looking feedback on solutions

Many new bloggers are afraid to reveal the secret sauce if they talk about their lessons learned or what makes them different. They fear the day where their competition will copy their brilliant ideas. In reality, ideas are free and execution is key. Furthermore, if all that distinguishes your franchise is the auto-flushing toilets you installed last year, you've got a problem. You shouldn't reveal every last detail about how you operate, but don't let paranoia overcome you with every little detail. Blogging is a rewarding experience because it puts you in touch with lots of new people which may help you down the road.

More franchisors should blog about the problems they have experienced and how they overcame them as it is an essential subject which will help others. The franchise world is full of people who are looking to make a quick buck and being open about your franchise is a good way to help build a relationship of trust with your service providers, prospects, and franchisees.

Last week, LavaBlast launched Franchise NewsBlast to help franchisors spread the word about their franchise. Our system doesn't focus on franchise opportunities but rather on franchise-related articles that have true value for web visitors

Conclusion

Some people find it easier than others to get their head around the new business context in which franchises operate. We've listed a few high level tasks which help clarify the possible ideological differences between franchisors and the Net Generation. Once these base concepts are better understood, franchisors will be in a better position to understand things such as viral marketing and social networks in order to take advantage of these business opportunities for their franchise systems. Take a look at Franchise NewsBlast, Blue Mau Mau, and FranMarket today!



blast it on Franchise NewsBlast


Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in