czwartek, 16 marca 2017

IoC contener i Umbraco

IoC contener i Umbraco

Bardzo lubię concept Inversion of Controll oraz Dependency Inversion. Ucieszyłem się na wieść, że IoC Contener będzie dostępny w Umboraco 8 “out of the box”, gdyż twórcy stwierdzili, że społeczność jest już na tyle dojarzała, że udźwignie nieco bardziej zaawansowane tworzenie obiektów. ;)

Niestety w wersji 7 Umbraco jeszcze nie mamy IoC Contenera, ale możemy temu zaradzić instalując samemu któryś z kontenerów. Lubię korzystać z StructureMap-a i to właśnie na niego padł mój wybór. Już nie pamiętam dokładnie skąd wziąłem przykład implementacji, ale np. tu jest dostępny opis, jak to zrobić:


Jest też nowa wersja tej biblioteki, która jest przystosowana do wersji 4 StructureMap-a i tej właśnie używam w swoim projekcie.

Dzięki temu, możemy korzystać z odwrócenia zależności (jednej z zasad SOLID-nego programowania) w prosty sposób. Ja używam StructureMapa do wstrzykiwania zależności w konstruktor klas. Można również za jego pomocą wstrzykiwać właściwości klas, ale ja nie jestem fanem tego rozwiązania. Moim zdaniem kod jest bardziej czytelny przy wstrzykiwaniu do konstruktorów.

Co zyskujemy używając „Dependency Inversion” w naszym kodzie za pomocą StructureMap-a? Postaram się to pokazać na podstawie mojego projektu J
W swoim kodzie mam coś takiego jak Buildery do sekcji na stronie. Żeby mieć spójną koncepcję jak wygląda Builder, wydzieliłem abstrakcję do Interfejsu, który każdy Builder musi implementować:

    public interface ISectionBuilder
    {
        string ViewName { get; }
        BaseViewModel CreateViewModel(IPublishedContent content);
        bool DeosApply(string documentAlias);
    }

 ViewName to Nazwa widoku (pliku cshtml) znajdującego się w folderze „Partials”, który ma zostać użyty do zbudowania sekcji.
Metoda DoesApply dostaje w parametrze alias noda aktualnie renderowanego i ma zwrócić czy ten builder się nada, czy też nie.
Ostatnia metoda jest odpowiedzialna za zbudowanie ViewModelu. Bardzo zależało mi na tym, by pozostawić ViewModele w postaci jak najczystszej (docelowo klasa z tylko i wyłącznie  właściwościami)

Dzięki StructureMap mogę zarejestrować wszystkie Buildery w registry:

     For().Use();
     For().Use();
     For().Use();
     For().Use();
     For().Use();
     For().Use();

W następnej kolejności stworzyłem klasę BuildersFactory, która w konstruktorze ma wstrzykiwaną listę interfejsów ISectionBuilder. Pod spodem IoC sprawdzi co zostało zarejestrowane jako implementacja ISectionBuilder i da mi wszystkie te klasy w liście. Dzięki temu, podczas renderowania danej sekcji Fabryka odpala metody DoesApply dla każdego buildera i daje wszystkie te, które potrafią obsłużyć dany node lub pierwszy Builder który się nada (w zależności od wywołanej metody). Sam Builder także posiada swój interfejs, który jest zarejestrowany podobnie jak SectionBuildery.

Jak do tej pory stworzyłem jeden kontroler, który będzie obsługiwał standardowe strony z Umbraco. Każdy Node typu Page będzie renderowany przez ten kontroler:

    public class PageController : RenderMvcController
    {
        private readonly IPageModelExtender _pageModelExtender;
        private readonly ISectionsProvider _sectionsProvider;

        public PageController(ISectionsProvider sectionsProvider, IPageModelExtender pageModelExtender)
        {
            _pageModelExtender = pageModelExtender;
            _sectionsProvider = sectionsProvider;
        }

        public override ActionResult Index(RenderModel model)
        {
            var allSections = model.Content.Children.ToList();
            var listOfSectionsToRender = _sectionsProvider.GetListOfSectionsToRender(allSections);
            var pageViewModel = new PageViewModel {Sections = listOfSectionsToRender};
            return View("Page", _pageModelExtender.ApplyLayoutToModel(pageViewModel, model.Content));
        }
    }

Zasada działania jest dość prosta. Podczas renderowania danego „Page-a”, brane są wszystkie jego dzieci (Nody będące piętro niżej w drzewku). Każde dziecko jest renderowane przez SectionBuilder, który potrafi obsłużyć dany alias. Na samym końcu, gdy mamy już listę ViewModeli oraz nazw widoków, by wyrenderować wewnątrz strony wszystkie sekcje, dokładane są header oraz footer wraz z ewentualnymi zmianami w meta tagach, gdy jest taka potrzeba.

Jak widać sama zasada działania jest prosta, czyli taka jak najbardziej lubię ;) Kolejnym dużym plusem stosowania IoC Contenera i wstrzykiwania zależności są znaczące ułatwienia przy testach jednostkowych. Dzięki takim bibliotekom jak Moq, możemy tworzyć „mocki” obiektów czyli takie wydmuszki, które mają np. zahardkodowaną wartość zwracaną z metod wstrzykniętych klas niezależnie od logiki w ciele tej metody. Dzięki temu możemy wyizolować testy, testować tylko i wyłącznie logikę danej klasy, nie przejmując się że coś pójdzie nie tak w jednej z klas używanej przez testowaną. Pewnie kiedyś szerzej opiszę koncepcję testów jednostkowych wykorzystujących zasadę odwróconej zależności w kodzie.

Uff… Jak do tej pory mój najdłuższy post, ale chciałem wyjaśnić sposób mojej implementacji J Blog nie jest na bieżąco z tym co aktualnie się dzieje w projekcie, jest trochę do tyłu. Obecnie nadal pracuję nad frontendem i rozbudową kolejnych bazowych sekcji w stronie. Mały screen na potwierdzenie, że coś się dzieje:



I to już wszystko na dziś, do następnego razu J

Brak komentarzy:

Prześlij komentarz