Parametrisierte Text Templates und System.Runtime.Serialization.SerializationException
Zurzeit beschäftige ich mich mit den T4 Templates. Ein sporadisches Problem ala Murphy trat bei parametrisierten Vorlagen auf. Wenn ich diese innerhalb einer anderen Textvorlage aufgerufen habe und einen Parameter mit einem benutzerdefinierten Objekt übergeben wollte begrüsste mich die Fehlermeldung:
Auszug Fehlermeldung Error Running transformation: System.Runtime.Serialization.SerializationException: Unable to find assembly 'UmlHelper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Server stack trace: at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name) at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm) at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage.FixupForNewAppDomain() at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm) at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] args) Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) at Microsoft.VisualStudio.TextTemplating.ITextTemplatingSessionHost.set_Session(ITextTemplatingSession value) at Microsoft.VisualStudio.TextTemplatingBD1EC50CBA912372FF5EDDFDC3DFE64D.GeneratedTextTransformation.GetGeneratedOutputWithTemplate(String templateName, ObjectData element) at Microsoft.VisualStudio.TextTemplatingBD1EC50CBA912372FF5EDDFDC3DFE64D.GeneratedTextTransformation.TransformText() at Microsoft.VisualStudio.TextTemplating.TransformationRunner.RunTransformation(TemplateProcessingSession session, String source, ITextTemplatingEngineHost host, String& result)
Der Aufruf der Parameter-Vorlage erfolgte in der Hauptvorlage nach folgenden Muster:
C# Class Feature Block
private string GetGeneratedOutputWithTemplate(string templateName, TestUML.ObjectData element)
{
string templateFile = this.Host.ResolvePath(templateName);
string templateContent = File.ReadAllText(templateFile);
TextTemplatingSession session = new TextTemplatingSession();
session["Element"] = element;
// requires host specific true
var sessionHost = (ITextTemplatingSessionHost) this.Host;
sessionHost.Session = session;
Engine engine = new Engine();
string generatedContent = engine.ProcessTemplate(templateContent, this.Host);
return generatedContent;
}
In der Session wurde ein Objekt übergeben, das in der aktuellen Vorlage erstellt wurde. Im Template mit dem Parameter soll dann nur noch die Ausgabe aufbereitet werden.
Die parametrisierte Vorlage ist so weniger komplex und enthält nur die Ausgabelogik:
Parametrisierte T4 Vorlage <#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ import namespace="System.Collections.Generic" #> <#@ parameter name="Element" type="TestUML.ObjectData" #> ///namespace <#= Element.Namespace #> { using System; public partial class <#= Element.ObjectName #> { <# foreach (var item in Element.ObjectAttributes) { #> public <#= item.TypeName #> <#= item.Name #> { get; set; } <# } #> } }
Wie kam nun dieser Fehler zustande und warum? Der Code wird innerhalb einer AppDomain ausgeführt. So war es sporadisch möglich, dass das Helper-Objekt nicht gefunden werden konnte, da die Templates bzw. die Dll-Datei nicht innerhalb der gleichen AppDomain liefen. Lösen konnte ich das Problem, indem ich der Hilfsklasse die Parent-Klasse MarshalByRefObject spendierte. Diese ermöglicht den Zugriff auf das Objekt über die AppDomain-Grenzen hinaus.
C# (Hilfsobjekt)
[Serializable]
public class ObjectData : MarshalByRefObject
{
public string Namespace { get; set; }
public string ObjectName { get; set; }
public List ObjectAttributes { get; set; }
}
Mal sehen, ob die sporadische Fehlermeldung nun auch definitiv beseitigt ist.
- 0 Kommentar(e)


Mein Kommentar