A while back, my friend and coworker Kris Scott posted an article about using a proxy around DataContext classes to add support for isolation/mocking for testing purposes. I won’t re-hash the basic idea here, as Kris’ blog entry explains it just fine. In that article he links to a Google Code project which generates these proxy classes for you. I’ve used it to great success on two separate projects now, but as I prepare to leave my current project, I didn’t feel altogether comfortable leaving behind a dependency on an external tool that none of the other developers were familiar with.
I’d been looking at Damien Guard’s T4 template replacement for generating an entire replacement Linq to Sql DataContext, as well as Oleg Sych’s articles regarding T4 template processing in Visual Studio 2008. This started me thinking about creating a T4 replacement for the proxy class. I spent some time this weekend taking a stab at it, and I now have a T4 template that generates the exact same thing as Kris’ original program. This template adapts a few of the classes from Kris’ generator, using Linq to XML in place of XML DOM code. The results are the same, but I like Linq a lot, and figured it would be worthwhile making the change as long as I was in the neighborhood.
One of the interesting things about using T4 templates is that they go to work whenever you make a change to the template itself. You’re can also right-click on the template and "Run Custom Tool" to re-run the generation whenever you like, but in our case we’d like the template to be re-run whenever we make a change to the .dbml file, which is going to change far more often than the template will. To accomplish this, I’ll again adapt something that was working for Kris’ technique. On the project he had written it for we were launching Kris’ generator program as a pre-build step of the project containing the .dbml file. Some searching on the web told me that the T4 template processor is called TextTransform.exe, and can be run as a command line tool. I changed the pre-build step to something like this.
"C:\Program Files\Common Files\Microsoft Shared\TextTemplating\1.2\TextTransform" "$(ProjectDir)\AbcData.tt"
Unfortunately, the TextTransform tool’s directory is not in any search path, so I’ve had to hard-code the entire path to the tool. This is fine for now, but if a future version of the tool is released, I’ll have to remember to update the pre-build step to call the newer version. In this example, the dbml file is called AbcData.dbml, so we call the T4 template "AbcData.tt" to match. All T4 templates have a ".tt" extension, and in our case I’m following Damien Guard’s lead and dynamically "figuring out" the path to the dbml by requiring the T4 template to have a similar name. The template will create a class called "AbcData.Proxy.cs". I wanted the file to be called "AbcDataProxy.cs" like Kris’ original program did, but the T4 processor seems to add the extra dot whether I want it to or not. The proxy class itself is still called "AbcDataProxy", though. I won’t dissect the template line by line here, but I’ll point out some of the highlights.
At the top of the template file we have a bunch of directives. You can get information on what they all mean off of MSDN or from Oleg’s posts. Most of them are essentially "using" statements which specify the .Net libraries that our template’s code will be using. The second line is where you specify what extension your resulting code should have. It’s actual filename will be determined by the name of the template itself. This is where that extra dot comes from. Whether I specify the extension as ".Proxy.cs" or "Proxy.cs" the T4 processor will helpfully make sure that there’s a dot there… gee, thanks.
<#@ template language="C#v3.5" hostspecific="True" #> <#@ output extension="Proxy.cs" #>
When we run the template, it will need to be able to write to a file that in all likelihood already exists, and is under source control. To accomplish this, we’ll need to make the file writeable. Visual Studio will somehow magically pick up on this and check the file out from source control, which is pretty cool, actually. We’ll take the name of the template, do some string manipulation to come up with the name of the resulting file, and then remove the ReadOnly attribute from that file before continuing with the code generation.
// Make the output file writable if it already exists string outputFileName = Host.TemplateFile.Replace(".tt", ".Proxy.cs"); if(File.Exists(outputFileName)) { FileAttributes attributes = File.GetAttributes(outputFileName); File.SetAttributes(outputFileName, attributes & ~FileAttributes.ReadOnly); }
After that, we’ll load the .dbml file, which is just an XML document, and begin using Linq to XML’s methods to start dissecting it, looking for the parts we’re interested in.
var dbml = XDocument.Load(Host.TemplateFile.Replace(".tt", ".dbml")); var root = dbml.Root; var Tables = (from t in root.Elements(NS + "Table") select new ContextType(NS, t)).ToList(); var Functions = (from t in root.Elements(NS + "Function") select new ContextFunction(NS, t)).ToList();
This is where my versions of Kris’ classes come into play. The ContextType and ContextFunction classes take an XElement into their constructors, and can generate the interface or proxy class signatures we’ll need later on. Next, we add some boilerplate stuff to the template output like using statements, the interface header, and some methods that appear in every interface without changing. After that, we escape out to code similar to classic ASP or CodeSmith templates and include two loops which will use the ContextType and ContextFunction classes we created earlier to emit the per-table and per-function contents of the interface.
namespace <#= ContextNamespace #> { public partial interface <#= InterfaceName #> { void CreateDatabase(); bool DatabaseExists(); ... <# foreach(var table in Tables) { WriteLine("\t\t" + table.InterfaceSignature()); } foreach(var function in Functions) { WriteLine("\t\t" + function.InterfaceSignature()); } #> }
After generating the interface, similar code will create the proxy itself. Toward the bottom of the template, you’ll find the declaration of the ContextType and ContextFunction classes themselves. They appear in a different kind of code block, delimited by "<#+" and "#>". Any classes your template needs can be declared in this way. I could have broken these out into a separate file and used directives to include it here, but I prefer having a single template file like this, at least at this scale.
So how do we use all this?
- Get the template and supporting classes (here).
- Unzip them into the folder containing the dbml file you want to generate a proxy for.
- Rename AbcData.tt to match the name of your dbml file, but with a ".tt" extension.
- Include the files in your project.
- Add a pre-build step to the project with the dbml file, similar to the following:
"C:\Program Files\Common Files\Microsoft Shared\TextTemplating\1.2\TextTransform" "$(ProjectDir)\AbcData.tt" - Build your project
- Enjoy
UPDATE: For those of you with 64 bit Windows installations
I KNEW the hard-coded path would cause a problem, and I was right. 64 bit windows adds "(x86)" at the end of the 32 bit "Program Files" folder, so the original hard-coded path won’t work. I’ve used a couple different techniques in the past to solve this, but my current favorite is using a relative path from the project itself. Find the TextTransform.exe file, which is located at "C:\Program Files\Common Files\Microsoft Shared\TextTemplating" (With or without the x86 part, as appropriate). Once you have found it, just copy it into a folder in your solution. In this example, I’ve copied it into a "libs" folder at the root of the solution. Now add a pre-build step to the project containing the .dbml file, and use relative paths to use your local copy of TextTransform.exe. It looks like this:
"$(ProjectDir)..\..\..\libs\TextTemplating\TextTransform" "$(ProjectDir)\AbcData.tt"
Interesting… I honestly had no idea what T4 templates were. That\’s a very handy way to generate code, and I can see how it would be easier than writing normal C# code to spit out code files.
Hello, Neat post. There is a problem with your website in web explorer, could test this?
IE nonetheless is the market leader and a large component to other folks will
omit your fantastic writing due to this problem.
Could you be more specific? Apart from some longer lines running off the right side of their box, which is more of a theme problem than an IE problem, I don’t see anything wrong.