2008-01-30

MonoRail

I'm working on a new website for work. I've decided to use ECO for the business model due to how much time it saves me. I took a look at the new MVC ASP approach provided by Microsoft recently and was a bit disappointed. There were bugs in some pretty basic errors that would have been an annoyance to code around, and it just didn't feel "ready".

So, I've decided to take another look at MonoRail. I'd already written an ECO implementation for MR in the past but I decided to start the implementation from scratch. This was mainly inspired by the new EcoSpaceManager in ECOIV for ASP .NET. Using an EcoSpaceManager you can easily utilise many instances of different types of EcoSpace in the same page. I decided I would do the same.

Unlike the EcoSpaceManager I haven't gone for unique string values for identifying the EcoSpace instance I want. That approach is good in ASP .NET where you want to bind different components together to generate your HTML but it doesn't really make as much sense when everything you produce is written in code. If you want two instances of the same EcoSpace you can just use EcoSpaceStrategyHandler.GetEcoSpace().

Anyway, on to the detail:

The first thing I have done is to specify EcoSpace and EcoSpaceProvider settings as reflection attributes on the class (controller) and method (action). Like so

[UseEcoSpacePool(true)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(false)]
  public void SomethingElse()
  {
  }
}
In this example the EcoSpace pool will be used for all actions (methods) except SomethingElse which explicitly says not to use it.

If you want to specify EcoSpace type specific settings in your web app then this is possible too:

[UseEcoSpacePool(true)]
[UseEcoSpacePool(typeof(MyEcoSpace), false)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(typeof(MyEcoSpace), true)]
  public void SomethingElse()
  {
  }
}
In this example the default is to use the EcoSpace pool unless you are retrieving an instance of MyEcoSpace in which case it wont be used. However, if the action being invoked is SomethingElse() then retrieving an instance of MyEcoSpace will use the pool. The order of priority is as follows:
  1. Apply settings on the class that are not specific to an EcoSpace type.
  2. Apply settings on the method that are not specific to an EcoSpace type.
  3. Apply settings on the class that are specific to an EcoSpace type.
  4. Apply settings on the method that are specific to an EcoSpace type.
So the order of priority is that Method settings override Class settings, and then EcoSpace type specific settings override non specific settings.

Pretty versatile eh? But how do you get an instance of an EcoSpace? Well, that's pretty simple too!
public void Index()
{
  MyEcoSpace myEcoSpace = GetEcoSpace<MyEcoSpace>();
  //This will return the same instance
  MyEcoSpace myEcoSpace2 = GetEcoSpace<MyEcoSpace>();
}
Any EcoSpaces requested in this manner will automatically be "released" immediately after the method finishes executing so you don't need to worry about it at all. I say "released" because it will either be Disposed, stuffed into the session, or returned to the pool based on your EcoSpaceProvider settings for this class/method/EcoSpace type.

That's not the end of the story though. You can bind instances of your objects directly to HTML and back. To do this you need to identify your business class with a reflection attribute "EcoDataBind", like so
[AllowDeactivateDirty(true)]
public void Create(
  [EcoDataBind(typeof(MyEcoSpace), "Product", CreateIfNoObjectId=true)]Product product)
{
  PropertyBag["Product"] = product;
}
Here I have stated that it is okay to Dispose the EcoSpace instance if it contains dirty objects at the end of this method. The method itself consists of a single parameter named "product" of type "Product". The EcoDataBind might look a bit overwhelming but it says this
  1. The EcoSpace type you need to home the Customer object is MyEcoSpace.
  2. The prefix in the form to look for is "Product", so when you see <input name="Product.Name">> MonoRail knows that the value should go into the Name property of this product.
  3. If there is no ExternalId in the form identifying the object to fetch from the data storage before updating then a new instance should be created.
As a result when you run your app and go to localhost/account/create you will see that the product method has a new instance in there. When the user posts the form back you will again see an instance but containing the updated values. What does the HTML look like? I have used the Brail view engine and HTML helpers to output the HTMl I need. This allows you to use whatever HTML you like but then easily add the <input> etc based on your current object.

${HtmlHelper.Form('create.rails')}

  ${EcoFormHelper.PersistedExternalId('Product.ExternalId', Product)}

  Name : ${HtmlHelper.InputText('Product.Name', Product.Name)}

  Current version number : ${HtmlHelper.InputText('Product.CurrentVersionNumber', Product.CurrentVersionNumber)}

  ${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndForm()}
I have split the lines up a bit to make them easier to visually separate.

First an EcoFormHelper is told to output a hidden input named Product.ExternalId for the product we set in the C# method (see PropertyBag["Product"] = product). EcoFormHelper.ExternalId will output the ExternalId for the object, PersistedExternalId will only output if the object is not new, this is useful in situations like this when the object was disposed of with the EcoSpace it belong too and we can just create a new instance.

Next the HtmlHelper gives us an <input> named "Product.Name" and its value is set to whatever is in the Product's Name property. The same is done for CurrentVersion.

A Submit button is then generated so that the user may post their changes.

Summary
Well, this little example shows that I can implement a nice clean MVC style approach to writing web apps with ECO and not have to worry about constructing EcoSpace instances in code, fetching objects from the data storage and so on manually; everything is done for me.

2008-01-29

What's in a name?

When thinking of a name for a new product I wish people would be more original. For example, I have just spent ages looking for information about how to create a ternary in a scripting language known as Brail. Instead of finding what I want I have had to work my way through loads of information about blind people.

So, I had this idea that I have just sent off to Google. If they implement it then you know where it came from :-)


Problem:
People don't think ahead when they name something. I have spent ages trying to find info on the scripting language Brail but mainly get info about blind people.

Proposal:
01: Google provides a public repository where people can register

ProductName/Description/GUID

02: Google defines a way for webmasters to specify this GUID.


Result:
When people are getting lots of irrelevant results they can select a product from a list and restrict their search to sites that relate to the GUID for that product. I.e. I only get results that are 100% relevant.

2008-01-24

Single application instance

I needed my app to be a single-instance app. It was easy to implement, like so:

bool mutexIsNew;
Mutex mutex = new Mutex(true, "SomeUniqueID", out mutexIsNew);
if (mutexIsNew)
Application.Run(new MainForm());


A problem arose though when my app started to need to receive command line arguments. How do you also pass those onto the original app? There is a class for this purpose named
"WindowsFormsApplicationBase", it's in Microsoft.VisualBasic.dll

01: Add a class to your project like so:
internal class SingleInstanceApplication : WindowsFormsApplicationBase
{
private MainForm MainFormInstance;
internal SingleInstanceApplication()
{
IsSingleInstance = true;
EnableVisualStyles = true;
MainFormInstance = new MainForm();
MainForm = MainFormInstance;
}

protected override bool OnStartup(StartupEventArgs eventArgs)
{
return base.OnStartup(eventArgs);
MainFormInstance.AcceptCommandArguments(eventArgs.CommandLine);
}

protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
base.OnStartupNextInstance(eventArgs);
MainFormInstance.AcceptCommandArguments(eventArgs.CommandLine);
}
}


02: Add a method to your MainForm class like so:
internal void AcceptCommandArguments(IList args)
{
}


03: Finally change your project source code like so:

[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SingleInstanceApplication singleInstanceApplication = new SingleInstanceApplication();
singleInstanceApplication.Run(args);
}

2008-01-22

ExternalID is not related to ECO_TYPE

I just thought I'd blog about this because it seems to be causing some confusion.

ECO_TYPE
Use:
In integer used in the database to identify the type of object a table row represents.

Obtained from:
The ECO_TYPE table using the name of the class.

Life span:
Permanent. The name of the class in ECO_TYPE changes when you rename your class, but the integer value never changes.


ExternalId
Use:
A way of identifying a class instance across EcoSpace instances. Kind of like a string version of a pointer to an object.

Obtained from:
Index of the class in TypeSystem.AllClasses + "!" + the ECO_ID of the instance.

Life span:
Although the ECO_ID never changes the class index might when you add or remove classes within the model.


I just want to point out that ExternalID and ECO_TYPE are unrelated. When you change your model it does not mean that the ECO_TYPE in your DB must change. Here is an example

01: You create a model with 2 classes in it. The first class is the root, the next is a subclass
ClassA <---- ClassC

02: You generate the DB for the first time. This creates the ECO_TYPE table data with the following values

ECO_TYPE CLASSNAME
1 ClassA
2 ClassC

03: You create an instance of ClassA and ClassB then look in the ClassA table in the DB, you will see something like this

ECO_ID ECO_TYPE
1 1
2 2


04: Retrieving the ExternalID for each class will return 1!1 and 2!2. This looks like the ECO_TYPE but it isn't!

05: You now change the model like so
ClassA <---- ClassB <---- ClassC

06: You evolve your DB.

The result of this is the following

A: A new entry in ECO_TYPE to enable ECO to see when a class is an instance of ClassB

ECO_TYPE CLASSNAME
1 ClassA
2 ClassC
3 ClassB

Note how the types are unchanged. There is no specific order to this list at all. It's a first-come-first-served kind of arrangement.

B: Your ExternalID's now change. The class index returned for each class is
ClassA = 1
ClassB = 2
ClassC = 3

Note that these have no relation at all to the ECO_TYPE. Just because they are both integers means nothing :-)

I hope this clears it up. Remodeling does not mean all your DB type data needs to be updated!


Pete

2008-01-20

Why I don't use ExternalID for URLs

An External ID consists of two parts, !

ClassID isn't really required for ECO controlled DB structures because there is always a root table containing the ObjectID + ClassID, but when you use ECO to map to an existing DB structure there needs to be a way to know that object 1234 needs to be fetched from the PERSON table.

So, now that we know why the ExternalID consists of two parts on to why I don't use it (much). If I am writing a website with ECO and I use the ExternalID for my URL like so

www.mysite.com/showarticle.aspx?id=23!1234

The number 23 is determined by looking for the class's index in the list of all classes in the model. This is the problem! If you change your model by adding a new class then your index may change from 23 to 24. Not a big problem for your application, but persistent links to that URL from other sites such as Google will no longer work.

An ExternalID is just like an object's pointer address. When you restart your application that address is no longer valid. ExternalID lives longer than an application restart, but doesn't always live past an application remodeling as explained above.

My tips are as follows:
  1. For passing object instances around in code, just past the object instance itself.
  2. For passing a reference to an object to another EcoSpace instance in the same application use ExternalID.
  3. For storing away a reference for an unknown period of time (config file settings, URLs) add a unique attribute to your class such as an AutoInc and retrieve it using that value in an OCL evaluation.
URLs are really the only example I can think of where an ExternalID is persisted externally and expected to live longer than the life of the current model. In this case I think the following URL is much more pleasing to the eye anyway

www.mysite.com/showarticle.aspx?id=1234

2008-01-17

RSA signed streams

I'm writing an app that needs me to be able to write to a file and make sure that nobody changes it. The idea that I had was to create 2 classes:

  1. WritableSignedStream:

    Accepts a targetStream parameter + a privateKeyXml string. I add some space at the beginning of the target stream to reserve it for header info. You then write to the stream as normal (it reports a smaller Length than that of the target stream so your app is unaware of the header). When you close the stream it writes the public key to the stream + an RSA encrypted hash of the data part of the file.
  2. ReadableSignedStream:

    Accepts a sourceStream parameter in the constructor. Just before you read for the first time it will compute an MD5 hash of the data part of the file, and then compare it with the signed hash in the file (which I first decrypt using the stored public key).

These classes therefore provide two functions:

  • You can sign a stream, the app can check the stream was signed by you by inspecting the public key within it.
  • You can additionally pass a publicKeyXml to the ReadableSignedStream constructor and it will use that instead of the public key within the outer stream, this allows you to ensure nobody has altered your file in any way.

Anyway, to the point. I could always use RsaCryptoServiceProvider.Encrypt() to encrypt the MD5 hash, but whenever I tried to DeCrypt() I would get an exception telling me that my public key was a "Bad key".

It annoyed me for a while, but then I realised something pretty obvious really. The way RSA works is that you are supposed to Encrypt using the Public key and DeCrypt using the Private key. I was using it the wrong way around. The solution was simple....

(Signing)
RSACryptoServiceProvider RsaProvider;
RsaProvider = new RSACryptoServiceProvider();
RsaProvider.FromXmlString(privateKeyXml);

MD5 md5 = new MD5CryptoServiceProvider();
byte[] md5HashBuffer = md5.ComputeHash(dataStream);
byte[] signedMD5HashBuffer = GetSignedMd5Hash(md5HashBuffer);


private byte[] GetSignedMd5Hash(byte[] hashBuffer)
{
RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter();
rsaFormatter.SetKey(RsaProvider);
rsaFormatter.SetHashAlgorithm("MD5");
return rsaFormatter.CreateSignature(hashBuffer);
}


(Verifying)
MD5 md5 = new MD5CryptoServiceProvider();
byte[] currentMD5HashBufferForFile = md5.ComputeHash(dataStream);
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
rsaProvider.FromXmlString(PublicKeyXml);


RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter();
rsaDeFormatter.SetKey(rsaProvider);
rsaDeFormatter.SetHashAlgorithm("MD5");
if (!rsaDeFormatter.VerifySignature(currentMD5HashBufferForFile, FileEncryptedMd5Hash))
throw new Exception("Someone has messed with the file");

I hope someone finds this useful :-)

No symbols loaded

This has been driving me mad for hours now! Whenever I run my PocketPC compact framework app I cannot debug it! None of the breakpoints will stop, each breakpoint just shows as an empty circle instead of a solid one.

So, what was the solution? I tried deleting all PDB files on my hard disk but that didn't do it. In the end manually deleting all of the files previously deployed to my PPC did the trick. Maybe VS couldn't overwrite them or something? No idea, but at least it works now :-)

2008-01-15

Geek quotes

This one is probably my favourite:
"There are 10 types of people in the world, those who understand binary and those who don't".

Here is one I came up with myself some years ago and used to use in my newsgroup signature:
"Blessed are the geek, for they shall public class GeekEarth : Earth".

2008-01-09

PPC

First a tip: If you spend ages wondering why you cannot connect your Pocket PC to your computer user ActiveSync etc here's something to check. Is the PPC plugged directly into your computer or via a USB hub? If it's via a hub then take it out and slap it into the computer itself.

Next a moan: Why oh why oh why..... Does the Compact Framework's NumericUpDown control have Value, Minimum, and Maximum defined as Decimal, but when you put a Maximum value in that is greater than 32767 it goes screwy when the user tries to put in a number larger than 32767? Sigh!