EDMDesigner v2 : PRISM et Unity
Suite aux remarques de Thomas et John, ce post a été mis à jour.
Certains d’entre vous le savent, je suis en train de redévelopper mon EDM Designer.
Tout d’abord, la première version était mon premier projet WPF et si je devais le faire aujourd’hui, il est évident que je ne procèderais pas de la même façon (à l’époque, je n’avais pas utilisé MVVM par ex).
De plus, ma première version n’était pas autonome. Il fallait dans un premier temps récupérer l’edmx généré à partir d’une base de données avec VS et ensuite seulement on pouvait travailler avec mon designer
A cela, il faut rajouter que ma version se basait sur EF v1 (EF4 n’existant pas encore à l’époque).
J’ai donc décidé de repartir d’une feuille blanche et de proposer de nombreuses nouvelles fonctionnalités qui, je suis sûr, ne manqueront pas de vous ravir mais dont je garde le secret pour l’instant.
// A ce propos, n’hésitez pas à me soumettre vos idées.
Parce que je trouve certains points de mon dev intéressants mais également parce que je suis friand de critiques constructives, j’ai décidé de ne pas développer seul dans mon coin mais de faire une série de post afin de vous faire partager les choix que j’ai pu faire et recueillir vos avis sur ceux-ci.
L’idée que je vais développer dans ce post est de rendre mon EDM Designer le plus modulaire possible afin de pouvoir
- développer un module par version d’Entity Framework sans tout redévelopper à chaque nouvelle version
- permettre de choisir dans l’application la version d’Entity Framework que l’on souhaite utiliser
- donner la possibilité d’intégrer d’autre base de données que SQL Server
- permettre d’externaliser la gestion du pluralize / singularize
- etc.
Pour l’aspect modulaire, j’ai utilisé PRISM couplé avec Unity.
Plusieurs personnes m’ont dit que PRISM et Unity c’était des trucs pour la “branlette intellectuelle” d’architecte. Je vais donc tenter à travers cet article de leur prouver le contraire.
Unity est particulièrement utile pour mocker nos classes. Cependant, dans mon cas, je ne vais pas l’utiliser pour cela.
Avant de regarder plus en détail la solution que je propose, voici la vision du solution explorer :
Dans mon cas je vais utiliser PRISM pour définir les versions d’Entity Framework disponible dans mon application : en l’occurrence, EF v1 et EF 4.
Pour cela, je vais définir les modules que je veux utiliser dans mon App.config :
<
configuration
>
<
configSections
>
<
section
name
=
"modules"
type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"
/>
</
configSections
>
<
modules
>
<
module
assemblyFile
=
"EDM.v1.dll"
moduleType="EDM.v1.Module, EDM.v1"
moduleName="EDM.v1"
/>
<
module
assemblyFile
=
"EDM.v4.dll"
moduleType="EDM.v4.Module, EDM.v4"
moduleName="EDM.v4"
/>
</
modules
>
</
configuration
>
Dans le constructeur de la classe App, je vais faire appel à mon Bootstrapper :
public
App()
{
new Bootstrapper().Run();
}
Dans mon Bootstrapper, j’utilise le UnityBootstrapper fournit par PRISM afin d’instancier chacun des différent modules puis de créer mon Shell et l’afficher :
public
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
ConfigurationModuleCatalog moduleCatalog = new ConfigurationModuleCatalog();
Container.RegisterInstance<IModuleCatalog>(moduleCatalog);
Container.RegisterType<IModuleManager, ModuleManager>();
IModuleManager moduleManager = Container.Resolve<IModuleManager>();
moduleManager.Run();
Container.RegisterType<IShellViewModel, ShellViewModel>();
Shell shell = Container.Resolve<Shell>();
shell.Show();
return shell;
}
}
J’utilise l’approche View First (ie : c’est la vue qui instancie le ViewModel). On pourrait croire que je fais du ViewModel first en voyant le code mais en réalité ce n’est pas le cas parce que j’utilise Unity.
public
partial class Shell : Window
{
public Shell(IShellViewModel shellViewModel)
{
InitializeComponent();
DataContext = shellViewModel;
}
}
Dans mon cas, IShellViewModel étant associé avec ShellViewModel dans mon UnityContainer, lors de l’instanciation de mon Shell, unity va m’instancier un ShellViewModel pour le passer en paramètre.
Mon ShellViewModel va me retourner la liste des modules disponibles afin que je puisse les afficher dans une ListBox.
<
ListBox
x
:
Name
=
"modulesLB"
ItemsSource="{Binding Modules}"
DisplayMemberPath="Name"
/>
public
interface IShellViewModel
{
IEnumerable<EDMModuleBase> Modules { get; }
}
Comment récupérer la liste des modules ?
Le code que j’utilise est le suivant :
public
class ShellViewModel : IShellViewModel
{
public ShellViewModel(Func<IEnumerable<IEDMModule>> modules)
{
Modules = modules();
}
public IEnumerable<IEDMModule> Modules { get; private set; }
}
Pour pouvoir l’utiliser il faut donc au préalable enregistrer les différents modules dans le container unity.
Je vais effectuer cette action dans le constructeur de mon module :
public
class Module : EDMModuleBase
{
public Module(IUnityContainer unityContainer)
: base(unityContainer)
{
}
public override string Name
{
get { return "EDM v1"; }
}
}
public
abstract class EDMModuleBase : IEDMModule
{
public EDMModuleBase(IUnityContainer unityContainer)
{
unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
}
public abstract string Name { get; }
void IModule.Initialize()
{
}
}
public
interface IEDMModule : IModule
{
string Name { get; }
}
Ainsi mes deux modules sont bien ajoutés dans le UnityContainer lors de leur instanciation dans le Bootstrapper ce qui permet à mon ViewModel de les retrouver.
Maintenant, allons un peu plus loin.
La structure d’un EDM (avec une partie SSDL, une partie CSDL et partie MSL) est la même pour les versions 1 et 4 d’Entity Framework.
Aussi, je n’ai pas envie de redéfinir la classe EDM pour EF4. En revanche, le CSDL lui s’est enrichi avec EF4 (ajout des CSDL Functions). Du coup, je veux que ma classe EDM définie dans EDM.Base m’instancie un EDM.v4.CSDL quand j’utilise le Module v4 et un EDM.Base.CSDL quand j’utilise la v1.
Comment faire cela ?
Unity va bien nous aider.
En partant du UnityContainer principal, je vais définir un ChildContainer dans chacun des modules. Dans le ChildContainer du Module v4, je vais associer le type EDM.Base.CSDL avec le type EDM.v4.CSDL puis je vais rajouter une propriété EDM dans mon module qui sera créé par mon ChildContainer et la classe EDM va utiliser ce UnityContainer pour instancier le CSDL :
public
interface IEDMModule : IModule
{
string Name { get; }
EDM.Base.EDM EDM { get; }
}
public
abstract class EDMModuleBase : IModule
{
public EDMModuleBase(IUnityContainer unityContainer)
{
unityContainer.RegisterInstance<EDMModuleBase>(Name, this);
IUnityContainer childContainer = unityContainer.CreateChildContainer();
Init(childContainer);
EDM = childContainer.Resolve<EDM.Base.EDM>();
}
protected virtual void Init(IUnityContainer unityContainer)
{
}
public abstract string Name { get; }
public EDM.Base.EDM EDM { get; private set; }
void IModule.Initialize()
{
}
}
namespace
EDM.v4
{
public class Module : EDMModuleBase
{
public Module(IUnityContainer unityContainer)
: base(unityContainer)
{
}
protected override void Init(IUnityContainer unityContainer)
{
base.Init(unityContainer);
unityContainer.RegisterType<Base.CSDL, CSDL>();
}
public override string Name
{
get { return "EDM v4"; }
}
}
}
public
class EDM
{
public EDM(SSDL ssdl, CSDL csdl, MSL msl)
{
SSDL = ssdl;
CSDL = csdl;
MSL = msl;
}
public SSDL SSDL { get; private set; }
public CSDL CSDL { get; private set; }
public MSL MSL { get; private set; }
public virtual IEnumerable<IEDMElement> EDMElements
{
get
{
yield return SSDL;
yield return CSDL;
yield return MSL;
}
}
}
Vérifions avec un petit exemple :
<
Grid
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
/>
<
ColumnDefinition
/>
</
Grid.ColumnDefinitions
>
<
ListBox
x
:
Name
=
"modulesLB"
ItemsSource="{Binding Modules}"
DisplayMemberPath="Name" />
<Grid Grid.Column="1">
<ListBox ItemsSource="{Binding SelectedItem.EDM.EDMElements, ElementName=modulesLB}" />
</Grid
>
</
Grid
>
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :