Roslyn: InvokeActivity using T4

Imagine the following scenario: in a project, there are developers and some functional experts. Functional experts have to design some workflows. Developers have to code some methods usable by workflows.

The issue in this scenario is InvokeActivity. To use it, they have to know the method signature to specify the good parameters / result type. Another issue is the fact that they have to write themselves the method name (with potential typo).

They would probably prefer to drag and drop some activities in the workflow.

So my idea is to encapsulate InvokeActivity into an Activity. Now it could be very bored for the developer to create all of these activities.

For this, developers can decorate methods using Attribute and they can use T4 to generate activities.

T4 metacode can use Roslyn in order to get method information.

I include the EF.Utility.CS.ttinclude that allows creating many files from a T4 (in my case, I want one file per method).

<#@ include file="EF.Utility.CS.ttinclude"#>

I add Roslyn assemblies in my T4:

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Compilers.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.CSharp.dll"#>

<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Roslyn\v1.0\Roslyn.Services.VisualBasic.dll"#>

The solutionPath and the projectAssemblyName are specified at the beginning of the T4. So now, I can load the solution using Roslyn and get methods decorated with my Attribute in the specified project.

var solution = Solution.Load(solutionPath);

var project = solution.Projects.First(p => p.AssemblyName == projectAssemblyName);

foreach (var document in project.Documents)

{

   //…

}

Then, to identify methods, I use a SyntaxVisitor:

public class InvokeActivityAttributeVisitor : SyntaxVisitor<object>

{

    protected override object VisitCompilationUnit(CompilationUnitSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitTypeDeclaration(TypeDeclarationSyntax node)

    {

        foreach (var n in node.ChildNodes())

            Visit(n);

        return null;

    }

 

    protected override object VisitMethodDeclaration(MethodDeclarationSyntax node)

    {

        if (node.Modifiers.Any(st => st.Kind == SyntaxKind.PublicKeyword) && node.Attributes.Any(a => a.Attributes.Any(a2 => Regex.IsMatch(a2.Name.GetFullText(), @"^(Roslyn.WF.ActivityGenerator.)?InvokeActivity$"))))

        {

            string methodName = node.Identifier.GetText();


            var returnTypeAsPredefinedTypeSyntax = node.ReturnType as PredefinedTypeSyntax;


            if (returnTypeAsPredefinedTypeSyntax == null || returnTypeAsPredefinedTypeSyntax.Keyword.Kind != SyntaxKind.VoidKeyword)


            {


                //…       


            }


            foreach (var parameter in node.ParameterList.Parameters)


            {


                //…       


            }


        }



    }



}



Now the point is to know types namespace and assembly. For this, we will use compilation symbols:



_syntaxTree = (SyntaxTree)_document.GetSyntaxTree();


_semanticModel = _document.Project.GetCompilation().GetSemanticModel(_syntaxTree);


var returnTypeSymbol = _semanticModel.GetSemanticInfo(node.ReturnType).Symbol;


string returnTypeAssemblyName = returnTypeSymbol.ContainingAssembly.AssemblyName.Name;


string returnTypeNamespaceName = returnTypeSymbol.ContainingNamespace.ToString();


We also need to know if parameters are In/Out/InOut:



parameter.Modifiers.Any(st => st.Kind == SyntaxKind.RefKeyword) ? Direction.InOut : (parameter.Modifiers.Any(st => st.Kind == SyntaxKind.OutKeyword) ? Direction.Out : Direction.In)


The rest of the code is basic, used to generate WF Activities.



You can download a sample here.

This entry was posted in 10511, 16181, 16402, 7672. Bookmark the permalink.

2 Responses to Roslyn: InvokeActivity using T4

  1. Matthieu MEZIL says:

    Thanks Kirill :)

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>