Sök…


Anmärkningar

Hela punkten med beroende injektion (DI) är att minska kodkopplingen. Föreställ dig någon form om interaktion som involverar nyskapning av något liknande i "Hårdkodat beroendeexempel".

En stor del av att skriva kod är förmågan att testa den. Varje gång vi uppdaterar ett nytt beroende gör vi vår kod svår att testa eftersom vi inte har någon kontroll över detta beroende.

Hur skulle du testa kod som till exempel beror på DataTime.Now? Det ändras alltid så att du inte har någon referens. Detta är när du injicerar en stabil parameter som utgångspunkt. Du kan kontrollera det, du kan skriva tester baserade på olika värden och se till att du alltid får rätt resultat.

Ett bra alternativ är därför att passera ett gränssnitt eller en abstrakt klass som en parameter i konstruktören DI.

Ett gränssnitt representerar ett väldefinierat kontrakt, du kan alltid lita på de metoder som finns där och du kan alltid lita på metodsignaturerna.

När du börjar använda DI öppnas andra aspekter. Till exempel, även om du passerar ett gränssnitt någon gång behöver du en verklig implementering för att faktiskt göra något arbete. Det är här andra begrepp visas. Vi kan använda IOC (Inversion of Control) för att lösa våra beroenden. Detta innebär att vi instruerar vår kod att alltid använda en specifik implementering för alla kontrakt. Naturligtvis finns det andra sätt att göra detta. Vi kan alltid instansera varje kontrakt med en specifik implementering och från och med den punkten kan vår kod använda den delen:

public ILogging Logging { get; set }

någon gång initierar vi det.

Logging = new FileLogging();

detta kommer alltid att fungera så länge vår klass uppfyller det förväntade kontraktet:

public class FileLogging : ILogging

Från initialiseringsmomentet och framåt använder vi alltid Logging-objektet. Detta gör livet enklare eftersom om vi någonsin bestämmer oss för att ändra och använda en databasloggning till exempel, måste vi bara ändra koden på ett ställe och det är precis där vi initialiserar loggningsklassen.

Är DI bara bra för testning? Nej, DI är också viktigt när du skriver underhållskod. Det gör det möjligt att tydliggöra separationen av oro.

När du skriver någon kod, tänk ... är den testbar, kan jag skriva ett test, det är när jag injicerar ett DateTime-värde istället för att använda DateTime.Now är meningsfullt.

Ninject-konfigurationer

Efter installationen av en IoC-behållare (Inversion of Control) behövs några justeringar för att den ska fungera. I det här fallet använder jag Ninject. I NinjectWebCommon-filen, som finns i mappen App_Start, ersätter metoden CreateKernel med:

private static IKernel CreateKernel()
    {  
        // Create the kernel with the interface to concrete bindings           
        var kernel = RegisterServices();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
           
            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

Och RegisterServices-metoden med:

 private static StandardKernel RegisterServices()
        {
            Container container = new Container();
            // encapsulate the interface to concrete bindings in another class or even in another layer 
            StandardKernel kernel = container.GetServices();
            return kernel;
        } 

Skapa en ny klass till den bindning som i detta fall kallas Container:

public class Container
{
    public StandardKernel GetServices()
    {
        // It is good practice to create a derived class of NinjectModule to organize the binding by concerns. In this case one for the repository, one for service and one for app service bindings
        return new StandardKernel(new NinjectRepositoryModule(),
            new NinjectServiceModule(),
            new NinjectAppServiceModule());
    }
}

Slutligen i varje härledd NinjectModule-klass ändra bindningarna som överbelaster Load-metoden som:

public class NinjectRepositoryModule: NinjectModule
{
    public override void Load()
    {
        // When we need a generic IRepositoryBase<> to bind to a generic RepositoryBase<>
        // The typeof keyword is used because the target method is generic
        Bind(typeof (IRepositoryBase<>)).To(typeof (RepositoryBase<>));

        // When we need a IUnitOfWorkbind to UnitOfWork concrete class that is a singleton
        Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
    }
}

Ett annat exempel på härledd NinjectModule:

public class NinjectServiceModule :NinjectModule
{
    public override void Load()
    {
        // When we need a IBenefitService to BenefitService concrete class           
        Bind<IBenefitService>().To<BenefitService>();

        // When we need a ICategoryService to CategoryService concrete class  
        Bind<ICategoryService>().To<CategoryService>();

        // When we need a IConditionService to ConditionService concrete class  
        Bind<IConditionService>().To<ConditionService>();           
    }
}

Användning av gränssnitten

I den konkreta klassen som behöver tjänsten använder du gränssnittet för att komma åt tjänsten istället för dess implementering som:

 public class BenefitAppService
{
    private readonly IBenefitService _service;
    public BenefitAppService(IBenefitService service)
    {
        _service = service;
    }      
   
    public void Update(Benefit benefit)
    {
        if (benefit == null) return           
        _service.Update(benefit);
        _service.Complete();
    }        
}

Om du behöver något i betongklassen ska du inte störa koden ovan. Du kan ändra implementeringen av tjänsten för en helt annan skillnad, och så länge det tillfredsställer gränssnittet är du bra att gå. Det gör det också mycket enkelt att testa det.

Injektion av konstruktörberoende

Constructor Dependency Injection kräver parametrar i konstruktören för att injicera beroenden. Så du måste skicka värdena när du skapar ett nytt objekt.

public class Example
{
    private readonly ILogging _logging;

    public Example(ILogging logging)
    {
        this._logging = logging;
    }
}

Hårkodad beroende

public class Example
{
    private FileLogging _logging;

    public Example()
    {
        this._logging = new FileLogging();
    }
}

parameter DI

public DateTime SomeCalculation()
{
     return DateTime.Now.AddDays(3);
}

mot

public DateTime SomeCalculation(DateTime inputDate)
{
    return inputDate.AddDays(3);
}

Injektion av beroende av beroende

Beroende resolver används för att undvika tätt sammankopplade klasser, förbättra flexibiliteten och göra testet enkelt. Du kan skapa en egen beroendeinjektor (inte rekommenderad) eller använda en av välskrivna och testade beroendeinjektorer. I det här exemplet kommer jag att använda Ninject .

Steg ett: Skapa beroende-upplösare.

Ladda först Ninject från NuGet. Skapa mapp med namnet Infrastructure och lägg till klass med namnet NinjectDependencyResolver :

using Ninject;
using System;
using System.Collections.Generic;
using System.Web.Mvc;

public class NinjectDependencyResolver
    : IDependencyResolver
{
    private IKernel kernel;

    public NinjectDependencyResolver()
    {
        // Initialize kernel and add bindings
        kernel = new StandardKernel();
        AddBindings();
    }

    public object GetService(Type serviceType)
    {
        return kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return kernel.GetAll(serviceType);
    }

    private void AddBindings()
    {
        // Bindings added here
    }
}

MVC-ramverket kommer att anropa GetService- och GetServices- metoderna när det behöver en insance av en klass för att betjäna en inkommande begäran.

Steg två: Registrera beroendeupplösare.

Nu har vi vår anpassade beroende-upplösare och vi måste registrera den för att säga till MVC-ramverket att använda vår beroende-upplösare. Registrera beroende-resolver i filen Global.asax.cs :

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    DependencyResolver.SetResolver(new NinjectDependencyResolver());
    
    // .....
}

Steg tre: Lägg till bindningar.

Föreställ dig att vi har följande gränssnitt och implementering:

public interface ICustomCache
{
    string Info { get; }
}

public class CustomCache : ICustomCache
{
    public string Info
    {
        get
        {
            return "Hello from CustomCache.";
        }
    }
}

Om vi vill använda CustomCache i vår controller utan att tätt koppla vår controller med CustomCache, måste vi binda ICustomCache till CustomCache och injicera den med Ninject. Först bör du först binda ICustomCache till CustomCache genom att lägga till följande kod till AddBindings () -metoden för NinjectDependencyResolver :

private void AddBindings()
{
    // Bindings added here
    kernel.Bind<ICustomCache>().To<CustomCache>();
}

Förbered sedan din controller för injektion enligt nedan:

public class HomeController : Controller
{
    private ICustomCache CustomCache { get; set; }

    public HomeController(ICustomCache customCacheParam)
    {
        if (customCacheParam == null)
            throw new ArgumentNullException(nameof(customCacheParam));

        CustomCache = customCacheParam;
    }

    public ActionResult Index()
    {
        // cacheInfo: "Hello from CustomCache."
        string cacheInfo = CustomCache.Info;

        return View();
    }
}

Detta är exempel på kostruktorinjektion och det är en form av beroendeinjektion . Som ni ser beror inte vår Home controller på CustomCache-klassen. Om vi vill använda en annan implementering av ICustomCache i vår applikation är det enda vi behöver ändra att binda ICustomCache till en annan implementering och det är det enda steget vi behöver ta. Vad som hände här är, MVC Framework bad vår registrerade beroende-resolver att skapa instans av HomeController- klassen via GetService- metoden. GetService-metoden ber Ninject-kärnan att skapa begärt objekt och Ninject-kärnan undersöker typen i dess term och finner ut att konstruktören av HomeController kräver en ICustomCache och bindning redan har lagts till för ICustomCache . Ninject skapar förekomst av bunden klass , använder den för att skapa HomeController och returnerar MVC Framework.

Beroende kedjor.

När Ninject försöker skapa typ undersöker den andra beroenden mellan typ och andra typer och om det finns någon Ninject försöker skapa dem också. Till exempel, om vår CustomCache-klass kräver ICacheKeyProvider och om bining tillagd för ICacheKeyProvider kan Ninject tillhandahålla det för vår klass.

ICacheKeyProvider- gränssnitt och SimpleCacheKeyProvider- implementering:

public interface ICacheKeyProvider
{
    string GenerateKey(Type type);
}

public class SimpleCacheKeyProvider
    : ICacheKeyProvider
{
    public string GenerateKey(Type type)
    {
        if (type == null)
            throw new ArgumentNullException(nameof(type));

        return string.Format("{0}CacheKey", type.Name);
    }
}

Modifierad CustomCache- klass

public class CustomCache : ICustomCache
{
    private ICacheKeyProvider CacheKeyProvider { get; set; }

    public CustomCache(ICacheKeyProvider keyProviderParam)
    {
        if (keyProviderParam == null)
            throw new ArgumentNullException(nameof(keyProviderParam));

        CacheKeyProvider = keyProviderParam;
    }

    ...........
}

Lägg till bindande för ICacheKeyProvider :

private void AddBindings()
{
    // Bindings added here
    kernel.Bind<ICustomCache>().To<CustomCache>();
    kernel.Bind<ICacheKeyProvider>().To<SimpleCacheKeyProvider>();
}

När vi nu navigerar till HomeController skapar Ninject instans av SimpleCacheKeyProvider använder den för att skapa CustomCache och använder CustomCache-instans för att skapa HomeController .

Ninject har många fantastiska funktioner som kedjad beroendeinjektion och du bör undersöka dem om du vill använda Ninject.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow