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.