Over Atos Origin
Contact
Diensten
Nieuws
Home
Feedback  |  Atos Origin.com  |  Syndicatie
Syndicatie
 
Links
Archief
Meer over de bloggers
Onderwerpen
Alle onderwerpen
Agile
Algemeen
Architectuur
Besturingssystemen
Bpm
Business intelligence
Business proces design
Cloud computing
Eai
Governance
Ibm
Integratie
It consultancy
Java
Microsoft
Nljug
Open source
Oracle
Process standaarden
Project management
Requirements engineering
Rich internet applications
Saas
Security
Sharepoint
Soa
Testing
Virtualisatie
Xml

Reflection als Wcf inpakpapier
Geplaatst op 28 November 2008, 00:56 door Sander van de Velde in soa, microsoft, architectuur

Reflection als WCF inpakpapier


Sander van de Velde heeft een oplossing bedacht voor het runtime inpakken van een WCF interface waarbij de ingepakte server een andere interface definitie dan de WCF interface ondersteunt. De twee interfaces moeten alleen dezelfde methodes bezitten. Sander maakt hierbij gebruik van het wrapper design pattern en Microsoft Intermediate Language generatie. Dit biedt de mogelijkheid om zelfs een Service locator te bouwen waarbij verschillende services dezelfde interface ondersteunen.

Interface en methode contracten


Tijdens een recentelijk project is een service eerst in Windows Communication Foundation (WCF) en later met Remoting geïmplementeerd. De webserver buiten de firewall (demilitarized zone, DMZ) moest hierbij als cliënt een applicatieserver binnen de firewall aanroepen. De communicatie was in eerste instantie in WCF ontwikkeld. Een WCF service is zo gebouwd, compleet met de WCF attributen ServiceContract en OperationContract op de bijbehorende methodes van de interface beschrijving. Helaas bleek in een later stadium dat op de webserver hoogstens het .Net 2.0 framework beschikbaar was dus moesten we voor de communicatie uitwijken naar .Net Remoting.


Met .Net Remoting was dezelfde oplossing met dezelfde methodes eigenlijk net zo eenvoudig gebouwd maar er waren wel enkele duidelijke verschillen met WCF welke direct opvielen. Ten eerste werd een design time gegeneerde cliënt proxy opgedrongen waar Remoting volstaat met enkele regels code om een cliënt proxy te genereren. Bij iedere wijziging van het servicecontract moest de WCF cliënt proxy opnieuw bijgewerkt worden. Gelukkig bleek deze cliënt proxy toch ook te genereren. Door het gebruik van een ChannelFactory kan dezelfde functionaliteit geboden worden (zie listing 1). 
 


[ServiceContract]

public interface IWcfService

{

  [OperationContract]

  string DoWork();

}

…

ChannelFactory<IWcfService> factory =

    new ChannelFactory<IWcfService>(new WSHttpBinding()))

 

IWcfService service =

    factory.CreateChannel(new EndpointAddress(

      "http://localhost/WcfHost/Service.svc"));

 string antwoord = service.DoWork();

((ICommunicationObject)service).Close();

factory.Close();

Listing 1: Een echte WCF aanroep zonder vooraf gegenereerde proxy


Bij WCF maken de cliënt proxy en de server gebruik van dezelfde service interface compleet met de WCF attributen. Hierdoor viel het op dat op de cliënt, de aanroepende partij, het nodig is om een referentie naar System.ServiceModel te leggen. Nu lijkt dit op zich triviaal; deze assembly is namelijk gewoon geïnstalleerd met het .Net framework en dus altijd overal aanwezig. Maar het is wel vreemd dat de gebruikte manier van communiceren zichtbaar wordt voor de aanroepende partij en dat dus afgedwongen wordt dat bepaalde gerefereerde assemblies geladen moeten worden. De WCF interface definitie is dus opdringerig, een cliënt moet niets merken over de manier van communiceren.


De WCF interface definitie is dus opdringerig, een cliënt moet niets merken over de manier van communiceren


Factory design pattern


Dit kan opgelost worden door gebruik te maken van een factory design pattern voor de communicatie. Design patterns zijn standaard oplossingen voor standaard problemen (zoals hier de wens tot abstractie van de communicatie) en binnen Atos Origin proberen wij altijd eerst terug te grijpen op deze bewezen oplossingen.


Figuur 1: Class diagram van factory pattern. De twee interface definities zijn helaas niet gelijk


Bij een factory design pattern wordt een instantie van de interface opgevraagd om bepaalde werkzaamheden uit te voeren, zonder kennis te nemen van de daadwerkelijke implementatie. De beslissing welke klasse wordt geïnstancieerd zal door de factory gemaakt worden. De factory kan bijvoorbeeld afhankelijk van het aanwezige .Net platform de keuze tussen .Net Remoting of WCF maken (zie figuur 1). Maar omdat de twee genoemde technieken ieder een eigen interface definitie vereisen, met of zonder WCF attributen, moet hier een tweede design pattern toegepast worden.


Wrapper design pattern


Het is dus niet mogelijk om WCF zonder de benodigde WCF attributen te laten communiceren. Een WCF cliënt proxy moet de benodigde kennis over de WCF interface bezitten en de WCF cliënt proxy aanroep moet uiteindelijk ergens uitgevoerd worden. Daarom is onderzocht of het mogelijk is om de WCF cliënt proxy wel te blijven aanroepen, maar deze cliënt proxy ‘in te pakken’ met een andere interface. De oplossing voor dit probleem kan uitgewerkt worden in het wrapper design pattern (ook wel adapter genoemd). 
 


Het wrapper design pattern wordt vaak toegepast, het is een fraaie manier om klassen samen te laten werken welke anders niet goed op elkaar aansluiten. Een nieuwe interface wordt letterlijk als inpakpapier rond de aan te roepen logica gelegd (zie figuur 2)


Figuur 2: Class diagram van wrapper design pattern



De wrapper class ondersteunt geheel of gedeeltelijk de logica van de wrapped class maar laat zich aanroepen met zijn eigen wrapper interface. De IWrapperInterface heeft hierbij geen enkele relatie met IWrappedInterface. IWrapperInterface hoeft dus niet eens alle members van IWrappedInterface te ondersteunen. Het is dus ook mogelijk om bepaalde complexiteit van het ingepakte object met het aanroepen van de wrapper te vereenvoudigen. Maar in onderstaand uitgeschreven voorbeeld houden we de twee interfaces gelijk (zie listing 2).


public class WrapperClass : IWrapperInterface

{

  private IWrappedInterface _WrappedClass;

 

  public WrapperClass(IWrappedInterface wrappedClass)

  {

    _WrappedClass = wrappedClass;

  }

 

  public string MethodOne(string parameter)

  {

    return _WrappedClass.MethodOne(parameter);

  }

 

  public int MethodTwo(int parameter)

  {

    return _WrappedClass.MethodTwo(parameter);

  }

}

Listing 2: een wrapper in code geschreven


Met een wrapper kan dus nog meer gedaan worden dan alleen maar het doorlussen van de methodes. Zo kan bijvoorbeeld aan iedere aanroep logging, een timer of extreme foutafhandeling toegevoegd worden.


Maar inpakken zoals in het bovenstaande voorbeeld wordt al snel monnikenwerk vanwege het uittypen, vooral indien de interface tijdens de ontwikkeling aan veel wijzigingen onderhevig is.


Het gebruik van reflection voor dynamische code generatie biedt uitkomst om wrappers dynamisch te genereren.


Het gebruik van reflection is een uitkomst bij dynamische code generatie


Bij dit artikel zijn twee implementaties van het wrapper design pattern meegeleverd: een versie via reflection en een versie via overerving van de RealProxy. Beiden zullen behandeld worden.


Reflection en MSIL generatie of RealProxy


Reflection biedt de mogelijkheid om nieuwe types, compleet met methodes en logica, runtime in het geheugen te brengen als Microsoft intermediate language (MSIL). Dit is de ultieme just-in-time (JIT) code generatie maar in de praktijk zie je het maar weinig zichtbaar toegepast worden. Dit komt enerzijds omdat de veilige wereld van code editor en design-time compiler verlaten moet worden. Anderzijds is de leercurve hoog want het komt dicht bij het zelf op de stack zetten van attributen en het aanroepen van methodes om de stack uit te lezen en weer verder te vullen. (kijk voor meer details op http://msdn2.microsoft.com/en-US/library/8ffc3x75(VS.80).aspx)
 


Wie ervaring heeft met assembler, beleeft hier het feest der herkenning. Toch is er een fundamenteel verschil met assembler. Ten eerste is er tegenwoordig intelliSence en dat scheelt heel wat zoekwerk daar waar vaak een tekst editor het enige gereedschap was. Maar belangrijker is dat deze MSIL wel degelijk typesafe is en blijft! De MSIL generator zal invalide opdrachten bij het samenstellen van het type gewoon afkeuren.


MSIL generatie is enkele factoren trager tov. het gebruik maken van voorgecompileerde code maar gelukkig kan de runtime gegenereerde code opgeslagen worden voor later hergebruik. Wrappen met MSIL heeft de voorkeur in een situatie met een hoge serverload.


Wrappen met MSIL heeft de voorkeur in een situatie met een hoge serverload


In System.Runtime.Remoting.Proxies wordt de abstracte base class RealProxy aangeboden, en deze geeft ook de mogelijkheid om twee interfaces op elkaar te mappen. Alle logica rond het inpakken wordt bij de RealProxy in één invoke methode runtime uitgevoerd, ieder methode aanroep komt hier langs. 
 


Alle logica rond het inpakken wordt bij de RealProxy in één invoke methode runtime uitgevoerd, ieder methode aanroep komt hier langs


Hierdoor is het echt geen vereiste meer dat de twee interfaces gelijkvormig zijn. Voordeel van de RealProxy is dat de C# broncode eenvoudig uit te breiden is met extra logica. Wel zal de RealProxy altijd via reflection de mapping moeten uitvoeren en dat is relatief trager.

Runtime wrapper generatie met MSIL


Onze MSIL wrapper gaat dus runtime twee willekeurige maar ‘gelijkvormige’ interfaces op elkaar laten aansluiten. Hiervoor is een static helper class geschreven om het gewenste type te genereren (zie listing 3). 
 


// Step 1: Generate the wrapper class type

TypeBuilder typeBuilder =

  GenerateWrapperType(typeOfWrapperInterface);

 

// Remember the getter and setter

MethodBuilder methodBuilderGet;

MethodBuilder methodBuilderSet;

 

// Step 2: Generate the wrapped object property

GenerateWrappedObjectProperty(

  typeBuilder, typeOfWrappedInterface,

  out methodBuilderGet, out methodBuilderSet);

 

// Step3: Generate the constructor

GenerateConstructor(

  typeBuilder, typeOfWrappedInterface, methodBuilderSet);

 

// Step 4: Generate all methods

GenerateWrappedMethodes(

  typeBuilder, typeOfWrappedInterface, methodBuilderGet);

 

// Finally, build this wrapper type and return it

return typeBuilder.CreateType();

Listing 3: Een wrapper helper class in slechts vier stappen


De eerste stap is van administratieve aard. Types kunnen niet gegenereerd worden zonder dat er een assembly voor gedefinieerd is. Ook moet een module aanwezig zijn. Dat is geen namespace maar geeft de mogelijkheid om types te groeperen. Geef hierbij aan de typebuilder door dat een class aangemaakt moet worden en geef ook de overerving op. We gaan een overerving van de Object class met de IWrapperInterface definitie implementeren.
 


De tweede stap stelt een field en een property samen voor het onthouden van de IWrappedInterface implementatie. Eerst worden de private field en de publieke property gedefinieerd. De publieke getter en de private setter van de property gedragen zich als methodes en die voeren code uit dus die code moet gedefinieerd worden via opcodes. Opcodes zijn de MSIL instructies waarmee bijvoorbeeld doorgegeven parameters op de stack worden geplaatst. Ook worden methode calls uitgevoerd die dan van de stack lezen en er weer op terugschrijven (zie http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_members.aspx voor details over de hier gebruikte opcodes).


De derde stap is het aanmaken van de constructor voor de doorgifte van IWrappedInterface. De property met de ingepakte klas wordt zo eenmailg gevuld en daarna afgeschermd voor overschrijven.


Als laatste stap moeten alle methodes van IWrappedInterface doorgelust worden. Voor iedere te ondersteunen methode moeten alle door te geven parameters op de stack geplaatst worden en daarna wordt de methode van het ingepakte object aangeroepen om de logica uit te voeren (zie listing 4). Hiervoor maken we gebruik van de getter van de property. Een geretourneerde waarde uit de aanroep naar de methode wordt gewoon weer terug op de stack gezet.


MethodInfo[] methodInfosWrappedInterface =

  typeOfWrappedInterface.GetMethods();

 

foreach (MethodInfo methodInfoInterfaceType in

  methodInfosWrappedInterface)

{

  //Put the parameter types in an array

  Type[] parameterTypes =

    new Type[

      methodInfoInterfaceType.GetParameters().Length];

   

  ParameterInfo[] pia =

    methodInfoInterfaceType.GetParameters();

  for (int i = 0; i < pia.Length; i++)

  {

    parameterTypes[i] = pia[i].ParameterType;

  }

 

  // Define the wrapper method for the wrapped method

  MethodBuilder methodBuilder =

    typeBuilder.DefineMethod(

      methodInfoInterfaceType.Name,

      MethodAttributes.Public

        | MethodAttributes.Virtual

        | MethodAttributes.NewSlot,

      methodInfoInterfaceType.ReturnType,

      parameterTypes);

 

  ILGenerator iLGenerator =

    methodBuilder.GetILGenerator();

  // Put 'this' on the stack

  iLGenerator.Emit(OpCodes.Ldarg_0);

  // Load the field on the stack

  iLGenerator.EmitCall(

    OpCodes.Call, methodBuilderGet, null);

 

  // Put every parameter passed on the stack

  for (int j = 0; j < pia.Length; j++)

  {

    iLGenerator.Emit(OpCodes.Ldarg, j + 1);

  }

 

  // Call the method of the wrapped object

  iLGenerator.Emit(OpCodes.Callvirt, methodInfoInterfaceType);

  // Ready and return the result

  iLGenerator.Emit(OpCodes.Ret);

}

Listing 4: Alle methodes overnemen en doorlussen


De MSIL generatie is afgerond, kijk nu nog eens naar Listing 3. Helemaal onderaan na de vierde stap wordt vanuit de typebuilder het type gecreëerd en bij MSIL generatie problemen (bijvoorbeeld onvoldoende of verkeerde parameters op de stack) zal hier een exception optreden. Als hier niet wordt geklaagd, dan hebben we nu het gewenste type van de wrapper class.


Om dit gegenereerde type te kunnen gebruiken moet de wrapper class geïnstancieerd worden waarbij de het ingepakte object aan de constructor meegegeven wordt (zie listing 5).

 

// Make an instance of the wrapped class

IWrappedInterface wrappedclass = new WrappedClass();

 

// Create the type of the wrapper

Type wrapperType = WrapperHelper.CreateWrapperType(

    typeof(IWrapperInterface),

    typeof(IWrappedInterface));

 

// Make an instance of the type of the wrapper

IWrapperInterface wrapper =

  (IWrapperInterface)Activator.CreateInstance(

    wrapperType, new object[] { wrappedclass });

 

// Call the wrapper instance

string returnValue = wrapper.MethodOne("It is a wrap!");

Console.WriteLine(returnValue);

Listing 5: Wrapper creëren en aanroepen


De gegenereerde wrapper wordt via een activator in een runtime object omgezet welke een IWrappedInterface inpakt.


Runtime wrapper generatie met RealProxy


Het inpakken kan ook zonder MSIL generatie door gebruik van de RealProxy (zie listing 6).


public class RealProxyWrapper<T, U> : RealProxy

{

  private U _wrappedInstance;

 

  public RealProxyWrapper(U wrappedInstance):base(typeof(T))

  {

    _wrappedInstance = wrappedInstance;

  }

 

  public override IMessage Invoke(IMessage message)

  {

    //Extract information about the method to call

    IMethodCallMessage methodCallMessage =

        new MethodCallMessageWrapper(

          (IMethodCallMessage)message);

 

    MethodBase methodBase = typeof(U).GetMethod(

        methodCallMessage.MethodBase.Name);

 

    //Invoke the method on the instance to collect result

    object returnValue = methodBase.Invoke(

        _wrappedInstance, methodCallMessage.Args);

 

    //Collect all information as if the method is called

    //on an instance with the wrapper interface

    ReturnMessage returnMessage = new ReturnMessage(

                    returnValue,

                    methodCallMessage.Args,

                    methodCallMessage.ArgCount,

                    methodCallMessage.LogicalCallContext,

                    methodCallMessage);

 

    return returnMessage;

  }

}

Listing 6: Wrapper creëren en aanroepen


Deze wrapper zal zich voordoen als een IWrapperInterface implementatie en iedere methode aanroep wordt doorgegeven aan de via de constructor verkregen IWrappedInterface implementatie. Het gebruikt intern de Invoke methode. Deze doet op zijn beurt een invoke maar daar omheen kan nog extra logica opgenomen worden zoals logging of tijdsduur metingen.


RealProxyWrapper<IWrapperInterface, IWrappedInterface>

  realProxyWrapper =

    new RealProxyWrapper

      <IWrapperInterface, IWrappedInterface>(wrappedclass);

 

IPlainService wrapper =

  realProxyWrapper.GetTransparentProxy() as IPlainService;

 

wrapper.MethodOne("It is a wrap!");

Listing 7: Wrapper creëren en aanroepen


Van de ReapProxyWrapper moet een runtime object gecreëerd worden welke op zijn beurt de wrapper levert rond het ingepakte runtime object.


Basis voor een WCF service locator


Hierboven zijn twee versies beschreven van wrappers. Met het gebruik van de wrappers is aangetoond dat werkelijk iedere willekeurige interface definitie met een andere interface definitie ingepakt kan worden. Deze kunnen dus ook een WCF service gaan inpakken (zie listing 8).


public interface IPlainService

{

  string DoWork();

}

…

ChannelFactory<IWcfService> factory =

  new ChannelFactory<IWcfService>( new WSHttpBinding());

 

IWcfService serviceToWrap =

  factory.CreateChannel(new EndpointAddress(

    "http://localhost/WcfHost/Service.svc"));

 

//choose between uncommenting

IPlainService wrapper = GetReflectionWrapper(serviceToWrap);

//or

IPlainService wrapper = GetProxyWrapper(serviceToWrap);

 

string answer = wrapper.DoWork();

 

factory.Close();

Listing 7: Wrapper creëren en aanroepen


We roepen hier een WCF service aan met een totaal andere interface (welke toevallig dezelfde methode deelt). Hiermee is de mogelijkheid ontstaan om zelf een service locator (ook wel ServiceProvider genoemd) te bouwen. Een aanroepende partij vraagt de service aan welke door een bepaalde interface type ondersteund wordt. De serviceprovider gaat hier naar op zoek, creëert de cliënt proxy en geeft deze cliënt proxy terug.


Hiermee is de mogelijkheid ontstaan om zelf een service locator te bouwen


Conclusie


Voor mij was het bouwen van de dynamische wrappers een aangename kennismaking met MSIL. Ik heb gemerkt dat de leercurve bij MSIL eerst redelijk stijl is maar als je eenmaal bezig bent, valt alles best te begrijpen. Een goede kennis van net .Net framework is wel een vereiste. De RealProxy variant is later ontworpen en lijkt sprekend op de MSIL variant maar is veel flexibeler in het dagelijks gebruik. Deze geniet bij onze projecten dan ook de voorkeur.


Toch wil ik nog een lans breken voor MSIL generatie. Met MSIL generatie zijn heel krachtige oplossingen te bouwen die de compiler wel maar C# normaal niet toelaat binnen de taalconstructie. Zo wordt het mogelijk om bv. properties, fields en methodes die normaal private gedefinieerd zijn, toch uit te lezen. Helaas is goede documentatie schaars maar met de juiste tools kan goed uitgezocht worden hoe bepaalde code constructies in MSIL gerepresenteerd moet worden.


Het is zeker de moeite waard om gegenereerde code eens met .Net Reflector van Lutz Roeder te openen (http://www.red-gate.com/products/reflector/) Met deze tool is het ook mogelijk om MSIL code naar andere .Net talen (C#, VB.Net, Chrome, Delphi, etc) te ‘reflecteren’. Via de plug-in techniek van .Net Reflector wordt deze lijst van talen regelmatig uitgebreid en zo is het inmiddels ook mogelijk om ter controle MSIL code om te zetten naar broncode met de ReflectionEmitLanguage plug-in.


Veel succes!

Deze blog is een aangepaste versie van een artikel in de SDN Magazine nr. 99 (nov. 2008) Zie ook www.sdn.nl




Share this | 721 keer bekeken | 4 reacties

Enclosure: http://adi.atosoriginblog.nl/static//enclosures/sandervandevelde_broncode_reflectionalswcfinpakpapier_.zip
Reacties Syndicatie en RSS

Leon Zandman reageert, op November 30, 2008 om 23:52 (GMT +01:00):

Interessant! En eindelijk eens wat code op ons blog in plaats van al dat semi-academisch geneuzel, gna gna gna... ;-)


robert mekking reageert, op December 1, 2008 om 09:09 (GMT +01:00):

@Leon,

Mee eens, volgende code-blog van jou?

>:-)


Leon Zandman reageert, op December 1, 2008 om 10:18 (GMT +01:00):

Wie kaatst kan de bal verwachten :-) Die opmerking had ik natuurlijk al verwacht.


Sander van de Velde reageert, op December 4, 2008 om 16:12 (GMT +01:00):

<reclame>Blader nog eens door mijn vorige bijdrages heen. Er is voldoende code/leesvoer :-)</reclame>


Reageer
 
 
Top artikelen
  • J-Spring 2009:Hop on board the Java Troubleshooting Platform
  • Softwaredocumentatie met Sandcastle
  • Agile Springboard
  • Blik op kwaliteit door middel van Six Sigma
  • Developers zijn kikkers
Recente reacties
  • IDC voorspelt dat SaaS mainstream wordt binnen paar ...
  • PrimeFaces is nauwelijks nog bekend terwijl het veel ...
  • Dank!
  • Het bijwerken is met behulp van if(contains()) remove() ...
  • Alleen keuren de bovengenoemde alternatieven duplicate entries af ...
  • Terms of use
  • Legal