Avatar billede softspot Forsker
13. oktober 2011 - 14:25 Der er 29 kommentarer og
3 løsninger

Hjælp til forståelse og brug af Ninject

Jeg vil igang med brugen af en IoC container og i den forbindelse har jeg valgt Ninject v2.2. Jeg har imidlertid lidt problemer med at forstå, hvordan man bedst integrerer den i f.eks. et webform-projekt.

Jeg synes kun jeg kan finde eksempler, hvor man instantierer en StandardKernel i forbindelse med at man skal bruge Ninject, men det virker, aht. performance, forkert at man skal gøre dette. Umiddelbart ville jeg forvente at man oprettede en klasse med en statisk metode, som kunne generere/returnere en forekomst af StandardKernel og dermed kun skulle binde interfaces og typer op på hinanden én gang (og ikke hver gang, som jeg vel kommer til, hvis StandardKernel skal sættes op hver gang...?).

Jeg mangler givetvis at forstå nogle centrale ting vedr. IoC. Jeg fornemmer en klasse med en statisk metode kan give mig problemer med at variere, hvilke instanser jeg kan bruge i forskellige kontekster. På den anden side kan man vel klare dette med kontekstuelle bindinger i Ninject...?

Er der nogen som kan hjælpe mig med, at forstå den lavpraktiske måde hvorpå jeg kan bruge Ninject i mine projekter?
Avatar billede janus_007 Nybegynder
13. oktober 2011 - 20:57 #1
Hej Softspot

Du skal opsætte StandardKernel i din global.asax

Og du har helt ret... DI skal ikke registreres/ opsættes ved hver page load.

Læs lidt her: http://davidhayden.com/blog/dave/archive/2008/06/20/ninjectdependencyinjectionaspnetwebpagessample.aspx

og her:
http://weblogs.asp.net/shijuvarghese/archive/2010/04/30/dependency-injection-in-nerddinner-app-using-ninject.aspx


Når Ninject er "wired up" så vil, alt afhængig af hvad du vælger som behavior, give dig den instans du forventer... eks.vis SingletonBehavior osv.

Jeg selv arbejder til dagligt med StructureMap, så ligefrem ekspert i Ninject vil jeg ikke påstå jeg er, men det er stort set samme patterns de følger.
Du vil aldrig fortryde at du skifter til DI, ja faktisk sidder jeg og tænker engang imellem... gad vide hvordan jeg kunne undvære det :)
Avatar billede softspot Forsker
14. oktober 2011 - 00:12 #2
Hej Janus_007!

Tak for dit svar.

Jeg har dog opfattet det således, at der er forskel på hvordan Ninject v1 og v2 fungerer i relation til aktivering (https://github.com/ninject/ninject/wiki/The-Activation-Process), så det første eksempel er jeg usikker på om jeg kan bruge.

Det andet du henviser til omhandler brugen af Ninject under MVC og jeg er igen lidt usikker på om det gøres på samme måde i et WebForms-projekt (det er i det mindste ikke i global.asax man skal sætte det op under MVC :-)).

Anyway! Jeg har en lille udfordring i at forstå, hvordan jeg får fat i en reference til Ninject's Kernel fra min kode. Jeg kunne f.eks. forestille mig en situation, hvor jeg skal oprette et objekt, der har nogle afhængigheder "i maven", som styres af Ninject. I den forbindelse ville jeg jo forvente at skulle gøre noget i stil med dette:

var person = kernel.Get<Person>();
person.Load(id);
person.Name = "New name";
person.Save();

for at få aktiveret Ninject i relation til de afhængigheder som Person-objektet måtte have "i maven". Jeg synes i det mindste ikke jeg kan få Ninject aktiveret, hvis jeg blot opretter Person-objektet med new...

Bare for en god ordens skyld så er Person defineret således:

public class Person
{
    private IPersonRepository _repository;

    public string Name { get; set; }

    public Person() : this(null) { }
    public Person(IPersonRepository repository)
    {
        _repository = repository;
    }

    public void Load(int id)
    {
        _repository.Load(this);
    }

    public void Save()
    {
        _repository.Save(this);
    }
}

Desuden kan jeg oplyse at mit Global-objekt i Global.asax nedarver fra NinjectHttpApplication og at jeg har overstyret CreateKernel-metoden, så der burde blive oprettet et StandardKernel-objekt.

Spørgsmålet må være: Kan jeg få fat i Ninject-kernel på en eller anden måde i mit Ninject PageBase-objekt (som min form nedarver fra), eller er der noget jeg skal instantiere for at få adgang til dette objekt...?

Jeg kan få det til at fungere, hvis jeg opretter en klasse med en statisk metode, som returnerer Kernel, men det føles ikke som den korrekte måde...
Avatar billede Syska Mester
14. oktober 2011 - 00:22 #3
WebForms er gammelt og ikke lavet som IOC i tankerne.

MVC har derimod super support for IOC containers over det hele.

Det er netop i Global.asax man netop også kan gøre det i MVC.

Man kan derimod også bruge WebActication ( mener jeg det lille projekt hedder ) som kicker stater ens Containers.

Du vil altid have et eller andet static ... StructureMap har:
ObjectFactory.GetInstance<Person>();

Ninject må have samme struktur.

mvh
Avatar billede janus_007 Nybegynder
14. oktober 2011 - 08:13 #4
Det eksempel på Github du henviser til, viser et module og det er så modulet som blot skal aktiveres... det er også det du siger :)

Det er typisk det man gør fra sin global.asax, eks.vis i Application Load : IKernel kernel = new StandardKernel(new WarriorModule());

Indgangen til en WebForm er igennem en .aspx som du ikke kan ændre constructoren i, det betyder at du skal bruge propertyinjection og altså ikke constructorinjection. DI skal primært foretages igennem contructoren, men altså ikke når det gælder .aspx/ .ascx

Jeg vil næsten tro du skal sætte [Inject] på de properties du vil injecte.

Det er et antipattern at gøre sådan her:
var person = kernel.Get<Person>(); eller ObjectFactory.GetInstance<Person>();
og bør undgåes, medmindre det har en helt klar funktionalitet/ factorytingopgave el. lign.

Her skal du netop anvende constructor injection, altså injecte Person ind i den klasse som skal bruge den.


hmmm, gav det dig et bedre svar?
Avatar billede softspot Forsker
14. oktober 2011 - 08:44 #5
buzzzz >> Der er, såvidt jeg har forstået, lavet en extension til Ninject som muliggør injection ifm. webforms (https://github.com/ninject/ninject.web). Jeg synes bare ikke jeg kan finde vildt meget dokumentation til dette område. Der er MASSER til MVC, men nu er min aktuelle interesse rettet mod IoC ifm. WebForms, så det hjælper ikke meget med eksempler rettet mod MVC... :-)


janus_007 >> Det jeg synes er lidt foruroligende ved siden bag linket jeg angav er, at der i toppen står: "WIP: This page has not been updated for Ninject2, the activation process has been change quite drastically since Ninject 1". Det får mig til at spekulere på, hvad det rent faktisk så er, de vil have mig til at gøre i forbindelse med aktieringen, hvis det ikke fungerer sådan som de artiklen beskriver... =/

Ang. antipattern, så kan jeg godt se dit argument, men hvis jeg nu står i en event-handler til et click og skal starte en behandling af en person ud fra nogle data jeg har fået af brugeren via min webform, så skal jeg vel bruge en ny instans og ikke en injiceret instans... eller mener du rent faktisk at jeg skulle erklære en protected property i min page-klasse med atributten [Inject]?

Eksempelvis:

partial class MinForm : PageBase
{
    [Inject]
    protected Person person { get; set; }

    protected void Button1_Click(object sender, EventArgs e)
    {
        person.Load(GetPersonIdFromForm());
        // yada yada yada...
    }
}

Jeg tror jeg mangler en forståelse af, hvordan det hele hænger sammen i et webforms-miljø, når man bruger Ninject sammen med Ninject.Web-extension, dvs. hvordan bruges det helt konkret fra vugge til grav...
Avatar billede Syska Mester
14. oktober 2011 - 11:38 #6
#janus_007
Yes, antipattern, men som du selv siger, nogen gange kan man blive nød til det.

Da jeg lærte IOC der var jeg allerede på MVC ... og har kun brugt det meget kort sammen på et WebForms projekt som kørte på MVP pattern. Der virker det hele faktisk super godt.

#softspot
Som janus er inde på ... så er der ikke et factory du kan bruge, så derfor er der ikke super mange måder at gøre det på.

http://www.gbogea.com/2009/12/07/using-structuremap-with-aspnet-webforms

Godt nok structuremap, men burde give dig endnu en ide. Lidt ala samme måde som janus beskriver.

mvh
Avatar billede janus_007 Nybegynder
14. oktober 2011 - 20:44 #7
#softspot
Jep... du skal sætte en Inject attribut på.

I Global, skal du nedarve og override.
public class Global : NinjectHttpApplication

Og så


protected override IKernel CreateKernel()
        {
            IKernel kernel = new StandardKernel(new SomeModule());
            return kernel;
        }

//noget wiring som ikke behøver ligge i global, men bare kan være et modul til genbrug.
        public class SomeModule : NinjectModule
        {
            public override void Load()
            {
                Bind<IPersonRepository >().To<PersonRepository >().InSingletonScope();
                Bind<Person>().To<Person>();
            }
        }


og så også sætte Inject på din constructor i Personklassen.

I ovenstående har du således injected 2 instancer, den ene som singleton og den anden transient (default) :)

Giver det dig en bedre forståelse? det er som som sagt et antipattern at lave en kernel Get og det kan du undgå ved Inject attributten.

Jeg er ikke helt med på hvad du ikke er med på *S*... men jeg tænker at det måske er "hvor kommer instancen fra hvis den man ikke laver en .Get<...>, hele fidusen ved DI er netop at man ikke requester instancen, men at den blot injectes hvor den skal bruges.
Avatar billede softspot Forsker
14. oktober 2011 - 22:34 #8
Nej, janus, det er ikke hvor instansen kommer fra jeg er i tvivl om, men snarere, hvorfor det eksempel du giver (hvilket er det samme som jeg selv har fundet frem til via Ninject's wiki og andre kilder) ikke fungerer!? :-)

Det er i øvrigt ikke nødvendigt at sætte en inject-atribut på constructor, da Ninject selv finder ud af hvilken den kan/skal bruge (men det er OK at sætte den på, hvis man vil sikre sig, at én specifik constructor benyttes af Ninject). Inject-atributten er nødvendig i forbindelse med property- og metode-injections.

Jeg har lidt på fornemmelsen at det ikke nødvendigvis direkte er Ninject der fejler. Jeg har set en artikel på stackoverflow, som nævner problemer med valget af pipeline under IIS, så det kan være jeg skal eksperimentere lidt med dette...
Avatar billede janus_007 Nybegynder
15. oktober 2011 - 00:58 #9
Hmmm.. jeg har prøvet at gøre sådan som du beskriver her.

http://stackoverflow.com/questions/3370213/dependency-injection-with-ninject-2-0-for-c-sharp-asp-net-not-mvc

Og det virker fint, jeg har selv prøvet :)

Du skal bare finde dig i at du skal kalde KernelContainer.Inject(this), hvergang :)

I StructureMap kalder man en BuildUp og det fungerer godt alligevel.

Alt wiringen er jo sket, så jeg tænker der er et minimalt overhead forbundet med det.

Jeg har smidt mit projekt her, hvis du mangler Ninject.Web: http://dl.dropbox.com/u/546202/NinjectTest.zip
Avatar billede arne_v Ekspert
16. oktober 2011 - 04:18 #10
Jeg tror at lidt af forviringen skyldes uklarhed om hvad der egentligt diskuteres.

Men lad os foerst tage nogle grundliggende ting:

DI mappingen skal naturligvis kun forekomme i en enkelt instans (per app domain).

DI loading sker rekursivt - man loader A eksplicit via DI, load af A trigger load af B og C, load af B trigger load af D og E, load af C trigger load af F og G etc..

Der skal altid goeres noget aktivt paa oeverste niveau (medmindre supporten er bygget ind i runtime/container).

Nu kommer vi saa til hvor vandene skilles. Der er 2 mulige scenarier for web forms.

1) det oeverste niveau er noget inden i Page
2) det oeverste niveau er selve Page

#1 er nem. Du kan gemme den ene instans af din mapping i noget globalt. Maaske gor dit DI framework det allerede helt automatisk. Ellers kan du bruge en singleton eller Application objektet. Initialiseringen kan ske ved application start. Saa bruger du DI i din Page til at instantiere toppen med og resten sker automatisk.

#2 er hvor problemt opstaar. Foerst del med at finde et globalt sted at gemme mappingen og faa den initialiseret er den samme som for #1. Men naeste del at faa den anvendt paa Page selv er hvor det reelle problem er.

Baseret paa ovenstaande links saa virker det som om den anbefalede maade er at putte en "apply DI paa this" i constructor/OnInit af en base class som arvr fra Page og som man bruger til alle sine Pages.
Avatar billede arne_v Ekspert
16. oktober 2011 - 04:22 #11
Med hensyn til DI saa overvej hvor det giver mening. Lidt DI er lidt godt. Meget DI er meget skidt.

Det er et maal at decouple noget som boer decouples. Det er ikke at maal at have kode uden brug af new.
Avatar billede softspot Forsker
16. oktober 2011 - 23:38 #12
arne >> Tak for dine indspark. Jeg finder din pragmatiske indgangsvinkel sympatisk og mit udgangspunkt har jo nok også været det du beskriver mht. hvor ofte opsætning af bindinger skal ske, samt at min DI starter inde i Page. Derfor har jeg også taget udgangspunkt i, at jeg skulle bruge et (singleton) kernel-objekt til at oprette mine forretningsobjeker i Page-objektet. Af samme årsag har jeg også undret mig over, at jeg ikke umiddelbart kunne finde en reference til kernel nogen steder ifm. mit Page-objekt...

Jeg fornemmer jeg skal lave mig en klasse á la dette:

public class NKernel
{
    private static IKernel _kernel { get; set; }

    public static IKernel Kernel
    {
        get
        {
            if(_kernel == null)
                _kernel = new StandardKernel(new Bootstrap());
            return _kernel;
        }
    }
}

hvor Bootstrap-klassen indeholder de bindinger som skal foretages imellem typerne og interfaces.

Herefter kan jeg benytte den således i min kode:

protected void button1_click(object sender, EventArgs e)
{
    var person = NKernel.Kernel.Get<Person>();
    // osv...
}

Med dette udgangspunkt formoder jeg, det ikke er nødvendigt at benytte ninject.web-extension...

Nu mangler jeg så bare at forstå meningen med CreateKernel i Global.asax, for den virker for mig overflødig, når nu jeg har min NKernel-klasse til at oprette mit kernel-singletonobjekt. Er der nogen som kan finde på argumenter for, hvorfor jeg skal medtage CreateKernel i Global.asax?
Avatar billede arne_v Ekspert
16. oktober 2011 - 23:51 #13
Global.asax CreateKernel relaterer sig saa vidt jeg kan se til scenariet hvor man vil starte med at injecte ind i Page klassen.
Avatar billede arne_v Ekspert
16. oktober 2011 - 23:54 #14
Det er mit indtryk at der er mange som bruger loesningen med at injecte ind i Page klassen.

Men nu kan du jo starte med det du har nu og saa se om du faar lyst til at tage skridtet videre.
Avatar billede softspot Forsker
17. oktober 2011 - 00:07 #15
Ja, det ville nok være godt at få noget praktisk erfaring med det inden jeg kaster mig ud i alt for omfattende scenarier :-)

Tak til alle der har budt ind. Smid svar allesammen! :-)
Avatar billede Syska Mester
17. oktober 2011 - 01:19 #16
Nu kender jeg ikke Ninject ... bruger selv SM.

Men den har alt man skal bruge per default.

Så at oprette din egen Singleton klasse virker mærkeligt.

SM har en Default container som kan tilgås via ObjectFactory.GetInstance og mange andre metoder. Derudover kan der selvfølgelig også oprette mere specifikke Containers, men i et Web projekt har jeg ikke været ude for at det var nødvendig.

Ninjet må virke på nogen lunde samme måde ... altså at du kan inject ting i den default container som de fleste folk nok vil bruge.

Btw ... din Singleton er ikke 100% thread safe ... sjovt arne_v ikke har nævnt det ... ham jeg har lært det af :-) *heheh*
Avatar billede Syska Mester
17. oktober 2011 - 01:19 #17
svar
Avatar billede arne_v Ekspert
17. oktober 2011 - 01:26 #18
NInject og StructurMap er tilsyneladende lidt forskellige - for den ene skal man instantiere for den anden er der en singleton.

Lidt googling antyder at: Unity of Castle Winsor ogsaa skal instantieres mens Spring.NET har en singleton factory.

Saa ...
Avatar billede arne_v Ekspert
17. oktober 2011 - 01:26 #19
Ja - der mangler en lock !  :-)
Avatar billede arne_v Ekspert
17. oktober 2011 - 01:26 #20
og et svar fra mig
Avatar billede softspot Forsker
17. oktober 2011 - 09:26 #21
Tak for svar. Janus, skal du også have dit svar med?

Mht. den manglende lock, skal jeg lige være sikker på at jeg har fattet det. Mener I min kode skal se således ud;

    public static IKernel Kernel
    {
        get
        {
            lock(_kernel)
            {
                if(_kernel == null)
                    _kernel = new StandardKernel(new Bootstrap());
            }
            return _kernel;
        }
    }

Eller skal lock-mekanismen ligge ind i if-sætningen?

Hvis den ligger udenom, opstår der vel en flaskehals omkring dette punkt... Kernel må forventes at blive kaldt ofte... eller er det stadig tilladt at læse en låst variabel?
Avatar billede Syska Mester
17. oktober 2011 - 09:39 #22
Du skal have en dobblet if

if(_kernel == null)
{
lock(myLock)
{
if(_kernel == null)
{
_kernel = new Kernel();
}
}
}
return _kernel;

Problemet er at du i den første kan ramme "if" sætningen på samme tid, og dermed få oprettet samme object 2 gange, til den samme variable. Ikke at det i den her kode ville have noget at sige. Da det er statiske ting den læser ind.

mvh
Avatar billede janus_007 Nybegynder
17. oktober 2011 - 10:16 #23
#softspot

Du behøver ingen singleton, opsæt din wiring i global.asax som jeg beskrev og du er flyvende :) læg den i application on start, så køres den med sikkerhed kun en gang.

I Structuremap smides BuildUp ind på en basepage og den er såvidt jeg lige kan se det samme som KernelContainer.Inject(this)

For lige at koge ned....
Jeg ville injecte og ikke lave et antipattern alá : NKernel.Kernel.Get<Person>();

En af de store emner omkring DI er netop at man uden videre kan skifte DI-framework og det kan hurtigt blive svært hvis koden er plastret til med DI-specifik syntax :)

Mht. at bruge DI allesteder, hmm..... jeg synes det er spild af tid at sidde med new og selv injecte dependencies ind i klasser, her vil nogen sikkert plædere for at man ikke behøver injection, men blot kan new'e i hver klasse :) Og ja... det er da helt sikkert man kan det, men du ødelægger totalt muligheden for at skrive nogle fornuftige unittests med tilhørende mockobjects.

Nogle gange er man dog nødt til at kalde new men det er mere på .NET framework klasser jeg gør det og altså ekstremt sjældent på de projekter jeg udvikler.

Det er klart hjælpeklasser mv. ikke injectes, men de optræder ofte som static alligevel :)
Avatar billede softspot Forsker
17. oktober 2011 - 10:17 #24
Ja, det tænkte jeg nok! :-)

Min egen umiddelbare tanke ifht. lock var, at det nok ikke havde den store betydning i dette tilfælde, men på den anden side ved jeg ikke nok om hvad der foregår i maven på Ninject til at jeg tør lade være med at sikre mod, at _kernel oprettes to gange...

I teorien kunne det vel ske, at singleton-instanser i _kernel kunne risikere at blive oprettet flere gange, hvis _kernel først udstedte en singleton og dernæst blev overskrevet med en ny kernel, som så udsteder en ny singleton (så der ender med at være flere singletons af samme type - altså i teorien)...
Avatar billede softspot Forsker
17. oktober 2011 - 10:56 #25
@janus >> Yes! Beklager jeg ikke lige fik taget dit eksempel i betragtning. Det har jeg nu og det fungerer (lettere tilpasset) som det skal (med et simpelt eksempel). Det er rigtig nice - man bliver jo helt opstemt! :D

Anyway! Jeg ved ikke hvorfor mit eget ikke fungerede, for det så nogenlunde ligesådan ud, blot var det lagt ind i et eksisterende projekt med lidt flere elementer (som ikke havde relation til dette i øvrigt).

Takker jer alle tre mange gange for hjælp og vejledning!
Avatar billede Syska Mester
17. oktober 2011 - 10:56 #26
Den første ville blive garbage collected, men den anden ville overleve da din static class altid vil have en reference til den.

mvh
Avatar billede softspot Forsker
17. oktober 2011 - 11:00 #27
@buzz >> jeg tænkte på den singleton som kernel producerer... de bliver vel ikke nedlagt før applikationen lukkes ned, eller hvad?
Avatar billede Syska Mester
17. oktober 2011 - 11:13 #28
Nej, ikke så længe et eller andet har en reference til den.

mvh
Avatar billede softspot Forsker
17. oktober 2011 - 11:29 #29
Aaaccch! Jeg tænker på singletons alene som static objekter, men det kan jo sagtens være et "normalt" objekt som bare returneres hver gang det skal benyttes. Takker :-)
Avatar billede arne_v Ekspert
31. oktober 2011 - 00:38 #30
re #21)

man kan ikke lock'e paa null !!
Avatar billede arne_v Ekspert
31. oktober 2011 - 00:51 #31
re #22)

Det er ikke mig du har laert double locking af !!

(det er jo som bekendt ikke sikkert mdmindre man bruger volatile eller en memory barriere)
Avatar billede softspot Forsker
31. oktober 2011 - 08:36 #32
arne >> Tak for den opdatering. Jeg fandt også ud af, at det ikke kunne lade sig gøre og at jeg i stedet skulle oprette et privat statisk objekt jeg kunne locke på. Rent bort set fra det, så var det janus havde lavet, løsningen på det konkrete problem, så det blev slet ikke nødvendigt at lave den hjemmestrikkede løsning og dermed blev min kode overflødig...
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester