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! :-)