Monthly Archives: September 2006

More on logging

Earlier this week, about the same time that I was playing with health monitoring. I found out after a while that log4net isn’t working on Vista RC1.

Now I don’t know about you but log4net seemed like the obvious choice for logging for me.  Now I was forced to have a look around what else is there. And castle supports also nlog. I took a closer look at nlog and I like it a lot. It has much better integration with visual studio 2005 than log4net has.

The configuration was much easier. I wrote a “log provider” (log target in the nlog dictionary) for base4 in about 15 minutes.  Ah yes extensibility is great for this product.

It’s open source but it is a really tight project.  Maybe you too should investigate :)

Custom Health monitoring for Asp.NET using Base4

It’s been pretty quiet on my blog the last couple of days. But I had something cooking :)

I think logging is a pretty important part of an application. I like to know what’s going on when for errors etc.

Asp.NET has a health monitoring service that does just that. With a couple of lines in the web.config you’re good to go.

I use base4 for my dataaccess and i want do do all my data access through base4. From the asp.net services I use Membership, roleManager, profiles and health monitoring.

This week I ported healthmonitoring, membership and rolemanager to Base4. I’ll post the health monitoring provider and when I have also completed the profile provider I’ll post a dll that contains all the providers and the Base4 schema’s if somebody is interested.

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Collections.Specialized;

using Flanders.Library.Provider;

using System.Web.Management;

using System.Globalization;

using Seshat.Data;

using System.Collections;

using System.Threading;

using System.Configuration;

 

namespace Flanders.Library.Base4Integration

{

    public class Base4WebEventProvider : BufferedWebEventProvider

    {

        const int SQL_MAX_NTEXT_SIZE = 1073741823;

        const int NO_LIMIT = -1;

 

        int _maxEventDetailsLength = NO_LIMIT;

 

        DateTime _retryDate = DateTime.MinValue; // Won’t try sending unless DateTime.UtcNow is > _retryDate

 

        public Base4WebEventProvider() { }

 

        public override void Initialize(string name, NameValueCollection config)

        {

 

            string temp = null;

 

            SecUtility.GetAndRemoveStringAttribute(config, “connectionStringName”, name, ref temp);

 

 

            SecUtility.GetAndRemovePositiveOrInfiniteAttribute(config, “maxEventDetailsLength”, name, ref _maxEventDetailsLength);

            if (_maxEventDetailsLength == SecUtility.Infinite)

            {

                _maxEventDetailsLength = NO_LIMIT;

            }

            else if (_maxEventDetailsLength > SQL_MAX_NTEXT_SIZE)

            {

                throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_max_event_details_length, name, _maxEventDetailsLength.ToString(CultureInfo.CurrentCulture)));

            }

 

            //SecUtility.GetAndRemovePositiveAttribute(config, “commandTimeout”, name, ref _commandTimeout);

 

            base.Initialize(name, config);

        }

 

        void FillParams(ApplicationEvent appEvent, WebBaseEvent eventRaised)

        {

            Exception exception = null;

            WebRequestInformation reqInfo = null;

            string details = null;

            WebApplicationInformation appInfo = WebBaseEvent.ApplicationInformation;

 

            appEvent.Id = eventRaised.EventID;

            appEvent.EventId = eventRaised.EventID.ToString(“N”, CultureInfo.InstalledUICulture) ;

            appEvent.EventTimeUtc = eventRaised.EventTimeUtc;

            appEvent.EventTime = eventRaised.EventTime;

            appEvent.EventType = eventRaised.GetType().ToString();

            appEvent.EventSequence = eventRaised.EventSequence;

            appEvent.EventOccurence = eventRaised.EventOccurrence;

            appEvent.EventCode = eventRaised.EventCode;

            appEvent.EventDetailCode = eventRaised.EventDetailCode;

            appEvent.Message = eventRaised.Message;

            appEvent.ApplicationPath = appInfo.ApplicationPath;

            appEvent.ApplicationVirtualPath = appInfo.ApplicationVirtualPath;

            appEvent.MachineName = appInfo.MachineName;

 

            // @RequestUrl

            if (eventRaised is WebRequestEvent)

            {

                reqInfo = ((WebRequestEvent)eventRaised).RequestInformation;

            }

            else if (eventRaised is WebRequestErrorEvent)

            {

                reqInfo = ((WebRequestErrorEvent)eventRaised).RequestInformation;

            }

            else if (eventRaised is WebErrorEvent)

            {

                reqInfo = ((WebErrorEvent)eventRaised).RequestInformation;

            }

            else if (eventRaised is WebAuditEvent)

            {

                reqInfo = ((WebAuditEvent)eventRaised).RequestInformation;

            }

            appEvent.RequestUrl = Convert.ToString((reqInfo != null) ? reqInfo.RequestUrl : Convert.DBNull);

 

            // @ExceptionType

            if (eventRaised is WebBaseErrorEvent)

            {

                exception = ((WebBaseErrorEvent)eventRaised).ErrorException;

            }

            appEvent.ExceptionType = Convert.ToString((exception != null) ? exception.GetType().ToString() : Convert.DBNull);

 

            // @Details

            details = eventRaised.ToString();

            if (_maxEventDetailsLength != NO_LIMIT &&

                details.Length > _maxEventDetailsLength)

            {

                details = details.Substring(0, _maxEventDetailsLength);

            }

            appEvent.Details= details;

        }

 

        void WriteToBase4(ICollection events, int eventsDiscardedByBuffer, DateTime lastNotificationUtc)

        {

            // We don’t want to send any more events until we’ve waited until the _retryDate (which defaults to minValue)

            if (_retryDate > DateTime.UtcNow)

            {

                return;

            }

 

            try

            {

                ApplicationEvent appEvent = new ApplicationEvent();

 

                try

                {

 

                    if (eventsDiscardedByBuffer != 0)

                    {

                        WebBaseEvent infoEvent = new Base4BaseEvent(

                            SR.GetString(SR.Sql_webevent_provider_events_dropped,

                                eventsDiscardedByBuffer.ToString(CultureInfo.InstalledUICulture),

                                lastNotificationUtc.ToString(“r”, CultureInfo.InstalledUICulture)),

                                null,

                                WebEventCodes.WebEventProviderInformation,

                                WebEventCodes.SqlProviderEventsDropped);

 

                        FillParams(appEvent, infoEvent);

                        appEvent.Save();

                    }

 

                    foreach (WebBaseEvent eventRaised in events)

                    {

                        FillParams(appEvent, eventRaised);

                        appEvent.Save();

                    }

                    EventProcessingComplete(events);

                }

                catch

                {

                    // Ignore all errors.

                }

            }

            catch

            {

                // For any failure, we will wait at least 30 seconds or _commandTimeout before trying again

                double timeout = 30;

                _retryDate = DateTime.UtcNow.AddSeconds(timeout);

                throw;

            }

        }

 

        public override void ProcessEventFlush(WebEventBufferFlushInfo flushInfo)

        {

            // Remove Debug.Trace from sample Debug.Trace(“SqlWebEventProvider”, “EventBufferFlush called: ” +

            WriteToBase4(flushInfo.Events, flushInfo.EventsDiscardedSinceLastNotification,

                flushInfo.LastNotificationUtc);

        }

        public override void ProcessEvent(WebBaseEvent eventRaised)

        {

            if (UseBuffering)

            {

                base.ProcessEvent(eventRaised);

            }

            else

            {

                 WriteToBase4(new Base4BaseEventCollection(eventRaised), 0, new DateTime(0));

            }

        }

 

        protected virtual void EventProcessingComplete(ICollection raisedEvents)

        {

        }

 

        public override void Shutdown()

        {

            try

            {

                Flush();

            }

            finally

            {

                base.Shutdown();

            }

        }

    }

    public class Base4BaseEvent : WebBaseEvent

    {

 

        public Base4BaseEvent(string message, object eventSource, int eventCode) : base(message, eventSource, eventCode) { }

        public Base4BaseEvent(string message, Object eventSource, int eventCode, int eventDetailCode)

            : base(message, eventSource, eventCode, eventDetailCode)

        { }

    }

    public sealed class Base4BaseEventCollection : ReadOnlyCollectionBase

    {

        public Base4BaseEventCollection(ICollection events)

        {

            if (events == null)

            {

                throw new ArgumentNullException(“events”);

            }

 

            foreach (WebBaseEvent eventRaised in events)

            {

                InnerList.Add(eventRaised);

            }

        }

 

        internal Base4BaseEventCollection(WebBaseEvent eventRaised)

        {

            if (eventRaised == null)

            {

                throw new ArgumentNullException(“eventRaised”);

            }

 

            InnerList.Add(eventRaised);

        }

 

        // overloaded collection access methods

        public WebBaseEvent this[int index]

        {

            get

            {

                return (WebBaseEvent)InnerList[index];

            }

        }

 

        public int IndexOf(WebBaseEvent value)

        {

            return InnerList.IndexOf(value);

        }

 

        public bool Contains(WebBaseEvent value)

        {

            return InnerList.Contains(value);

        }

    }

}

Base4 and Castle

Something I found very useful to use in castle is Base4 

And as it so happens Nic Wise just asked me if you could use the 2 together. The answer is yes you can.

I know they support nhibernate but that is not really the same :)   Base4 just goes that extra mile.

Since data access is always the same. And the examples on the castle site use concrete implementations of data access objects. I thought I really don’t need a facility for the moment and can just use base4.

Anyway here is the class I use to do all my data access. it’s an abstract class that has concrete implementations so that I can extend the functionality if I need to.

The interface for the class (the colors look different on my screen I swear  the gray bits should be some type of blue and the yellow also isn’t quite right):

 

using System;

namespace TimeSheets.Application.Contract.Data

{

    public interface IDataObject<T>

    where T : class, Base4.Storage.IItem, new()

    {

        void Delete(T item);

        Base4.Storage.IItemList<T> Find(string oPath, string sortExpression, int pageNumber, int pageSize, out int pageCount);

        Base4.Storage.IItemList<T> Find(string oPath, string sortExpression);

        Base4.Storage.IItemList<T> Find(string oPath);

        Base4.Storage.IItemList<T> FindAll(string sortExpression, int pageNumber, int pageSize, out int pageCount);

        Base4.Storage.IItemList<T> FindAll(string sortExpression);

        Base4.Storage.IItemList<T> FindAll();

        Base4.Storage.IItemList<T> FindById(Guid Id);

        T GetById(Guid Id);

        T GetOne(string oPath);

        T Insert(T item);

        string SortExpression { get; set; }

        T Update(T item);

    }

}

 

And this is the abstract class :

 

using System;

using System.Collections.Generic;

using System.Text;

using Base4.Storage;

using System.ComponentModel;

 

namespace TimeSheets.Application.Base

{

 

    ///

    /// A generic class that handles data access through Base4

    ///

    ///

    public abstract class BaseDataObject<T> : Base.DataObject, Contract.Data.IDataObject<T> where T : class,IItem, new()

    {

        private string m_SortExpression;

 

        ///

        /// Gets or sets the sort expression.

        ///

        /// The sort expression.

        public virtual string SortExpression

        {

            get { return m_SortExpression; }

            set { m_SortExpression = value; }

        }

 

        ///

        /// Finds the specified in the object path.

        ///

        /// The Object path.

        ///

        public virtual IItemList<T> Find(string oPath)

        {

            //set the default sort expression to none

            this.m_SortExpression = string.Empty;

 

            //Get my stuff

            return StorageContext.Find<T>(initialiseObjectPath(new ObjectPath(oPath)));

        }

 

        ///

        /// Finds the specified Object path sorted by the provided sort expression.

        ///

        /// The Object path.

        /// The sort expression.

        ///

        public virtual IItemList<T> Find(string oPath, string sortExpression)

        {

            //Set the default sort expression

            this.m_SortExpression = sortExpression;

 

            //Get the data out

            return StorageContext.Find<T>(initialiseObjectPath(new ObjectPath(oPath)));

        }

 

        ///

        /// Finds the specified Object path with server side paging.

        ///

        /// The Object path.

        /// The sort expression.

        /// The page number.

        /// Size of the page.

        /// The page count.

        ///

        public virtual IItemList<T> Find(string oPath, string sortExpression, int pageNumber, int pageSize, out int pageCount)

        {

            //initialise the pagecount parameter

            pageCount = 0;

 

            //Set the pagecount parameter tot he actual value

            pageCount = (int)Math.Ceiling(Convert.ToDouble(getCount() / pageSize));

 

            //if we got a sort expression do initialize with it please

            if (!string.IsNullOrEmpty(sortExpression))

                m_SortExpression = sortExpression;

 

            //initialise the object pathe with the provide oPath parameter and initialise the order by

            ObjectPath path = initialiseObjectPath(new ObjectPath(oPath));

 

            //Tell Base4 which page to retrieve

            path.PageSize = pageSize;

            path.PageNumber = pageNumber;

 

            //Get the items

            return StorageContext.Find<T>(path);

        }

 

        ///

        /// Finds all items.

        ///

        ///

        public virtual IItemList<T> FindAll()

        {

            this.m_SortExpression = string.Empty;

            return StorageContext.Find<T>(initialiseObjectPath());

        }

 

        ///

        /// Finds all the items sorted.

        ///

        /// The sort expression.

        ///

        public virtual IItemList<T> FindAll(string sortExpression)

        {

            this.m_SortExpression = sortExpression;

            return StorageContext.Find<T>(initialiseObjectPath());

        }

 

        ///

        /// Finds all.

        ///

        /// The sort expression.

        /// The page number.

        /// Size of the page.

        /// The page count.

        ///

        public virtual IItemList<T> FindAll(string sortExpression, int pageNumber, int pageSize, out int pageCount)

        {

            //initialise the pagecount parameter

            pageCount = 0;

 

            //Set the pagecount parameter tot he actual value

            pageCount = (int)Math.Ceiling(Convert.ToDouble(getCount() / pageSize));

 

            //if we got a sort expression do initialize with it please

            if (!string.IsNullOrEmpty(sortExpression))

                m_SortExpression = sortExpression;

 

            //initialise the object pathe with the provide oPath parameter and initialise the order by

            ObjectPath path = initialiseObjectPath();

 

            //Tell Base4 which page to retrieve

            path.PageSize = pageSize;

            path.PageNumber = pageNumber;

 

            //Get the items

            return StorageContext.Find<T>(path);

        }

 

        //internal method that gives the rowcount of all items.

        protected int getCount()

        {

            return StorageContext.FindAll<T>().Count;

        }

 

        protected ObjectPath initialiseObjectPath()

        {

            ObjectPath path = new ObjectPath();

            return initialiseObjectPath(path);

        }

 

        ///

        /// Initialises the object path.

        ///

        /// The path.

        ///

        protected ObjectPath initialiseObjectPath(ObjectPath path)

        {

            //Do we have a sort expression

            if (!string.IsNullOrEmpty(m_SortExpression) && m_SortExpression.Trim().Length > 0)

            {

                //Give me all the sort parameters

                string[] sortParams = m_SortExpression.Split(“,”.ToCharArray());

 

                //Now go work out their object path

                foreach (string s in sortParams)

                {

                    //Work out the direction

                    OrderByDirection direction = s.IndexOf(“DESC”) > -1 ? OrderByDirection.Descending : OrderByDirection.Ascending;

 

                    //Work out the column

                    string orderby = s.Substring(0, s.IndexOf(“DESC”) > -1 ? s.IndexOf(“DESC”) : s.Length);

 

                    path.AddOrderBy(orderby, direction);

                }

            }

 

            return path;

        }

 

        ///

        /// Gets the Item as an item through a specified Object path.

        ///

        /// The Object path.

        ///

        public virtual T GetOne(string oPath)

        {

            return StorageContext.FindOne<T>(new ObjectPath(oPath));

        }

 

        ///

        /// Finds the item by id and returns a list.

        ///

        /// The id.

        ///

        public virtual IItemList<T> FindById(Guid Id)

        {

            IItemList<T> itemList = StorageContext.Find<T>(“Id=’{0}’”, Id.ToString());

            return itemList.Count > 0 ? itemList : new ItemList<T>() as IItemList<T>;

        }

 

        ///

        /// Gets the item by id as an item.

        ///

        /// The id.

        ///

        public virtual T GetById(Guid Id)

        {

            IItemList<T> itemList = FindById(Id);

            return itemList.Count > 0 ? itemList[0] : new T();

        }

 

        ///

        /// Inserts the specified item.

        ///

        /// The item.

        ///

        public virtual T Insert(T item)

        {

            ((IItem)item).Save();

 

            return item;

        }

 

        ///

        /// Updates the specified item.

        ///

        /// The item.

        ///

        public virtual T Update(T item)

        {

            ((IItem)item).Save();

 

            return item;

        }

 

        ///

        /// Deletes the specified item.

        ///

        /// The item.

        public virtual void Delete(T item)

        {

            ((IItem)item).Delete();

 

        }

    }

}

This is the concrete implementation:

 

using Fogcreek.FogbugzEx;

 

namespace TimeSheets.Application.Dao

{

    public class TimesheetDao : Base.BaseDataObject<Timesheet>

    {

    }

}

A bit further down the Castle track

I’ve been playing with castle all day yesterday and I can say the more I work with it the more I like it and start to see the full power of the framework.

My previous post was more about my frustrations when using the webforms model.

One of castle’s strengths is the Inversion of Control pattern, an introduction to IoC can be found on the Joy of code partI, partII.  In castle they apply it via the windsor container

An introduction to castle can be found on the codeproject :
Introducing castle part I
Introducing castle part II

If you’re creating a website that has not a lot of interaction or a very complex interface you probably won’t benefit too much from castle. However if you know your project will be growing a lot or the interface is becoming quite complex then you’re probably better of using castle.

Asp.NET gives you instant speed for development but this slows down as the project becomes larger and your pages more complex it loses it’s power and attraction, at least in my case.  If you have ever spent a couple of hours on a site figuring out why something doesn’t have a value that should have one etc. you’ll understand what I’m talking about probably.

Castle on the other hand starts to really shine the more complex you’re application gets.  You define something once and you can re-use it in your whole application.  This is nice it means I can define a master layout, have a standard layout render in that master so I have a page :)

If you then add a list to that page with a detail you’ll quickly see that you would be able to call that listview over and over again from anywhere.
Yes, I see the power of castle. The question is do you ?

Tomorrow I’ll start a series on how to create a master detail using castle if somebody is interested. One that walks you through all the steps involved because I had a hard time figuring some stuff out. Not too hard if you consider that it took me about 1 week to get started with asp.net and about 2 weeks to get started with the atlas library. I have a written a timesheet application in  a couple of hours

Am I too late ?

Yesterday I had a discussion with Alex about asp.NET where I vented some of the frustrations I have building a CRM application on one form with multiple user controls that have enough functionality in them that other people would create separate pages for it.

I don’t like the fact that you loose information about something you’re working on because you need to change a small piece of a bigger whole.

Anyway my problem is that to do something that is more complex than just simply displaying one form on a page asp.NET gets pretty complicated. Events are firing all over the place. I have to take viewstate into account. (I turned viewsate off because it was about 50K half way through my app.) And then it became even more difficult to get stuff to respond the way I want them to respond.

All in all it takes a really long time before I get something done properly. Where all it is actually doing is rendering small portions html out to the browser.

I have also been discussing with Alex on how to create something that takes a view definition and merges it with base4 domain objects to get to a view.

Well enter the castle project. I looked at it before but never really saw the point. Yesterday I got down and dirty with it and let me tell you I DO see the point now.

The way they built their framework resembles very closely how I was going to build ours.

It provides a more natural way of creating webpages in my mind than trying to force a winform programming model into a stateless model. Not to mention the speed in which you get things done.

I’m not the only one who joins the party this late

Underwear goes inside the pants

On myspace there is a page dedicated to a song Underwear goes inside the pants by Lazy Boy

I got to this page via the blog of a friend of mine,Miel Van Opstal 

It’s worth a listen definitely. An excerpt of the lyrics would be:

There are homeless people everywhere.
This homeless guy asked me for money the other day.
I was about to give it to him and then I thought he was going to use it on drugs or alcohol.
And then I thought, that’s what I’m going to use it on.
Why am I judging this poor bastard.
People love to judge homeless guys. Like if you give them money they’re just going to waste it.
Well, he lives in a box, what do you want him to do? Save it up and buy a wall unit?

Enjoy.

Open source project management

Yesterday I got to meet an Argentinian guy who joined the NBlogr

He showed me a couple of really cool sites that I thought would be a good idea to share.

An open-source resource oriented project management system : Open Workbench

An open-source search engine (finds open source programs built on Lucence) : Krugle 

And last but not least an article on the codeproject :

This article describes a “script engine” for the C# language. Here are some of the key features of the presented scripting system:

  • Based on the full featured C#.
  • Full access to CLR and CTS.
  • Can be run with any version of CLR (.NET 1.1/2.0), even potentially with future releases.
  • Possibility of extending the functionality of any CLR application with “scripting”, by hosting the script engine in this application.
  • Integration with common IDEs and debuggers.
  • Integration with OS.
  • Availability of comprehensive documentation (local and online Help, tutorials, samples…).

http://www.codeproject.com/csharp/cs-script_for_cp…

Adding a dynamic language to the toolbelt

It may or may not be a surprise to people that read my blog but I do web development and really like it.  But lately I feel constricted in my programming environment so I want to add another language. But let me share some of my history first.

I started programming as a young boy and wrote my first menu system for my pc when I was 8 I think. It was nothing really complicated a series of batch scripts so that my mother could easily navigate the pc. She has been having problems with a computer for as long as I can remember :)  

After that I moved to GW BASIC like other people will have had the pleasure of jumping back and forth to line numbers. A couple of years later I wrote snake in that language and some game related to soccer.

Around that time they put QuickBasic on the market and this is the point where everything that has the name Basic in it just looses my attention. Dim variable type = value just does NOT feel natural to me and that is just a variable declaration.. Don’t get me started on the other stuff.

So I kind of lost track of pc for a while and got a bass guitar instead.  Also a lot of fun, and the coolnes factor is not to be underestimated.

Next I came in contact with TurboPascal (I’m still not talking Object Oriented here) I think Turbopascal 4 and 5 it was, that I used to program some stuff in. Turbo pascal was a cool language and i wrote Black jack in that as my end project for the IT course in year 9 (3rd year of high school).

Next I didn’t really program at night anymore due to family related things and the fact that I didn’t have a pc at home would have a lot to do with it. This situation remained until I was 20 something. I must say that I did meet a lot of girls and learnt how to pour cocktails etc :)
In school I did get to do some programming I learnt dbaseIV and access.

So then I lost track of pc / IT all together for a while until I got a job as a helpdesk officer at an ISP. Helpdesk officer ==> Team leader ==> Junior Network engineer ==> Own business as webdevelopment.

I hadn’t programmed in a long time 8+ years but had asked all the companies that hired me if I could get to some programming soon. Of course I could but never really got to do it. That’s why I started my own company and so I re-learnt programming 3 years ago. I started with .NET in visual basic.NET that lasted about 5 days and switched to C#.

Now 3 years later I know how to write C#, javascript, actionscript, xml, xsl, xsl-fo, T-SQL And that would be it. Not too much but also not too bad for 3 years working knowledge.

Lately there has been ruby on stage as THE buzzword for productivity in programming. I think that it is more dynamic languages that are the strong point. Things I like in javascript are the anonymous functions or whatever they are called (  mypage.doSomework(‘myVar’, function(){ do some other work in a function }); ) would be an example.

Although a couple of people have been complaining about Joel’s posts on ruby. And I do think that on ruby Joel doesn’t hit the nail on the head but generally he does. John Lam strongly disagrees but he wrote rubyclr so he’s allowed to be defending his camp.

Anyway I felt it was time to learn a new language. Since everything I know are mostly C-based languages I toyed with the idea of C++ but then I feel like I should practice a different strain of languages.
As you may or may not know, I talk quite a lot to Alex James of Base4. And he put me on to ironpython.  The choice falls on IronPython or Ruby at this point.  On investigating both languages i came to the constation that they are both very similar like java and c# or javascript and actionscript.

And tonight I made a decission on which language to learn and it is IronPython
Going by the rule if both sides of a dilemma are equal chose the one with the biggest emotional response :) So I chose IronPython because it is invented by Guido Van Rossum (on wikipedia) who is a dutch guy which coincidentally happens to be my native language but I’m from Belgium so a very different accent.

I’ll let you know how the python adventures go as i move through them.. But depending on the workload I have I might not be able to do much with it in a short period of time.

In all fairness to flight center

Flight center has posted a comment on the post that dealt with flight center.

It’s quite lengthy so I’ll just reblog it in a proper post.

 

Hi Ivan,
Here’s our response to Craig, let me know if you have any more questions:
Hi Craig,
Your matter has now been discussed at length within our organisation, and while I can’t offer you the answer you want, I can certainly explain exactly how we have reached our conclusions, and offer further explanations to some of the questions you have posed. I have also posted this on your blog, and the other blogs that have been looking at this issue.
It’s very easy in blogs to portray a company as obstinate and heartless, but we have a very good track record of doing our utmost to solve any problem encountered by our clients – as you will appreciate, in the extremely competitive environment that is international travel, every client you have is one you try your best to keep.
But in this case we have assessed all of the evidence presented, including me personally contacting some of the people you have listed in your email, and have still come to the same conclusion – our agent would never have offered you indefinite validity on an overseas ticket.
The main reason for this are:
1. Tickets never have indefinite validity. This is one of the fundamental rules of air travel, and something our agents are trained in and reminded of on an ongoing basis. It would be just too high a liability for airlines to issue tickets such as this. In fact I can think of no tickets for services that have indefinite validity. Imagine cashing in an airfare bought in the early 1990’s for current air travel.
2. You had a complicated itinerary (hence the cost):
Outbound: Christchurch-Auckland-LA-Munich-Bratislava
Return: Bratislava-Munich-LA-Seattle-LA-Auckland-Christchurch
This is a very extensive return journey to book for an indefinite stay, something that would have been impossible to organise for indefinite travel, as you would have had to secure all these different legs to line up as indefinite.
3. You are a good client, you were spending a large amount of money with our agent whether buying one way or return, she would quickly have to take responsibility for her comments when you inevitably wanted to take up the return journey, and above all she is a well-respected and honest part of our team. Why, then, would she even consider ‘shafting’ you?
The evidence you have provided is circumstantial at best, and in a case of ‘he said, she said’, we, like any other organisation, will back our people when we are convinced our team member is genuine and the evidence compelling. She could not have said this by mistake, and the fact that not only would she not mislead you, but there would also be no real benefit from misleading you, speaks for itself.
Yes, we understand from the references you have provided that you felt you may not return. In this light, we strongly believe that you have either misheard or misjudged the terms of your tickets, thinking there would be some way to extend you tickets indefinitely. Again, it is extremely unfortunate, but there is no way to extend these tickets – we have exhausted every avenue over the past three months in searching for a way.
I apologise for bringing in the possibility of legal action, it was unnecessary, and a full and comprehensive answer should have always be the first port of call. This is the first time we have had to deal with a problem within a blog, and our response left much to be desired. I realise this will continue to be an ongoing debate within the blogosphere, but I offer anyone the chance to contact me and discuss this matter, as we want to keep our lines of communication open.
We have received a great deal of negative feedback (towards the individual concerned in particular) from the many people who have viewed this blog and the blogs that have now linked to it without offering our full side of the story. It is also our assessment that our company has already suffered a loss of business more than the costs of the refund, and will continue to suffer adverse effects from the posts. But we simply can’t pay off everyone who threatens our people with a high profile, negative blogging campaign. We will instead proactively discuss these points within whatever medium they come up, including this and any other blogs that have these threads, on a case by case basis. We would of course prefer the old method of conflict resolution, but we are quickly learning to adapt in this brave new world.
This is our point of view in a nutshell, and I hope you can understand that unless new and compelling evidence presents itself, it will not change. We would appreciate it if you could also remove the names and contact details of other participants in this dialogue; anyone wanting clarification or to make a further point is welcome to contact me on john.mcguinnessAT NOSPAMflightcentre dot co dot nz.

Yours sincerely,
John McGuinness
Communications Manager
Flight Centre (NZ) Ltd

A good visual of the asp.net page life cycle

While I was looking for something completely unrelated. I came across this picture.

It’s a detailed graph on the page life cycle with the controls covering both asp.net 1.1 and 2.0

http://pointerx.net/photos/screenshots/images/852/…

Page 1 of 212