Damian Mehers' Blog Android, VR and Wearables from Geneva, Switzerland.

26Jun/070

The pain of software product pricing

I used to think that I was pretty good at pricing software products I created.  I was forced to revise my high opinion of myself when a company bought the IP to a product I'd been selling and increased the price.  By a factor of eight.

My first (good) experience in pricing software products came in the late nineties when I released a Java-COM bridging product called J-Integra.  I figured I could sell it at US$75 a license, so I initially priced it at US$375 for a five client license pack.

Not long after first releasing it I went to visit Lehman Brothers in Manhattan, at the very bottom of the island.  When they found out that the entire deployment, world-wide, of a solution based on J-Integra was going to cost them less than US$1K, they laughed.  I cringed.

Instead of taking a taxi back to the mid-town area where I was staying, I walked.  And I thought.  By the time I got back I'd decided to change the licensing model.   I kept the existing US$375 for 5 client licenses, but I added a server license at US$3000.  I'd "segmented" my market -- depending on how J-Integra was being used, the client or the server license would apply.

No one complained about the price increase, although plenty of people complained about the price in general over the years.  I came to realize that this was good.  I realized that you want to have enough people complaining about the price so that you know it is not too low, and enough sales so that you know it is not too high.

So how did I end up charging eight times less than the market would bear, when I sold my next product, an SQL Intellisense tool called PromptSQL, for US$25 a copy?

I have plenty of excuses:  PromptSQL was a "hobby" product that I created it on the train going to and from my day job as a consultant.  One of my friends thought that US$25 a copy was ridiculously high.

The reality is that the US$25 price was OK as an initial "best guess" as to what was appropriate.  The mistake I made was in not changing the pricing once it was clear that it was too low.  The signs were there -- indeed one of the reviews said At just $25 for a single copy, this utility provides value which far exceeds its rather modest cost.  That is a pretty clear hint!

Of course it isn't as clear cut as that.  Red-Gate (the company that acquired PromptSQL from me) actually released the version that I'd been selling, for free, while they wrote a new version from scratch.  I tell myself that Red-Gate has serious credibility in the market, and they invested a lot in creating a slick new version, and that is why they can charge so much more that I did.  And its partly true.  But I am sure I could at least have charged US$75 a copy.  Ouch.

26Jun/071

Dynamically creating ASP.NET AJAX controls

In my previous post I showed how you can cache large HTML elements, such as SELECTs in the web browser's document cache.  What if you want to dynamically create and attach ASP.NET AJAX Control Extenders to the dynamically created SELECT?

I've already shown how you can call extenders from JavaScript, but if you want to create and delete them from scratch, you can use functions such as these:

function AddListSearchTo(list) {
    list.listSearch = $create(AjaxControlToolkit.ListSearchBehavior,
                              { 'promptCssClass' : 'listSearchPrompt' },
                              {}, {}, list);
}

function RemoveListSearchFrom(list) {
    if(list.listSearch ) {
        list.listSearch .dispose();
        list.listSearch = null;
    }
}

I use an expando on the target SELECT to point to the ListSearch Extender that I've dynamically created, which means that I can easily get hold of it if I want to remove it. 

As you can see you can easily pass initial properties too.  The $create function is a shortcut to Sys.Component.create

I use these functions when dynamically fetching HTML SELECTs from the server, in response to user input -- I've rolled my own CascadingDropDown. When I get the new SELECT back from the server the first thing I do is remove the existing extender from the existing SELECT, then I replace the old SELECT with the new one that I've just received, and then I dynamically instantiate a new ListSearch extender:

function OnGetListSucceded(list) {
    var theListDiv = $get('TheListDiv'); // Contains TheList
    var theList = $get('TheList');       // An HTML SELECT
    RemoveListSearchFrom(theList);

    theListDiv.innerHTML = list;
    theList = $get('TheList');
    AddListSearchTo(theList);
}
Filed under: AJAX 1 Comment
24Jun/072

Caching large HTML elements in the Browser’s document cache

Sometimes we have to do things that we know are wrong.

For example, in my current project we have to present a list of customers so that the user can select one or more of them.  I work for a large company that has been around for many many years, and the list of customers can exceed 15,000 for some countries.

But I can't decide which ones should be shown, and which ones should not be shown.  Instead I need to show the complete list to the user.   What is painful is that it can take a bit of time for this list to be sent down to the browser.  It would be much better would be if the customer list could be cached on the browser's document cache.  This is what this blog post is about.

Note that in order for this to work, the list is presented as a simple HTML SELECT, and the selected items need to be processed correspondingly on the server using the Request.Form[SelectName] property.

In the ASPX page, I define my customer list like this: 

<div id="ListDiv">
    <select id="List" multiple="multiple" size="15" disabled="disabled">
        <option>Loading List ...</option>
        <option>This may take a moment the first time...</option>
    </select>
</div>

Note that the SELECT is surrounded by a DIV, so that I can replace the DIV contents with a new list.

Then I have some JavaScript that fires when the page loads, and asynchronously fetches the list:

<script language="javascript" type="text/javascript">
// This is called by the ASP.NET AJAX Framework automatically
function pageLoad() {
    var wRequest =  new Sys.Net.WebRequest();
    wRequest.set_url('ListHandler.ashx?Version=<%=ListVersion.Value%>)');
    wRequest.set_httpVerb("GET"); // GETs can be cached, POSTs can not
    wRequest.add_completed(OnFetchListCompleted);
    wRequest.invoke();
}

The pageLoad function gets invoked automatically.  It simply fires off an HTTP GET request to a handler, passing as a parameter the value of the ListVersion hidden field.  By changing the value of this field, the server-side code can control when the browser-cached contents are expired, and a new version is fetched (since changing the URL will mean there is no browser-cached version).

When the response comes back, it replaces the contents of the DIV with the value that is returned from the handler:

function OnFetchListCompleted(executor, eventArgs)
{
    var listDiv = $get('ListDiv');

    if(executor.get_responseAvailable())
    {
        var list = executor.get_responseData();
        if(list && list.startsWith('<select')) {
            listDiv.innerHTML = list;
        }
    }
}
</script>

The handler simply generates the appropriate content:

<%@ WebHandler Language="C#" Class="ListHandler" %>

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;

public class ListHandler : IHttpHandler {

    public void ProcessRequest (HttpContext context) {
        context.Response.Clear();
        context.Response.ContentType = "text/html";
        context.Response.Cache.SetCacheability(HttpCacheability.Public);
        context.Response.Cache.SetExpires(DateTime.Now.AddMonths(1));
        context.Response.Cache.SetSlidingExpiration(true);

        HtmlTextWriter htmlTextWriter = new HtmlTextWriter(
            context.Response.Output);
        ListBox listBox = GetListBox();
        listBox.RenderControl(htmlTextWriter);
        htmlTextWriter.Flush();

        context.Response.End();
    }

    private static ListBox GetListBox()
    {
        ListBox listBox = new ListBox();
 ...
        return listBox;
    }
... 

Note the bolded directives to enforce the browser-side caching of the response.

I have a complete example here.

You might be wondering why I've used the Sys.Net.WebRequest mechanism with the HttpHandler.  Why not simply use a Web Service? That would indeed be simpler, however there is no easy way to set the appropriate cache expiration headers, although it is possible if you are willing to use reflection -- the PageFlakes guys use it to speed up their pages.

Filed under: AJAX 2 Comments
19Jun/075

Speeding Up ListSearch Extender initialization with massive lists

When a ListBox that has an associated ListSearch Extender is first used, the ListSearch Extender does some one-time initialization.

It delays this work this for very good reasons, as Ted Glaza explains here.

The extender needs to decide whether it can use the very fast binary search when the user types, or whether it must use the slower linear search.  It does this by determining if the list is sorted, which can take a long time if there are thousands and thousands of entries in the ListBox.

The best thing would be to somehow give the ListSearch Extender a hint as to whether the ListBox is already sorted or not.

You can do this by making a couple of changes to the ListSearch extender.  If you'd like to see this behaviour become a standard part of the ListSearch Extender, please vote for it and I'll add it if there is sufficient interest.

Modifying the ListSearch Extender is extremely simple.  First download the AJAX Control Toolkit, and then open the AjaxControlToolkit.sln

Open the behaviour JavaScript file "ListSearchBehavior.js".  At the top of the file you'll see some properties.

Add a couple of new property:
    // Properties
    this._isSortedHintSet = false;
    this._isSortedHint = false;
...

These indicate whether the user has told the extender if the list is sorted or not, and if so, whether it is sorted.

Next find the "_isSorted" function, and at the start of it (after the documentation comment), add this code:

if(this._isSortedHintSet) {
    return this._isSortedHint;
}
...

Finally you need to expose this property so that it can be set from outside the class, although you could actually set the  _isSortedHintSet and _isSortedHint from JavaScript, this is considered very bad form since the underscore prefix indicates they are private.

Define a getter and setter accessor at the end of the bevahior, where you'll find the other accessors:
    get_isSortedHint : function() {
        /// <value type="AjaxControlToolkit.ListSearchPromptPosition">
        /// Where the prompt should be positioned relative to the target control.
        /// Can be Top (default) or Bottom
        /// </value>
        return this._isSortedHint;
    },

    set_isSortedHint : function(value) {
        if (!this._isSortedHintSet || this._isSortedHint != value) {
            this._isSortedHintSet = true;
            this._isSortedHint = value;
            this.raisePropertyChanged('isSortedHint');
        }
    }

If you add these two functions after the existing last function (which is "set_raiseImmediateOnChange" in the current build), then you must be sure to add a comma after the closing brace of this last function:
...
    },

    get_isSortedHint : function() {
....

Build the solution, and make sure projects that use the AJAX Control Toolkit now reference the AjaxControlToolkit.dll in AjaxControlToolkit\AjaxControlToolkit\bin\Debug

Now you can set this property in JavaScript when your page first loads, so that the extender does not have to work out whether the list is sorted:

    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM1" runat="server" />

        <asp:ListBox ID="LB1" runat="server">
            <asp:ListItem>Hello</asp:ListItem>
        </asp:ListBox>
        <cc1:ListSearchExtender ID="LSE1" runat="server"
            TargetControlID="LB1"></cc1:ListSearchExtender>

        <script language="javascript" type="text/javascript">
            function pageLoad(sender, args) {
                var extender = $find('LSE1');
                if(extender) {
                    extender.set_isSortedHint(true);
                }
            }
        </script>
    </form>

This is the complete code (no changes to the code-behind file).

The pageLoad function is called automatically by the ASP.NET AJAX Framework (see http://ajax.asp.net/docs/overview/AJAXClientEvents.aspx) when all controls within the page have been loaded.

The function first finds the ListSearch Extender, and then gives it a hint that the list that it is targeting is actually sorted.

If you wanted to be able to set the property from the server side, instead of from JavaScript, you can edit the ListSearchExtender.cs file, and add the appropriate declaration of the property:

        [ExtenderControlProperty]
        [ClientPropertyName("isSortedHint")]
        [DefaultValue("")]
        public string IsSortedHint
        {
            get { return GetPropertyValue("isSortedHint", ""); }
            set { SetPropertyValue("isSortedHint", value); }
        }
 
Filed under: AJAX 5 Comments
19Jun/0712

Hiding the ListSearch Extender Prompt Message

You can hide the ListSearch Extender's prompt message, so that it does not show, by setting the style to "display:none", like this.

Define a CSS class in a StyleSheet"StyleSheet.css":
.DontShowListSearchPrompt
{
    display:none;
}

Then in your ASPX use the Style for the ListSearch Prompt:

<head runat="server">
    <title>Don't show ListSearch Extender Prompt</title>
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM1" runat="server" />

        <asp:ListBox ID="LB1" runat="server">
            <asp:ListItem>Hello</asp:ListItem>
        </asp:ListBox>
        <cc1:ListSearchExtender ID="LSE1" runat="server"
                           PromptCssClass="DontShowListSearchPrompt"
                           TargetControlID="LB1">
        </cc1:ListSearchExtender>
    </form>
</body>
</html>

Filed under: AJAX 12 Comments