I have started dabbling in T4 and first got along pretty well, but then ran into an issue that's actually pretty obvious and might not be solvable, but maybe there is a way that I just lack the experience to know or see.
Given the following class:
public class T4Test : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
}
[Input("InX")]
public InArgument<string> InX { get; set; }
[Output("OutX")]
public OutArgument<string> OutX { get; set; }
}
I want this as the output:
public class ActivityWrapper
{
private readonly T4Test _activity;
private readonly ActivityContext _context;
public ActivityWrapper(T4Test activity, ActivityContext context)
{
this._activity = activity;
this._context = context;
}
public string InX
{
get { return this._activity.InX.Get(this._context); }
}
public string OutX
{
get { return this._activity.OutX.Get(this._context); }
set { this._activity.OutX.Set(this._context, value); }
}
}
I have figured out the Reflection stuff I need, and I know what the T4 code should look like, but there's one problem: I need it in the same project as the T4Test
class. However, to load the assembly and reflect over it, it needs to be compiled - but of course that's a bit difficult if I intend to modify that same assembly's code. (And I guess NCrunch doesn't simplify things.)
Now here's the things that I hope might still make it possible to solve this:
ActivityContext
can't be mocked..cs
file around).Is there any way to achieve this? (And was that even clear enough?)
I would like to propose an alternative to reflecting the generated assembly, since transforming the T4 only works when the project successfully built and generates proper output iff the assembly is not outdated.
If you use a hostspecific T4 template you gain access to the Visual Studio automation model through the EnvDTE interfaces. Using this you can walk the CodeModel of your currently loaded Visual Studio solution without the need to building it first.
Have a look at my answer to this SO question: Design Time Reflection. Using the aid of the free template from tangible's Template Gallery you could easily "reflect" your existing classes at design time and detect properties decorated with the desired attributes:
<#
var project = VisualStudioHelper.CurrentProject;
// get all class items from the code model
var allClasses = VisualStudioHelper.GetAllCodeElementsOfType(project.CodeModel.CodeElements, EnvDTE.vsCMElement.vsCMElementClass, false);
// iterate all classes
foreach(EnvDTE.CodeClass codeClass in allClasses)
{
// iterate all properties
var allProperties = VisualStudioHelper.GetAllCodeElementsOfType(codeClass.Members, EnvDTE.vsCMElement.vsCMElementProperty, true);
foreach(EnvDTE.CodeProperty property in allProperties)
{
// check if it is decorated with an "Input"-Attribute
if (property.Attributes.OfType<EnvDTE.CodeAttribute>().Any(a => a.FullName == "Input"))
{
...
}
}
}
#>
T4Test.tt
<#@ include file="Activities.tt" #>
<#
var t4test = new Activity("T4Test")
{
Input("InX"),
Output("OutX"),
};
GenerateCode(t4test);
#>
Activities.tt
<#@ template language="C#" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
class Activity : IEnumerable<Property>
{
private string name, wrapper;
private List<Property> properties;
public Activity(string name, string wrapper = null)
{
this.name = name;
this.wrapper = wrapper ?? name + "Wrapper";
this.properties = new List<Property>();
}
public void Add(Property property)
{
this.properties.Add(property);
}
public IEnumerator<Property> GetEnumerator()
{
return this.properties.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void GenerateCode()
{
// ...
}
}
class Property
{
private bool output;
private string name, type;
public Property(bool output, string name, string type)
{
this.output = output;
this.name = name;
this.type = type;
}
}
Property Input(string name, string type = "string")
{
return new Property(false, name, type);
}
Property Output(string name, string type = "string")
{
return new Property(true, name, type);
}
void GenerateCode(params Activity[] activities)
{
WriteLine("namespace Foo");
WriteLine("{");
PushIndent(" ");
foreach (var activity in activities)
{
WriteLine("class " + activity.name);
WriteLine("{");
PushIndent(" ");
// ...
PopIndent();
WriteLine("}");
}
PopIndent();
WriteLine("}");
}
#>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With