Przejdź do głównej zawartości

Dependency Injection w Windows Universal Apps (UWP)

Ostatnio zacząłem pisać aplikację na platformę Windows Universal Apps, która już jakiś czas temu mnie bardzo zaciekawiła. Poza tym jest to okazja aby się trochę rozwinąć i oderwać od świata javy ;). Aplikacja prawdopodobnie pojawi się niedługo na GitHub ale w związku z tym pojawi się pewnie kolejny post.
W trakcie pisania pojawia się jak to zwykle bywa mnóstwo problemów. Staram się rozwiązywać je sukcesywnie, a jedno z tych rozwiązać przedstawię w tym wpisie.

Problem

Okazuje się, że platforma UWP jest na tyle uboga, że brakuje w niej mechanizmów pozwalających na skorzystanie ze wstrzykiwania zależności. Dla mnie jest to jeden z najważniejszych wzorców, który chce mieć wdrożony w niemal każdej aplikacji, którą piszę. W sieci nie ma zbyt wielu materiałów na ten temat, szczególnie takich zrozumiałych dla mnie - programisty mającego mało dotychczas wspólnego z technologiami ze stajni Microsoftu. Znalazłem jednak fajne rozwiązanie, które jest proste i sprawdza się w moim przypadku. Zainspirowane zostało wpisem Dominika Webera na jego blogu.

Rozwiązanie

Aplikacja UWP składa się ze stron [ang. page]. Każda taka strona to jeden widok aplikacji. Celem jest wstrzyknięcie do danej strony wybranych zależności. Zależnościami do tych zależności zajmie się już wybrany kontener IoC. Strony są tworzone przez framework więc nie ma zbytnio możliwości zlecenia tworzenia stron do kontenera. Odpada zatem preferowana przeze mnie możliwość wstrzykiwania przez konstruktor. W takim wypadku jest możliwość wstrzykiwania właściwości [ang. property], którą umożliwia wybrany przeze mnie kontener IoC, o którym póżniej.
Ok, ale żeby wstrzyknąć coś do obiektu, trzeba mieć najpierw do niego referencje.Okazuje się, że najłatwiej o taką referencje z poziomu ramki [ang. frame]. Ramka ta jest tworzona w pliku App.xaml.cs i reprezentuje okno naszej aplikacji:
rootFrame = new Frame();
Okazuje się, że wystarczy rozszerzyć klasę Frame i nadpisać metodę OnContentChanged aby mieć dostęp do właśnie uruchamianej strony. W tym właśnie miejscu postanowiłem wstrzykiwać brakujące zależności do przełączanych stron.

Kontener

Wybrany przeze mnie kontener DI to Autofac. W moim przypadku, cała konfiguracja kontenera znajduje się w konstruktorze ramki, natomiast wykorzystanie go do wstrzyknięcia zależności we wspomnianej wcześniej metodzie OnContentChanged. Poniżej kod gotowej ramki:
public class AutofacFrame : Frame
{
    private readonly IContainer _container;

    public AutofacFrame()
    {
        var containerBuilder = new ContainerBuilder();

        //Dependencies registration
        containerBuilder.RegisterType<VideoLibraryVideoDownloader>()
            .As<IVideoDownloader>()
            .SingleInstance();
        containerBuilder.RegisterType<MediaTranscoderAudioExtractor>()
            .As<IAudioExtractor>()
            .SingleInstance();
        containerBuilder.RegisterType<YouTubeSearchEngine>()
            .As<ISearchEngine>()
            .SingleInstance();
        containerBuilder.RegisterType<SearchResultsVm>();
        containerBuilder.RegisterType<YouTubeSearchEngine>()
            .As<ISearchEngine>()
            .SingleInstance();
        containerBuilder.RegisterType<YouTubeApi>()
            .SingleInstance();
        containerBuilder.RegisterType<HttpClient>()
            .SingleInstance();
        containerBuilder.RegisterType<Settings>()
            .SingleInstance();
        //Dependencies registration

        _container = containerBuilder.Build();
    }

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);
        _container.InjectUnsetProperties(newContent);
    }
}
I tworzenie ramki aplikacji zmieniamy na:
rootFrame = new AutofacFrame();
Metoda InjectUnsetProperties pozwala wstrzyknąć zależności do wszystkich właściwości strony, które mają wartości null. Należy również pamiętać aby zapewnić settery dla tych właściwości. U mnie wygląda to tak:
private Settings _settings;

public Settings Settings
{
    set => _settings = value;
}
Póki co rozwiązanie uważam za spełniające wymagania, a przy tym bardzo proste ;)

Komentarze

Popularne posty z tego bloga

Wzorzec Open Session in View w Spring Boot

Dzisiejszy post będzie z cyklu: "Wtf? Dlaczego to działa?". A dotyczy on pewnego wzorca, którego implementacja jak się okazuje jest we frameworku Spring Boot domyślnie włączona, czego nie wszyscy programiści mogą się spodziewać. Geneza Zaczęło się od tego, że pisałem kolejny test integracyjny do kolejnego kontrolera. Test napisany, uruchamiam, zielono. No i przeglądam sobie jeszcze raz kod zanim wypchnę zmiany do review. I rzuciło mi się w oczy, że zapomniałem dodać adnotacji @Transactional (do tej pory uważałem, że to jest zawsze wymagane). I wtedy zadałem sobie przytoczone na wstępie pytanie. Dlaczego test przeszedł skoro mój serwis dociąga sobie obiekt oznaczony jako LAZY ? Dużo się nagimnastykowałem by wpisać odpowiednie query do Google'a i znaleźć odpowiedź: Open Session in View. Ale o tym za chwilę. Jak działa Hibernate Żeby lepiej zrozumieć opisywane zagadnienie warto przypomnieć jakie kroki w uproszczeniu musi wykonać Hibernate podczas komunikacji z bazą d...

Analiza podatności CVE-2021-22119

W tym wpisie chciałbym przyjrzeć się jednej z podatności bezpieczeństwa, którą odkryłem w jednym z projektów wykorzystując plugin do mavena skanujący zależności - org.owasp:dependency-check-maven . Narzędzie to pomaga wychwycić znane podatności, które dotyczą naszej aplikacji tylko dlatego, że korzystamy z konkretnej wersji zależności (biblioteki, frameworka), która zawiera błąd bezpieczeństwa. Błędy te zaliczają się do kategorii A06 (przed 09.2021 - A09) z listy OWASP TOP 10 ( https://owasp.org/www-project-top-ten/ ). Swoją drogą polecam każdemu przeskanowanie swoich projektów takim skanerem aby zobaczyć jak wiele niebezpieczeńsw pociąga za sobą beztroskie korzystanie z mnóstwa bibliotek. Względne bezpieczeństwo daje nam tylko przypadek spowodowany tym, że akurat nie korzystamy z jakiejś konkretnej funkcjonalności. Tak będzie i w moim przypadku :). CVE-2021-22119 Dokładny opis błędu można znaleźć m.in. tu https://nvd.nist.gov/vuln/detail/CVE-2021-22119 . Dlaczego skupiłem się n...

One-to-one i lazy loading

Hibernate jest frameworkiem, który nie przestaje zaskakiwać. Kryje się w nim mnóstwo tajemnic, pułapek i zagadek. Jedną taką pułapkę postaram się opisać w tym wpisie, a dotyczy ona nieoczywistego na pierwszy rzut oka zachowania adnotacji @OneToOne z parametrem fetchType=LAZY . One-to-one Adnotacja @OneToOne służy w JPA do oznaczania pól encji, które odnoszą się do obiektów będących z nią w relacji jeden-do-jeden. W znormalizowanym schemacie relacyjnej bazy danych oznacza to sytuację, w której mamy do czynienia z dwoma tabelami A oraz B , a jedna z nich posiada kolumnę, której wartości wskazują na klucz identyfikujący krotkę w drugiej z nich. Np: Lazy fetch Adnotacja @OneToOne zawiera również atrybut fetch , który może przyjąć wartości EAGER lub LAZY (domyślnie EAGER ). O ile ustawienie wartości EAGER oznacza, że Hibernate musi pobrać dodatkowy wiersz natychmiast (za pomocą klauzuli JOIN bądź dodatkowego zapytania) to zastosowanie LAZY może ale nie musi spowodowa...