Wednesday, November 10, 2010

Static reflection... or T4 with EnvDte?

So I like static reflection for getting Class, Property or Method names for databinding, but a lot of people aren't comfortable with the possible performance implications, or for whatever reason (usually FUD).  I've found an alternative using T4+EnvDte.

Code is included in the link...






    <#@ template language="C#" hostspecific="True" debug="True" #>
    <#@ output extension="cs" #>
    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Xml" #>
    <#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
    <#@ assembly name="EnvDTE" #>
    <#@ assembly name="EnvDTE80" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Text.RegularExpressions" #>
    <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
    <#@ import namespace="EnvDTE" #>
    <#@ import namespace="EnvDTE80" #>
   
        <#
    var serviceProvider = Host as IServiceProvider;
        if (serviceProvider != null) {
            Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
        }

        // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
        if (Dte == null) {
            throw new Exception("T4Generator can only execute through the Visual Studio host");
        }
       
        foreach(var project in Dte.Solution.Projects.Cast<Project>()
            .Where(p=> p.CodeModel!=null && p.Name!="Solution Items"))
        {
            //var appRoot= Path.GetDirectoryName(project.FullName) + '\\';
            var rootNamespace = project.Properties.Item("RootNamespace").Value.ToString();
            WriteLine("//Project:"+project.Name);
            #><#
        WriteLine(string.Empty);#>namespace <#= rootNamespace #>.T4Helper{
        <#
           
                WriteLine("#region "+project.Name+ " properties");
                WriteLine(string.Empty);
            foreach(var prop in project.Properties.Cast<EnvDTE.Property>())
            {
           
                Write("//"+prop.Name+" : ");
                try
                    {           
                        WriteLine(prop.Value.ToString());
                    }
                    catch (Exception ex)
                    {
                        WriteLine("Exception : "+ex.Message);
                    }
            }
            WriteLine("#endregion");
            WriteLine(string.Empty);
            ProcessFileCodeModel(project);
            WriteLine(string.Empty);
            WriteLine("}");
        }
       
    #>


<#+
    //const string Kind_PhysicalFolder = "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}";
    bool AlwaysKeepTemplateDirty = true;
    static DTE Dte;

       
void ProcessFileCodeModel(Project project)
    {
        foreach(var item in project.ProjectItems.Cast<ProjectItem>())
        {
            WriteLine("//Found item:"+item.Name);
            if(item.FileCodeModel!=null )
            {
                WriteLine("//Found CodeModel:"+item.FileCodeModel.GetType().FullName);
                RecurseFindElements(item.FileCodeModel.CodeElements);
           
            }
           
        }
       
       
    }
    void WriteMapping(CodeClass codeClass)
    {
        WriteLine("///"+codeClass.FullName);
        WriteLine("public static class "+MakeIntoValidIdentifier(codeClass.Name)+"{");
        PushIndent("\t");
       
        WriteLine("public static string MetaName{get{return \""+codeClass.Name+"\";}}");
       
        RecurseFindElements(codeClass.Children);       
        PopIndent();
            WriteLine("}");
        WriteLine(string.Empty);
    }
    void WriteMapping(CodeProperty codeProperty)
    {
        WriteLine("///"+codeProperty.FullName);
        WriteLine("public static string Meta"+MakeIntoValidIdentifier(codeProperty.Name)+"{ get{ return \""+
            codeProperty.Name+"\";}}");
        //WriteLine("public static string MetaBinding"+MakeIntoValidIdentifier(codeProperty.Name)+"{ get{ return \""
            //+codeProperty.Parent.Name+"."+codeProperty.Name+"\";}}");
    }
    void WriteMapping(CodeFunction codeFunction)
    {
        WriteLine("///"+codeFunction.FullName);
        WriteLine("public static string Meta"+codeFunction.Name+"{ get{ return \""+
            codeFunction.Name+"\";}}");
    }
 private void RecurseFindElements(CodeElements codeElements)
    {
     
      var q=codeElements.Cast<CodeElement>( )
        //.Where(x =>x is CodeInterface||x is CodeClass || x is CodeNamespace) //&& ((CodeInterface)x).Attributes.Count>0)
        //.Where(x => x.Name.StartsWith("System")==false)
        //.Where(x => x.Name.StartsWith("Infragistics")==false)
        //.Where(x => x.Name.StartsWith("Microsoft")==false)
        //.Where(x => x.Name.StartsWith("ICSharpCode")==false)
        ;
        var functions=new List<string>();
      foreach (var element in q)
      {
       
        if (element is CodeFunction && functions.Contains(element.Name)==false)
        {
           
            WriteMapping(element as CodeFunction);
            functions.Add(element.Name);
        }
        if(element is CodeProperty)
        {
            WriteMapping(element as CodeProperty);
        }
        if (element is CodeInterface)
        {
          var c = (CodeInterface)element;
          RecurseFindElements(c.Members);

        }
        if (element is CodeClass)
        {
          var c = (CodeClass)element;
            //verify element is a project element not an external
          if (c.InfoLocation==vsCMInfoLocation.vsCMInfoLocationProject)
            {
                WriteMapping(c);
            }
        
        }
        if (element is CodeNamespace)
        {
           
            var ns=(CodeNamespace)element;
           
          RecurseFindElements(ns.Members);
        }
      }
    }

string MakeIntoValidIdentifier(string arbitraryString)
{
    var validIdentifier = Regex.Replace(arbitraryString, @"[^A-Za-z0-9-._]", " ");
    validIdentifier = ConvertToPascalCase(validIdentifier);
    if (Regex.IsMatch(validIdentifier, @"^\d")) validIdentifier = "_" + validIdentifier;
    return validIdentifier;
}

string ConvertToPascalCase(string phrase)
{
    string[] splittedPhrase = phrase.Split(' ', '-', '.');
    var sb = new StringBuilder();

    sb = new StringBuilder();

    foreach (String s in splittedPhrase)
    {
        char[] splittedPhraseChars = s.ToCharArray();
        if (splittedPhraseChars.Length > 0)
        {
            splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0];
        }
        sb.Append(new String(splittedPhraseChars));
    }
    return sb.ToString();
}

void EmitNamesInnerClass(List<string> names)
{
    if(names.Any())
    {
        WriteLine("\r\n\t\tpublic static class Names");
        WriteLine("\t\t{");
        foreach(var name in names)
            WriteLine(string.Format("\t\t\tpublic const string {0} = \"{0}\";", name));
        WriteLine("\t\t}");

        names.Clear();
    }
}

Project GetProjectContainingT4File(DTE dte) {

    // Find the .tt file's ProjectItem
    ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);

    // If the .tt file is not opened, open it
    if (projectItem.Document == null)
        projectItem.Open(Constants.vsViewKindCode);

    if (AlwaysKeepTemplateDirty) {
        // Mark the .tt file as unsaved. This way it will be saved and update itself next time the
        // project is built. Basically, it keeps marking itself as unsaved to make the next build work.
        // Note: this is certainly hacky, but is the best I could come up with so far.
        projectItem.Document.Saved = false;
    }

    return projectItem.ContainingProject;
}

#>

4 comments:

  1. hi,
    thanks for the post is is very useful like example to combine t4 and envdte.
    only one question
    in the line

    "Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
    "

    what is it "SDTE" could be substituted by EnvDTE.DTE
    thanks again

    ReplyDelete
  2. This should be Microsoft.VisualStudio.Shell.Interop.SDTE

    http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.sdte.aspx

    ReplyDelete
  3. thanks again
    I'm not able to find it

    ReplyDelete
  4. sorry for my english, I mean "I wasn't able" until your explanation.
    and thanks for the post.

    ReplyDelete