Wednesday, February 02, 2011
Happy Groundhog Day!
Sunday, January 23, 2011
Referencing Assemblies: T4 Gotcha
When I run T4 Templates through Visual Studio 2008, I don't have to give the full path to reference GAC assemblies:
<#@ assembly name="System.Data.dll" #>
But when I run the template in my own executable using Microsoft.VisualStudio.TextTemplating.Engine, I have problems.
If the Template code is in VB.NET, I get:
vbc : Command line (0,0) : error BC2006: Compiling transformation: option 'r' requires ': <file_list>'
If the Template code is in C#, I get:
c:\Users\jacks\AppData\Local\Temp\rnftuopt.0.cs(3,18) : error CS0234: Compiling transformation: The type or namespace name 'Data' does not exist in the namespace 'System' (are you missing an assembly reference?)
To fix this, I referenced the full path of the assembly.
<#@ assembly name="C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.dll" #>
NOTE: This doesn't seam to apply to non-GAC assemblies; I have noticed that I don't have give the full path of my own assemblies as long as they are in the same folder as my executable.
Friday, January 14, 2011
Fulfilled my contract with AT&T
Just over a two years ago, I bought an iPhone 3G (no S).
In Spokane, I haven't have any problems AT&T. But Spokane is the first place named in AT&T's Postcard Commercial in 2009, so AT&T knows that we get better than average service here in Spocompton.
If I was to stay in Spokane, I would probably be safe getting an upgrade from AT&T. The trouble is that I am working month to month as a temp and I don't know where I will be working if/when my current contract runs out.
I've hear horror stories of bad service in real cities like New York or Chicago. And Verizon will offer the iPhone 4 next month; some say that AT&T's problems are caused by the popularity of the iPhone and Verizon will degrade too.
I think I will stay with my antique phone for awhile.
Wednesday, January 05, 2011
Running FTP.EXE from C#
NOTE: Ultimately, I didn't go with this solution.
Since I need to log on to various FTP servers with various security configurations and FTP.EXE seams to figure out these configurations and the free FTP libraries I’ve played with can’t I have decided to wrap FTP.EXE with C#.
Telling it what to do
I wanted to avoid sending my commands to FTP.EXE via command files because I would have to dynamically write text files and make sure that they are secure. System.Diagnostics.Process allows me to send commands to its StandardInput stream, but I can’t figure out how to make it accept passwords. To solve these problems, I log in using command files (which I can secure and only allow myself and the program to see, and then write the rest of the commands to the StandardInput stream.
The Command File
The command file needs to reside in a directory with no spaces and the file needs to be saved using ANSI encoding. Here I open a server and login, the rest is up to the commands that I send to the stream.
open localhost username password
Sample Application
This simple console application demonstrates using the command file to log in and StandardInput to send a List command (and Quit).
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace DemoFtp
{
class Program
{
private const string FTP_EXE_LOCATION = @"C:\Windows\System32\ftp.exe";
private const string DEFAULT_COMMAND_FILE_LOCATION = @"C:\temp\localhost.txt";
static void Main(string[] args)
{
try
{
string commandFileLocation = getCommandFileLocation(args);
var commands = new List<string>();
commands.Add("ls -la");
commands.Add("quit"); // Don't forget to log out!
string output = RunWithResponses(
FTP_EXE_LOCATION,
string.Format("-s:{0}", commandFileLocation),
commands);
Console.WriteLine(output);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
private static string getCommandFileLocation(string[] args)
{
string commandFileLocation;
if (args.Length > 0)
commandFileLocation = args[0];
else
commandFileLocation = DEFAULT_COMMAND_FILE_LOCATION;
return commandFileLocation;
}
public static string RunWithResponses(string exeName, string argsLine,
List<string> commands)
{
StreamReader outputStream = StreamReader.Null;
StreamReader errorStream = StreamReader.Null;
StreamWriter inputStream = StreamWriter.Null;
string output = string.Empty;
try
{
// Setup the process
Process newProcess = new Process();
newProcess.StartInfo.FileName = exeName;
newProcess.StartInfo.Arguments = argsLine;
newProcess.StartInfo.UseShellExecute = false;
newProcess.StartInfo.CreateNoWindow = true;
newProcess.StartInfo.RedirectStandardInput = true;
newProcess.StartInfo.RedirectStandardOutput = true;
newProcess.StartInfo.RedirectStandardError = true;
newProcess.Start();
inputStream = newProcess.StandardInput;
outputStream = newProcess.StandardOutput;
errorStream = newProcess.StandardError;
inputStream.AutoFlush = true;
// Issue My commands
foreach (string command in commands)
{
inputStream.WriteLine(command);
}
// Get the rsults and wait for it to end
output = outputStream.ReadToEnd();
newProcess.WaitForExit();
}
finally
{
// Clean up
if (outputStream != StreamReader.Null)
outputStream.Close();
if (errorStream != StreamReader.Null)
errorStream.Close();
if (inputStream != StreamWriter.Null)
inputStream.Close();
}
return output;
}
}
}
Thursday, December 30, 2010
The Seahawks and the Playoffs
If the Seattle Seahawks win their football game against the Rams early next year, a team with a losing record will make the NFL playoffs!
If I were the King of Football (or any other sport), I’d make a rule that a team would have to win at least as many games as they lose to make the playoffs.
If a division winner had a losing record, that playoff position would go to an additional wild card team. The New York Giants and Tampa Bay (both have 9-6 records and would lose tie breakers to Green Bay) may be home watching the Seahawks. Under my scheme New Orleans could host a game.
In College Football, a team must have an even record to qualify for a bowl game. There was a big deal around here about the Washington Huskies having to win the Apple Cup in Pullman to get to 6-6 to qualify for a bowl.
Sunday, December 19, 2010
Generating a Nullable ADO.NET DataSet Wrapper
Part 4: The Template and putting it all together
So, I have my target code, my DataSet describer and some T4 template tools (including the ITextTemplatingEngineHost class I took from MSDN), but I haven’t generated any DataSet Wrapper code! It is Christmas time, but that’s still a lame excuse.Running the Template
In Part 3, I created a class to pass parameters to a T4 Template and an overly simple template that echoed the parameters to the generated document. Here I’m using the same basic from the Test Console Application, but I am referencing a template that will generate the DataSet Wrapper and passing the appropriate parameters to generate the wrapper I want. This code use a DataSet in an assembly.
// Set up some parameters:
var parms = new TTCommucator.GetParams("setting.txt");
parms.CurrentParameters.Add("DsSource", "Assembly");
parms.CurrentParameters.Add("AssembyPath", @"C:\GeneratorProject\MyAssembly.dll");
parms.CurrentParameters.Add("DataSetName", "MyDS");
parms.CurrentParameters.Add("FileNameSpace", "MyAssembly.DataSets");
parms.CurrentParameters.Add("DsNameSpace", "NewProject.DataSetWrappers");
parms.Save();
// Set the Template File
string templateFileName = "DataSetX.tt";
// Set up host and engine
CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
host.TemplateFileValue = templateFileName;
//Read the text template.
string input = File.ReadAllText(templateFileName);
//Transform the text template.
string result = engine.ProcessTemplate(input, host);
// Save logic would be here…
Notes on the Template
In the template, I am using TTCommucator.GetParams to read the parameter I’ve written in code above; same as in Part 3. I use the DataSetDesciber.ReflectDataSet.DataSetDef from Part 2 to get the tables, columns, types, etc. from the target DataSet. The generated code will be different from the target code in Part 1. For example:- I’m using .NET type names (like System.Int32) instead of C# type names (like int). That’s what Type.ToString() returns and I see no reason to convert it just to look pretty.
- I’m not using Nullable
’s ? operator. No good reason, it just seems more consistent with the .NET types in code (I don’t see System.Int32? in code all that often).
The Template
<#@ template debug="true" hostspecific="true" language="VB" #>
<#@ output extension=".cs" #>
<#@ assembly name="C:\GeneratorProject\TTCommucator.dll" #>
<#@ assembly name="C:\GeneratorProject\DataSetDesciber.dll" #>
<#@ import namespace="DataSetDesciber.ReflectDataSet" #>
<#@ import namespace="TTCommucator" #>
<#
' Load the Parameter object
Dim parms As new GetParams(Host.ResolvePath("setting.txt"))
parms.Load()
' Get the namespace
Dim FileNameSpace As String = parms.CurrentParameters.Item("FileNameSpace")
Dim DsNameSpace As String = parms.CurrentParameters.Item("DsNameSpace")
' a description of the DataSet to be wrapped
Dim refx As DataSetDef = GetDsDescripter(parms)
#>
// Generated with DataSetWrapper Version 1
using System;
using System.Collections;
using System.Collections.Generic;
using <#=DsNameSpace #>;
namespace <#=FileNameSpace #>
{
public class <#=refx.DataSetName #>X
{
private <#=refx.DataSetName #> _ds;
public <#=refx.DataSetName #>X(<#=refx.DataSetName #> ds)
{
_ds = ds;
}
<#For Each table As TableDef In refx.TableDefList #>
public <#=table.TableName#>DataTableX <#=table.TableName#>
{
get { return new <#=table.TableName#>DataTableX(_ds.<#=table.TableName#>); }
//set { _ds.<#=table.TableName#> = value; }
}
<#Next#>
public <#=refx.DataSetName #> DataSet
{
get { return _ds; }
}
}
<#For Each table As TableDef In refx.TableDefList #>
public class <#=table.TableName#>DataTableX: IEnumerable<<#=table.TableName#>RowX>
{
private <#=refx.DataSetName #>.<#=table.TableName#>DataTable _theTable;
public <#=table.TableName#>DataTableX(<#=refx.DataSetName #>.<#=table.TableName#>DataTable table)
{
_theTable = table;
}
public <#=refx.DataSetName #>.<#=table.TableName#>DataTable Table
{
get { return _theTable; }
}
#region IEnumerable<<#=table.TableName#>RowX> Members
public IEnumerator<<#=table.TableName#>RowX> GetEnumerator()
{
//return new <#=table.TableName#>RowXEnum(_rows);
return new <#=table.TableName#>TableEnum(_theTable);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)this;
}
#endregion
}
public class <#=table.TableName#>TableEnum : IEnumerator<<#=table.TableName#>RowX>
{
private <#=refx.DataSetName #>.<#=table.TableName#>DataTable _collection;
private int curIndex;
private <#=table.TableName#>RowX curBox;
public <#=table.TableName#>TableEnum(<#=refx.DataSetName #>.<#=table.TableName#>DataTable collection)
{
_collection = collection;
curIndex = -1;
curBox = default(<#=table.TableName#>RowX);
}
public bool MoveNext()
{
//Avoids going beyond the end of the collection.
if (++curIndex >= _collection.Count)
return false;
else
// Set current <#=table.TableName#>RowX to next item in collection.
curBox = new <#=table.TableName#>RowX((<#=refx.DataSetName #>.<#=table.TableName#>Row)_collection.Rows[curIndex]);
return true;
}
public void Reset() { curIndex = -1; }
public void Dispose() { }
public <#=table.TableName#>RowX Current
{
get { return curBox; }
}
object IEnumerator.Current
{
get { return curBox; }
}
}
public class <#=table.TableName#>RowX
{
<#=refx.DataSetName #>.<#=table.TableName#>Row _row;
public <#=table.TableName#>RowX(<#=refx.DataSetName #>.<#=table.TableName#>Row row)
{
_row = row;
}
/// <summary>
/// The wrapped DataRow
/// </summary>
public <#=refx.DataSetName #>.<#=table.TableName#>Row Row
{
get { return _row; }
}
#region Properties
<#For Each column As ColumnDef In table.ColumnDefList#>
<#If column.AllowDBNull Then#>
public <#=GetNullableType(column.DataType.ToString())#> <#=column.ColumnName#>
{
get
{
if (_row.Is<#=column.ColumnName#>Null())
return null;
else
return _row.<#=column.ColumnName#>;
}
set
{
if (<#=GetHasValueFunction(column.DataType.ToString())#>)
_row.Set<#=column.ColumnName#>Null();
else
_row.<#=column.ColumnName#> =
<#=GetNullableValueName(column.DataType.ToString())#>;
}
}
<#Else#>
public <#=column.DataType.ToString()#> <#=column.ColumnName#>
{
get { return _row.<#=column.ColumnName#>; }
set { _row.<#=column.ColumnName#> = value; }
}
<#End If#>
<#Next#>
#endregion Properties
}
<#Next#>
}
<#+
''' <summary>
''' Gets a DataSetDef bases on parms passed in
''' </summary>
''' <param name="parms">
''' The GetParms object containing parameters needed to create DataSetDef:<br />
''' AssembyPath: The path of the assembly containig the DataSet<br />
''' DataSetName: The fully qualified name of the dataset
''' </param>
''' <returns>The DataSetDef</returns>
Function GetDsDescripter(parms As GetParams) AS DataSetDef
Dim AssembyPath As String = parms.CurrentParameters.Item("AssembyPath")
Dim DataSetName As String = parms.CurrentParameters.Item("DataSetName")
Return DataSetReflector.GetDataSetDef(AssembyPath, DataSetName)
End Function
''' <summary>
''' Gets the nullable type name for a given type name
''' </summary>
''' <param name="typeName">
''' The type name as a string (as returned from Type.ToString())</param>
''' <returns>The Nullable type name</returns>
''' <remarks>
''' Since strings are nullable and not compatible with Generic Nullable class,
''' must be treated special
''' </remarks>
Function GetNullableType(typeName As String) As String
Dim rVal as string
Select Case typeName
Case "System.String"
rVal = typeName
Case Else
rVal = "Nullable<" & typeName & ">"
End Select
Return rVal
End Function
''' <summary>
''' Gets the string used to check nullablity in a set property method
''' </summary>
''' <param name="typeName">
''' The type name as a string (as returned from Type.ToString())
''' </param>
''' <returns>
''' string of an expression that will return true if value is null
''' </returns>
''' <remarks>
''' Most nullable types have a HasValue property but not all
''' </remarks>
Function GetHasValueFunction(typeName As String) As String
Dim rVal as string
Select Case typeName
Case "System.String"
rVal = "System.String.IsNullOrEmpty(value)"
Case Else
rVal = "!value.HasValue"
End Select
Return rVal
End Function
''' <summary>
''' Gets the string used on the right side of set property method
''' </summary>
''' <param name="typeName">
''' The type name as a string (as returned from Type.ToString())
''' </param>
''' <returns>The Value property name</returns>
''' <remarks>
''' Most nullable types have a Value property but not all
''' </remarks>
Function GetNullableValueName(typeName As String) As String
Dim rVal as string
Select Case typeName
Case "System.String"
rVal = "value"
Case Else
rVal = "value.Value"
End Select
Return rVal
End Function
#>
Generating a Nullable ADO.NET DataSet Wrapper: The Series
- Part 1: The Target Code
- Part 2: Describing the DataSet
- Part 3: Detour hacking parameters for T4 Templates (VS 2008)
- Part 4: The Template and putting it all together
Thursday, December 09, 2010
Generating a Nullable ADO.NET DataSet Wrapper
Part 3: Detour hacking parameters for T4 Templates (VS 2008)
I decided to use T4 Templates instead of my home grown code generation techniques. I didn’t want to spend several pages on my way; besides, I probably need to get with the times and play with the same toys as the cool kids.
This post is to describe a punt. I couldn’t find a way to pass parameters to a VS 2008 .tt file. I know that VS 2010 has the parameter directive, but I don’t have VS 2010, so thus the hack below.
TTCommucator.GetParams
Since I can reference .NET assemblies, I am storing my parameters as a Dictionary to a file outside of the template and then reading it in from inside the template.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace TTCommucator
{
/// <summary>
/// Used to pass parameters to a T4 Template
/// </summary>
public class GetParams
{
private const char SEPERATOR_CHAR = '|';
private string _fileName;
/// <summary>
/// The Dictionary containing the Parameters
/// </summary>
public Dictionary<string, string> CurrentParameters { get; set; }
/// <summary>
/// Creates a GetParmas with a Filename
/// </summary>
/// <param name="fileName">The name of the file to store the parameters</param>
/// <remarks>Both sides of the transaction must agree on file name</remarks>
public GetParams(string fileName)
{
CurrentParameters = new Dictionary<string, string>();
this._fileName = fileName;
}
/// <summary>
/// Loads the data from the file into the Dictionary CurrentParameters
/// </summary>
public void Load()
{
CurrentParameters = new Dictionary<string, string>();
string line;
using (TextReader tr = new StreamReader(_fileName))
{
while ((line = tr.ReadLine()) != null)
{
string[] parts = line.Split(new char[] { SEPERATOR_CHAR }, 2);
if (parts.Count() > 1)
CurrentParameters.Add(parts[0], parts[1]);
}
tr.Close();
}
}
/// <summary>
/// Saves the data in the Dictionary CurrentParameters to the file
/// </summary>
public void Save()
{
using (TextWriter tw = new StreamWriter(_fileName))
{
foreach (var item in CurrentParameters)
{
tw.WriteLine(item.Key + SEPERATOR_CHAR + item.Value);
}
tw.Close();
}
}
}
}
Test Template
I created a simple template to demonstrate that I can receive parameters using my GetParams class. Note that I use VB.NET for code in my templates. Since I usually generate C# code, I find it easier to read templates when the template code isn’t C#, besides “<#}#>” is downright nasty looking.
<#@ template debug="true" hostspecific="true" language="VB" #>
<#@ assembly name="C:\Projects\TTCommucator\TTCommucator.dll" #>
<#@ import namespace="TTCommucator" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>
<#
' Load the Parameters here:
Dim parms As new GetParams(Host.ResolvePath("setting.txt"))
parms.Load()
#>
Values in parms:
<#For Each item As KeyValuePair(Of String, String) In parms.CurrentSettings#>
<#=item.Key#> = <#=item.Value#>
<#Next#>
Test Console Application
Here is a simple program to test with. It doesn’t do anything more than necessary. NOTE: You will need to install Visual Studio 2008 SDK v1.1 to run this code:
using System;
using System.IO;
using Microsoft.VisualStudio.TextTemplating;
using TTCommucator;
namespace TTCommunicatorConsole
{
class Program
{
static void Main(string[] args)
{
// Set up some parameters:
var parms = new GetParams("setting.txt");
parms.CurrentParameters.Add("Greeting", "Hello World!");
parms.CurrentParameters.Add("Run Time", DateTime.Now.ToString());
parms.Save();
string templateFileName = "SimpleTemplate.tt";
// Set up host and engine
CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
host.SetTemplateFile(templateFileName);
//Read the text template.
string input = File.ReadAllText(templateFileName);
//Transform the text template.
string output = engine.ProcessTemplate(input, host);
// Show your work
Console.WriteLine(output);
// Wait for me
Console.ReadKey();
}
}
}
Host Class
I lifted this class from MSDN and removed the comments to save space. You can learn more about it by going here. NOTE: The MSDN code doesn't include the Serializable attribute, but I needed to add it to avoid errors. You will need to include a reference to Microsoft.VisualStudio.TextTemplating.
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
namespace TTCommucator
{
[Serializable]
public class CustomCmdLineHost : ITextTemplatingEngineHost
{
internal string _templateFile;
public string TemplateFile
{
get { return _templateFile; }
}
private string _fileExtension = ".txt";
public string FileExtension
{
get { return _fileExtension; }
}
private Encoding _fileEncoding = Encoding.UTF8;
public Encoding FileEncoding
{
get { return _fileEncoding; }
}
private CompilerErrorCollection _errors;
public CompilerErrorCollection Errors
{
get { return _errors; }
}
public IList StandardAssemblyReferences
{
get { return new string[] { typeof(System.Uri).Assembly.Location }; }
}
public IList StandardImports
{
get { return new string[] { "System" }; }
}
public bool LoadIncludeText(string requestFileName, out string content,
out string location)
{
content = string.Empty;
location = string.Empty;
if (File.Exists(requestFileName))
{
content = File.ReadAllText(requestFileName);
return true;
}
else
return false;
}
public object GetHostOption(string optionName)
{
object returnObject;
switch (optionName)
{
case "CacheAssemblies":
returnObject = true;
break;
default:
returnObject = null;
break;
}
return returnObject;
}
public string ResolveAssemblyReference(string assemblyReference)
{
if (File.Exists(assemblyReference))
return assemblyReference;
string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile),
assemblyReference);
if (File.Exists(candidate))
return candidate;
return string.Empty;
}
public Type ResolveDirectiveProcessor(string processorName)
{
if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase)
== 0)
{
}
throw new Exception("Directive Processor not found");
}
public string ResolvePath(string fileName)
{
if (fileName == null)
throw new ArgumentNullException("the file name cannot be null");
if (File.Exists(fileName))
return fileName;
string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile),
fileName);
if (File.Exists(candidate))
return candidate;
return fileName;
}
public string ResolveParameterValue(string directiveId, string processorName,
string parameterName)
{
if (directiveId == null)
throw new ArgumentNullException("the directiveId cannot be null");
if (processorName == null)
throw new ArgumentNullException("the processorName cannot be null");
if (parameterName == null)
throw new ArgumentNullException("the parameterName cannot be null");
return string.Empty;
}
public void SetFileExtension(string extension)
{
_fileExtension = extension;
}
public void SetTemplateFile(string templateFile)
{
_templateFile = templateFile;
}
public void SetOutputEncoding(Encoding encoding,
bool fromOutputDirective)
{
_fileEncoding = encoding;
}
public void LogErrors(CompilerErrorCollection errors)
{
_errors = errors;
}
public AppDomain ProvideTemplatingAppDomain(string content)
{
return AppDomain.CreateDomain("Generation App Domain");
}
}
}
Generating a Nullable ADO.NET DataSet Wrapper: The Series
- Part 1: The Target Code
- Part 2: Describing the DataSet
- Part 3: Detour hacking parameters for T4 Templates (VS 2008)
- Part 4: The Template and putting it all together