2007-09-29

Derived properties that you don't want cached

I'm rewriting a Win32 application using ECO. One of the things this application allows the user to do is to specify their preferred unit of measurement for height, distance, depth, weight, and speed.

To achieve this I have an ECO class ApplicationSettings which is a singleton which identifies the units to use. I then have 2 properties for each value....

InternalLandingDistance
DisplayLandingDistance

The idea is that I should never expose Internal* properties in the GUI but display their corresponding Display* property instead. The Display* property will read/write the Internal* property and perform some kind of conversion on it.

If I always store my values using the unit with the highest accuracy (Distance = feet, Depth = millimetres, Weight = Pounds, Speed = KPH) then I just need to convert them by the correct factor when reading/writing from the Display* properties.

This isn't rocket science, obviously I can just implement these as reverse derived attributes right? The problem with this is that I am using a new EcoSpace per form, so if someone changes the ApplicationSettings.DistanceMeasurement in one EcoSpace my derived member has no way of knowing unless I want to put in synchronisation.

Well, my app can work stand-alone or on a network. I don't want to over complicate things, so what I decided I needed was a reverse derived property with no subscriptions + caching, but that just isn't possible.

Well, actually, it is. It's just so obvious! Instead of marking my members Derived I marked them as Transient and HasUserCode. I implement the code like so.....


public float DisplayLandingDistance
{
get
{
return ConvertDistanceToDisplay(InternalLandingDistance);
#if NeverDoThis
{EcoModeler generated code here}
#endif
}
set
{
InternalLandingDistance = ConvertDistanceToInternal(value);
#if NeverDoThis
{EcoModeler generated code here}
#endif
}
}



Now my values will be calculated every time they are read instead of being cached. The "#if NeverDoThis" bit is there so that EcoModeler will always put its auto-generated accessor code within a region that never executes. I could just put a "return" above it, but I don't like those "Unreachable code detected" warnings!

2007-09-10

Mr thicko thicky thickpants

Well, I feel stupid. Worse than that, I generally feel very thick. Especially yesterday!

So, what happened yesterday? I went to Bletchley Park. What a brilliant place! Walking around what was for approximately 50 years one of the biggest secrets of WWII was really quite strange. The stuff these people did was simply amazing!

One guy managed to figure out the inner workings of a machine none of them had ever seen just by looking at two *almost* identical messages enciphered using the same machine configuration. The second message was 5 characters shorter than the first because the operator repeated the message but decided to abbreviate some of the words to save some time. This was all they needed. Not only was the functionality of the machine divined but what seems to be the world's first (semi) programmable computer was then designed + built to decipher messages received by wireless and then transcribed to punch holes. This thing has a optical reader that works at 5,000 chars per second.

The mathematics involved in working this sort of stuff out must have been quite brain numbing. I left feeling quite depressed with myself but it was still a great experience. Standing next to a Bombe machine (cracked enigma codes) was brilliant!

2007-08-31

RetrieveChanges(out ignoredChanges)

Here's another quick blog about a new ECO IV feature. I asked for this during development because I had a particular problem I had to solve.

My new application has a class named "PlannedCall". When the user logs in they see a list of planned calls that are
A: Active
B: Not already actioned
C: Assigned to the current user, or not assigned to anyone
D: EarliestCallTime <= Today + 1

As time goes on there will be a lot of instances of this class in my DB, so obviously I want to use the OclPSHandle to select my objects otherwise I will end up with a whole load of objects in memory that I do not need. However, when another user actions the planned call the Actioned property becomes true. If I use an ExpressionHandle then this planned call would disappear from the presented list, but with OclPSHandle it will not. This is where the new feature comes in!

When you call PersistenceService.RetrieveChanges a collection of DBChange will be sent to the client from the server. The EcoSpace will inspect this list and create an IChange each time the DBChange refers to an object instance loaded into the EcoSpace cache.

In the past the DBChanges that do not apply were discarded, but now you can get hold of them. Take a look at this:


DBChangeCollection ignoredChanges;
EcoSpace.Persistence.RetrieveChanges(ignoredChanges);

//Do what you normally do with the IChange list here!

//Now check if there are relevant changes to objects we have not loaded
int plannedCallClassId =
EcoSpace.TypeSystem.GetClassByType(typeof(PlannedCall)).InternalIndex;

foreach(DBChange currentChange in ignoredChanges)
if (currentChange.ObjectId.ClassId = plannedCallClassId)
{
SelectPlannedCalls();
break;
}


SelectPlanendCalls will just execute an OclPsHandle...

sqlPlannedCalls.Expression = "PlannedCall.allInstances->select(...whatever...)";
sqlPlannedCalls.Execute();


This will only load objects that meet the correct criteria.

Finally, my expression handle can work on PlannedCall.allLoadedObjects instead of PlannedCall.allInstances - something like this


PlannedCall.allLoadedObjects
->select(active)
->select(not actioned)
->select( (assignedTo->isEmpty) or (assignedTo = var_CurrentUser) )
->select(earliestCallTime <= DateTime.Now.AddDays(1))


So now I have the benefit of being able to limit which objects I use by utilising the OclPsHandle, but also the benefit of the list being subscribed to so that changes to my local cache will add/remove rows in the grid displaying the planned calls + the list is updated if someone else updates a planned call.

I've just tested it in my application. I'm so pleased that I felt compelled to tell the world :-)

2007-08-23

ECO IV

CodeGear are letting some people blog about features in the next release of Delphi (Highlander) before it has even been released. How cool is that? A lot less secrecy, what a great move!

Sooooo. How about I spill some beans about ECO IV? Maybe I can find a thing or two to mention :-) I'll just throw together something unplanned, so expect a weird mixture of stuff in no logical order whatsoever!


Well, first of all, you're going to get a whole load of source code. I mean *lots* of it!


There's a new service called the ICacheContentService. Let's say you know for a fact that there is an object in the DB with the ECO_ID 1234 and it is a Customer. Instead of loading that object into the cache with an OclPS evaluation you can simply use this service to register it in the cache. I've found this really useful in an app I am writing where I use SQL to find customers matching a certain sales criteria. I then create an IObjectList of these customers by injecting their ID's into the cache.


There are some generics in there. The generated code is smaller as a result because you don't need a seconary "List" class for each class in your model. PersonListAdapter, CustomerListAdapeter etc. In addition to this it is easier to move from the Element world to the Business Object world. You can do stuff like this

MyElement.GetAsObject<Customer>.Name := 'Hello';
for currentCustomer in SomeElement.GetAsCollection<Customer> do
currentCustomer.DoSomething;


You can hibernate EcoSpaces. Can you deactivate an EcoSpace to a stream, a kind of snapshot. You can then later activate an EcoSpace from a stream, restoring the original state of the hibernated EcoSpace. This is useful when you want to shut down an application and then restart it where you left off. Maybe you could use it to "keep changes" made on the client without having to save them, and then later update the database. One certain use for this feature I see would be for storing EcoSpaces in a session where ASP.NET is using a DB as the session state so that multiple machines can serve the pages (a farm). I've done this using MonoRail rather than ASP.NET.


You can write ECO VCL.NET apps. Okay, it's not a big secret or anything, but I can tell you how good it is! I haven't played with TDataSet components in years now, but it all came rushing back to me as I played with components such as TExpressionHandle and TReferenceHandle, both descendants of TDataSet. Hooking up a TDBGrid to an ECO expression handle was so strange at first. It seemed like I was hooking up to a TQuery, that is until I started executing methods on the current row, and invoking state machine transitions. Then I felt at home again :-)


ASP.NET DataSource. ASP.NET stuff is so much more simple now. You have an EcoSpaceManager component on the web page that provides an instance of an EcoSpace. Then you have an EcoDataSource component which you can bind GUI controls to that are capable not only of showing the data of the selected ECO objects but also update it too without a single line of code. This approach is so much more simple than using all of those ECO handles on the form and having to manually update the form data into the objects.


ASP.NET Providers. You know 'em, the user, roles, settings etc that come with ASP.NET 2. Well, ECO supports a whole load of those too!


More frequent updates. You wont see this one in the box, this is one that ships afterwards. In the past ECO updates could only be shipped as part of a Delphi update. Well, things have changed. The ECO team (www.capableobjects.com) can now determine their own release schedules.


OCL enhancements. The OCL implementation used has now been extended so that it mirrors the .NET framework more. So you can do stuff like this (in OCL action language)

DatePosted := DateTime.Now.Date.AddDays(1)


BlackFish support. What else do I need to say?


Mapping enhancements. This one might not be a big feature that you'd write on a box, but it saves so much time! Remember in ECO III when you had to write a persistence mapper for every ENUM type in your model? No more! The mapper classes are now provided with type information when converting between DB/Model values so it is easy to write a single generic mapper class to work with any type (enums for example). In fact, you can now just set the PMapper of any ENUM to GenericEnumAsInteger and your enum property will be mapped to an INT column in your DB. What a time saver this one has proven to be!


Nullable types. You can now model your class attributes as Nullable<Integer> etc. So the following code...

Self.AsIObject.Properties.GetByLoopbackIndex(Eco_LoopbackIndices.DatePosted).AsObject := nil;

you can now do this

DatePosted := default(Nullable<datetime>);


Well, that's it for now. If I think of anything else I'll let you know. It's hard to remember what's new when you've been playing with it for so long!

Disclaimer: The code in this blog might not compile after shipping. Not because it is subject to change and other such legal stuff, but because I am typing from memory :-)

2007-08-21

More secure passwords

Storing a plain-text password in a DB leaves your system open to abuse from anybody with access to the tables (sys admin for example). Here is the technique I recently used. Instead of storing the password itself I store a hash of the password, and a random "salt" that was used to create the hash to make it less predictable.


[UmlTaggedValue("Eco.Length", "255")]
private string PasswordHash
{
get
{
...
}
set
{
...
}
}

[UmlTaggedValue("Eco.AllowNULL", "True")]
[UmlTaggedValue("Eco.Length", "40")]
private string PasswordSalt
{
get
{
...
}
set
{
...
}
}



As you can see I do not store the password, just a hash + salt, both of
which are private. Here are the methods used to set the values of these properties.

public void SetPassword(string newPassword)
{
if (newPassword == null)
throw new ArgumentNullException("password");
if (newPassword.Length < 6)
throw new ArgumentException("Password must be at least 6 characters");

PasswordSalt = Guid.NewGuid().ToString();
PasswordHash = EncryptPassword(newPassword);
}

private string EncryptPassword(string password)
{
TripleDESCryptoServiceProvider des;
MD5CryptoServiceProvider hashmd5;
byte[] pwdhash, buff;
hashmd5 = new MD5CryptoServiceProvider();
pwdhash = hashmd5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(PasswordSalt));
hashmd5 = null;
des = new TripleDESCryptoServiceProvider();
des.Key = pwdhash;
des.Mode = CipherMode.ECB; //CBC, CFB
buff = ASCIIEncoding.ASCII.GetBytes(password);
string result =
Convert.ToBase64String(
des.CreateEncryptor().TransformFinalBlock(
buff, 0, buff.Length)
);
return result;
}


Now the code required to get a user by ID/Password

public static User FindUserByUserIDAndPassword(IEcoServiceProvider serviceProvider, 
string userID, string password)
{
string query =
string.Format("->select(userID.sqlLikeCaseInsensitive('{0}'))",
BusinessClassesHelper.EscapeOcl(userID));

User user =
BusinessClassesHelper.SelectFirstObject(serviceProvider, "User", query);

if (user != null && user.EncryptPassword(password) == user.PasswordHash)
return user;
return null;
}


The BusinessClassesHelper is a class with static methods to help with OCL operations, such as escaping user input values to avoid OCL injection.

using System;
using System.Collections.Generic;
using System.Text;
using Eco.ObjectRepresentation;
using Eco.Services;
using Eco.Handles;

namespace xxxxxxxxxxxxxxx
{
public static class BusinessClassesHelper
{
public static string EscapeOcl(string ocl)
{
if (string.IsNullOrEmpty(ocl))
throw new ArgumentNullException("ocl");

ocl = ocl.Replace("\\", "\\\\");
ocl = ocl.Replace("'", "\\'");
return ocl;
}

public static T SelectFirstObject(IEcoServiceProvider serviceProvider,
string className, string criteria)
{
if (serviceProvider == null)
throw new ArgumentNullException("serviceProvider");
if (string.IsNullOrEmpty(className))
throw new ArgumentNullException("className");
if (criteria == null)
throw new ArgumentNullException("criteria");

IOclPsService psService =
EcoServiceHelper.GetOclPsService(serviceProvider);
IOclService oclService =
EcoServiceHelper.GetOclService(serviceProvider);

IObjectList result =
psService.Execute(className + ".allInstances" + criteria);
result =
oclService.Evaluate(className + ".allLoadedObjects" + criteria) as IObjectList;
if (result.Count == 0)
return default(T);
else
return (T)result[0].AsObject;
}
}
}

2007-08-17

Printing bitmaps using CPCL

I've had no end of grief trying to print a PCX to a Zebra Printer using the CPCL printer language. Silly me, didn't notice the EG command (expanded graphics) so there was no need to convert my BMP to a PCX and then struggle with binary data.

I still had a bit of grief working out how to print using the EG command because the documentation is quite frankly crap. The expected command format is

EG {WidthInBytes} {HeightInPixels} {XPos} {YPos} {Data}\r\n


The printer expects a 1 bit pixel matrix. So if pixel(0, 0) is set you will set "80" in the data. If pixel(0, 0) is set and pixel (7, 0) is also set you would sent "81". Basically what you need to do is to read each set of 8 horizontal pixels and then use bit operations to create a byte value 0..255, and then output this as hex 00..FF.

Here's the routine :-)

  public void DrawBitmap(Bitmap bmp, int xPosition, int yPosition)
{
if (bmp == null)
throw new ArgumentNullException("bmp");

//Make sure the width is divisible by 8
int loopWidth = 8 - (bmp.Width % 8);
if (loopWidth == 8)
loopWidth = bmp.Width;
else
loopWidth += bmp.Width;

DataString.Append(string.Format("EG {0} {1} {2} {3} ", loopWidth / 8, bmp.Height, xPosition, yPosition));

for (int y = 0; y < bmp.Height; y++)
{
int bit = 128;
int currentValue = 0;
for (int x = 0; x < loopWidth; x++)
{
int intensity;

if (x < bmp.Width)
{
Color color = bmp.GetPixel(x, y);
intensity = 255 - ((color.R + color.G + color.B) / 3);
}
else
intensity = 0;

if (intensity >= 128)
currentValue |= bit;
bit = bit >> 1;
if (bit == 0)
{
DataString.Append(currentValue.ToString("X2"));
bit = 128;
currentValue = 0;
}
}//x
}//y
DataString.Append("\r\n");
}

2007-08-15

The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

This new compile error was driving me mad! My project used to work, then I upgraded to a newer version of ECO.

There were 3 projects using an EcoSpace, 2 would compile but not the 3rd. In the end the solution was simple but annoying to track down so I thought I'd mention it here just in case anyone ever strolls across it and finds it useful.

In my licenses.licx files for the 2 projects I had the line

Eco.Handles.DefaultEcoSpace, Eco.Handles

but in the project that was failing somehow a strong name had been used, even though I originally entered it manually! Changing it back to the weak name fixed the problem.

2007-07-18

Source control

At work we use SourceSafe for our version control, and I used SourceAnyWhere as the client. I would like to outline the facts and let you decide what happened for yourself.

01: I checked out my model (EcoModeler)
02: I added a new class.
03: Generated code.
04: Checked in the mode.

05: Checked out.
06: Added two new associations
07: Generated code.
08: Checked in.

09: Checked out.
10: Added a parameter to a method.
11: Changes a state machine diagram.
12: Checked in.

After each of the changes I would implement code in my application that used them, so if the changes were lost at any point during this process then I would notice because my app would no longer compile.

Today I checked out the model and noticed that my new class was missing. I backed up my generated source code and then regenerated code from the model. Using BeyondCompare I checked for differences and came up with the list of changes in the steps above.

Somehow the model in my source control had 3 revisions missing. I checked the history and sure enough there were no check in actions since June 27th. I had to recreate the changes in my model by reading the differences.

So, what do you think caused this problem?

2007-07-13

UndoBlocks, SyncServer, and multiple EcoSpaces

In an ECO app I am writing it is very important that updates by other users are identified as soon as possible. This is because there will be multiple users all selecting jobs to do from a predefined list, and it is not only possible but also very likely that more than one user will select the same job at the same time (the job at the top of the list).

To implement this I chose to use the remote persistence feature of ECO and in particular its Sync Server feature. Every 5 seconds my client apps will contact the server and retrieve a list of updates made by other users, it will then apply those updates to the cache of its own EcoSpace. So when a user selects a job to do at the top of the list their app will stamp it as InProgress and update the DB, the job will then automatically disappear from the screens of all other users.

As I like to use undo blocks in my app to allow the user to cancel changes (using my DirtyObjectCatcher component) it is highly likely that during these synchronisation operations there will be an active undo block. As a result of this any changes applied to the cache via the sync' process will be caught by the undo block. If the user decides to cancel their changes then all values in the undo block will be undone, including the synchronised changes from other users. This isn't a great problem for me because I am using the TimeStamp feature in ECO to ensure that when I update the DB I am actually updating the same values I originally fetched, but it does mean that the user is looking at old data. For example

01: User A is looking at a list of jobs, they choose to action Job 1
02: User B sees Job 1 disappear from their screen and choose to action Job 2
03: User A sees Job 2 disappear from their screen.

So far, so good. Now remember, the change of state to Job 2 was caught by the undo block that was activated when User A started to edit Job 1.

04: User A decides to cancel their changes.
05: The active undo block reverses its changes, this includes the changes applied by the sync server.

As a result user A sees Job 2 reappear on their screen.

This didn't cause me an operational problem, because when actioning a job I do this...

01: Assign to current user
02: Update DB
03: If no exception then continue to edit
04: If OptimisticLockException occurs then unload the job and tell the user another user is working on the same job and ask them to select another + unload the job from the cache (will causes it to be reloaded with the new info and disappear from the screen).

This is essential anyway because the Sync server only makes it less likely to experience a conflict, it does not make it impossible (always use OptimisticLocking unless overwrites are acceptable!).

Still, I wanted to prevent the user from seeing this message as much as I possibly could. The answer is very simple, you just use a new instance of the EcoSpace for editing the job. The UndoBlock isn't actually needed now, but I still have it because the DirtyObjectCatcher provides a great way of providing validation feedback to the user. When the UndoBlock is reversed though it will revert to old values in the new EcoSpace instance, which is irrelevant because it will be disposed once the owning form is closed. Any updates to the data will appear in the main app within 5 seconds due to the fact that the second EcoSpace instance will also use remote persistence to save the changes which will in turn ensure that all other EcoSpaces (in the same app or on different machines) will be notified of the changes.

I think I might play with this technique some more. It looks like quite a nice way of working with ECO and multiple users!

2007-07-10

CapableObjects announced

There are exciting times ahead! The team that brought you ECO for Delphi are now a separate company named Capable Objects (http://www.capableobjects.com).

ECO will still be part of the next release of Delphi, but according to the official statement (http://dn.codegear.com/article/3673>) and comments in the newsgroups it is quite obvious there will also be a version of ECO for Visual Studio!

In the past the ECO guys released patches for ECO bugs, unfortunately it was not always possible to provide a patch. With the guys now being a separate company they are free to decide their own release schedules. Instead of having to wait for a Delphi update they can now release updates as often as they wish. Just like the good old BoldSoft days when we used to get an official update really quickly if ever a critical bug was found. Not only that, but it would also seem that they will be shipping source code too!

But that's not all John!

The guys at Capable Objects have asked me to write documentation. I've been adding XMLDoc to the source code so the API help will be more up to date. As soon as possible I will be writing a quick start document for Delphi (and any other IDE's) too.

Can't wait! :-)

2007-06-23

I've changed my mind on MonoRail

It is amazing!

I'll leave that previous post just in case anyone ever needs to create an ECO object instance first and then connect it with an EcoSpace afterwards, but you don't need it for MonoRail!

  public void Join([EcoDataBind("User")]User user)

{

PropertyBag["User"] = user;

if (this.Params["User.FirstName"] != null)

{

GetErrors(user.AsIObject(), Errors);

if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)

Errors.Add("Email address confirmation does not match email address.");

if (Errors.Count == 0)

{

//TODO EcoSpace.UpdateDatabase();

Redirect("Account", "Home");

}//No errors

}

}


That's all there is to it now! I have created a small set of classes to enable ECO support in MonoRail. Instead of descending your controllers from SmartDispatcherController you will now descend from EcoSmartDispatcherController. This gives the following abilities:

  1. The EcoSpace instance is created automatically for each request.

  2. If the EcoSpace is dirty at the end of the request it is serialized and stored in the Session. Next request it is deserialized from the Session instead of being created new.

  3. You can use the [EcoDataBinder] attribute as displayed above. This will ensure that the object instance of the parameter type is created within the EcoSpace of the controller. No need to modify your business classes at all. Form data is automatically bound to the object, and when posted the binding is automatically reversed.

  4. You can add the ExternalId to your form as a hidden input like so $EcoFormHelper.ExternalId("User", $User).



I really hope I don't find an insurpassable obstacle with this framework, because I can really see myself liking it!

MonoRails, loving it

So I didn't like Ruby on Rails much. More accurately I didn't like the Ruby language or ActiveRecord much, but the "Rails" part I really quite liked! So my investigation continues and I have found myself looking at MonoRails.

MonoRails is what I would have as a child called "a rip off", but these days it is known as a "clone" :-) It's basically a .NET version of Rails, which obviously appeals to me because I liked the Model-View-Controller approach of Rails and I obviously like C#.

MonoRails has its own version of ActiveRecord (which I shall be avoiding) and an interface into NHibernate too (which I haven't looked at in great depth, but it certainly doesn't look as powerful as ECO). So I have been trying to get MonoRails working with ECO instead. Considering I don't know MonoRails at all I am surprised at how quickly I managed to do what I wanted. Take the following controller method as an example, when the user visits localhost/Account/Join the following method will be executed...

public void Join([DataBind("User")] User user)


MonoRails will automatically create an instance of User (my ECO class) and then automatically populate its contents from the form that was posted. The first problem I had here was that ECO classes have no parameterless constructors. I could have gone down the following route:

public void Join(string salutation, string firstName,
string lastName, string emailAddress, string password)


but that would mean I have to create the User instance myself and then populate its properties from the parameters. The problem with this approach is that I am just too damned lazy :-) So, my first hurdle was to allow ECO classes to be constructed + have their properties set without an EcoSpace. I created a base RootClass that all of my business classes will ultimately descend from, and then I did this...

First I created a simple class that implements IContent and stores the values in a Dictionary.
 internal class TemporaryCache : IContent
{
private Dictionary<int, object> MemberValues = new Dictionary<int, object>();
#region IContent Members
object IContent.get_MemberByIndex(int index)
{
return MemberValues[index];
}

void IContent.set_MemberByIndex(int index, object value)
{
MemberValues[index] = value;
}

#endregion

public void ApplyValues(IContent destination)
{
foreach (int currentKey in MemberValues.Keys)
destination.set_MemberByIndex(currentKey, MemberValues[currentKey]);
}
}


All other methods of IContent throw a NotImplementedException, all we need here is get_MemberByIndex and set_MemberByIndex so that simple property values (not associations) may be set before the object has an EcoSpace. At some point we need to update the values in the EcoSpace, which is exactly what ApplyValues does, it just copies its own values across to a target IContent.

Now we need to use this class as our business class’s IContent and implement a parameterless constructor.

  public RootClass()
{
eco_Content = new TemporaryCache();
}


And finally we need at some point after creation + setting the property values to be able to attach this object + its contents to the new EcoSpace.

  public void AttachToEcoSpace(DefaultEcoSpace ecoSpace)
{
if (ecoSpace == null)
throw new ArgumentNullException("ecoSpace");
if (this.eco_Content != null && !(this.eco_Content is TemporaryCache))
throw new InvalidOperationException("EcoSpace already assigned");

TemporaryCache oldContent = (TemporaryCache)eco_Content;
eco_Content = null;
this.Initialize(ecoSpace);
oldContent.ApplyValues(eco_Content);
}


Now my Join() method looks like this:
  public void Join([DataBind("User")] User user)
{
user.AttachToEcoSpace(EcoSpace);
PropertyBag["User"] = user;
if (this.Params["User.FirstName"] != null)
{
user.GetErrors(Errors);
if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)
Errors.Add("Email address confirmation does not match email address.");
if (Errors.Count == 0)
{
EcoSpace.UpdateDatabase();
Redirect("Account", "Home");
}//No errors
}
}


Standard stuff


I descend all new Controller classes from my own abstract BaseController which merely creates a new EcoSpace at the start of a request and disposes it at the end. This class also exposes an Errors property (List<string>) that I can add errors to.

 public class BaseController : SmartDispatcherController
{
protected readonly List<string> Errors = new List<string>();

protected override void Initialize()
{
base.Initialize();
EcoSpace = new ApplicationEcoSpace();
PropertyBag["Errors"] = Errors;
}

protected override void ReleaseResources()
{
EcoSpace.Dispose();
base.ReleaseResources();
}

private ApplicationEcoSpace ecoSpace;
protected ApplicationEcoSpace EcoSpace
{
get
{
ecoSpace.Active = true;
return ecoSpace;
}
private set
{
ecoSpace = value;
}
}
}

My Account controller then descends from this, like so:
 [Layout("default")]
[Rescue("generalerror")]
public class AccountController : BaseController
{
public void Join([DataBind("User")] User user)
{
user.AttachToEcoSpace(EcoSpace);
PropertyBag["User"] = user;
if (this.Params["User.FirstName"] != null)
{
user.GetErrors(Errors);
if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)
Errors.Add("Email address confirmation does not match email address.");
if (Errors.Count == 0)
{
EcoSpace.UpdateDatabase();
Redirect("Account", "Home");
}//No errors
}
}
}


Why do I set PropertyBag["User"] = user;? Simple, I can then databind to it in my view...
<h2>Join</h2>
$HtmlHelper.Form("Join.rails")
<table summary="Enter your information">
<caption>Please provide the following information.</caption>
<tbody>
<tr>
<th>
<label for="User.Salutation">Salutation</label>
</th>
<td>$HtmlHelper.InputText("User.Salutation", $User.Salutation)</td>
</tr>
<tr>
<th><label for="User.FirstName">First name</label></th>
<td>$HtmlHelper.InputText("User.FirstName", $User.FirstName)</td>
</tr>
<tr>
<th>
<label for="User.LastName">Last name</label>
</th>
<td>$HtmlHelper.InputText("User.LastName", $User.LastName)</td>
</tr>
<tr>
<th>
<label for="User.EmailAddress">Email address</label>
</th>
<td>$HtmlHelper.InputText("User.EmailAddress", $User.EmailAddress)</td>
</tr>
<tr>
<th>
<label for="ConfirmEmailAddress">Confirm email address</label>
</th>
<td>$HtmlHelper.InputText("ConfirmEmailAddress", $ConfirmEmailAddress)</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<input type="Submit" value="Join"/>
</td>
</tr>
</tfoot>
</table>
$HtmlHelper.EndForm

Did you notice the [Layout("default")] on my AccountController? That tells MonoRails to use my default.vm file as the master page for the HTML output. It basically sets up the header information etc and displays any errors, here is how it looks:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<meta name="keywords" content="" />
<title>NotASausage</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link href="/StyleSheets/Main.css" rel="stylesheet" media="all" type="text/css" />
<link href="/StyleSheets/Menu.css" rel="stylesheet" media="all" type="text/css" />

<link href="/StyleSheets/ThumbnailViewer.css" rel="stylesheet" media="all" type="text/css" />
<script src="/Scripts/ThumbnailViewer.js" type="text/javascript"></script>
</head>
<body>
<div id="MainFrame">
<div id="Header"><img id="HeaderBanner" src="/Images/Banner.jpg" alt="NotASausage" /></div>
<div id="SideBar">
<div id="MainMenu">
<h2>Menu</h2>
<ul>
<li><a href="/default.aspx">Main page</a></li>
</ul>
</div>
</div>
<div id="Contents">
#if ($Errors && $Errors.Count > 0)
<ul class="ErrorList">
#foreach($ErrorMessage in $Errors)
<li>$ErrorMessage</li>
#end
</ul>
#end
$childContent
<div id="Footer">
©Peter Morris - All rights reserved.
</div>
</div>
</body>
</html>

The $childContent indicates where to inject the output of the current page.

How cool is that? I can now model in ECO, generate my business objects, automatically generate my DB, and then finally put together some very simple application actions which will control access to those business objects and display any validation problems back to the user!

So far I like what I see!

2007-06-21

Quantum bugs

I've just been driven mad by a "Quantum bug". What is a quantum bug? Well, it's a bug that doesn't exist unless you look at it :-)

I kept getting a NullReferenceException in my code. It happened whenever I changed a property of a class. I assumed it was something in the framework, but it was in fact in a library of my own. When the property changed it would trigger an observer I had attached, the purpose of this observer is to check if the form that owns my component is the active form, if it is then it records the object that changed; the purpose is to know which objects were modified by which form.

Anyway, to check if the form is active I do this

Form activeForm = System.Windows.Forms.Form.ActiveForm;
Form activeMdiChildForm = activeForm.ActiveMdiChild;
if (activeForm == this.Form || activeMdiChildForm == this.Form)
.....

Now the quantum!

If you are debugging your app Form.ActiveForm will always return null! How useful is that eh? :-) So, when debugging my app I get a NullReferenceException, when I let it run through I don't.

There was me being a good boy, stepping through the code I had just added to check it does what it is supposed to, and Bill punishes me for it!

Damn :-)

2007-06-16

Cocoa, falling at the first hurdle?

I was REALLY looking forward to programming some apps on the Mac using Cocoa and objective-C.

I am reading "Cocoa programming for MAC OS X" by Aaron Hillegas. I had read as far as page 30 when I saw the following:

"Objective-C is a very simple language. It has no visibility specifiers: All methods are public; and all instance variables are protected. (Actually, there are instance specifiers for instance variables, but they are rarely used. The default is protected, and that works nicely.)"


WHAT?

Works nicely? I disagree! It is actually possible to make a class's method private by not including it in the interface declaration (myclass.h) and just adding the implementation file instead, but what about protected methods?

public
I expose as little as needed to ensure the class provides the service it was designed to. The signatures/names of members in my public area change as little as possible so as not to break other people's code. Public members are therefore inflexible.

protected
I am happy to expose a few more methods here. Typically virtual methods so that anyone inheriting from my class can hook in their own code and modify the behaviour of my class. Protected members are a little more flexible to change as fewer lines of code depend on them, but I still try to change them as little as possible.

internal / friend
These are vital to me. They allow me (and only me) access to certain members of other classes. I find this invaluable when I need to have some kind of collaboration between classes in a framework that nobody else should have access to, including descendants of my own classes that are not in the same assembly/module.

private
An absolute necessity. Access to some members would allow a consumer to manipulate members of your class without obeying "the rules". This would include adding items to a private array member without going through the relevant method which might perform other tasks such as


  1. Enforcing read-only.

  2. Checking security permissions.

  3. Executing other methods to enforce proper business procedure.



As you can see, I feel very strongly about encapsulation. It provides flexibility to change the way you provide your service and enforces correct behaviour. When you order new computer from a manufacturer you don't walk along the conveyor belt interfering with the way each element is added do you? You interface with the manufacturer using the approved process (public PlaceOrder) and then leave them to get on with the 100 or so private procedures they go through to fulfill the order.

Never before have I read so little about a language before deciding it is a waste of time proceeding! Maybe encapsulation will be implemented along with the new garbage collection features (I wont go into reference counting) that is coming in the next version of Mac OS X. I'll have to wait and see!

Enough rails for me

That's it, I've had enough of Ruby on Rails!

I like the rails part, it's a very clever approach, but I really dislike the Ruby part!

The final straw occurred yesterday. It's very common in OOP to have the constructor set default values for your object just in case the consumer of your class does not set them. In C# I would do something like this.....

public class Post : MyBaseClass
{
private bool isPost = true;
}

In Ruby I was trying to achieve this simple behaviour...

01: IsPost is set to true
02: A form is displayed with the default value
03: User changes the value
04: My Post instance is updated with the values from the form

I tried to override Initialize() only.

def Initialize
super
is_post = true
end

Now Post.new(params[:post]) is not reachable for some reason.


So in my Post class I did this

def Initialize(* params)
super(params)
is_post = true
end

The problem here is when I try to initialize the values from the form in my controller class.

post = Post.new(params[:post])

This means that the super(params) is called (which sets is_post to the value in the form) and then sets is_post = true. So my value is always true regardless of what the user enters.

So I tried putting "is_post = true" before the call to super, but that throws a null reference exception.

Finally someone told me to merge hashes like this

{:is_post => true}.merge(params);
super(params)

I don't recall the error, but that didn't work either.


I'm sorry, but ANY language that gives me this much hassle just to set a default is not worth it.

C# on rails, now *that* would be nice!

2007-06-01

Installing Rails on a Mac

What a lot of grief!

After trying more simple steps on another site (and failing) I eventually managed to get the following steps to work:

http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx


There were however some changes:

01: Make sure you download TextMate. I kept reading it as TextEdit for some reason, so just be aware of that when "mate ~/.bash_login" wont work.

02: One of the steps tells you to type "make ~/.bash_login". I found that adding the suggested text to that file made no difference when you log out/in or close the Terminal window. This was because a file named "~/.bash_profile" existed. If this file exists then you should modify it instead of the one mentioned in the article.

03: When trying to do "rake db:migrate" I would experience the following error:

otherwise you will experience the following error:
dyld: NSLinkModule() error
dyld: Library not loaded: /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib
Referenced from: /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7/lib/mysql.bundle
Reason: image not found
Trace/BPT trap


This is because the latest version of MySql has changed a path from lib/mysql to just lib/. To fix this problem you need to type the following (it's all one line):

sudo install_name_tool -change /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib /usr/local/mysql/lib/libmysqlclient.15.dylib /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7/lib/mysql.bundle

Right then. Now that those Hellish few days are over it's time to get annoyed with trying to write a website I suppose :-)

2007-05-16

Convert absolute path to relative path

Today I needed to convert an absolute path to a relative path based on a specified base path. E.g.

c:\a\b\c -> c:\a\b\c\d\file.txt = d\file.txt
c:\a\b\c -> c:\a\file.txt = ..\..\file.txt
c:\a\b\c -> c:\a\x\file.txt = ..\..\x\file.txt

I am surprised there is nothing in the .NET framework so I had a hunt around and converted the code from the following URL (http://www.vergentsoftware.com/blogs/ckinsman/default.aspx?date=2006-08-07) into C#....


private string RelativePath(string absolutePath, string relativeTo)
{
string[] absoluteDirectories = absolutePath.Split('\\');
string[] relativeDirectories = relativeTo.Split('\\');

//Get the shortest of the two paths
int length = absoluteDirectories.Length < relativeDirectories.Length ? absoluteDirectories.Length : relativeDirectories.Length;

//Use to determine where in the loop we exited
int lastCommonRoot = -1;
int index;

//Find common root
for (index = 0; index < length; index++)
if (absoluteDirectories[index] == relativeDirectories[index])
lastCommonRoot = index;
else
break;

//If we didn't find a common prefix then throw
if (lastCommonRoot == -1)
throw new ArgumentException("Paths do not have a common base");

//Build up the relative path
StringBuilder relativePath = new StringBuilder();

//Add on the ..
for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
if (absoluteDirectories[index].Length > 0)
relativePath.Append("..\\");

//Add on the folders
for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
relativePath.Append(relativeDirectories[index] + "\\");
relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);

return relativePath.ToString();
}

2007-05-08

Acer Skoda - I mean Ferrari

I have owned an Acer Ferrari now for approximately 13 months. Previously I had a problem with it freezing randomly. I spent some time trying to reproduce the problem and after a month or two was finally able to reproduce it 100% of the time. I sent my laptop back to Acer armed with exact steps and as a result my laptop was returned to me after only a couple of days.

For the past 2-3 months I have been seeing very rare, random resets. Obviously I blamed Windows, as you do :-) Some software I had to convert AVI to MPEG would always reset the laptop, but I just put that down to dodgy software. More recently I noticed that 7-zip would also reset my laptop *only* if I used ULTRA compression on a file larger than 500MB.

So I tried Winzip, same problem. I installed Vista and tried both Winzip and 7-zip in there, same problem. With steps to reproduce + proof that it was not the OS I felt I could finally send the laptop back for repair without running the risk of it coming back "No fault found".

The problem lies in my opening sentence "I have owned an Acer Ferrari now for approximately 13 months". The problem is that in my quest to find 100% reproducible steps my warranty has expired by about 30 days.

I explained all of this to the guy on the phone at Acer. "If only more customers were like you!" he said, and then continued to tell me that I had to pay for the repair. After talking over me for a few minutes I finally lost my temper and swore at him, something I have never done before.

So, now I am really annoyed! I have an old £400 Hewlett Packard laptop that has been working (slowly) without problems for years now, yet I pay thousands for a laptop with a quality brand such as "Ferrari" and it breaks twice in 13 months! I'd expect the quality to be better, wouldn't you?

What *really* annoys me though is that Acer didn't send me a card telling me my warranty was nearly up and asking if I would like to extend it (which I would have done), and ofcourse I had no idea it was coming to an end because the receipt is.......surprise surprise it is with my accountant who is preparing last year's accounts!

Ferrari I am disappointed. You have put your name to a machine that has the body of an F1 and the inner workings of a cheap old banger!

Sprites

Someone sent me this on Skype this morning. I think it's really cool, it reminds me of the hardware sprites on the old Commodore 64!

01: Go to any site with lots of images (image search on google is a good one)
02: Once the images appear copy/paste this text into your address bar.

javascript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI=document.images; DIL=DI.length; function A(){for(i=0; i<DIL; i++){DIS=DI[ i ].style; DIS.position='absolute'; DIS.left=Math.sin(R*x1+i*x2+x3)*x4+x5; DIS.top=Math.cos(R*y1+i*y2+y3)*y4+y5}R++}setInterval('A()',5 ); void(0);

2007-02-16

ECO JumpStart

I've been up quite late working out what I want to cover in the "ECO jump start" document. The trick is to start at a level where the user knows absolutely nothing, and end up where they know enough to decide whether or not they wish to spend some time learning how to use ECO or not.

I think I should start off explaining why multi-tiered app development is a good idea; then I think I should go on to creating a package in a DLL; then onto creating a non-persistent WinForm app using that package; then make it persistent; and so on. My problem is that ECO just does so much!

I'll paste what I have so far at the bottom of this blog entry to get any feedback. As the list progresses the subjects become more advanced and I am worried that the jump start might actually frighten people off by making them feel overwhelmed. Maybe I should just take it so far and then leave the more advanced items out? Maybe I should just include a section at the end of the document explaining what advanced features are available without demonstrating how to use them? Then maybe do an "Advanced ECO" document in which I follow on where I left off and demonstrate the more advanced stuff.

At the moment I am undecided, what do you think? Remember, this document is really intended to server two purposes; it should be there to help new users to understand ECO, and also to act as a promotional ("marketing" if you like) document to boast how good ECO is without scaring people away.

And now the table of contents:


Overview
Why create separate layers (GUI / Business / Data access)
An overview of how ECO uses UML as the design tool to sepcify the business layer
Explain that ECO can persist those business objects in a number of ways, the DB being switched in a matter of seconds


Creating a business classes package
Create project
Add references
Configure package settings
Add 2 classes with n--m association
Add properties
Add association
Generate code

Creating a non-persistent WinForm app
Create ECO WinForm project
Add reference
Select packages

Adding some simple GUI
Delete all but rhRoot
(Design support)
Set EcoSpace type
Add DataGrid
Set EcoSpaceType
Add ExpressionHandle (allInstances)
Bind the datagrid to the ExpressionHandle

Adding new objects
Add EcoListActions
Set RootHandle
Add button
Set the ListAction to Add

Drag and drop
Add another ExpressionHandle
Add another DataGrid
Add another "Add" button
Add CurrencyManagerHandle, set parent = DataGrid1
Add ExpressionHandle, expression = "self.AssociationToOtherClass"
Add a DataGrid
Add DragDrop extender
DataGrid2 = DragSource
DataGrid3 = DropTarget
Run + create + drag/drop

Making the objects persistent
Add XML persistence to EcoSpace
Add EcoGlobalActions to form
Connect to RootHandle
Add button
Set GlobalAction to UpdateDatabase
Run, create, save, close, run

Creating a database
Remove the XmlPersistence
Add DB persistence + set configuration
Add connection
Generate DB
Run, create data, save

Evolving a database
Add new properties
Rename a property + set FormerName
Add new super class + move property up
Evolve DB
Run, show same data

Quick prototyping the model
Set AutoForm = True on grids
Run app, show auto forms
Add button
Set GlobalAction = debugger
Run app, show debugger, show access to auto forms

State machines
A simple state machine with triggers
Triggers with paramters
Transitions with guards
Multiple transitions out of a single state with guards (explain how only 1 must be true)
Multiple transitions out of a single state with the same trigger
Nested regions
Concurrent regions
Final state

Multi user
Unloading the cache to see changes by other users
Turning on optimistic locking
Getting conflict information
ChangeActionKind
Locking regions

Remote persistence
Creating the remote persistence server
Registering as a remotable server
Registering as a HTTP remotable server available via IIS
Switching the WinForm to RemotePersistenceClient

Multi user synchronization server
What it is, how it works
Adding it to the remote persistence server
Requesting changes from the WinForm client
Run two apps, show the propagation of changes between them

ECO and ASP .NET
Create ECO web application
Reference the same model
Explain why we have a PersistenceMapperProvider component (only about caching the mapping information for now)
Show how to generate DB / evolve from PersistenceMapperProvider instead of the EcoSpace

ECO ASP .NET autoforms for protoyping in ASP .NET

Show the ECO WebForm
Recreate the same WinForm gui in a WebForm

ASP .NET performance
Show the pool size in Web.Config
Explain what happens when more people request pages than there are EcoSpaces in the pool
Explain the SessionStrategy
Explain the maximum lifetime setting in Web.Config

ASP .NET multi-user
Explain how EcoSpaces are kept in sync with each other via the PersistenceMapperProvider
Demonstrate how WebForm data is in sync
Demonstrate how the WinForm / WebForm apps do not stay in sync with each other
Migrate the ASP .NET app to use the persistence server
Demonstrate WinForm app reflecting changes to data made in a WebForm app

Overview of each of the ECO components

ECO in code
Switching between Object and IObject
Overview of some of the ECO services
UndoService, In-memory transactions + undo blocks
DirtyListService
PersistenceService
OCLPS Service
OCL service
OCL action language service


Advanced modeling techniques
HasUserCode = true
OCL derived attributes
Overriding OCL derived attributes in subclasses
Code derived attributes
Code reverse derived attributes
Derived associations
Transient properties
Transient classes
Transient associations
Association classes
Platform independant property types
AutoInc
Blob
Memo
Delayed fetch properties
Referencing other packages
Extending classes
Adding associations
Adding class constraints
Enforcing in a WinForm application
Enforcing the same constraints in a webform application
Disabling the auto-generated multiplicity constraints
Object versioning
Navigating historical data
Retrieving all historical versions of an object

Advanced database techniques
Reverse engineering a database
Cusomizing the mapping information to fit an existing database
Evolving the structure of an existing database
Using a single model to pull data from multiple databases (including mixed servers) and updating the data too

Disabling BlueTooth on a Pocket PC

We use wireless printing through a COM port over BlueTooth. Having BlueTooth on all of the time can contribute towards energy consumption and cause the battery life on the Pocket PC to deplete faster. Now I disable BT when the application starts, and then re-enable it to print and disable it immediately afters. This adds about 1 second to each print job but it should save the battery power.


[DllImport("BthUtil.dll")]
private static extern int BthGetMode(out BlueToothRadioMode dwMode);

[DllImport("BthUtil.dll")]
private static extern int BthSetMode(BlueToothRadioMode dwMode);

public static BlueToothRadioMode BlueToothRadioMode
{
get
{
BlueToothRadioMode result;
BthGetMode(out result);
return result;
}
set
{
if (value != BlueToothRadioMode)
BthSetMode(value);
}
}

2007-02-14

Keeping a Pocket PC awake

My compact framework application imports XML into a local database. As there is so much data to import this can take up to an hour. During development there were no problems with this, but of course during development the Pocket PC is docked in its cradle which provides it with power.

When a Pocket PC is removed from its cradle it manages power differently, just like an unplugged laptop. So every five minutes the Pocket PC would hibernate and the user would have to turn it back on in order for the import to continue. During an hour the user would have to do this approximately twelve times. How annoying, and dangerous too if the employee is driving to their first job.

Anyway, I found the following very useful code on the web and thought I'd point it out as it was so useful!


public class Device
{
#region Device sleep support
[DllImport("CoreDll.dll")]
public static extern void SystemIdleTimerReset();

private static int DisableSleepCallsCount = 0;
private static System.Threading.Timer PreventSleepTimer = null;
#endregion

public static void EnableDeviceSleep()
{
DisableSleepCallsCount--;
if (DisableSleepCallsCount == 0)
{
if (PreventSleepTimer != null)
{
PreventSleepTimer.Dispose();
PreventSleepTimer = null;
}
}
}

public static void DisableDeviceSleep()
{
DisableSleepCallsCount++;
if (DisableSleepCallsCount == 1)
{
TimerCallback keepAlive = new System.Threading.TimerCallback(
delegate(object applicationData)
{
try
{
SystemIdleTimerReset();
}
catch (Exception)
{
}
}
);
PreventSleepTimer = new System.Threading.Timer(keepAlive, null, 0, 30 * 1000);
}
}
}


Now I can do this


Device.DisableDeviceSleep();
try
{
//Do stuff
}
finally
{
Device.EnableDeviceSleep();
}

2007-02-12

Dotting the I's and crossing the T's

Ever played "Spot the difference"? I'm sure you have :-)

I'm just looking through an application I have inherited from a Turkish company it was outsourced to. I was just browsing through a 5MB SQL script to generate the DB + stored procs when I saw this....

@MATERIAL_CODE=REPLACE(@REPLACE_MATERIAL_CODE,'Imprinter','Impr─▒nter')

Does it do anything? Sure it does, but can you see what it is? There are two clues in this post but I wont tell you where!

2007-02-07

ECO book at last!

Hi all

I have finally reached a point where I can dedicate a few hours each today to "something new" and have decided that the ECO book I have always wanted to write would be great fun. My first idea is to create 2 or 3 separate items:

Title: ECO jump start
This would be in paper / PDF format and would contain a set of exercises for a new user to follow in order to get started with ECO as quickly as possible. It would basically cover creating a simple model, prototyping using auto-forms, using the different handles, databinding, parent-child grids, creating/evolving the DB, OCL / code derived / reverse derived attributes, and stuff like that.

Title: ECO API
This would be an electronic format which would basically be a "Press F1" reference.

Title: ECO book
This would either be a paper/pdf format or, if possible, part of the ECO API document. It would be a technical overview of the framework + its services and abilities. I imagine it might be similar to my ECO Services chapter, except I'd probably include a set of "How to" items at the end of each chapter.


The plan here is that we get

1: An easy way for people to assess exactly what ECO does, and to quickly start creating simple "test" applications.
2: A complete reference for information on specific items where you would normally find yourself hitting F1.
3: A conceptual overview of the whole thing, explaining how it all works together and how to achieve certain goals.


This is what I have in mind at the moment, but it could all change based on the feedback I get :-) The above is basically the order I learned in when I learned Bold and then later moved over to ECO, it goes from simple through to in-depth. What I want to know is

A: Do you think I should go for 3 separate items or try to combine the last 2?
B: Is there anything I have missed?
C: What information would you like to see?

As a closing note I will paste in a table of contents I wrote 729 days ago when I first decided this would be a good idea if only I had the spare time :-) It's not necessarily what I still have in mind, but it might get some brain cells bubbling in your heads to get us all started off. If there are any topics you guys mention that I have missed I will add them to my list. Due to its age (1 day short of 2 years old) it is based on ECO II, so there are lots of nice new ECO III items I can put in such as creating your own services, state machines etc.


An overview of ECO
What is an ECOSpace
Modeling an application
Attributes, Methods, and Relationships
OCL Derived attributes
Code derived attributes
Reverse derived attributes
Inheritance Vs Composition
Packages
Creating a simple ECO WinForms app
Creating a simple ECO ASP.net app
Creating a simple ECO Webservices app
Borland.ECO namespaces
Handles
ObjectRepresentation
Persistence
Services
Subscription
UmlRt
WinForm
Persistence
Changing persistence
Evolution
Custom persistence mapping - AutoInc etc
Reverse engineering an existing database
OCL



Also need to include multi-user support, optimistic locking, change propagation, reconciliation, object locking regions, writing your own services + OCL extensions.



Thanks for your feedback!


Pete

2007-02-01

ECO extensions 2.1 released

I have just released a minor update to ECO extensions. It contains a fix for a bug that would prevent the DirtyObjectCatcher from catching modified objects for MDI child forms.

string.GetHashCode

I recently developed a simple support application that allows users on a PC to provide a one-off security number to a Pocket PC user and when entered the PPC will perform a specific support function.

I used the same class to generate these security codes in both the desktop and compact framework applications, tested it quite thoroughly and it all seemed to work fine. However once deployed it became evident that the codes provided by our support department were being rejected by the PPC as invalid. So what went wrong?

Seeing as the same class was used for both applications I thought it would be okay to test the encoding/decoding of command numbers on only a single platform, and this was my mistake! The routines use string.GetHashCode() to add a checksum to the end of the security codes just to prevent the user from performing actions without authorisation. For reasons I cannot imagine the implementation of string.GetHashCode() is different in the compact framework from the one in the full .NET framework. It was due to the result of this method being different on the two platforms that the codes were being rejected.

So, I have disassembled the compact framework version and used that in my desktop application instead, and now it works just fine. Here is the code....

public int MyHashCode(string value)
{
int charIndex = 0;
int num1;
int num2 = 0x1505;
int num3 = num2;
char currentChar;
while (charIndex < value.Length)
{
currentChar = value[charIndex];
num1 = (int)currentChar;
num2 = ((num2 << 5) + num2) ^ num1;

if (charIndex == value.Length - 1)
break;

num1 = value[charIndex + 1];
if (num1 == 0)
{
break;
}
num3 = ((num3 << 5) + num3) ^ num1;
charIndex += 2;
}
return (num2 + (num3 * 0x5d588b65));
}

2007-01-19

Testing a website with a root path

This post is really a reminder to myself, so that I can find the information quickly for the next time I reformat my hard drive!

To run a website with a root path in Visual Studio 2005 follow these steps:
  1. Tools->External tools menu
  2. Click Add
  3. Title = Webserver
  4. Command = C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.EXE
  5. Arguments = /port:8080 /path:$(ProjectDir)
  6. Tick "Use output window"
Whenever you need the webserver running just open your project and go to Tools->Webserver.

2007-01-17

Calling base constructors in C#

I occasionally find it annoying that I cannot specify at which point in a class's constructor I wish to invoke the base constructor. Having C# always invoke it before any of the code in my descendant constructor is executed sometimes causes me problems.

Considering .NET is capable of calling the ancestor constructor at any point I wondered why C# wont allow it. I contacted Anders Hejlsberg and he was kind enough to reply, unfortunately he seems to have answered a question I didn't ask :-)

Anyway, I have been deleting some old emails today and I came across his response, which he gave me permission to publish:

The problem with Delphi's model (allowing constructors to be called on
an already constructed object) is that it makes it impossible to have
provably immutable objects. Immutability is an important concept because
it allows applications to hand objects to an external party without
first copying those objects and still have a guarantee that the objects
won't be modified. If constructors can be called on already constructed
objects it obviously isn't possible to make such guarantees. In Delphi's
case that may be ok since Delphi doesn't really make type safety
guarantees anyway (you can cast any object reference to a
pointer-to-something and start poking away), but .NET goes further with
type safety and this would be a big hole.

There, now get out of my head ;-)

Anders

2007-01-11

SQL Server amazes me!

Well, SQL Server amazes me, but not because I am impressed!

Today I wrote an application that does the following:
01) Find all ZIP files in a specific folder
02) Open each ZIP file in turn
03) Extract an XML file from the ZIP into an array of bytes
04) Insert that data into a table

There are 1,978 files in this folder and the XML within each ZIP file is around 2MB in size.

I ran this app and was really surprised at how soon my PC started to crawl, it was so slow that it was taking over 10 seconds to switch between MSN and a Skype text-chat window. So I decided to monitor the process.....

Importing the zip data into SQL Server took a total of 19 minutes and 15 seconds (this includes unzip time). What concerns me is that SQL Server's RAM usage went up to 830MB at its peak, this is what was crippling my PC. Twenty minutes after my app had finished SqlSevr.exe was still holding over 700MB of RAM, I then restarted my PC. SQL Server was using more RAM than I had available and was forcing my PC to use virtual memory!

So I decided to try FireBird 2.0

Importing the zip data into FB took a total of 15 minutes and 15 seconds (again including unzip time). RAM usage for FB started at 21MB but dropped to 16MB at home point within the first 5 minutes. It's memory consumption remained at 16MB.

In both cases I was creating + committing a transaction around each INSERT command. In fact it was the exact same code, I just did a search + replace from Sql to Fb.

Amazing!

2007-01-02

DaDa

Today my baby girl looked at me, said "DaDa" for the first time ever, and then giggled. What a great day! :-)