Jeff Sheldon

Mostly.NET
posts - 15, comments - 1, trackbacks - 0

Sunday, September 05, 2010

More T4 Template Happiness!

So this is another post about T4 templates.   But not just ANY T4 templates, Preprocessed Text Template’s (oooooh, ahhhhh)

Long story short, these are templates you can embed in an application to execute at runtime rather than at design time.  There are some limitations compared to regular T4 templates, but needless to say, it sure makes generating code a breeze.

Back Story

I have this project I needed to work on that involved generating code from an Xsd.   This Xsd changed often, so I created a project that did it for me because I’m lazy.  So here’s how I did it.

1.  Created a Console Application that took some parameters, namely, the string value for the namespace, the file location for the output dll, and the file location of the input Xsd file.

2.  Inside of the project that used this file, I added a pre-build event that executed the generation of the dll.

3.  Profit!

So here’s a little bit on how I went about the code generating console application.

First off, I do a lot of Xml manipulation, parsing, etc at work.  But, like I said before, I’m lazy.  I prefer to do my Xml work in C#.  So in order to process an Xsd I (in my opinion) cheated.  I created a set of classes that allow me to simply deserialize the xsd into a class.

image

 

Notice XsdBase, there’s not much to this, it’s just a base class created to handle Xsd element types that have annotations, maxOccurs, and minOccurs.  It’s mostly there for extension method purposes.

Now with this in place, I can take the Xsd path, and simply deserialize it to the XsdSchema class.  yay!

   1: public static XsdSchema Parse(string filePath)
   2: {
   3:     var document = XDocument.Load(filePath);
   4:     var schema = Parse<XsdSchema>(document.Root);
   5:     schema.SetParent(null, null);
   6:     return schema;
   7: }
   8:  
   9: public static T Parse<T>(XElement element)
  10: {
  11:     using (var sr = new StringReader(element.ToString()))
  12:     {
  13:         using (var r = new XmlTextReader(sr))
  14:         {
  15:             var s = new XmlSerializer(typeof (T), "http://www.w3.org/2001/XMLSchema");
  16:             s.UnreferencedObject += (sender, e) => Console.WriteLine("Unreferenced Object: {0}", e.UnreferencedId);
  17:             s.UnknownElement += (sender, e) => Console.WriteLine("Encountered an unknown element on line {0}: {1}", e.LineNumber, e.Element.LocalName);
  18:             s.UnknownAttribute += (sender, e) => Console.WriteLine("Encountered an unknown attribute on line {0}: {1}.{2}", e.LineNumber, e.ObjectBeingDeserialized.GetType().Name, e.Attr.LocalName);
  19:             s.UnknownNode += (sender, e) => Console.WriteLine("Encountered an unknown node on line {0}: {1}", e.LineNumber, e.LocalName);
  20:             return (T) s.Deserialize(r);
  21:         }
  22:     }
  23: }

 

So now I have some T4 template, and some classes I can use with it, Now what?

Well, I start off by adding two parameters to the top of my T4 template for passing in some information. 

   1: <#@ parameter name="FilePath" type="System.String" #>
   2: <#@ parameter name="Namespace" type="System.String" #>

 

And I use them like so:

   1: var xsdSchema = XsdSchema.Parse(Session["FilePath"].ToString());
   2: var ns = Session["Namespace"].ToString();
 

Now my T4 template has access to those files, and it can do it’s thing.   But wait, how do I pass that into the T4 template in the first place?
Well I did it like this!
   1: var codeGen = new ClassGenerator
   2:             {
   3:                 Session = new Dictionary<string, object>
   4:                                                 {
   5:                                                     {"FilePath", xsdPath},
   6:                                                     {"Namespace", ns}
   7:                                                 }
   8:             };
   9:  
  10:  
  11:             var code = codeGen.TransformText();

 

 

And now the code variable has all my generated code in it.  Neat!
So that’s pretty much it.  I’ve attached the project for you so you can see the in’s and out’s.  Keep in mind though, it’s not quite production ready.  Overall it was written in a bit of a hurry, so I think there are some unused methods and cheesy code hacks in there.

Also, it won’t necessarily work for ANY xsd.  I tried to make it someone generic, but you might need to modify it some to work with your specific Xsd (I did).

Hrm, I think that’s all of the disclaimers I need.

Attachments:  GenerateFromXsd.IV.zip

posted @ Sunday, September 05, 2010 1:18 PM | Feedback (1) | Filed Under [ t4 Xsd code-generation ]

Entity Framework – T4 Template Happiness

 

I was going to make this one long post, but there’s just too much to cover.   I’ll start with this one talking about the new Entity Framework changes and my hacks to use them my way.

Disclaimer:  I got the base concept of how I do things from someone else, but for the life of me I can’t find the blog post I found months ago that outlines this.  I’ll come back and update it once I find it, I’m pretty sure it was posted right before the EF 4.0 was released if that helps anyone find it.

Ok, so, back to my way.

When using the new Entity Framework features, I almost always use the new Self Tracking Entities by using the Context Menu in the Edmx designer and selecting “Add Code Generation Item”.

image

One of the major reasons I love the new STE templates is the separation of my domain objects from the data context.  So let me backup by saying I almost always setup my project structure like so:

image

Now, once we’ve added the STE project we’ll have 3 files created.   The Edmx, the Name.Context.tt, and the Name.tt.   The Name.Context file holds the ObjectContext class which manages the connections, adding, editing, etc.   The Name.tt file handles the generation of all your domain objects.

So the first thing I’ll do after doing that, is add a DomainObjects class library project.  Create my generated folder (because the contents is generated) and add the Name.tt file “As a Link” I also select the original file and empty the “Custom Tool” property.

This causes all of my domain objects to be created in a separate project. (Yay!)  Once that’s done, there are a few tweaks I still need to make.  Because my files are in the generated folder, they’re namespace will also end with “Generated”.  I can’t have that, so I edit the property “Custom Tool Namespace” on each file so that it makes sense.

So in the end, we’re left with a project structure somewhat like the screenshot above.  Take note of the “Partials” folder.  that’s where I stick my partial classes for my domain objects, this holds customized metadata like the following for the Agent class.

   1: using System.ComponentModel;
   2: using System.ComponentModel.DataAnnotations;
   3:  
   4: namespace Pax.DomainObjects
   5: {
   6:     [MetadataType(typeof(IAgentMetaData))]
   7:     public partial class Agent
   8:     {
   9:     }
  10:  
  11:     public interface IAgentMetaData
  12:     {
  13:         [StringLength(50)]
  14:         [Required]
  15:         [DisplayName("First Name")]
  16:         string FirstName { get; set; }
  17:  
  18:         [StringLength(50)]
  19:         [Required]
  20:         [DisplayName("Last Name")]
  21:         string LastName { get; set; }
  22:  
  23:         [DataType(DataType.PhoneNumber)]
  24:         string BusinessNumber { get; set; }
  25:  
  26:         [DataType(DataType.PhoneNumber)]
  27:         string HomeNumber { get; set; }
  28:  
  29:         [DataType(DataType.PhoneNumber)]
  30:         string FaxNumber { get; set; }
  31:  
  32:         [DataType(DataType.PhoneNumber)]
  33:         string MobileNumber { get; set; }
  34:  
  35:         [DataType(DataType.EmailAddress)]
  36:         string EmailAddress { get; set; }
  37:  
  38:         [DataType(DataType.Url)]
  39:         string WebPage { get; set; }
  40:  
  41:         [DataType(DataType.MultilineText)]
  42:         string Notes { get; set; }
  43:     }
  44: }

 

At this point we’re all setup.  But wait, what if we want to customize the code generated by the T4 template?  Well go right ahead, I do it all the time.  But one thing that has always bugged me in the past, is I need to go and modify this every time I start a new project.  Well not any more! Introducing the “Common” folder.

What I did was took a generic project and did the steps above.  I then took the T4 templates and modified them the way I like them, for example, auto adding the StringLength property, auto adding the Using statement for my DomainObjects class etc.  Next I wrapped them so that I could call them as a function so that I could include them, rather than execute them directly.   Then I put them in the Common Folder like so:

image

I’m not sure if I need to include the EF.Utility.CSv2.ttinclude or not, but I put it there anyway.  So now, my Name.Content.tt and my Name.tt files simply include them.  The code for each looks like this:

Pax.Context.tt

   1: <#@ include file="Common/Context.tt"#><#
   2: BeginCode(@"Pax.edmx");
   3: #>
   4:  
   5: <#+
   6: string GetCustomUsings()
   7: {
   8:     return "using Pax.DomainObjects;";
   9: }
  10: #>

 

Pax.tt

   1: <#@ include file="Common/DomainObjects.tt"#><#
   2: BeginCode(@"Pax.edmx");
   3: #>

 

There are two huge benefits to this.

1.  At work, I often use multiple Edmx’s for a project.  I’m not going to explain why, needless to say, if my T4 templates are setup like this, then any modifications I need to make to the main template, automagically get applied to every template that implements them.

2. When I create a new project, I can copy this setup into it, and I’m off and running with all of my normal customizations, without having to dig through the created template to make the changes.


I’m not going to post the entire contents of my Context/DomainObjects templates, but I have them attached for your use.  Keep in mind, they’re not changed drastically, I mostly just made some tweaks, and then wrapped the primary code generation portion in a method called BeginCode that accepts an input file.  I also added a virtual method so you can auto include using statements.   That’s about it.

Hope you find this useful!

Attachments: Custom.T4.SelfTrackingEntities.zip

posted @ Sunday, September 05, 2010 12:53 PM | Feedback (0) | Filed Under [ entity-framework t4 ]

Blog Updated…

Well I finally got around to updating my blog to the new version of Subtext

Big thanks to the whole Subtext team for a smooth upgrade process.  A short video by Phil Haack really helped the process a lot.  Not to mention the Subtext Upgrade Tool.

Now it’s time to put up some code related blog posts!

posted @ Sunday, September 05, 2010 11:49 AM | Feedback (0) |

Powered by:
Powered By Subtext Powered By ASP.NET