Wstrzykiwanie zależności w C# MVC

0

Czy zna ktoś jakiś sensowny przykład ze wstrzykiwaniem zależności?

Jak to stosować? Jak wprowadzić to do swojej aplikacji? Na czym to polega?
Bardzo proszę o przykład :)

0

ŁOŁ! Tyle wygrać!

A coś sam od siebie nie potrafisz wymyślić?

Tu jest coś fajnego jakby ktoś potrzebował//bit.ly/1pefZ9q

1

Zadajesz denne pytania, to się nie dziw.
Temat DI w MVC jest już przeorany w necie na tysiąc sposobów, równie dobrze mogłeś na forum zadać pytanie co to jest programowanie obiektowe.

0

Dlaczego? To skoro jest taki oklepany to może mi wyjaśnisz? Czy nie umiesz?
Jak nie masz niczego sensownego do powiedzenia to milcz...

Natomiast jeśli chciałbyś podzielić się wiedzą to jestem otwarty na wszelkie wskazówki :)

2

Załóżmy, że w aplikacji masz następujące klasy: kontrolery, serwisy, kontekst Entity Frameworka. Kontrolery korzystają z serwisów, a serwisy z kontekstu EF. W serwisach masz pole typu kontekstu EF, a w kontrolerach pola typów serwisów. W typowym podejściu, w konstruktorach serwisów tworzyłbyś obiekt kontekstu, a w konstruktorach kontrolerów tworzyłbyś obiekty serwisów, wszystko za pomocą operatora new.
Jeśli użyjesz jakiś kontener IoC, nie musisz sam pamiętać o tworzeniu potrzebnych Ci obiektów. Wystarczy, że napiszesz konstruktory pozwalające na ustawienie tych pól, a potrzebne obiekty zostaną automagicznie utworzone i wstrzyknięte przez kontener.

0

Analizuje to właśnie wpierw na aplikacji konsolowej.

Mam sobie klasę:

 

interface ISubstance // interfejs zawierajacy zadeklarowane metody różnych typów
{
    void Zazyj();
}

// klasa dziedziczaca po tym interfejsie
class Ketonal : ISubstance
{
     public void Zazyj()
        {
            Console.WriteLine("Środek przeciwbólowy");
        }
}

// klasa dziedziczaca po tym interfejsie
class Insulina : ISubstance
{
     public void Zazyj()
        {
            Console.WriteLine("Transportuje środek przeciwbólowy");
        }
}

// klasa czlowiek
class Human
{
private ISubstance _substancja; // zawiera prywatna wlasciwość typu ISubstance

public Human(ISubstance sub) // wstrzykiwanie przez konstruktor
{
   this._substancja = sub;
}

public void Przyswajaj()
        {
            this._substancja.Zazyj();
        }

        public void ZmienSubstancje(ISubstance sub)
        {
            this._substancja = sub;
        }
}

class Program
    {
        static void Main(string[] args)
        {
            Human human = new Human(new Ketonal());
            human.Przyswajaj();
            human.ZmienSubstancje(new Insulina());
            human.Przyswajaj();

            Console.ReadLine();
        }
    }

Czyli rozumiem, że zamiast tworzyć obiektu, to w momencie tworzenia obiektu klasy Human wywołujemy new z innej klasy dziedziczącej po interfejsie ISubstance i wtedy mamy dostęp do metod zawartych w tym interfejsie?



A jak się ma DI do IoC? Bo rozumiem, że wstrzykiwanie to nie to samo co odwrócenie sterowania?
Jak to DI można fajnie pokazać w ASP .NET MVC? Jak to połączyć z Autofaciem?

Wiem że pytam dużo :) Będę wdzięczny za odpowiedź :)

0

Czyli rozumiem, że zamiast tworzyć obiektu, to w momencie tworzenia obiektu klasy Human wywołujemy new z innej klasy dziedziczącej po interfejsie ISubstance i wtedy mamy dostęp do metod zawartych w tym interfejsie?

Nie. Chodzi o to, że piszesz sobie:

var human = container.Resolve<Human>();

i dostajesz gotowy obiekt, ze wszystkimi jego zależnościami, w tym przykładzie z Ketonalem.

DI jest rodzajem IoC, wstrzykiwanie jest rodzajem odwrócenia sterowania.

0

Jako, że ten temat wyskakuje jako pierwszy (zaraz po MSDN) w googlach przy zapytaniu o wstrzykiwanie zależności. Zapodam linka do filmu na YT który moim zdaniem doskonale tłumaczy Dependency Injection (na podstawie MVC Core).
Link do YT
Myślę, że wielu się przyda. A kto wie, może OP będzie chciał jeszcze się zapoznać.
Odkopuję, bo sam zacząłem niedawno zgłębiać temat. Mam nadzieję, że nie dostanę za to bana :D

4

Generalnie sprawa jest dość prosta.

Kiedyś kod wyglądał mniej więcej tak:

class MojaKlasa
{
    InnaKlasa ik1;
    JeszczeInnaKlasa ik2;

   public void Foo()
   {
      ik1.Bar();
      ik2.FooBar();
   }
}

Przy czym konstruktor mógł wyglądać tak:

ik1 = new InnaKlasa(WebApiClient.Get());
ik2 = new JeszczeInnaKlasa();

W pewnym momencie okazało się, że takie programowanie powoduje pewne problemy, np:

  1. Klasa MojaKlasa jest ściśle uzależniona od klas InnaKlasa i JeszczeInnaKlasa
  2. Ten kod jest całkowicie nietestowalny.

Idąc krok dalej, mamy np. fabrykę albo wzorzec strategia, np:

public interface IDzialanie
{
    int Dzialaj(int x, int y);
}

class Dodawanie: IDzialanie
{
    public int Dzialaj(int x, int y)
    {
        return x + y;
    }
}

class Odejmowanie: IDzialanie
{
    public int Dzialaj(int x, int y)
    {
        return x - y;
    }
}

Teraz jeśli mamy klasę wykonującą jakieś obliczenia, to może być ona zależna nie tyle od innych klas, co od interfejsów, np:

class MojaKlasa
{
    IDzialanie dzialanie;
    public MojaKlasa(IDzialanie dzialanie)
    {
        this.dzialanie = dzialanie;
    }

    public int Foo()
    {
        int x = WezSkadsJakiegosX();
        int y = WezSkadsJakiesY();
        return dzialanie.Dzialaj(x, y); 
    }
}

Teraz plus jest taki, że możesz mieć JAKIEKOLWIEK działanie operujące w tym przypadku na dwóch liczbach i nie musisz nic zmieniać w klasie MojaKlasa! Super! Za rok ktoś może dorobi logarytm, czy coś innego i nawet nie będzie musiał patrzyć na Twoją klasę, a ona i tak będzie działać z logarytmem. Ale to nie wszystko... Można ją testować. Teraz weź pod uwagę, że metoda Foo wygląda jakoś inaczej:

    public int Foo()
    {
        int x = WezSkadsJakiegosX();
        int y = WezSkadsJakiesY();
        int wynik = dzialanie.Dzialaj(x, y); 
        return jakisWspolczynnik / wynik;
    }

Możesz teraz napisać testy takie, że:

class TestDzialanieZero: IDzialanie
{
    public int Dzialaj(int x, int y)
    {
        return 0;
    }
}

To daje Ci tyle, że możesz sprawdzić, jak Twoja klasa zachowa się w przypadku, gdy z obiektu dzialanie dostanie rezultat 0. Jak widać było to potrzebne, bo program się wywali. A więc już wiesz, że musisz tam zrobić jakieś zabezpieczenie...

Idźmy o krok dalej. Mamy klasę, która działa z jakimś WebApi, np:

class Pracownicy
{
    WebApi api = new WebApi("https://example.com/api/");
    public IEnumerable<Pracownik>()
    {
         return api.PobierzPracownikow();
    }
}

Jak widać klasa pobiera pracowników z jakiegoś WebApi. Ale tu pojawiają się te dwa problemy, co na początku - nie można jej testować (a co jeśli po pobraniu pracowników klasa coś z nimi jeszcze robi?); no i klasa jest ściśle uzależniona od konkretnej implementacji WebApi - jest ścliśle uzależniona od konkretnej klasy. Co jest głównym problemem.

No to znowu robimy to, czego się już nauczyliśmy. Tworzymy interfejs IWebApi z metodą PobierzPracowników. Do klasy Pracownicy w jakiś sposób wstrzykujemy to. Najpewniej w metodzie main:

public void Main()
{
    IWebApi api = new WebApi("https...");
    Pracownicy p = new Pracownicy(api);
}

I potem już tylko w jakiś sposób gdzieś w programie pobieramy tych pracowników. Potem w testach robimy podobnie:

public void MetodaTestujaca()
{
    IWebApi api = new FakeApi("siema");
    //teraz jakaś konfiguracja tego fake'owego api, np. żeby zwracał konkretnych na stałe wprowadzonych pracowników - niezależnie od WebApi
    //no i potem
    Pracownicy p = new Pracownicy(api);
    Assert.AreEqual(1, p.PobierzPracownikow().Count());
}

(to oczywiście pewne uproszczenie).

I teraz pojawiają się kolejne dwa problemy... Głupie problemy...

  1. Za każdym razem trzeba tworzyć FakeoweApi
  2. Nadal nie wiadomo jak w dobry sposób w programie pobrać obiekt klasy Pracownicy. A może za każdym razem go tworzyć? Ale wtedy skąd pobrać obiekt WebApi?

Więc powstał kontener IoC... To w skrócie działa tak (pseudokod):

public void Main()
{
   IOCContainer ioc = new IOCContainer();
   ioc.Register<IWebApi, WebApi>("http://...");
   ioc.Register<Pracownicy>();
}

I teraz z dowolnego miejsca w programie możesz zrobić coś takiego:

class JeszczeInnaKlasa
{
    IWebApi api;
    Pracownicy p;

    public JeszczeInnaKlasa(IWebApi api, Pracownicy p)
    {
      //wiesz, co dalej
    }
}

I w dowolnym miejscu możesz zażądać od swojego kontenera ioc tych rzeczy, np:

Pracownicy p = ioc.Resolve<Pracownicy>();
IWebApi api = ioc.Resolve<IWebApi>();
JeszczeInnaKlasa = ioc.Resolve<JeszczeInnaKlasa>();

Tutaj uwaga - nie wpadnij w antywzorzec ServiceLocator (do poczytania).

Jaki jest tego plus? Kontener IOC dokładnie zna zalezżości między Twoimi klasami. Tzn., że jeśli prosisz o obiekt typu JeszczeInnaKlasa, to kontener wie, że on jest zależny od obiektu Pracownicy, który jest zależny od obiektu implementującego interfejs IWebApi... A co implementuje ten interfejs? Ano tak! Klasa WebApi, którą zarejestrowałeś. Więc kontener utworzy (lub weźmie istniejącą instancę) obiekt WebApi, wstrzyknie go do konstruktora Pracownicy, następnie tak utworzony obiekt klasy Pracownicy wstrzyknie do konstruktora klasy JeszczeInnaKlasa. Doda tam też obiek WebApi. Taki mądry ten kontener.

W rzeczywistości kontenery bywają jeszcze mądrzejsze i można je naprawdę świetnie pokonfigurować. I generalnie w swojej aplikacji nie musisz martwić o to, jak gdzieś utworzyć jakiś obiekt. Możesz po prostu posłużyć się DependencyInjection - tworząc zależność w konstruktorze.

0
Juhas napisał(a):

I w dowolnym miejscu możesz zażądać od swojego kontenera ioc tych rzeczy, np:

Pracownicy p = ioc.Resolve<Pracownicy>();
IWebApi api = ioc.Resolve<IWebApi>();
JeszczeInnaKlasa = ioc.Resolve<JeszczeInnaKlasa>();

Tutaj uwaga - nie wpadnij w antywzorzec ServiceLocator (do poczytania).

Tutaj uwaga - ServiceLocator właściwie wygląda jak kod powyżej. :)

W rzeczywistości kontenery bywają jeszcze mądrzejsze i można je naprawdę świetnie pokonfigurować. I generalnie w swojej aplikacji nie musisz martwić o to, jak gdzieś utworzyć jakiś obiekt. Możesz po prostu posłużyć się DependencyInjection - tworząc zależność w konstruktorze.

Ja bym jednak podkreślił, że nie każda klasa powinna być zarządzana przez kontener, bo nie każda jest zależnością.
Poza tym mnogość możliwości zaawansowanych kontenerów sprawia, że niektórzy w kodzie konfigurującym kontener ukrywają logikę biznesową. To jest bardzo zła praktyka.

1
somekind napisał(a):
Juhas napisał(a):

I w dowolnym miejscu możesz zażądać od swojego kontenera ioc tych rzeczy, np:

Pracownicy p = ioc.Resolve<Pracownicy>();
IWebApi api = ioc.Resolve<IWebApi>();
JeszczeInnaKlasa = ioc.Resolve<JeszczeInnaKlasa>();

Tutaj uwaga - nie wpadnij w antywzorzec ServiceLocator (do poczytania).

Tutaj uwaga - ServiceLocator właściwie wygląda jak kod powyżej. :)

No chyba, że ten kod jest w Main ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1