Thursday, 6 October 2011

Dynamic logical expressions in .NET 3.5

For a project I'm working on, I  use an external XML file to define various BL rules. One such rule involves checking if an instance of a custom record-type object contains various entries. So that our technical support guys (with only a very basic knowledge of XML) could maintain the rules, I decided on going with a format like:
field1,field2,field3,((field4,field5)|(field6))
, which means: fields1-3 MUST exist, AND EITHER (Field4 AND 5) OR Field6 must exist

In other words, what I needed to do was create a logical-expression parser. Now one way to do this would be to manually parse the string, i.e. look for the innermost brackets, convert the expression to booleans, evaluate that, then evaluate the next 'level' etc. Now not only would this get pretty complicated, but it's a little limiting.

Ideally I would have loved to create a lambda expression dynamically (i.e. convert a string to a lambda), however this functionality was not readily available. NOTE: in .NET4, it looks like this is covered with the DynamicExpression class, but I'm unfortunately limited to 3.5 atm.

What I went for in the end is probably overkill, but the concept is pretty cool. C# obviously already handles mathematical expressions parsing . So I thought: "hey, why not format the evaluation into c# code and execute it?!". Well, this is actually pretty easy. It involves leveraging the CodeDom functionality. Now you can manually construct a class, as explained here, but that involves a fair bit of work. The option I went for was to use the CSharpCodeProvider class, which enables you to specify the class as text, and create an in-memory assembly from it.

It's as easy as this:
CompilerResults cr;
    Dictionary<string, string> d = new Dictionary<string, string>();
    d.Add("CompilerVersion", "v3.5");
    CSharpCodeProvider myCodeProvider = new CSharpCodeProvider(d);
    CompilerParameters cp = new CompilerParameters(new string[] { "DevSaffa.AReferencedProject.dll" });
    cp.GenerateExecutable = false;
    cp.GenerateInMemory = true;
    cp.OutputAssembly = "OutMod"; 
    string sourceText =       "using System;" +
                              "using DevSaffa.AReferencedProject;" +
                              "namespace DevSaffa.DynamicCode{" +
                              "public class Class1{" +
                              "public static bool Evaluate(MyClass1 data){return " + logicalOperation + ";}}} ";  

    cr = myCodeProvider.CompileAssemblyFromSource(cp, sourceText);
    if (cr.Errors.Count > 0)
        throw new ArgumentException("Expression cannot be evaluated, please use a valid C# expression");
A couple of things:
  • Be sure to create the CSharpCodeProvider instance specifying the CompilerVersion, otherwise you may run into issues around referenced assemblies.
  • Also, see how you can reference one of your own projects: not only do you need to include the #using reference, but you also need to specify the referenced assembly in the compile-parameters
  • If any errors occur with the compilation, you can iterate through the cr.Errors collection, which contain the actual compile errors (as would be displayed in your build-error window in Visual studio for a normal build)
I'm passing in an instance of the custom record-type class(MyClass1) to the dynamically created method, and pre-formatting the XML-file validation-string(the logicalOperation variable above) using a simple Regex (not shown) so that:
field1,field2,field3,((field4,field5)|(field6))
becomes
data.FieldExists("field1") && data.FieldExists("field2") && data.FieldExists("field3") && 
((data.FieldExists("field4") && data.FieldExists("field5"))|(data.FieldExists("field6")))
, where my data-class has a method:
public bool FieldExists(string code)

I can then call the compiled in-memory assembly's Evaluate() method like so:
MethodInfo Methinfo = cr.CompiledAssembly.GetType("DevSaffa.DynamicCode.Class1").GetMethod("Evaluate");
    return (bool)Methinfo.Invoke(null, new object[] { data });
(where 'data' is an instance of my record-type object (MyClass1) that I want to evaluate)

Nifty!

Thursday, 8 September 2011

Retrieving available COM ports

I recently had to deal with a dodgy USB driver that didn't null-terminal it's virtual com-ports in the registry. As a result, when retrieving the ports-list via the usual SerialPort.GetPortNames(), I got values like "COM7รข".

Here's a little method to not only remove any unwanted characters from the port-names, but (using an anonymous delegate to implement an IComparer on the Sort() extension method) order the list of COM ports by port-number too (otherwise COM11 would come before COM2):

Tuesday, 30 August 2011

DateTime.ToString() custom format

I always forget which letters represent what date/time format in the ToString() method.
Commonly I use it for logging, and the format is:
DateTime.Now.ToString("yyyyMMdd HH:mm:ss:FFFFFF")
, which results in:
20110830 08:22:26:160586
The full list is available here (MSDN)

Tuesday, 23 August 2011

TCP Load-testing

Just had to whip up a load-tester for a server that communicates with clients via TCP. Can process concurrent TCP connections, and has graphical reporting, and should be easily extensible to dynamically change requests. Made use of the open-source ZedGraph graphing utility, which is pretty damn cool.

Download the source-code here
The ZedGraph binary is located here

Monday, 22 August 2011

BER-TLV Parsing

One of the EMV devices I need to connect to uses the BER-TLV (wiki) protocol for communications. Simply put, data fields are defined by a (T)ag, (L)ength and (V)alue.
  •  The tag field (T) consists of one or more consecutive bytes. It codes a class, a type, and
    a number:

  • The length field (L) consists of one or more consecutive bytes. It codes the length of
    the following field.
  • The value field (V) codes the value of the data object
Sample code removed

Hexidecimal logging

I do a fair bit of serial comms in my current job (interfacing to EMV terminals, printers etc), and needed to log a low-level trace of the binary data transferred to the com port.
I wanted the output to look something like the hex-editor of Norton Commander (now I'm showing my age). i.e. the part in blue below:
22/08/2011 10:34:54:293 Sending 'DisplayMessageRequest' request (33 bytes):
01 00 1D D2 01 00 01 18 09 00 09 0D 09 50 6C 65 ...?.........Ple 61 73 65 20 53 69 67 6E 20 52 65 63 65 69 70 74 ase Sign Receipt 93 ?
And here's a way to to this:

Cross-process application detection

Further to my previous post on detecting a terminal-service session, one may need to limit an application being started up twice within the same terminal-service session, but allow the application to be started up in different sessions (possibly started by the same user).

Normally (i.e. without TS), You can detect if your app is already running by using something like this:
public static bool IsAppAlreadyRunning()
{
    Process currentProcess = Process.GetCurrentProcess();
    return Process.GetProcessesByName(currentProcess.ProcessName).Length > 1;
}
Now obviously, this will find a running instance in a different TS session too - not what we want.

Enter the Mutex:

Wednesday, 17 August 2011

Detect a Terminal Server session

For a project I'm currently working on, I needed to determine if the session the app is running in is a TS session.

Well, It's this easy:
/// <summary>
/// Detect if app is running through a remote session, like terminal-services
/// </summary>
public static bool IsRemoteSession()
{
    return System.Windows.Forms.SystemInformation.TerminalServerSession;
    // TODO: Citrix check
}
OK, I'm trying to get the code-formatting working in Blogspot. This will have to do for now!
As you can see, I'm still looking to see how to get this right when running in Citrix, so I'll have to get back to this once I've got a 2008 VM installed with Hyper-V enabled, in order to mount the Citrix server I've just downloaded for evaluation.

I'm (a)live!

Well, this is it. After years of having no central repository for my software thoughts, and continually having to search old projects for pieces of code, I've decided to create a blog to keep all my ramblings. Hey, perhaps someone else could make use of them too.