Monday, May 30, 2011

EmitMapper: eliminiamo le stringhe dalla configurazione

Una delle cose fantastiche di un linguaggio fortemente tipizzato e compilato sono gli errori del compilatore, EmitMapper fa di tutto per complicarvi la vita e rendervi facile fare errori banali che scoprite solo a runtime:
image
supponendo di voler mappare un modello come quello esposto avete bisogno di spiegare al mapper che la proprietà “Name” deve essere mappata su “FullName” del tipo di destinazione. EmitMapper offre questa funzionalità attraverso il metodo MatchMembers esposto dal motore di configurazione:
var mapper = ObjectMapperManager.DefaultInstance.GetMapper<SourceClass, DestinationClass>
(
new MappingConfiguration()
.MatchMembers( ( s, d ) =>
{
if( s == "Name" && d == "FullName" )
{
return true;
}

return s == d;
})
);
metodo decisamente prolisso e prono ad errori perché usa le stringhe per identificare le proprietà, ergo non siete molto refactoring-friendly. Perché quindi non esprimere la stessa cosa così:
var mapper = ObjectMapperManager.DefaultInstance.GetMapper<SourceClass, DestinationClass>
(
new MappingConfiguration()
.MatchMembers<SourceClass, DestinationClass>( m  =>
{
m.Match( s => s.Name, d => d.FullName );
} )
);
Sfruttiamo l’espressività delle Lambda Expression per definire il match tra le proprietà, internamente poi non facciamo altro che rimappare il tutto sul metodo esposto da EmitMapper, semplice, potente, intuitivo, refactoring-friendly e con il supporto per l’intellisense il che non fa schifo proprio a nessuno credo… Smile
.m

Thursday, May 26, 2011

Usi “strani” del linguaggio

A cosa mai potrebbe servire?:
IList Create( T obj ) 
{
return new List();
}
A prima vista a nulla, soprattutto il parametro “obj” che viene completamente ignorato… Smile, ma se saltiamo di palo in frasca e facciamo una cosa curiosa del genere:
static void Main( string[] args )
{
var a = new { SampleProperty = "mauro" };
var b = new { SampleProperty = "mauro" };

Console.WriteLine( a.GetType() == b.GetType() ); //true...
Console.WriteLine( a.GetType().IsAssignableFrom( b.GetType() ) ); //true...
Console.WriteLine( b.GetType().IsAssignableFrom( a.GetType() ) ); //true...

Console.Read();
}
Allora potremmo dire che anche questo funziona:
static void Main( string[] args )
{
var list = Create( new { SampleProperty = "" } );
list.Add( new { SampleProperty = "mauro" } );

var item = list.First();
Console.WriteLine( item.SampleProperty );

Console.Read();
}
Figo Smile with tongue out stiamo usando il primo “anonymous type” come template per il tipo T del metodo “Create” creando a tutti gli effetti una lista di “anonymous types” fortemente tipizzata.
.m

Wednesday, May 25, 2011

EmitMapper: a questo punto sbizzarriamoci

Visto che abbiamo fatto 30 con la nostra configurazione custom perché non fare 31?
mapperManager.GetMapper<SpecialCategory, SpecialCategoryDto>
(
     new MappingConfiguration()
         .MatchListItemType<SpecialReferencedType, SpecialReferencedTypeDto>()
         .DeepMap()
);
Molto semplicemente vogliamo che l’utilizzatore possa spiegare come mappare i tipi presenti in una lista, senza necessariamente dover intercettare il processo di conversione. MatchListItemType fa proprio questo vi permette di associare in maniera statica un tipo nel dominio di partenza con un tipo nel dominio di destinazione.
A questo punto potreste avere un interessante problema, se il dominio di partenza è gestito da NHibernate i vostri item non saranno mai del tipo “descritto” perché quanto iterate gli elementi in realtà state iterando i proxy generati da NHibernate, per ovviare a questo problema la configurazione vi permette di esplicitare un TypeResolver che è una Func che vi permette di customizzare in che modo il processo di mapping risolva i tipi:
new MappingConfiguration()
    .MatchListItemType<SpecialReferencedType, SpecialReferencedTypeDto>()
    .ResolveListItemTypeUsing( o => o.GetType() )

Figo Smile, se poi pensiamo questa cosa combinata con la possibilità di iniettare un container per IoC nella configurazione abbiamo la massima flessibilità.
.m

Friday, May 20, 2011

EmitMapper: tutto bello ma come le mappiamo le liste?

Abbiamo introdotto le problematiche e lo scenario, ma non abbiamo parlato delle soluzione. Cominciamo con il vedere cosa vogliamo dal punto di vista del client:
mapperManager.GetMapper<SpecialCategory, SpecialCategoryDto>
(
     new MappingConfiguration()
         .OnListItemMapping<Category, CategoryDto>( e =>
         {
             if( e.Source is SpecialCategory )
             {
                 e.Destination = specialCategoryMapper.Map( ( SpecialCategory )e.Source );
                 e.Handled = true;
             }
         } )
         .OnListItemMapping<SpecialReferencedType>( e =>
         {
             e.Destination = specialReferencedTypeMapper.Map( ( SpecialReferencedType )e.Source );
             e.Handled = true;
         } )
         .DeepMap()
         .ConvertUsing<Category, Int32>( ei => ei == null ? -1 : ei.Id ) );
Vi ricordate lo scenario? bene, cosa risolviamo con la nostra configurazione? due possibili casistiche:
  • OnListItemMapping: ci permette di intercettare il processo di mapping in modalità wide-scoped, quindi devo esplicitare i tipi noti ad EmitMapper e non gli eventuali tipi derivati, degli elementi di una list esposta dal nostro modello e decidere se ci interessa prendere in carico il processo di mapping;
  • OnListItemMapping: ci permette di intercettare il processo di mapping in modalità narrow-scoped, quindi posso esplicitare un qualsiasi tipo e se l’item che si sta convertendo è di quel tipo specifico l’interceptor verrà invocato, degli elementi di una list esposta dal nostro modello e decidere se ci interessa prendere in carico il processo di mapping;
Non sto qui a dilungarmi su come sono state risolte le problematiche, i sorgenti sono su Radical e le cicche americane sprecate per combattere le idiosincrasie di EmitMapper sono veramente tante… Smile with tongue out
.m

Thursday, May 19, 2011

EmitMapper e IoC: l’unione fa la forza

…e compensa un po’ di magagne di EmitMapper, questo test fallisce:
[TestMethod]
[TestCategory( "EmitMapper" )]
public void EmitMapper_does_not_user_mapper_manager_to_provide_mappers()
{
     var actual = false;
     ObjectMapperManager mm = new ObjectMapperManager();
     var m = mm.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig()
             .ConvertUsing<int, int>( i =>
             {
                 actual = true;
                 return i;
             } )
     );
     var sut = mm.GetMapper<SpecialCategory, SpecialCategoryDto>
     (
         new DefaultMapConfig()
             .DeepMap()
             .ConvertUsing<Category, Int32>( ei => ei == null ? -1 : ei.Id )
     );
     var sc = new SpecialCategory()
     {
         Id = 0,
         Name = "Special Category",
         Parent = null,
         IsValid = true
     };
     sc.ReferencedTypes.Add( new ReferencedType() { Foo = 12 } );
     var dto = sut.Map( sc );
     actual.Should().Be.True();
}
In soldoni l’ObjectsMapperManager non serve ad una emerita cippa… Smile, quello che mi aspetterei è che se il “manager” ha un mapper definito per un tipo di conversione lo usi, invece no, ne crea uno internamente e vi rimbalza Confused smile
Vista l’esperienza fatta con l’estensione per gestire le liste non mi ci sono neanche messo a capire perché, semplicemente ho esteso la mia configurazione custom per supportare l’iniezione di un container per IoC da interrogare per sapere se esiste un mapper per soddisfare la necessità:
[TestMethod]
[TestCategory( "EmitMapper" )]
public void EmitMapper_extendedMapConfig_should_be_able_to_resolve_mappers_using_IServiceProvider()
{
     var actual = false;
     ObjectMapperManager mm = new ObjectMapperManager();
     IServiceProvider container = new MyServiceProvider( t =>
     {
         var m = mm.GetMapper<ReferencedType, ReferencedTypeDto>
         (
             new DefaultMapConfig()
                 .ConvertUsing<int, int>( i =>
                 {
                     return i;
                 } )
         );
         actual = true;
         return m;
     } );
     var sut = mm.GetMapper<SpecialCategory, SpecialCategoryDto>
     (
         new MappingConfiguration( mm )
             .ResolveSubMappersUsing( container )
             .DeepMap()
             .ConvertUsing<Category, Int32>( ei => ei == null ? -1 : ei.Id )
     );
     var sc = new SpecialCategory()
     {
         Id = 0,
         Name = "Special Category",
         Parent = null,
         IsValid = true
     };
     sc.ReferencedTypes.Add( new ReferencedType() { Foo = 12 } );
     var dto = sut.Map( sc );
     actual.Should().Be.True();
}
Il tutto è definitivamente diventato un estensione per EmitMapper presente in Radical.
.m

Tuesday, May 17, 2011

Pensare l’estendibilità non basta: il caso EmitMapper

Ho già parlato di EmitMapper, lodandone le prestazioni, in questi giorni stiamo però lavorando ad un progetto dove abbiamo la necessità di personalizzare, in piccola parte a dire il vero, il processo di mapping.
Lo scenario
Avete un bel servizio WCF che fa da front-end per un modello, il servizio espone un ampio set di operazioni, siccome gli use case sono molto vari, il servizio WCF modella i DTO che ritorna proprio un base allo use case; quindi avete un Product che viene mappato su un ProductDto, ma avete anche una projection di Product che viene mappata su un ProductSummaryDto.
n.d.r.: i DTO sulla carta sono una gran bella cosa ma in pratica rischiate di passare la vita a manutenere DTO, EmitMapper (o uno strumento per automatizzare il processo di mapping) diventa uno strumento vitale, soprattutto nel caso di un servizio WCF che fa uso di NHibernate e che per ovvio motivi non può far viaggiare on-the-wire il dominio. Una delle cose interessanti di EmitMapper, oltre alle prestazioni, è che facendo uso di IL generato a runtime, sono ovviamente spaziali, è che potete mappare i costruttori quindi nulla vi vieta di avere oltre ad un mapping automatico “modello –> DTO” anche uno “DTO –> modello”
A complicare le cose avete un albero di elementi “Category” e volete un albero di elementi “CategoryDto”, la peculiarità è che “Category” è una classe base da cui ad esempio deriva “SpecialCategory”, quindi se uno dei nodi è una “SpecialCategory” volete che il corrispondente DTO sia uno “SpecialCategoryDto”:
image
Se avete giocato un po’ con EmitMapper la prima cosa che fate è definire i mapper:
var mapperManager = ObjectMapperManager.DefaultInstance;
 
var specialCategoryMapper = mapperManager.GetMapper<SpecialCategory, SpecialCategoryDto>
(
     new DefaultMapConfig()
         .DeepMap()
         .ConvertUsing<Category, Int32>( p => p == null ? -1 : p.Id )
);
 
var categoryMapper = mapperManager.GetMapper<Category, CategoryDto>
(
     new DefaultMapConfig()
         .DeepMap()
         .ConvertUsing<Category, Int32>( p => p == null ? -1 : p.Id )
);
Qualche commento sul codice:
  • DeepMap serve per istruire il mapper che vogliamo che l’intero grafo, e non solo il primo livello, venga mappato;
  • ConvertUsing istruisce il mapper che nel momento in cui trova una proprietà di tipo Category e la vuole mappare su una di tipo Int32 (perché i nomi delle proprietà coincidono) deve chiamare la nostra Func<,>;
La configurazione che abbiamo definito non funziona, ma direi che è anche abbastanza evidente che non possa funzionare, anche se fastidioso, il processo di mapping incontra una lista di nodi che è di tipo IList, esposti dalla proprietà ChildCategories, e non si sogna minimamente di cercare di capire che l’istanza dell’elemento n può essere qualcosa che eredita da Category limitandosi a mappare l’oggetto come se fosse un elemento di tipo Category e basta.
Il primo tentativo che fate è quello di usare ConvertUsing<,> purtroppo senza successo perché non è pensato per gli elementi di una lista ma solo per le proprietà direttamente esposte dagli oggetti coinvolti nel processo di mapping, e già qui io semplicemente storco il naso...
A questo punto vi armate di pazienza e scoprite, non senza fatica per la totale assenza di documentazione, che esiste un metodo ConvertGeneric che prende in pasto 3 parametri:
  1. Un tipo (che scoprite a vostre spese dover essere generico) che è il tipo di partenza: e.g. IList<>;
  2. Un tipo (che scoprite a vostre spese dover essere generico, anche se non è sempre vero…) che è il tipo di destinazione: e.g. ISet<>;
  3. Un oscuro, e quanto meno teribbbbbile Smile, robo che deve implementare l’interfaccia ICustomConverterProvider;
dopo non pochi smadonnamenti (fate molto prima a spulciare nel terrificante sorgente di EmitMapper) scoprite che un ICustomConverterProvider ha lo scopo di fornire al mapper (o meglio alla configurazione) un’istanza di un CustomConverterDescriptor…che è molto peggio…
Un CustomConverterDescriptor ha 3 proprietà:
  1. ConversionMethodName: è il nome del metodo che il custom converter implementerà al fine di eseguire la conversione;
  2. ConverterImplementation: è il tipo (System.Type) del custom converter;
  3. ConverterClassTypeArguments: è un Type[] che rappresentano i tipi di ingresso e i tipi di ritorno del famigerato ConversionMethodName;
Ora…già qui io sono inorridito…perché mi sono chiesto: a che serve un’interfaccia? e mi è sorto il primo sospetto che l’autore di EmitMapper non avesse mai provato neanche di striscio il suo meccanismo di estendibilità.
Ma andiamo avanti perché c’è una sorpresa più interessante…. Confused smile
Tornando al nostro problema quello che abbiamo bisogno di fare è intercettare la conversione di ogni singolo item della lista sorgente al fine di decidere come convertirlo, vi armate di pazienza, molta…ma proprio molta, e cercate di capire come funziona il meccanismo basato sul modello che abbiamo appena esposto, la prima cosa che cercate di fare è quindi quella di iniettare un vostro custom ICustomConverterProvider in cui piazzare un bel breakpoint per cominciare a capire come gira il fumo. Una cosa del tipo:
new DefaultMapConfig()
      .ConvertGeneric
      (
          typeof( ICollection<> ),
          typeof( Array ),
          new MyConverterProvider()
      )
Non viene mai invocato… ma proprio mai. Anche qui dopo un po’ di enumerazioni di santi scoprite che la simpatica DefaultMapConfig ha un metodo protected RegisterDefaultCollectionConverters che registra un “collection converter” che fondamentalmente prende in pasto tutte le casistiche di fatto rendendo pressoché impossibile fare l’override del comportamento di EmitMapper.
Quello che fate quindi è derivare una nuova configurazione da quella di default e cercare di cambiare quel comportamento, con l’obiettivo di rendere iniettabile dall’esterno il default converter. Approfondendo scoprite che non è così semplice perché nella configurazione di default non c’è nulla di lazy e nello specifico quel metodo viene chiamato dal costruttore, quindi in modalità top-down, rendendo di fatto impossibile intercettarlo dall’esterno.
Il primo obiettivo è quindi poter fare questo:
new MappingConfiguration( mm )
     .OverrideDefaultCollectionConverter( typeof( ICollection<> ), typeof( Array ), myProvider )
La soluzione passa dal capire quale sia la relazione tra l’ObjectsMapperManager e la configurazione: una volta che la configurazione è stata creata la prima cosa che l’ObjectsMapperManager fa è chiamare GetMappingOperations, perché quindi non fare una cosa del tipo:
public class MappingConfiguration : DefaultMapConfig
{
     protected override sealed void RegisterDefaultCollectionConverters()
     {
         //NOP :-)
     }

     Boolean defaultCollectionConverterInitialized = false;

     public MappingConfiguration OverrideDefaultCollectionConverter( Type from, Type to, ICustomConverterProvider converterProvider )
     {
         if( !this.defaultCollectionConverterInitialized )
         {
             this.ConvertGeneric
             (
                 from,
                 to,
                 converterProvider
             );

             this.defaultCollectionConverterInitialized = true;
         }

         return this;
     }

     void RegisterDefaultCollectionConverterIfRequired()
     {
         if( !this.defaultCollectionConverterInitialized )
         {
             this.ConvertGeneric
             (
                 typeof( ICollection<> ),
                 typeof( Array ),
                 new CollectionArrayConverterProvider()
             );

             this.defaultCollectionConverterInitialized = true;
         }
     }

     public override IMappingOperation[] GetMappingOperations( Type from, Type to )
     {
         this.RegisterDefaultCollectionConverterIfRequired();
         return base.GetMappingOperations( from, to );
     }
}
Ci limitiamo a bloccare la funzionalità che non ci piace, per fortuna che il metodo è virtual, e poi esponiamo la nostra invocandola al momento giusto. A questo punto possiamo fare quello che vogliamo con la configurazione, ma che fatica.
Nelle prossime puntate vedremo un bel set di novità che possiamo esporre dalla nostra configurazione custom al fine di renderla veramente malleabile per l’utilizzatore. Questo caso è di sicuro interesse perché ci fa capire come pensare gli extension point senza mai di fatto usarli abbia come risultato solo ed esclusivamente sovra ingegnerizzazione inutile, totalmente inutile.
.m

Monday, May 16, 2011

Installare l’Oracle Client a manina

Avete bisogno di accedere dall’applicazione che state sviluppando a Oracle, l’istanza di Oracle è installata su una macchina in rete, dovete quindi installare solo il client ma avete, giustamente Smile, paura ad installare il runtime di Java… e perché mai dovreste aver paura?
Semplice…scaricate, da Oracle.com dopo non pochi sacramenti, una roba che si chiama “ODTwithODAC112021” se provate ad installarla vi dice che avere bisogno della JRE, scaricabile da javasoft.com… peccato che Javasoft.com non esista più… scaricate comunque in qualche l’ultima versione della JRE e l’installer di Oracle (anche dopo un riavvio) insiste nel dirvi che non è installata…
Roteano, e pure parecchio Smile
La soluzione “semplice” è di installare tutto a manina, senza JRE, passando dalla versione “ODAC112021Xcopy_32bit”:
  • la scaricate;
  • la scompattate;
  • lanciate da un command prompt elevated “install.bat” (vedere il readme per i dettagli);
…e continua a non andare una cippa… grrrrr… riavviate non va…. ri-grrrrrr… mettete nei path di sistema la directory di installazione di orcale (tipicamente quella cagata pazzesca di c:\oracle) e finalmente non va ma l’errore cambia Smile with tongue out
Siccome nel mio caso siamo in un’applicazione web è necessario configurare gli app pool per far girare anche codice x86 altrimenti il processo a 64bit quando cerca di caricare le dll unmanaged a 32bit si arrabbia non poco Smile with tongue out
Si ma che sudata…erano mesi che non riavviavo così tante volte in meno di un’ora…
.m

Thursday, May 12, 2011

VSPackage: startup all’avvio di Visual Studio

Un’estensione per Visual Studio, by default, viene caricata e inizializzata solo ed esclusivamente nel momento in cui l’utente cerca di interagirvi, quindi ad esempio se iniettate un command nei menu nel momento in cui l’utente clicca sul command. mentre se l’estensione ad esempio aggiunge una ToolWindow potreste trovarvi di fronte a comportamenti un po’ bizzarri:
  • Visual Studio parte e la ToolWindow non è presente tra quelle visibili: il package non viene inizializzato;
  • Visual Studio parte e la ToolWindow è presente tra quelle visibili ma non visualizzate (ad esempio perché è collapsed): il package non viene inizializzato;
  • Visual Studio parte e la ToolWindow è presente tra quelle visibili ed è effettivamente visibile: il package viene inizializzato;
Ora…è ovvio che non potete fare affidamento sul layout di Visual Studio per inizializzare il vostro package, se quindi avete bisogno di il vostro package venga inizializzato (invocato alla fine della fiera) in momenti ben precisi del ciclo di vita dell’IDE potete decorarlo con un attributo:
[ProvideToolWindow( typeof( MyToolWindowPane ) )]
[ProvideAutoLoad( UIContextGuids.NoSolution )]
public sealed class MyPackage : Package
L’attributo “ProvideAutoLoad” serve proprio per spiegare all’IDE che vogliamo essere invocati in determinati contesti, NoSolution identifica il contesto di default di Visual Studio in fase di avvio, quindi se vogliamo essere inizializzati durate il processo di avvio di Visual Studio e non aspettare l’interazione dell’utente ci basta marcare il nostro package con quell’attributo.
.m

Tuesday, May 10, 2011

EmitMapper… grrrr…

Mauro stai calmo Smile, questo test non passa…
[TestMethod]
[TestCategory( "EmitMapper" )]
public void EmitMapper_using_two_mappers_with_same_config_and_same_mapper_manager_should_not_use_one_config_instance()
{
     var manager = new ObjectMapperManager();
     var aMapper = manager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig()
     );
     var anotherMapper = manager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig()
     );
     var aCfg = aMapper.MapperImpl.MappingConfigurator;
     var anotherCfg = anotherMapper.MapperImpl.MappingConfigurator;
     var eq = aCfg.Equals( anotherCfg );
     var refEq = Object.ReferenceEquals( aCfg, anotherCfg );
     eq.Should().Be.False();
     refEq.Should().Be.False();
}
e che diamine… Smile with tongue out, come è possibile?
MapperImpl.MappingConfigurator è l’istanza di “DefaultMapConfig” che viene passata all’ObjectsMapperManager in fase di creazione del mapper, facciamo due “new” come è possibile che due mapper diversi abbiano la stessa configurazione?
La prima domanda che potreste fare è: perché mai dovrei aver bisogno di due mapper per gli stessi tipi ma distinti?
Domanda più che lecita, in effetti quell’esempio è semplicemente mirato a evidenziare il problema, lo scenario è quello di strategie di mapping diverse per lo stesso tipo in base al contesto, quindi stesso mapper ma configurazioni diverse. Potrei avere il mapper registrato in un container per IoC taggato in qualche modo per far si che se sono in back-office vengo restituito il mapper configurato per il back-office, mentre se sono in front-office venga restituito quello per il front-office.
Semplice… EmitMapper si prende la libertà di stabilire che la configurazione è strutturalmente uguale e quindi ricicla quella del primo mapper per il secondo. Ora il comportamento ci può anche stare, anche se visto che faccio esplicitamente una “new” il riciclo non dovrebbe proprio avvenire, ma se state pesantemente lavorando sull’estendere la configurazione e non è più che ben chiaro questo meccanismo semplicemente impazzite…maledetto lui Confused smile.
Parlerò ampiamente più avanti, e molto male per giunta, della presunta estendibilità di EmitMapper, sta di fatto che il riciclo viene fatto internamente in un modo talmente assurdo che non avete mezzo di metterci mano se non in due modi alquanto bizzarri:
[TestMethod]
[TestCategory( "EmitMapper" )]
public void EmitMapper_mapping_using_two_mappers_with_same_config_but_different_mapper_manager_should_not_use_one_config_instance()
{
     var aManager = new ObjectMapperManager();
     var aMapper = aManager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig()
     );
     var anotherManager = new ObjectMapperManager();
     var anotherMapper = anotherManager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig()
     );
     var aCfg = aMapper.MapperImpl.MappingConfigurator;
     var anotherCfg = anotherMapper.MapperImpl.MappingConfigurator;
     var eq = aCfg.Equals( anotherCfg );
     var refEq = Object.ReferenceEquals( aCfg, anotherCfg );
     eq.Should().Be.False();
     refEq.Should().Be.False();
}
Avere due ObjectsMapperManager, cosa totalmente insensata, oppure banalmente:
[TestMethod]
[TestCategory( "EmitMapper" )]
public void EmitMapper_mapping_using_two_mappers_with_same_config_and_different_name_but_same_mapper_manager_should_not_use_one_config_instance()
{
     var manager = new ObjectMapperManager();
     var aMapper = manager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig().SetConfigName( "aMapper-unique-name" )
     );
     var anotherMapper = manager.GetMapper<ReferencedType, ReferencedTypeDto>
     (
         new DefaultMapConfig().SetConfigName( "anotherMapper-unique-name" )
     );
     var aCfg = aMapper.MapperImpl.MappingConfigurator;
     var anotherCfg = anotherMapper.MapperImpl.MappingConfigurator;
     var eq = aCfg.Equals( anotherCfg );
     var refEq = Object.ReferenceEquals( aCfg, anotherCfg );
     eq.Should().Be.False();
     refEq.Should().Be.False();
}
Dare un nome univoco alla configurazione…
.m

Friday, May 6, 2011

VSPackage: sapere se siete in “debug mode” o “design mode”

State scrivendo un’estensione per Visual Studio e avete bisogno di intervenire nel momento in cui si passa da design a debug e viceversa. Spulciando la scarnissima documentazione trovata la soluzione, o presunta tale, abbastanza rapidamente, anzi trovate più di una di strada per arrivare al risultato desiderato:
this.dte.Events.DTEEvents.ModeChanged += ....
this.dte.Events.DebuggerEvents.OnEnterDesignMode += ...
this.dte.Events.DebuggerEvents.OnEnterRunMode += ...
funziona a singhiozzo… Smile, l’inghippo principale è che VS è un gigantesco oggettone COM con un po’ di .Net intorno quindi scrivendo così succede che il vostro event handler viene collectato dal Garbage Collector al primo giro perché nessuno si rende conto che avete una reference, ad esempio, a DTEEvents… aggirare il problema è comunque abbastanza facile:
var events = this.dte.Events;
if( events != null )
{
     this.dteEvents = events.DTEEvents;
     if( this.dteEvents != null )
     {
         this.dteEvents.ModeChanged += new _dispDTEEvents_ModeChangedEventHandler( OnDTEModeChanged );
     }
}
Funziona, ma è comunque la strada sbagliata. La documentazione dice chiaro e tondo che tutti quegli eventi sono ad uso interno (punto).
Quindi?
Non è proprio così immediato seguire la strada ufficiale ma si può fare, in primis recuperiamo una reference a un servizio che si chiama IVsMonitorSelection:
var monitorSelectionService = this.GetService( typeof( IVsMonitorSelection ) ) as IVsMonitorSelection;
Fatto questo possiamo chiedere al servizio di essere notificati quando succede qualcosa all’interno dell’IDE, nello specifico ci interessa sapere quando cambia lo UI Context, purtroppo non è così semplice come agganciare un event handler; la prima cosa che dobbiamo fare è implementare da qualche parte (tipicamente dove abbiamo bisogno di ricevere la notifica) l'interfaccia IVsSelectionEvents:
public class MyToolWindowViewModel : ..., IVsSelectionEvents
{
}
L’interfaccia prevede che vengano implementati 3 metodi:
int IVsSelectionEvents.OnCmdUIContextChanged( uint dwCmdUICookie, int fActive )
{
     return VSConstants.S_OK;
}
 
int IVsSelectionEvents.OnElementValueChanged( uint elementid, object varValueOld, object varValueNew )
{
     return VSConstants.S_OK;
}
 
int IVsSelectionEvents.OnSelectionChanged( IVsHierarchy pHierOld, uint itemidOld, IVsMultiItemSelect pMISOld, ISelectionContainer pSCOld, IVsHierarchy pHierNew, uint itemidNew, IVsMultiItemSelect pMISNew, ISelectionContainer pSCNew )
{
     return VSConstants.S_OK;
}
A noi interessa solo il primo, quindi gli altri due li lasciamo con la semplice “return OK”.
OnCmdUIContextChanged viene chiamato una quantità spaventosa di volte praticamente per ogni minima variazione di contesto all’interno dell’IDE, è potentissimo perché vi permette di sapere pressoché tutto quello che succede, è altrettanto scomodissimo sapere che cosa succede:
uint debuggerCookie;
var debugging_guid = VSConstants.UICONTEXT.Debugging_guid;
ErrorHandler.ThrowOnFailure( this.monitorSelectionService.GetCmdUIContextCookie( ref debugging_guid, out debuggerCookie ) );
if( debuggerCookie == dwCmdUICookie )
{
     this.OnDTEModeChanged();
}
Brutto… Smile with tongue out ma che cosa succede:
  1. per ogni variazione di stato l’IDE ci chiama passandoci:
  1. un “cookie” che possiamo considerare come un “token” che identifica la cosa che è cambiata;
  2. il nuovo stato: attivo (1), disattivo (0);
A questo punto abbiamo bisogno di sapere se il cookie è quello che fa per noi:
  1. Recuperiamo un riferimento al GUID che rappresenta lo “UI Context” che interessa a noi, nel nostro caso Debugging;
  2. Passiamo il GUID in questione a GetCmdUIContextCookie e otteniamo in cambio il cookie/token che rappresenta quel contesto;
  3. Se il cookie coincide con quello che ci viene passato significa che il contesto che è cambiato è quello che interessa a noi;
Possiamo quindi intervenire di conseguenza: OnDTEModeChanged() è un metodo della mia estensione che si limita a prendere una decisione sulla base del nuovo stato:
void OnDTEModeChanged()
{
     switch( this.dte.Mode )
     {
         case vsIDEMode.vsIDEModeDebug:
             {
                 //Siamo in debug mode
             }
             break;

         case vsIDEMode.vsIDEModeDesign:
             {
                 //Siamo in design mode
             }
             break;
     }
}
Semplice no? Smile
.m

Thursday, May 5, 2011

Visual Studio “Zombie Mode”

Questa è una figata, o meglio il tizio che ha pensato il nome Zombie Mode è il mio idolo Smile
Se state sviluppando un’estensione per Visual Studio dovete tenere in considerazione uno scenario decisamente poco documentato…mannaggia a loro Smile with tongue out
Durante il processo di startup dell’IDE c’è un periodo di tempo (lo Zombie Mode) in cui non potete interagire con l’IDE, come fare quindi a sapere lo stato dell’IDE? Non è proprio immediato… diciamo che avete una ToolWindow in cui avete bisogno di visualizzare informazioni (more to come…Smile) relative allo stato dell’IDE quello che potete fare è:
[Guid( "2f48535f-40c3-45af-bdb1-b4860982d508" )]
public class MyToolWindowPane : ToolWindowPane, IVsShellPropertyEvents
  • Registrare la vostra ToolWindow come qualcuno che è in grado di ricevere eventi relativi alla variazione delle proprietà dell’IDE, implementando l’interfaccia IVsShellPropertyEvents:
public int OnShellPropertyChange( int propid, object var )
{
}
  • e specificando in fase di inizializzazione della ToolWindow che volete essere “avvisati”:
protected override void Initialize()
{     
      base.Initialize();     
      var shellService = GetService( typeof( SVsShell ) ) as IVsShell;
      if( shellService != null )
      {
           Object zm;
           var result = shellService.GetProperty( ( int )__VSSPROPID.VSSPROPID_Zombie, out zm );
           var zombieMode = ( Boolean )zm;
           if( zombieMode )
          {
                     ErrorHandler.ThrowOnFailure( shellService.AdviseShellPropertyChanges( this, out cookie ) );
          }
          else
          {
                     this.FinishInitialization();
          }
      }
}
In fase di inizializzazione verifichiamo se siamo in “Zombie Mode”, se siamo in Zombie Mode ci registriamo per la notifica delle variazioni di valore delle proprietà dell’IDE altrimenti semplicemente procediamo con la nostra inizializzazione.
Per determinare la variazione dello Zombie Mode possiamo fare così:
uint cookie;
 
public int OnShellPropertyChange( int propid, object var )
{
     if( ( int )__VSSPROPID.VSSPROPID_Zombie == propid )
    {
         if( ( bool )var == false )
         {
             var shellService = GetService( typeof( SVsShell ) ) as IVsShell;
             if( shellService != null )
             {
                 ErrorHandler.ThrowOnFailure( shellService.UnadviseShellPropertyChanges( this.cookie ) );
             }

             this.cookie = 0;
             this.FinishInitialization();
         }
     }

     return VSConstants.S_OK;
}
Il metodo OnShellPropertyChange viene chiamato dall’IDE per ogni proprietà il cui stato varia, l’IDE ci passa l’id della proprietà e il valore corrente, quindi se la proprietà è quella che ci interessa e il valore è passato a false significa che non siamo più in Zombie Mode quindi:
  • ci deregistriamo dalle notifiche;
  • completiamo il processo di inizializzazione;
Processo in cui ad esempio possiamo fare questo:
void FinishInitialization()
{
     var dte = this.GetService( typeof( SDTE ) ) as DTE;
}
Il tentativo di recuperare un’istanza dell’IDE in Zombie Mode fallisce e GetService ritorna null, in questo modo siamo invece sicuri che la chiamata venga fatta nel momento corretto.
.m

Wednesday, May 4, 2011

Personalizzazione del Process Template: gruppi e permessi personalizzati

Una delle cose veramente interessanti di TFS 2010 è la pressoché infinita possibilità di personalizzazione a qualsiasi livello, o quasi Smile with tongue out
In questi giorni sono uscite un paio di necessità:
  • far si che per impostazione predefinita i “Contributors” abbiano i permessi per amministrare le Branch: se lavorate su un modello basato su branch-per-feature o se fate molti spike è vitale che il developer abbia molta libertà altrimenti l’amministratore del TFS impazzisce;
  • far si che in ogni nuovo Team Project che viene creato esista un nuovo gruppo “Managers” che abbia gli stessi permessi dei Contributors ma in più possa anche amministrare Aree e Iterazioni, cosa che per impostazione predefinita possono fare solo gli amministratori; Anche in questo caso è una questione di “comodità” i manager coincidono con i “project manager” e hanno quindi tutti i diritti e le competenze per fare questa attività sul progetto di cui sono responsabili;
L’operazione nel complesso è molto semplice, dopo aver scaricato il Team Project Template che volete modificare (nel nostro esempio “Microsoft Visual Studio Scrum 1.0”) dovete editare i template relativi ai vari task di creazione del Team Project (in grassetto le cose che abbiamo modificato):
File: ProcessTemplate.xml

 
    Microsoft Visual Studio Scrum 1.1 (custom)
    ….
   
 

 
    … [CUT] …
   
     
       
     

      Groups and Permissions\GroupsAndPermissions.xml" />
   

    … [CUT] …
   
     
       
       
       
     

      Version Control\VersionControl.xml" />
   

    … [CUT] …
 


Ho anche evidenziato un paio di cose degne di nota. Se fate bene attenzione vi rendete conto al volo che avete davanti un sistema per descrivere un workflow. Procediamo quindi con le modifiche che ci interessano:
File: Groups and Permissions\GroupsAndPermissions.xml
Nel file mi sono limitato ad aggiungere la definizione di un nuovo gruppo “Managers”, un elenco dettagliato dei permessi è disponibile su MSDN.

 
   
   
   
   
   
   
   
   
   
               
   
   
               
   
   
   
   
          
 


Ho evidenziato i permessi che ho aggiunto rispetto al gruppo Contributors per garantire la possibilità di amministrare le Aree e le Iterazioni.
File: Version Control\VersionControl.xml


 
   
   
     
      ManageBranch" identity="[$$PROJECTNAME$$]\Contributors" />
     
      " identity="[$$PROJECTNAME$$]\Managers" />
     
     
     
     
     
     
   

 


Questo è l’ultimo passaggio, definiamo che per la parte di Source Control vogliamo garantire ai Contributors il permesso “ManageBanch” e aggiungiamo anche qui il gruppo Managers con gli stessi permessi di Contributors.
.m

Monday, May 2, 2011

TFS Backup Power Tool (after disaster recovery)

Avete fatto tutto ma quando cercate di configurare un nuovo backup plan vi compare questo errore:
[Error @…] !Verify Error!: A backup plan already exists for this Tfs configuration
Niente panico Smile è proprio perchè il tool non è pensato per la “migrazione”… nei commenti di un post di Brian Harry la soluzione, da Sql Management Studio:
Use Tfs_Configuration
EXEC sp_dropextendedproperty @name = 'TFS_BACKUP_PLAN_CONTROLLER'

.m