Cygnis talangprogram får våra traineer genomföra och redovisa ett antal kompetensutvecklingsuppgifter. Syftet är att bekanta sig med designmönster och tekniker som är vanliga inom agil utveckling. Övningarna genomförs på olika plattformar, t.ex. Java, .NET och JavaScript. En av dessa övningar fokuserar på Dependency Injection (DI), och vi lyfte nyligen över .NET-varianten av den till ASP.NET 5. Det ledde till ett par intressanta lärdomar och insikter i hur DI är implementerat i ASP.NET 5.

När övningen genomförs i Java används DI-containern i Spring. Den har stöd för konfiguration i XML, programmatisk konfiguration i kod, och deklarativ konfiguration med annotationer. På så sätt får man pröva olika tekniker för hur man registrerar och beskriver sina beroenden, och hur dessa beroenden löses vid exekvering. På .NET-sidan har får man välja vilken DI-container som man vill använda. DI-containers är ju möjligen ett av få områden där .NET-världen är mer heterogen än javamotsvarigheten! Populära val är t.ex. Ninjectoch Autofac.

ASP.NET kommer dock med en helt ny DI-container, som självklart är open source. Källkoden finns tillgänglig här. Övningen går utmärkt att genomföra med denna container, men just eftersom floran av DI-containers för .NET är så heterogen vore det bra att även fortsatt kunna välja container fritt. I dokumentationen för ASP.NET finns också följande lilla ”friskrivning” angående olika containers.

The default services container provided by ASP.NET 5 provides a minimal feature set and is not intended to replace other containers.

Men det finns ett litet problem att lösa här: DI-containern i ASP.NET är global, och används för att lösa upp alla beroenden. Det innebär att samma container används både för beroenden djupt inne i ASP.NET som inuti din applikation. Att plugga in en annan DI-implementation ska i teorin vara enkelt, man behöver bara implementera det enkla interfacet IServiceProvider. Det visar sig dock att det är långt ifrån enkelt att skriva en sådan adapter, och det finns de som har väldigt starka åsikter i ämnet. Detta innebär att det i dagsläget inte alltid är så lätt att få sin container att fungera i ASP.NET 5.

Men hur gör jag om jag vill använda en viss DI-container i min applikation, utan att påverka hela min ASP.NET-pipeline? Oftast vill jag ju bara skjuta in ett par beroenden i konstruktorn på mina kontrollerklasser, och det bör jag kunna göra utan påverka hur ASP.NET internt skapar de tjänster och resurser ramverket använder i sin pipeline.

Det visar sig att det finns en mer lättviktig lösning på detta problem, som inte kräver att vi byter ut containern för hela ASP.NET. Lösningen finns beskriven här, men det exemplet går att förenkla och generalisera en del. Idén är att vi låter ASP.NET använda sin inbyggda container för allting utom just att skapa vår kontrollers. På så sätt påverkar vi inget i vårt ramverk utom just den factory som används för att skapa instanser av våra kontroller. Som exempel använder vi ett API med enkel controller. Den kan t.ex. se ut så här:

public interface IRepository 
{ 
    string[] GetStrings(); 
}

public class Repository : IRepository 
{ 
    public string[] GetStrings() 
    { 
        return new[] { "Hello", "Brave", "New", "World" }; 
    } 
}

[Route("api/[controller]")] 
public class ValuesController : Controller { 
    private readonly IRepository repository; 
    public ValuesController(IRepository repository) 
    { 
        this.repository = repository; 
    } 

    [HttpGet] 
    public IEnumerable<string> Get() 
    { 
        return repository.GetStrings(); 
    } 
}

Tanken är nu att använda Ninject för att lösa upp vårt beroende till repositoryt och instansiera vår kontroller. ASP.NET 5 använder en IControllerActivator för att skapa kontroller-klasser, och vi kan registrera en egen implementation av detta interface, där vi använder Ninject. Vår ControllerActivator ser ut så här:

class MyNinjectControllerActivator : IControllerActivator 
{ 
    private readonly IKernel kernel; 

    public MyNinjectControllerActivator() 
    { 
        kernel = new StandardKernel(); 
        kernel.Bind<IRepository>().To<Repository>(); 
    }

    public object Create(ActionContext context, Type controllerType) 
    { 
        return kernel.Get(controllerType); 
    } 
}

Vi registrerar denna i ASP.NETs DI-container, vilket gör att ASP.NETs egen implementation byts ut mot vår:

public void ConfigureServices(IServiceCollection services) 
{
    services.AddInstance<IControllerActivator>(new MyNinjectControllerActivator()); 
    services.AddMvc(); 
}

När vår applikation nu instansierar en kontroller kommer vår egen implementation av IControllerActivator användas, och i den kan vi konfigurera vår Ninject-container helt fritt, utan att påverka något i resten av vår ASP.NET-pipeline. Detta exempel är självklart inte komplett eller färdigt för produktion, men det visar på hur man utan att behöva implementera en fullständig adapter för ASP.NET 5 kan använda en valfri DI-container för att instansiera sina kontroller-klasser.