Zoeken…


Opmerkingen

Het hele punt van afhankelijkheidsinjectie (DI) is het verminderen van codekoppeling. Stel je elke vorm voor als interactie waarbij iets moet worden vernieuwd, zoals in het voorbeeld "Hard gecodeerde afhankelijkheid".

Een groot deel van het schrijven van code is de mogelijkheid om deze te testen. Telkens wanneer we een nieuwe afhankelijkheid nieuw maken, maken we onze code moeilijk te testen omdat we geen controle hebben over die afhankelijkheid.

Hoe zou u de code testen die afhankelijk is van DataTime. Nu bijvoorbeeld? Het verandert altijd, dus u hebt geen referentie. Dit is wanneer u een stabiele parameter als uw startpunt injecteert. U kunt het besturen, u kunt tests schrijven op basis van verschillende waarden en ervoor zorgen dat u altijd het juiste resultaat krijgt.

Een goede optie is daarom om een interface of een abstracte klasse door te geven als parameter in de constructor DI.

Een interface vertegenwoordigt een goed gedefinieerd contract, u kunt altijd vertrouwen op de methoden die er zijn en u kunt altijd vertrouwen op de handtekeningen van de methode.

Zodra u DI gaat gebruiken, worden andere aspecten geopend. Zelfs als u bijvoorbeeld op een bepaald moment een interface passeert, hebt u een echte implementatie nodig om daadwerkelijk werk te doen. Dit is waar andere concepten verschijnen. We kunnen IOC (Inversion of Control) gebruiken om onze afhankelijkheden op te lossen. Dit betekent dat we onze code instrueren om altijd een specifieke implementatie voor elk contract te gebruiken. Natuurlijk zijn er andere manieren om dit te doen. We kunnen elk contract altijd instantiëren met een specifieke implementatie en vanaf dat moment kan onze code dat deel gebruiken:

public ILogging Logging { get; set }

op een gegeven moment initialiseren we het.

Logging = new FileLogging();

dit zal altijd werken zolang onze klas aan het verwachte contract voldoet:

public class FileLogging : ILogging

vanaf het initialisatiemoment gebruiken we altijd het Logging-object. Dit maakt het leven eenvoudiger, omdat als we ooit besluiten om een DatabaseLogging te wijzigen en te gebruiken, we de code slechts op één plaats hoeven te wijzigen en dit is precies waar we de klasse Logging initialiseren.

Is DI alleen goed voor testen? Nee, DI is ook belangrijk bij het schrijven van onderhoudbare code. Hierdoor kan de scheiding van zorgen duidelijk zijn.

Als je een code schrijft, denk dan ... is het testbaar, kan ik een test schrijven, dat is wanneer een DateTime-waarde wordt geïnjecteerd in plaats van DateTime.Now is logisch.

Ninject-configuraties

Na de installatie van een IoC-container (Inversion of Control) zijn enkele aanpassingen nodig om deze te laten werken. In dit geval zal ik Ninject gebruiken. Vervang in het bestand NinjectWebCommon, dat zich in de map App_Start bevindt, de methode CreateKernel door:

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;
        }
    }

En de methode RegisterServices met:

 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;
        } 

Maak een nieuwe klasse aan voor de binding die in dit geval Container wordt genoemd:

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());
    }
}

Wijzig ten slotte in elke afgeleide NinjectModule-klasse de bindingen die de Load-methode overbelasten, zoals:

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();
    }
}

Een ander voorbeeld van afgeleide 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>();           
    }
}

Gebruik van de interfaces

In de concrete klasse die de service nodig heeft, gebruikt u de interface voor toegang tot de service in plaats van de implementatie ervan, zoals:

 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();
    }        
}

Als je nu iets in de concrete klasse nodig hebt, zal je je niet in de bovenstaande code mengen. U kunt de service-implementatie voor nog een volledig verschil wijzigen, en zolang deze voldoet aan de interface bent u klaar om te gaan. Het maakt het ook heel gemakkelijk om het te testen.

Constructor afhankelijkheid injectie

De Constructor Dependency Injection vereist parameters in de constructor om afhankelijkheden te injecteren. U moet dus de waarden doorgeven wanneer u een nieuw object maakt.

public class Example
{
    private readonly ILogging _logging;

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

Hard gecodeerde afhankelijkheid

public class Example
{
    private FileLogging _logging;

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

parameter DI

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

vs

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

Ninject afhankelijkheid injectie

Afhankelijkheid resolver wordt gebruikt om strak gekoppelde klassen te voorkomen, flexibiliteit te verbeteren en testen eenvoudig te maken. U kunt uw eigen afhankelijkheidsinjector maken (niet aanbevolen) of een van de goed geschreven en geteste afhankelijkheidsinjectoren gebruiken. In dit voorbeeld ga ik Ninject gebruiken .

Stap één: Maak een resolver voor afhankelijkheid.

Download allereerst Ninject van NuGet. Maak een map met de naam Infrastructuur en voeg een klasse met de naam NinjectDependencyResolver toe :

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
    }
}

Het MVC Framework roept de methoden GetService en GetServices aan wanneer het een klasse vereist om een inkomend verzoek te kunnen behandelen.

Stap twee: Registreer de afhankelijkheidsresolver.

Nu hebben we onze aangepaste afhankelijkheidsresolver en we moeten deze registreren om MVC framework te vertellen onze afhankelijkheidsresolver te gebruiken. Registreren afhankelijkheid resolver in Global.asax.cs bestand:

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

Stap drie: Bindingen toevoegen.

Stel je voor dat we de volgende interface en implementatie hebben:

public interface ICustomCache
{
    string Info { get; }
}

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

Als we CustomCache in onze controller willen gebruiken zonder onze controller strak aan CustomCache te koppelen , moeten we ICustomCache binden aan CustomCache en het injecteren met behulp van Ninject. Eerst en vooral, bind ICustomCache aan CustomCache door de volgende code toe te voegen aan de methode AddBindings () van NinjectDependencyResolver :

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

Bereid vervolgens uw controller voor op injectie zoals hieronder:

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();
    }
}

Dit is een voorbeeld van injectie met costructor en het is een vorm van injectie met afhankelijkheid . Zoals u ziet, is onze Home-controller niet afhankelijk van de CustomCache-klasse itslef. Als we een andere implementatie van ICustomCache in onze applicatie willen gebruiken, is het enige dat we moeten veranderen, ICustomCache aan een andere implementatie te binden en dat is de enige stap die we moeten nemen. Wat hier is gebeurd, is dat MVC Framework onze geregistreerde afhankelijkheidsresolver heeft gevraagd om een instantie van de HomeController- klasse te maken via de GetService- methode. De methode GetService vraagt de Ninject-kernel om het gevraagde object te maken en de Ninject-kernel onderzoekt het type in zijn term en ontdekt dat de constructor van HomeController een ICustomCache vereist en dat binding al is toegevoegd voor ICustomCache . Ninject maakt een instantie van een gebonden klasse , gebruikt deze om HomeController te maken en retourneert deze MVC Framework.

Afhankelijkheidsketens.

Wanneer Ninject type probeert te maken, onderzoekt het andere afhankelijkheden tussen type en andere typen en probeert Ninject ze ook te maken. Als onze CustomCache-klasse bijvoorbeeld ICacheKeyProvider vereist en als bining toegevoegd voor ICacheKeyProvider Ninject dit voor onze klasse kan bieden.

ICacheKeyProvider- interface en SimpleCacheKeyProvider- implementatie:

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);
    }
}

Gewijzigde CustomCache- klasse

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

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

        CacheKeyProvider = keyProviderParam;
    }

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

Binding toevoegen voor ICacheKeyProvider :

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

Wanneer we nu naar HomeController navigeren, maakt Ninject een instantie van SimpleCacheKeyProvider gebruikt deze om CustomCache te maken en gebruikt de instantie CustomCache om HomeController te maken.

Ninject heeft een aantal geweldige functies zoals geketende afhankelijkheidsinjectie en u moet ze onderzoeken als u Ninject wilt gebruiken.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow