T4 templates have been a pretty popular topic lately. If you have no idea what they are, don't feel bad, I didn't either only a couple weeks ago! In a nutshell, it's a simple template processor that's built into VS and allows for all kind of cool code generation scenario. For a bunch of information about them, check out the following two blogs:
The basic idea is that you can drop a hello.tt file into a project, and it will generate a hello.cs (or hello.anything) file via its template. The template syntax of .tt files is very similar to ASP.NET's <% . %> blocks, except that they use <# . #> instead.
For a cool example of what you can do with T4, check out this post from Danny Simmons, which shares out a .tt which generates all the Entity Framework classes automatically from an edmx file. What's really nice is that you can easily customize the template to generate exactly the code that you want, instead of being locked into what the designer normally generates.
So how does that have anything to do with Dynamic Data?
One weakness we currently have with Dynamic Data is that we don't have a great way to get started with a custom page. Suppose you start out with a web app in full scaffold mode, and find the need to customize the Details page for Products. The 'standard' procedure to do this is:
- Create a Products folder under ~/DynamicData/CustomPages
- Copy ~/DynamicData/PageTemplates/Details.aspx into it
- Modify the copied file to do the customization you need
While this generally works, it has one big weakness: you need to start your customization from a 'generic' file which knows nothing about your specific table. e.g. you start out with a <asp:DetailsView> control that uses AutoGenerateRows to generate all the fields. So if you want to do any kind of real UI customization, you'll likely have to do a bunch of repetitive steps, e.g.
- Get rid of the DetailsView and replace it with a FormView (or ListView) so you can get full control over the layout
- In the FormView's item template, you'll then need to add one <asp:DynamicControl> for each field that you want to display, along with formatting UI between them (e.g. <tr>/<td>).
A T4 template to the rescue
In a few hours, I was able to put together a quick T4 template that replaces most of those repetitive steps. The steps using this template become simply:
- Create a Products folder under ~/DynamicData/CustomPages (as above)
- Drop Details.tt into it, and instantly it generates the aspx file, all expanded out with a FormView and all the DynamicControls! This .tt file is attached at the end of this post.
While this is cool, there is one thing that is a bit strange about it: you're left with a .tt file you don't want in your project:
And since details.tt file generates details.aspx file, you can't really change the aspx until you delete the .tt file. Unfortunately, VS makes this more difficult than you would think because the two files are linked to each other. The best steps I found to delete it are:
- Right click the .tt file and choose Exclude From Project
- Right click the Products folder in open it in Windows Explorer
- Delete the .tt file in the explorer
- Back in VS, click Show All Files in the solution explorer. You should see the aspx
- Now right click it and Include it in the project
I know, that's painful!! Ideally, what we really want to do is use the .tt do to a one time transform, but never actually have it be part of the project. At this point, I'm not yet sure how to do this, but I'm hopeful that there is a way. Suggestions are welcome! :)
How does the template work?
In order to perform its task, the template needs to figure out what all the entity type's fields are for the custom page. Doing this is a bit non-trivial, and what I do here is not as clean as it could be, as this is just a quick prototype. Here is a brief description of what it does:
- Based on the folder name, it knows what the Entity Set name is (e.g. Products)
- It then locates the app's assembly in bin (caveat: this only works in Web Apps, not Web Sites!)
- It copies it to a temp location and loads it from there, to avoid locking the bin assembly
- In the assembly, it finds the DataContext/ObjectContext (it handles both Linq To Sql and Entity Framework)
- From the context and the entity set name, and can know what the Entity Type is
- Once it has that, it knows the fields and can generate the page!
If you look at the t4 files, you'll see all this logic at the end. That part is certainly quick and dirty, and could use some rewriting. But what comes before it is a pretty clean template that's easy to tweak to your needs. e.g.
<table class="detailstable"> <# foreach (var prop in data.Properties) { #> <tr> <th> <#= prop.Name #> </th> <td> <asp:DynamicControl DataField="<#= prop.Name #>" runat="server" /> </td> </tr> <# } #> </table>
As you can see, it looks very much like inline code in an aspx file, except that instead of executing at runtime on the web server, it executes in Visual Studio to generate a file that becomes part of the project.
What about all the other views?
So I'm only talking about Details.tt/Details.aspx. What about the other Dynamic Data views: List, Edit, Insert? Well, those would all work in pretty much the same way, and I simply haven't had a chance to get to them. For now, I just wanted to get this first T4 file out as a proof of concept that shows the kind of things that can be done. Contributions are welcome! :)
Conclusion
Clearly, a lot of what I describe here is pretty raw, and it's only a very early prototype. But I think it should be enough to convey the potential power of design time file generation via T4 templates. Later, I'd like to get better integration with VS. e.g instead of dropping the .tt file in the project and later having to delete it, it'd be nice to get a custom action by right clicking on the CustomPages folder, that would let you generate the aspx file. It would still use T4 under the cover, but you wouldn't have to see it unless you want to.
Hopefully, I'll be writing more about this in the future if there is interest.
Source
Click Here.