Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C#] Dependency Injection met Windows Forms

Pagina: 1
Acties:

  • Lethalis
  • Registratie: April 2002
  • Niet online
De laatste tijd experimenteer ik met dependency injection.

Nu is het zo dat bij een Windows Forms applicatie de composition root in principe zo dicht mogelijk bij de entry point van de applicatie moet staan. Met andere woorden: in de main.

Als ik dan een MainForm heb, dan kan ik deze met zijn dependencies resolven. Dus stel ik heb een MainForm, die afhankelijk is van een MainService, die weer afhankelijk is van een MainRepository, die weer een ISessionFactory gebruikt, enzovoorts dat wordt dit in 1 keer voor mij geregeld.

Hartstikke leuk natuurlijk, maar de gemiddelde Windows Forms applicatie bestaat niet uit 1 form. Zo zou MainForm, een CustomerForm, OrderForm, InvoicesForm, enzovoorts kunnen starten. Als het een beetje gek loopt, heeft MainForm een menu van waaruit je 80 verschillende forms kan laden. En veel van die forms kunnen ook weer andere forms laden.

Wil je dependency injection "netjes" implementeren, dan moet je dus eigenlijk al die forms als dependency aan MainForm meegeven. Kortom, het wordt al snel een zooitje.

Hoe zouden jullie dit aanpakken?

Service Locator is 1 manier, maar wordt over het algemeen als anti-pattern beschouwd. Aan de ene kant krijg je er een hoop gemak bij, aan de andere kant wordt het onduidelijker welke dependencies een class heeft en kan dit unit testen moeilijker maken (Static Gateway pattern kan dit weer deels verhelpen, maar voelt ook niet "goed").

De IoC container doorgeven als dependency zodat classes zelf weer andere kunnen resolven, is ook niet netjes.

Oftewel; ik kan van alles bedenken om het werkbaar te maken, maar heb elke keer het idee dat ik er dan eigenlijk een zooitje van maak (Service Locator, Static Gateway, IoC container injecten).

Help :D

Ask yourself if you are happy and then you cease to be.


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Ik zou gewoon iets van een abstract factory pattern gebruiken om je Forms te maken. Het is dan wel een beetje afhankelijk van welke DI container je gebruikt hoe je de Factory weer implementeerd, maar ik zou niet zo snel je IoC container in je form injecten, want dan ben je weer erg afhankelijk van je DI container.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Lethalis
  • Registratie: April 2002
  • Niet online
Woy schreef op maandag 24 november 2014 @ 16:23:
Ik zou gewoon iets van een abstract factory pattern gebruiken om je Forms te maken. Het is dan wel een beetje afhankelijk van welke DI container je gebruikt hoe je de Factory weer implementeerd, maar ik zou niet zo snel je IoC container in je form injecten, want dan ben je weer erg afhankelijk van je DI container.
Abstract factory is op zich wel een idee. Om te voorkomen dat deze heel groot wordt, kan ik hem misschien nog opsplitsen in meerdere factories per onderdeel. Bijvoorbeeld een abstract factory die alle forms die met de boekhouding te maken hebben kan aanmaken? En weer eentje die alle order forms kan aanmaken. Enzovoorts.

Ik zal eens een testproject maken om dit uit te proberen.

Ask yourself if you are happy and then you cease to be.


  • D-Raven
  • Registratie: November 2001
  • Laatst online: 16-10 10:47
Ik denk niet dat dat is wat Woy bedoeld.. :P

Hij bedoeld eerder:(zo lees ik het tenminste, en zou ik het zelf ook doen) maak een abstractie waarmee je het instantieren van je form abstraheert. Zoiets als een IFormCreator interface.
Met daarachter een implementatie welke op basis van een generic variabele je form instantieert. Welke dan je container gebruikt om de form daadwerkelijk aan te maken.

Dan gebruik je dus inderdaad een service locator patroon. Zonder dat dit overal door je applicatie verspreid zit. Dus mocht je ooit een betere oplossing vinden dan heb je dat zo doorgevoerd.

Je wilt niet handmatig een abstract factory implementeren als je al een DI container gebruikt welke in feite al een gigantische factory is. :+

[ Voor 3% gewijzigd door D-Raven op 24-11-2014 17:08 ]


  • Lethalis
  • Registratie: April 2002
  • Niet online
D-Raven schreef op maandag 24 november 2014 @ 17:08:
Ik denk niet dat dat is wat Woy bedoeld.. :P

Hij bedoeld eerder:(zo lees ik het tenminste, en zou ik het zelf ook doen) maak een abstractie waarmee je het instantieren van je form abstraheert. Zoiets als een IFormCreator interface.
Met daarachter een implementatie welke op basis van een generic variabele je form instantieert. Welke dan je container gebruikt om de form daadwerkelijk aan te maken.

Dan gebruik je dus inderdaad een service locator patroon. Zonder dat dit overal door je applicatie verspreid zit. Dus mocht je ooit een betere oplossing vinden dan heb je dat zo doorgevoerd.

Je wilt niet handmatig een abstract factory implementeren als je al een DI container gebruikt welke in feite al een gigantische factory is. :+
Een IFormCreator interface is inderdaad ook een goed idee. De reden dat ik het antwoord van Woy anders interpreteer is omdat hij het abstract factory pattern erbij haalt, dat vaak gebruikt wordt om een verzameling van factory methods bij elkaar te abstraheren.

Maar goed, kort samengevat geef ik mijn componenten dus de mogelijkheid om forms te instantiëren. Dat is dus de dependency die ik inject, net zoals dat ik een ISessionFactory in mijn services inject, zodat deze met NHibernate sessies kunnen aanmaken.

Als ik jou goed begrijp, moet de IFormCreator interface dan dus een CreateForm<TFormInterface> functie krijgen en dit generieke interface type kan ik dan weer gebruiken om aan een IoC container te vertellen dat hij hem moet resolven.

Enige nadeel is dat er een dependency ontstaat tussen de aanroepende class en de interface die geinstantieerd wordt, zonder dat deze dependency direct duidelijk is (staat immers niet meer in de constructor). Abstract factory kent dit nadeel niet (als je dus wel de boel bij elkaar zet en de aanroepende code geen kennis hoeft te hebben van "wat" hij instantieert als je begrijpt wat ik bedoel.. die kennis zit dan in de abstract factory ipv de class zelf).

Ik moet overigens nog uitzoeken welke IoC container ik zal gebruiken. Of ik gewoon voor Unity ga, of Ninject, of iets anders :D

[ Voor 10% gewijzigd door Lethalis op 24-11-2014 19:28 ]

Ask yourself if you are happy and then you cease to be.


  • D-Raven
  • Registratie: November 2001
  • Laatst online: 16-10 10:47
Ik ben een fan van Autofac. Ninject is ook nice. Unity.. .. mwa not a fan.

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Lethalis schreef op maandag 24 november 2014 @ 19:24:
[...]

Een IFormCreator interface is inderdaad ook een goed idee. De reden dat ik het antwoord van Woy anders interpreteer is omdat hij het abstract factory pattern erbij haalt, dat vaak gebruikt wordt om een verzameling van factory methods bij elkaar te abstraheren.
Vziw is het grote verschil tussen een factory en een abstract factory dat bij een factory het resultaat type al vast staat, en bij een abstract factory het resultaat type nog vrij is. Exacte invulling verder buiten beschouwing gelaten.
offtopic:
Abstract abstract factory? Beetje meta...


De standaard ASP.NET MVC DefaultControllerFactory van de IControllerFactory interface implementatie is bijvoorbeeld een abstract factory. Trouwens; dat zou niet eens een gek vertrekpunt zijn om tot een goede implementatie van een FormFactory te komen...

[ Voor 6% gewijzigd door R4gnax op 24-11-2014 21:40 ]


  • Lethalis
  • Registratie: April 2002
  • Niet online
De IControllerFactory interface gebruikt een naam (string) om een controller te verkrijgen.

Wat zouden nu de voor- en nadelen daarvan zijn? Het zorgt wel voor loose coupling, anderzijds krijg ik geen interface binnen die ik kan doorgeven aan een IoC container.
D-Raven schreef op maandag 24 november 2014 @ 19:31:
Ik ben een fan van Autofac. Ninject is ook nice. Unity.. .. mwa not a fan.
Ik lees inderdaad vaker dat mensen geen fan zijn van Unity. Er schijnt wel veel te veranderd zijn in Unity 2, anderzijds gaat het wel vaker zo met Microsoft libraries. Entity Framework is hem bij mij ook nooit geworden (ik vind NHibernate echt veel flexibeler en handiger).

[ Voor 47% gewijzigd door Lethalis op 25-11-2014 08:36 ]

Ask yourself if you are happy and then you cease to be.


  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Lethalis schreef op dinsdag 25 november 2014 @ 08:34:
De IControllerFactory interface gebruikt een naam (string) om een controller te verkrijgen.

Wat zouden nu de voor- en nadelen daarvan zijn? Het zorgt wel voor loose coupling, anderzijds krijg ik geen interface binnen die ik kan doorgeven aan een IoC container.
De standaard controller factory implementatie scant door de ingeladen assemblies heen voor classes die de IController interface implementeren. Daarna filteren ze deze op naam (voor een naam 'Foo' een class 'FooController'). De eerste match wordt vervolgens via de MVC dependency resolver opgehaald. De resolver is eigenlijk een service locator en als je van een DI framework gebruik maakt, dan is dat de plek waar je je DI container aan het framework verbindt.

Dit gebeurt dus via het concrete type en niet via de interface, die enkel gebruikt wordt als marker voor de asembly type scan. Goede DI frameworks zullen geen enkel probleem hebben om een concreet type direct zonder expliciete mappings op te bouwen door gewoon constructor injection toe te passen en eager de langste constructor te pakken.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Het meeste is inderdaad hierboven al gezegd, maar ik bedoelde inderdaad gewoon een IFormFactory interface die aan de hand van een Generic parameter een form aanmaakt ( Je kunt daar natuurlijk nog verschillende keuzes in maken afhankelijk van je usecase ). Daar maak je dan b.v. een AutofacFormFactory concrete implementatie van d.m.v. van je DI container. In je MainForm laat je dan gewoon een IFormFactory injecten door je DI container.

Je IFormFactory is dan wel dependant op je DI container, maar je hoeft daarmee niet door je hele applicatie hard aan je DI container te koppelen.
D-Raven schreef op maandag 24 november 2014 @ 19:31:
Ik ben een fan van Autofac. Ninject is ook nice. Unity.. .. mwa not a fan.
Ik ben inderdaad ook fan van Autofac en werkt IMHO net iets fijner dan Ninject.

Unity heb ik ook minder goede ervaringen mee, maar op zich voldoet het wel.

[ Voor 23% gewijzigd door Woy op 25-11-2014 12:44 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Lethalis
  • Registratie: April 2002
  • Niet online
Nog een vraag.. stel dat je vanwege beperkingen van Visual Studio designers (met name de Windows Forms designer) altijd een default constructor nodig hebt. Dan kun je in feite geen constructor injection doen en ben je in principe aangewezen op property injection.

Property injection is echter iets dat de meesten liever mijden, tenzij het om functionaliteit gaat die niet per se noodzakelijk is:

http://stackoverflow.com/...ctors-or-property-setters

Maar ja, ik heb uiteraard wél mandatory dependencies die ik via property injection wil zetten dan.

Wat zijn nu mijn opties?

1. Property injection gebruiken bij forms en user controls i.c.m. Null object pattern (of juist ArgumentNullException throwen).

2. Tweede constructor aanmaken (maar dat voelt "fout" en niet echt anders dan property injection die wellicht zelfs flexibeler is).

3. Altijd met een Controller werken die de Forms / User Controls (of beter gezegd "views") aanstuurt. Dus alle injection gebeurt op de Controller en niet op de Views. Überhaupt wil ik zoveel mogelijk werken met MVVM (voor zover WinForms dat toelaat met zijn bindings)

4. Iets anders? :D

Ask yourself if you are happy and then you cease to be.


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Lethalis schreef op woensdag 26 november 2014 @ 10:42:
Nog een vraag.. stel dat je vanwege beperkingen van Visual Studio designers (met name de Windows Forms designer) altijd een default constructor nodig hebt. Dan kun je in feite geen constructor injection doen en ben je in principe aangewezen op property injection.
Waarom niet? Bij de meeste frameworks wordt volgens mij automatisch de "grootste" constructor gepakt waarbij hij de dependancy's kan vullen.
2. Tweede constructor aanmaken (maar dat voelt "fout" en niet echt anders dan property injection die wellicht zelfs flexibeler is).
Ik zou gewoon lekker een default constructor maken, en daar als het geen Design mode is een Exception gooien. Eventueel zelfs conditional maken in debug mode met preprocessor directives

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Woy schreef op woensdag 26 november 2014 @ 11:46:
Ik zou gewoon lekker een default constructor maken, en daar als het geen Design mode is een Exception gooien. Eventueel zelfs conditional maken in debug mode met preprocessor directives
Dat is inderdaad wat ik ook zou doen. Niets mis mee.

  • Lethalis
  • Registratie: April 2002
  • Niet online
Okee bedankt allemaal!

Ask yourself if you are happy and then you cease to be.

Pagina: 1