Scripting Session
Saturday, July 23, 2005
Today, I'm just playing with new .NET Console capabilities. I'm running WebEdit.NET, and my aim is to access an ODBC database, perform a query, and create an HTML table from it.
First, let's make tracing easier:
tr(obj)
{
WebEditApp.Trace(obj);
}
Next, a function that shovels data to a new document:
toNewDoc(obj)
{
if(WebEditApp.DocumentManager.OpenNewDocument()){
Vars.TextAll = Conv.ObjectToString(obj);
}
tr(null);
}
Exercise: what is the trace call for?
To access an ODBC database, I have installed the DataDocs AddIn. I've defined a data source called "BloodBank". The handy ReadStorage method makes WebEdit's extensible document storage mechanism easy to use from code. So, wrapping it up, an "sql" command of the "data:" protocol is issued:
readData(sSql)
{
sCmd = string.Concat("data:sql? ", sSql, " @BloodBank");
WebEditApp.DocumentManager.ReadStorage(sCmd);
}
Next, we need to cross one of those domain boundaries - data to markup:
tableLines(asLines)
{
sb = new StringBuilder();
sb.Append("<table>");
sb.Append(Environment.NewLine);
foreach(sLine in asLines){
if(Flow.IsString(sLine)){
sb.Append("<tr>");
aStr = sLine.Split(Chars.Tab);
foreach(sCol in aStr){
sb.Append("<td>");
sb.Append(sCol);
sb.Append("</td>");
}
sb.Append("</tr>");
}
// keep cosmetic blank lines
sb.Append(Environment.NewLine);
}
sb.Append("</table>");
sb.Append(Environment.NewLine);
sb.ToString();
}
That done, we can call run the query:
toNewDoc(tableLines(Parse.SplitToLines(readData("select * from Donor"))))
Note that WebEdit optionally persists user functions, so it's enough to execute "code:Vars.Eval(Vars.TextAll)" just once.
Passwords
Now suppose our database requires a password. With ADO.NET, we use a connection string, but we don't want to persist the password within it. The good news is that the DataDocs AddIn support parameters in the connection string:
Password="$getPassword()%"
WebEdit's parameter dialog does not mask out passwords. And since WebEdit persists code interpreter string variables as well (optionally), it's not a good idea to use a string variable, either. Therefore, a user function call.
Update (Sunday, August 14, 2005): from now on, the parameter dialog does mask out parameters starting with a Plus sign (ig., $+Password%); see the static Gregor.AppCore.CParameterTemplate.MaskPrefix property).
Here's getPassword:
getPassword()
{
ret = null;
sName = "BloodBankPassword";
if(Vars.ExistsValue(sName)){
ret = Vars.GetValue(sName);
}else{
frm = new Gregor.UICore.Dialogs.FInput("Password", "Enter Password:", string.Empty);
frm.PasswordChar = '*';
frm.ShowDialog();
if(Flow.IsString(frm.UserText)){
ToString(){
this.GetProperty("value");
}
ret = UserObject.Create(ToString);
ret.AddProperty("value", frm.UserText);
Vars.SetValue(sName, ret);
}
}
ret;
}
A few observations:
- The password is stored as a special kind of object, called a user object. User objects are not persisted, so they're safe to use as a password store.
- The parameter expansion mechanism will call this object's ToString method, which yields the actual password.
- The password object is created on demand only. So getPassword is a lazy factory function.
- Data stored via Vars.SetValue always goes into global scope, therefore it can be called from the most nested of blocks in interpreter code.
I haven't talked about user objects so far - these are basically instances of a special class defined in the Gregor.NetConsole assembly. User objects reference user functions that act as methods. These user functions are typically nested in a factory function:
createPoint(x, y)
{
ToString(){
string.Concat('{', this.GetProperty("x"), ',', this.GetProperty("y"),'}');
}
ret = UserObject.Create(ToString);
ret.AddProperty("x", x);
ret.AddProperty("y", y);
ret;
}
User objects also support a few standard interfaces, such as IDisposable, provided the corresponding methods are present. Virtual methods defined by System.Object may also be overridden. There's even a little inheritance (all methods are virtual) and flexible property support. More on user objects later.