lundi 8 octobre 2012

Best of C# videos

Ci dessous des vidéos qui expliquent le langage C# et ces différentes évolutions, efficaces, très didactiques, à voir absolument.

C# 2

Les génériques

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-1-What-new-in-C-2-Generics

Les itérateurs

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-2-Whats-new-in-C-2-Iterators

Types partiels et méthodes anonymes

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-3-Whats-new-in-C-2-Partial-types-anonymous-methods

Visibilité des accesseurs, classes statiques et types nullables

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-4-Whats-new-is-C-2-Accessors-Static-Classes-Nullable-Types

C# 3

Propriétés automatiques, inférence de type, initialiseurs

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-5-Whats-new-in-C-3-Automatically-Implemented-Properties-Type-Inference-Initializer

Types anonymes et méthodes d’extensions

http://channel9.msdn.com/blogs/bruceky/whirlwind-6-whats-new-in-c-3-anonymous-types-extension-methods

Les expressions Lambda

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-7-Whats-New-in-C-3-Lambda-Expressions

Linq ! (the legend of Zelda => Shield+Sword)

http://channel9.msdn.com/Blogs/bruceky/Whirlwind-8-LINQ

C# 4

Le langage deviens dynamique ?!

http://channel9.msdn.com/blogs/bruceky/whirlwind-10-whats-new-in-c-4-dynamic-lookup

Paramètres nommées et paramètres optionnels

http://channel9.msdn.com/blogs/bruceky/whirlwind-11-whats-new-in-c-4-named--optional-parameters

COM Interop, c’est mieux maintenant

http://channel9.msdn.com/blogs/bruceky/whirlwind-12-whats-new-in-c-4-more-com-love

Covariance et Contravariance

http://channel9.msdn.com/blogs/bruceky/whirlwind-13-whats-new-in-c-4-covariance—contravariance

Un changement sur la gestion des évènements

http://channel9.msdn.com/blogs/bruceky/whirlwind-14-whats-new-in-c-4-events

mercredi 3 octobre 2012

Automapper, un framework qu'il est bien ! mais ...

Je viens de livrer un petit projet où j'ai utilisé automapper et il m'a économisé des centaines de lignes de code.

Si ce Framework est vraiment très pratique, et installable rapidement avec NuGet, il m’a cependant déçu par sa documentation, pauvre voire inexistante. Il n’y a qu’à télécharger les sources pour s’en convaincre : https://github.com/AutoMapper/AutoMapper ; pas la moindre doc Xml en entête des classes et méthodes, walou, pour un Framework aussi plébiscité, ça fait cheap !

La documentation de base recommande l’utilisation de la classe statique “Mapper” pour configurer et exécuter le mapping et sans vouloir jouer les rabat joie, ça fait pas très objet tout ça. De plus j’ai eu le besoin d’avoir plusieurs “profiles” de configuration à ma disposition et Google pour une fois ne m’a pas vraiment aidé, c’est uniquement en regardant les sources que j’ai trouvé le truc.

Donc voici, pour avoir plusieurs mappers configurés différemment (et injectable en plus !), le code :

public class ClassA
{
public ClassA() { ClassBs = new HashSet<ClassB>(); }
public int Id { get; set; }
public string Prop1 { get; set; }
public ICollection<ClassB> ClassBs { get; set; }
}

public class ClassB
{
public int Id { get; set; }
public string Prop2 { get; set; }
}

public class ConfigTest : MappingEngine
{
static readonly ConfigurationStore _configurationStore;
static ConfigTest()
{
_configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.AllMappers());

_configurationStore.CreateMap<ClassA, ClassB>()
.ForMember(e => e.Prop2, conf => conf.MapFrom(a => a.Prop1))
;
}

public ConfigTest() : base(_configurationStore) { }
}


Prenez exemple sur l’objet ConfigTest pour avoir un mapper personalisé, instanciable, tout bien comme il faut.



Un autre besoin sur automapper m’a demandé beaucoup moins de temps de recherche : faire en sorte de filtrer un collection pour que tous ses éléments ne soient pas reproduits.



public class ConfigFilter : MappingEngine
{
static readonly ConfigurationStore _configurationStore;
static ConfigFilter()
{
_configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.AllMappers());

_configurationStore.CreateMap<ClassA, ClassA>()
//http://stackoverflow.com/questions/6226419/using-automapper-to-apply-a-filter-to-a-collection
.ForMember(e => e.ClassBs, conf => conf.MapFrom(a => a.ClassBs.Where(b => b.Id % 2 == 0)))
;
}

public ConfigFilter() : base(_configurationStore) { }
}


Enjoy :-), n’hésitez pas à vous servir de ce Framework, une fois les interrogations de config passée, c’est que du bonheur, et n’oublier pas de faire appel à la méthode “AssertConfigurationIsValid()” du “ConfigurationProvider” dans vos tests unitaires, sans quoi vous pourriez vous retrouver avec une propriété non mappée lors de la maintenance de l’application, un oubli est si vite arrivé.

jeudi 16 février 2012

Castle Windsor Container Builder

Ci dessous, une petite classe de derrière les fagots permettant de configurer Castle par le code avec une syntaxe fluide.

Cette classe “ContainerBuilder” permet aussi de configurer l’interception par proxy sur utilisation de certains attributs. Rappelons que Castle Windsor permet en plus de l’injection de dépendances de faire de la programmation orientée aspects par interception. Ce n’est pas aussi puissant qu’avec un véritable tisseur comme Postsharp mais c’est déjà pas si mal.

Cette implémentation de la programmation orientée aspect étant très fortement liée à l’injection de dépendance à travers le Framework Castle Windsor, il n’est pas possible :

  • D’avoir un aspect sur une méthode ou un objet qui ne serait pas gérés par l’injection de dépendances
  • Les aspects sont tissés par proxy. En pratique, un appel de méthode sera intercepté, pour une interface par une implémentation de cette méthode d’interface ; et pour une classe, par surcharge de cette méthode à travers un héritage. Par conséquent dans le cas de l’interception sur une méthode de classe, la classe doit pouvoir être héritée (pas de static, ni de sealed) et la méthode être surchargée (virtual en C#).
  • Les Aspects ne fonctionnent pas sur les évènements.
  • Dans le cas d’une interception à travers un proxy d’interface, l’objet final n’étant pas hérité, l’interception ne fonctionne que sur les méthodes portées par l’interface, pas les autres même si elles sont « public virtual ». Dans ce même cas, la méthode ne sera interceptée que si elle est appelée directement depuis le code client, et pas dans le cas où elle est appelée depuis une autre méthode de la classe.
/// <summary>
/// Cette classe encapsule la création d'un container Windsor
/// pour permettre d'enregistrer plus facilement des composants
/// et des intercepteurs déclenchés par des attributs
/// </summary>
public class ContainerBuilder
{
#region Registrations
class ComponentRegistration
{
public Type ServiceType { get; set; }
public Type ImplType { get; set; }
public LifestyleType LifeStyle { get; set; }
public object CtorParams { get; set; }
}

class InterceptorRegistration
{
public Type InterceptorType { get; set; }
public Type AttributeType { get; set; }
}

List<ComponentRegistration> _components = new List<ComponentRegistration>();
List<InterceptorRegistration> _interceptors = new List<InterceptorRegistration>();

void AssertNoRegistrationYetForComponent(Type serviceType)
{
if (_components.Any(c => c.ServiceType == serviceType))
throw new InvalidOperationException(string.Format("Le type de service {0} a déjà été enregistré.", serviceType));
}

void AssertNoRegistrationYetForInterceptor(Type interceptorType)
{
if (_interceptors.Any(c => c.InterceptorType == interceptorType))
throw new InvalidOperationException(string.Format("Le type d'intercepteurs {0} a déjà été enregistré.", interceptorType));
}

/// <summary>
/// Enregistre un composant constitué de l'interface de service
/// le répresentant et de son implémentation
/// </summary>
/// <typeparam name="TService">Interface du service</typeparam>
/// <typeparam name="TImpl">Implémentation du service</typeparam>
/// <param name="lifeStyle">Type de cycle de vie du composant, par défaut le composant sera créé à chaque demande</param>
/// <param name="ctorParamsAsAnonymousType">
/// Type anonyme représentant le dictionnaire des clé/valeurs de paramètres de constructeur
/// par exemple si le TImpl possède un constructeur dont la signature est la suivante : TImpl(string s1, int i2)
/// il faudra passer par exemple le parametre : new { s1 = "test", i2 = 42 }
/// </param>
public ContainerBuilder RegisterComponent<TService, TImpl>(LifestyleType lifeStyle = LifestyleType.Transient, object ctorParamsAsAnonymousType = null)
where TImpl : TService
{
AssertNoRegistrationYetForComponent(typeof(TService));
_components.Add(new ComponentRegistration()
{
ServiceType = typeof(TService),
ImplType = typeof(TImpl),
LifeStyle = lifeStyle,
CtorParams = ctorParamsAsAnonymousType
});
return this;
}

/// <summary>
/// Enregistre un composant directement par son implémentation finale
/// </summary>
/// <typeparam name="TImpl">Implémentation du service</typeparam>
/// <param name="lifeStyle">Type de cycle de vie du composant, par défaut le composant sera créé à chaque demande</param>
/// <param name="ctorParamsAsAnonymousType">
/// Type anonyme représentant le dictionnaire des clé/valeurs de paramètres de constructeur
/// par exemple si le TImpl possède un constructeur dont la signature est la suivante : TImpl(string s1, int i2)
/// il faudra passer par exemple le parametre : new { s1 = "test", i2 = 42 }
/// </param>
public ContainerBuilder RegisterComponent<TImpl>(LifestyleType lifeStyle = LifestyleType.Transient, object ctorParamsAsAnonymousType = null)
{
return RegisterComponent<TImpl, TImpl>(lifeStyle, ctorParamsAsAnonymousType);
}

/// <summary>
/// Enregistre un intercepteur déclenché par un attribut
/// </summary>
/// <typeparam name="TInterceptor">Type de l'intercepteur</typeparam>
/// <typeparam name="TAttribute">Type de l'attribut déclencheur</typeparam>
public ContainerBuilder RegisterInterceptor<TInterceptor, TAttribute>()
where TInterceptor : IInterceptor, new()
where TAttribute : Attribute
{
AssertNoRegistrationYetForInterceptor(typeof(TInterceptor));
_interceptors.Add(new InterceptorRegistration()
{
InterceptorType = typeof(TInterceptor),
AttributeType = typeof(TAttribute)
});
return this;
}

#endregion

#region Nested Classes
/// <summary>
/// Classe ajoutant les intercepteurs passés en constructeur
/// à chaque élément du model
/// </summary>
class AddingInterceptorSelector : IModelInterceptorsSelector
{
private readonly InterceptorRegistration[] _interceptors;

/// <param name="interceptors">Enregistrements d'intercepteurs à ajouter</param>
public AddingInterceptorSelector(InterceptorRegistration[] interceptors)
{
this._interceptors = interceptors;
}

public bool HasInterceptors(global::Castle.Core.ComponentModel model)
{
// On intercepte pas les intercepteurs
if (typeof(IInterceptor).IsAssignableFrom(model.Implementation)) return false;

var interceptorRefs = _interceptors
.Where(i => ReflectionHelper.HasTypeOrMembersAttribute(i.AttributeType, model.Service, model.Implementation));

return interceptorRefs.Any();
}

public InterceptorReference[] SelectInterceptors(global::Castle.Core.ComponentModel model, InterceptorReference[] interceptors)
{
// Selection des reférences d'intercepteur à ajouter
var interceptorRefs = _interceptors
.Where(i => ReflectionHelper.HasTypeOrMembersAttribute(i.AttributeType, model.Service, model.Implementation))
.Select(i => InterceptorReference.ForType(i.InterceptorType));

// Union des deux listes d'intercepteurs
return interceptors.Union(interceptorRefs).ToArray();
}
}

/// <summary>
/// Classe sélectionnant l'exécution des intercepteurs
/// en fonction des attributs de la méthode ou du type
/// </summary>
class AttributesSelectionSelector : IInterceptorSelector
{
readonly IEnumerable<InterceptorRegistration> _interceptors;
/// <param name="interceptors">Enumeration de clée/valeur représentant type d'intercepteur/type d'attribut</param>
public AttributesSelectionSelector(IEnumerable<InterceptorRegistration> interceptors)
{
this._interceptors = interceptors;
}

public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
// transformation en liste pour supprimer facilement
var interceptorsList = interceptors.ToList();

foreach (var interceptor in interceptors)
{
// Sélection de l'intercepteur, s'il est présent dans la liste gérée par cet objet
var selected = _interceptors.Where(kv => kv.InterceptorType == interceptor.GetType()).ToArray();
if (selected.Length == 0) continue;
var intAndAtt = selected[0];

// Si l'attribut est absent l'intercepteur est supprimé
// pour ne pas être sélectionné
var finalTypeMethod = ReflectionHelper.GetFinalMethodInfo(type, method);
if (!ReflectionHelper.HasContextAttribute(intAndAtt.AttributeType, method, finalTypeMethod))
interceptorsList.Remove(interceptor);
}

// transformation en tableau pour résultat
return interceptorsList.ToArray();
}
}
#endregion

private static Parameter[] GetParametersFromAnonymous(object anonymous)
{
if (anonymous == null) return new Parameter[0]; // early return !

var result = new List<Parameter>();
foreach (var prop in anonymous.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var propValue = prop.GetGetMethod().Invoke(anonymous, new object[0]);
if (propValue == null) propValue = "";

result.Add(Parameter.ForKey(prop.Name).Eq(propValue.ToString()));
}
return result.ToArray();
}

/// <summary>
/// Exécute la création d'un container prêt à l'emploi
/// en fonction des différents enregistrement déjà exécutés sur le builder
/// </summary>
public IWindsorContainer CreateContainer()
{
var container = new WindsorContainer();

// Creation du selecteur
var selector = new AttributesSelectionSelector(_interceptors.ToArray());

// Enregistrement des intercepteurs
foreach (var interceptor in _interceptors)
container.Register(Component.For(interceptor.InterceptorType));

// Enregistrement des composants
foreach (var component in _components)
{
var comp =
Component
.For(component.ServiceType) // Pour le service
.ImplementedBy(component.ImplType) // c'est cette implémentation
.SelectInterceptorsWith(selector) // avec la sélection d'intercepteurs
.LifeStyle.Is(component.LifeStyle) // et le cycle de vie
;

// avec ses paramètres statiques de constructeur
if (component.CtorParams != null)
comp = comp.Parameters(GetParametersFromAnonymous(component.CtorParams));

// avec ses paramètres dynamiques
// comp = comp.DynamicParameters .....
// TODO, si besoin

container.Register(comp);
}

// Ajout d'un ajout automatique de tous les intercepteurs sur tous les composants
container.Kernel.ProxyFactory.AddInterceptorSelector(
new AddingInterceptorSelector(_interceptors.ToArray())
);

return container;
}

}


En pratique voici un extrait du code des tests unitaire montrant l’utilisation de la classe :




IWindsorContainer GetContainer()
{
var builder = new ContainerBuilder()
.RegisterComponent<IServiceA, ServiceA>()
.RegisterComponent<IServiceB, ServiceB>()
.RegisterComponent<IServiceC, ServiceC>()
.RegisterComponent<IServiceD, ServiceD>()
.RegisterComponent<IServiceE, ServiceE>()
.RegisterComponent<IServiceF, ServiceF>()
.RegisterComponent<ServiceG>()
.RegisterComponent<IServiceH, ServiceH>(ctorParamsAsAnonymousType: new { stringForInternal = TEST_STRING_I })
.RegisterComponent<IServiceI, ServiceI>()
.RegisterInterceptor<ExceptionIntercept, ExceptionInterceptedAttribute>()
;

return builder.CreateContainer();
}


Le code source complet, comprenant une petite classe d’interrogation de la Reflection et bien sûr les tests unitaires, est téléchargeable ici.