ASP.NET MVC, controller factory, DI/IoC - kuidas need koos töötavad?

26.09.2010  |  Gunnar

DI/IoC konteinerid on asi, mille kasutamist on mul kallil kodumaal haruldaselt vähe silma hakanud – nagu ka testidega kaetud koodi. Mõtlesin, et teen ühe lihtsa näite DI/IoC konteineri kasutamise teemal ja demon kuidas ASP.NET MVC rakenduses distantseerida objektide loomine kontrolleritest võttes kasutusele enda loodud controller factory. See kanne aitab teil aru saada esimesest sammust selle poole, et livesse lastud rakendustest saaks edulugu, mitte õudusunenägu, nagu see tihti juhtub.

Mis asi on controller factory?

ASP.NET MVC peab oma töö käigus olema võimeline kontrollereid looma. See võiks küll idee poolest juhtuda kuskil sügaval raamistiku kõhus, nii et me seda ei näe ja tegelikult nii ongi, kuid sisseehitatud mehhanism ei ole meile alati abiks. Näiteks kui me tahame kontrolleri loomisel anda kontrolleri konstruktorisse kaasa parameetreid.

Selleks, et kontrollerite loomist saaks arendajad ise vajadusel kontrollida, kasutab ASP.NET MVC sellist asja nagu controller factory. Controller factory käest küsitakse konkreetset tüüpi kontroller kohe, kui ASP.NET MVC raamistiks on valmis kontrolleri küljest meetodi kutsuma. Controller factory võtab antud tüübi ja püüab selle põhjal luua kontrolleri instantsi ning selle ASP.NET MVC raamistikule tagastada.

See tähendab seda, et raamistik kasutab sisemiselt vaikimisi enda controller factory-t, kuid selle saab välja vahetada enda oma vastu ja seda ilma, et peaksime ASP.NET MVC raamistiku enda koodis midagi muutma.

Liiga targad kontrollerid

Algajad kirjutavad enamasti lihtsad, kuid see-eest raskesti testitavad kontrollerid, sest kontrollerite sees luuakse objektid, mida me testimise ajal seal näha ei taha. Lisaks kaob meil võimalus lihtsasti vahetada kontrollerites kasutatavaid objekte paremate vastu välja, kui me need kirjutame. Vaatame näidet kah.

public class ContactsController : Controller
{
    public const int SidebarListLength = 5;

    private readonly PartyRepository _partyRepository;

    public ContactsController()
    {
        _partyRepository = new PartyRepository();
    }

    [HttpGet]
    public ActionResult Index(int page)
    {
        var model = new ContactsIndexModel();
        model.Parties = _partyRepository.ListParties(page, 10, string.Empty, true);
        model.LastInsertedParties = _partyRepository.ListLastInsertedParties(SidebarListLength);
        model.LastModifiedParties = _partyRepository.ListLastModifiedParties(SidebarListLength);

        return View(model);
    }
}

Selle kontrolleri juures on probleemiks see luuakse kindlat tüüpi PartyRepository. Kui me tahame selle välja vahetada, siis pole meil muud võimalust kui käia kood läbi ja vahetada antud repository loomine välja kõikjal. Teine ja veel haigem variant on olemasoleva töötava repository enda lähtekoodi muutmine, sest siis jäävad ju ometi avalikud liidesed muutumata. Halvad ja haiged variandid on need mõlemad.

Parem variant oleks see, kui meil on olemas liidesed, millele vastavad objektid meile kontrollerisse kaasa antakse.

Vaikimisi controller factory jääb nüüd hätta

Kui oleme viinud koodi liidestele üle ja teeme nii, et kontrollerile antakse juba eelnevalt valmis loodud objektid kaasa, siis saame sellise koodi.

public class ContactsController : Controller
{
    public const int SidebarListLength = 5;

    private readonly IPartyRepository _partyRepository;

    public ContactsController(IPartyRepository partyRepository)
    {
        _partyRepository = partyRepository;
    }

    [HttpGet]
    public ActionResult Index(int page)
    {
        var model = new ContactsIndexModel();
        model.Parties = _partyRepository.ListParties(page, 10, string.Empty, true);
        model.LastInsertedParties = _partyRepository.ListLastInsertedParties(SidebarListLength);
        model.LastModifiedParties = _partyRepository.ListLastModifiedParties(SidebarListLength);

        return View(model);
    }
}

Kui nüüd rakenduse käivitame, siis saame tulemuseks vea, sest vaikimisi kasutatav controller factory ei leia eest argumentideta konstruktorit.

Võtame kasutusele enda controller factory

Sellest, et aidata ASP.NET MVC hädast välja, võtame kasutusele enda controller factory, mis oskab liidestele vastavad objektid valmis luua konfiguratsiooni põhjal. Minu näide kasutab sellist jubinat nagu Unity ja veebirakenduse konfiguratsioonis on mul liidesed ja tüübid omavahel kokku viidud.

Minu controller factory näeb välja selline:

public class UnityControllerFactory : DefaultControllerFactory
{
    protected override IController GetControllerInstance(RequestContext requestContext,
        Type controllerType)
    {
        if (requestContext.HttpContext.Request.FilePath == "/favicon.ico")
            return null;

        if (controllerType == null)
            return base.GetControllerInstance(requestContext, controllerType);

        if (!typeof(IController).IsAssignableFrom(controllerType))
            throw new ArgumentException(string.Format(
                "Type requested is not a controller: {0}",
                controllerType.Name),
                "controllerType");
        return Resolver.Resolve(controllerType) as IController;
    }
}

Siin toimub kontrollerite loomine DI/IoC konteineri abil. Liidestele vastavad tüübid tuvastatakse konfiguratsioonist (koos objektide eluea määrangutega), luuakse tüüpidele vastavad objektid ning tagastatakse kontrolleri instants, kuhu on objektid juba kaasa antud.

ASP.NET MVC raamistikule teeme uue controller factory olemasolu teatavaks Global.asax failis.

protected void Application_Start()
{
    ControllerBuilder.Current.SetControllerFactory(
        typeof(UnityControllerFactory)
    );

    RegisterRoutes(RouteTable.Routes);
    RegisterBinders();
}

Mis me võitsime? Peamiselt selle, et me saame repository implementatsioone lihtsasti välja vahetada. Samuti võitsime selles, et kontroller tunneb ainult repository liidest, kuid ei oska ise repository objekti luua. Seega sisaldab kontroller oluliselt vähem teavet kui enne. Kolmandaks on meil nüüd võimalik kontrollerit testida, sest me saame luua ise näiteks fake repository või võtta kasutusele Moq ja lasta sellel luua repository instants, mille käitumise me ise ette defineerime ja mis ei lähe andmebaasist andmeid tooma.

Kokkuvõtteks

Milleks peaks üldse olema mingist andmeallikast võimalik mitu versiooni? Proosaline ja populaarne vihje kodumaal oleks selline: kui üks andmekiht üle jõu käib ja teise kasutamine paremini välja tuleb, siis ei pea presentatsioonikihis koodi muutma. Piisab sellest, kui muudame DI/IoC konteineri konfi ja lisame uued teegid veebirakenduse bin kataloogi. Sama käib näiteks rakenduse funktsionaalsuste kohta, mis asuvad eraldi teekides ja mille vahetamise järgi võib tekkida otsene vajadus (näiteks mingite keerukate tegevuste läbiviimine; täna saime asjad selliselt tööle, kaks kuud hiljem on meil aga olemas juba esimene versioon samast asjast oluliselt optimeeritud kujul).

Arendajad, kelle ehitatud rakendused on lakoonilised ja paindlikud, ei pea muretsema seepärast, et igasugused muudatused paiskavad nende ja klientide elu mõneks ajaks segi.

2 kommentaari sissekandele “ASP.NET MVC, controller factory, DI/IoC - kuidas need koos töötavad?”

  1. Karel

    Siia juurde võiks veel mainida, et selle kõige “kõrvalproduktina” on nüüd ka võimalik lihtsasti rakendada AOP-d, mille abil on väga mugav ja lihtne lahendada läbivaid probleeme, nagu erandite haldamine, logimine, cachemine jne.

  2. Gunnar

    Mul tekkis selline tunne, et äkki ma pingutan liialt üle ja jätan inimestele muidu võimaluse endale öelda, et see kanne sisaldab midagi suurt ja keerukat. Sellist võimalust ei tohi siinmail arendajatele veel järgmised 10 aastat vist jätta…. :P

Kommenteeri

sulge
Saada link e-postiga

© DT 2012 | Creative Commons Attribution-Noncommercial 3.0 License | WordPress