<rss version="2.0"><channel><title>Blogi Cezarego Walenciuka</title><description>Tutaj piszę o przemawianiu i programowaniu</description><generator>Miniblog.Core</generator><link>https://cezarywalenciuk.pl/</link><item><title>Asynchroniczny C# : IAsyncEnumerable</title><link>https://cezarywalenciuk.pl/blog/programing/iasyncenumerable-w-c</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/iasyncenumerable-w-c</guid><pubDate>Sat, 16 Jul 2022 09:46:45 GMT</pubDate><description><![CDATA[<p>[metro]LINQ i IEnumerable&lt;T&gt;. LINQ od .NET 3.5 daje nam wiele możliwości do modyfikowania, filtrowania sekwencji danych.&nbsp;</p>
<p>Co, jednak jeśli tw&oacute;j kod pracuje asynchronicznie. Jak wtedy ustawić te rury LINQ, aby prze-mapować dane lub je przefiltrować.&nbsp;</p>
<p>Z .NET 6 pojawiła się nowy interfejs "IAsyncEnumerable&lt;T&gt;", kt&oacute;re zostały stworzone z myślą o tak zwanych asynchronicznych strumieniach".&nbsp;</p>
<p>W esencji "IAsyncEnumerable&lt;T&gt;&rdquo; działa tak samo, jak&nbsp; IEnumerable&lt;T&gt; tylko każde elementy takiej strumieniowanej kolekcji wychodzą asynchronicznie. Co nie blokuje programu, gdy wyciągniecie każdego elementu ze strumienia może nawet trwać na przykład ponad sekundę.</p>
<p>Postanowiłem odświeżyć wątek asynchronicznego C# ponieważ wierz mi mamy jeszcze wiele temat&oacute;w do om&oacute;wienia.&nbsp; Zobaczmy co potrafi IAsyncEnumerable&lt;T&gt;.</p>
<p>[more]</p>
<h3>Dlaczego IAsyncEnumerable, a nie Task&lt;IEnumerable&lt;T&gt;&gt;</h3>
<p>Wr&oacute;ćmy do podstaw IEnumerable&lt;T&gt;. Gdy jakaś metoda zwraca IEnumerable&lt;T&gt; to znaczy, że dana sekwencja element&oacute;w nie została jeszcze wygenerowana.&nbsp;</p>
<p>Pętla foreach wyciągnie pojedyncze element z tego interfejsu, a operacja "Count()" wymusi przetworzenia całej kolekcji, gdyż inaczej nie można poznać całkowitej liczby element&oacute;w w takiej leniwej kolekcji.</p>
<p>Dodatkowo IEnumerable&lt;T&gt; daje możliwość tworzenia swoich kolekcji, kt&oacute;re są wirtualne albo i nie.</p>
<p>Tutaj przykładowo opakowuje tylko listę element&oacute;w.</p>
<pre class="language-csharp"><code>class MyObjects : IEnumerable&lt;MyObject&gt;
{
    List&lt;MyObject&gt; mylist = new List&lt;MyObject&gt;();

    public MyObject this[int index]  
    {  
        get { return mylist[index]; }  
        set { mylist.Insert(index, value); }  
    } 

    public IEnumerator&lt;MyObject&gt; GetEnumerator()
    {
        return mylist.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}</code></pre>
<p>Przy użyciu słowa kluczowego yield można tworzyć coś, co ja nazywam wirtualnymi kolekcjami.</p>
<p>Kod pochodzi z <a title="yield słowo kluczowe Ułatwienie implementacji IEnumerable" href="https://cezarywalenciuk.pl/blog/programing/yield-sowo-kluczowe--uatwienie-implementacji-ienumerablelttgt" target="_blank" rel="noopener">mojego starego wpisu.</a></p>
<pre class="language-csharp"><code>static public IEnumerable&lt;string&gt; Napisy()
{
    yield return "Ala";
    yield return "ma";
    yield return "radioaktywnego";
    yield return "kota";
    yield return "z";
    yield return "Marsa";
}

static void Main(string[] args)
{
    int i = 0;
    foreach (var item in Napisy())
    {
        i++;
        Console.WriteLine(item);
    }
}</code></pre>
<p>A tutaj przy pomocy słowa kluczowego "yield" tworzę wirtualną kolekcję, kt&oacute;ra jest nieskończona.</p>
<pre class="language-csharp"><code>public static class YieldCollections
{
    public static IEnumerable&lt;int&gt; RandomNumberCollection()
    {
    
        while(true)
        {
            Random r = new Random(Guid.NewGuid()
                .GetHashCode());
    
            int number = r.Next();
    
            yield return number;
        }
    }
}</code></pre>
<p>Ten kod pochodzi <a title="10 funkcjonalności do odświeżenia w C#" href="https://cezarywalenciuk.pl/blog/programing/10-powodow-aby-odswiezyc-swoja-wiedze-na-temat-csharp" target="_blank" rel="noopener">z tego wpisu.</a></p>
<p>Wracajmy jednak do naszego gł&oacute;wnego tematu. Teraz gdy .NET 6 oferuje nam IAsyncEnumerable rodzi się pytanie, kiedy z niego skorzystać.</p>
<p>Może IAsyncEnumerable powinno zastąpić Task&lt;List&lt;T&gt;&gt; albo Task&lt;IEnumerable&lt;T&gt;&gt;?</p>
<p>A jeśli tak to dlaczego?</p>
<p>Moim zdaniem korzystanie z Task, gdy wykonujesz jakąś operację asynchroniczną per iteracje dla danej kolekcji jest w porządku.</p>
<p>Task też super się spełnia, gdy wiemy, że chcemy zwr&oacute;cić jakąś kolekcję z g&oacute;ry kt&oacute;ra powstała w wyniku operacji asynchronicznej.&nbsp;</p>
<p>Oto przykład takie scenariusza. Pobieramy listę tekst&oacute;w z danego REST API, kt&oacute;ry zwr&oacute;ci tę listę tekst&oacute;w w formacie JSON. Oczywiście ta operacja jest asynchroniczna.</p>
<pre><code class="hljs cs ">async Task&lt;List&lt;string&gt;&gt; GetTexts()
{
    using var client = new HttpClient();

    var texts = await client.
        GetFromJsonAsync&lt;List&lt;string&gt;&gt;
        ("/api/texts");

    return texts;
}
</code></pre>
<p>Oto uruchomienie tej metody</p>
<pre><code class="hljs cs ">await GetTexts();</code></pre>
<p>IEnumerable&lt;string&gt; wydaje się bezsensowny. Iteracja asynchroniczna oczywiście nie zajdzie. Sam Task powinien nam zwr&oacute;cić kompletną kolekcję i opakowanie tego w IEnumerable jest tylko iluzją&nbsp;</p>
<pre><code class="hljs cs ">async Task&lt;IEnumerable&lt;string&gt;&gt; GetTexts2()
{
    using var client = new HttpClient();
    var ids = new int[] { 11, 22, 33 };
    foreach (var id in ids)
    {
        var text = await client.
        GetFromJsonAsync&lt;string&gt;
            ($"/api/customers?id={id}");

        yield return text;
    }
}</code></pre>
<p>Na szczęście zachowanie leniwego pobierania danych asynchronicznie można osiągnąć w takim spos&oacute;b.</p>
<pre><code class="hljs cs ">async IAsyncEnumerable&lt;string&gt; GetTexts3()
{
    using var client = new HttpClient();

    var ids = new int[] { 11, 22, 33, 44,
        55, 66, 77 ,88, 99 };

    foreach (var id in ids)
    {
        var text = await client.
            GetFromJsonAsync&lt;string&gt;
            ($"/api/texts?id={id}");

        yield return text;
    }
}</code></pre>
<p>Jak każdą technikę asynchroniczną tak i IAsyncEnumerable można zepsuć. Ten przykład poniżej jest jeszcze okej. Co, jeśli jednak "nazwa gry" nie przekazywana, a jest wynikiem działania jeszcze innej metody asynchronicznej.&nbsp;</p>
<pre><code class="hljs cs ">async IAsyncEnumerable&lt;string&gt; GetTexts4(string game)
{
    using var client = new HttpClient();

    var ids = new int[] { 11, 22, 33 };

    foreach (var id in ids)
    {
        var text = await client.
            GetFromJsonAsync&lt;string&gt;
            ($"/api/texts?id={id}&amp;game={game}");

        yield return text;
    }
}
</code></pre>
<p>Oto przykład takiej metody asynchronicznej.</p>
<pre><code class="hljs cs ">async Task&lt;string&gt; GetGame()
{
    await Task.Delay(4000);
    return "Mortal Kombat 2";
}</code></pre>
<p>Jak to można zrobić źle? Opakowanie IAsyncEnumerable w Task psuje cały mechanizm strumieniowania.</p>
<pre><code class="hljs cs ">//BAD IDEA
async Task&lt;IAsyncEnumerable&lt;string&gt;&gt; GetTextsFromGame()
{
    var game = await GetGame();
    return GetTexts4(game);
}</code></pre>
<p>Lepiej jest to zrobić tak. Pobrać asynchronicznie wszystkie potrzebne parametry, a potem zrobić await foreach.</p>
<pre><code class="hljs cs ">//GOOD IDEA
async IAsyncEnumerable&lt;string&gt; GetTexts5()
{
    var game = await GetGame();
    await foreach (var c in GetTexts4(game))
    {
        yield return c;
    }
}
</code></pre>
<p>W ten spos&oacute;b możemy skorzystać z "IAsyncEnumerable"</p>
<h3>IAsyncEnumerable, a LINQ</h3>
<p>Używanie LINQ na leniwych kolekcjach strumieniowych jest miejscami dziwne. Zobaczmy, o co chodzi. Oto przykład metody, kt&oacute;ra zwr&oacute;ci mi listę folder&oacute;w.</p>
<pre><code class="hljs cs ">async IAsyncEnumerable&lt;string&gt; GetFolders()
{
    List&lt;string&gt; folders = new List&lt;string&gt;()
    {
        @"D:\__zdjecia",
        @"D:\_smieciedoanalizy",
        @"D:\----------------",
        @"D:\00",
        @"D:\0Prezentacja",
        @"D:\Camera",
    };

    foreach (var item in folders)
    {
        await Task.Delay(4000);
        yield return item;
    }
}</code></pre>
<p>Oto inna metoda, kt&oacute;ra zwr&oacute;ci mi listę informacji o plikach w danym folderze.</p>
<pre><code class="hljs cs ">async IAsyncEnumerable&lt;FileInfo&gt; GetFilesInfoAsync(string folder)
{
    var directory = new DirectoryInfo(folder);

    foreach (var item in directory.
           GetFiles("*", SearchOption.TopDirectoryOnly))
    {
        await Task.Delay(100);
        yield return item;
    }

}</code></pre>
<p>Oto podobna metoda do poprzedniej ta tylko zwraca Taska, a nie IAsyncEnumerable.</p>
<pre><code class="hljs cs ">async Task&lt;FileInfo[]&gt; GetFilesInfoTaskAsync(string folder)
{
    await Task.Delay(100);
    var directory = new DirectoryInfo(folder);

    return directory.
           GetFiles("*", SearchOption.TopDirectoryOnly);
}
</code></pre>
<p>Stworzyłem też proste metody, kt&oacute;re sprawdzają, czy dany plik jest podejrzany. Jedna metoda jest asynchroniczna, a druga nie&nbsp;</p>
<pre><code class="hljs cs ">async Task&lt;bool&gt; IsSusAsync(FileInfo file)
{
    await Task.Delay(2000);
    Random r = new Random();

    return r.Next(1, 10) &gt; 8;
}

bool IsSus(FileInfo file)
{
    Random r = new Random();

    return r.Next(1, 10) &gt; 8;
}</code></pre>
<p>Całą kolekcję IAsyncEnumerable można w każdej chwili zamienić na listę korzystając z ToListAsync().</p>
<pre><code class="hljs cs ">List&lt;string&gt; foldersExampleLinqAsync = await GetFolders().ToListAsync();</code></pre>
<p>Alternatywnie można zawsze stworzyć swoją listę używając "await foreach".</p>
<pre><code class="hljs cs ">List&lt;string&gt; folders = new List&lt;string&gt;();
await foreach (var folder in GetFolders())
{
    folders.Add(folder);
}
</code></pre>
<p>To jak to działa z LINQ. Mamy Select z Select, kt&oacute;re wyciągają dane ze dw&oacute;ch strumieni.&nbsp;</p>
<pre><code class="hljs cs ">var collection1_1 = GetFolders()
                    .Select(f =&gt; GetFilesInfoAsync(f))
                    .Select(f =&gt; f.Where(file =&gt; IsSus(file)));</code></pre>
<p>Taki kod jest poprawny i LINQ takie, jakie jest da sobie radę. W końcu select z select robi dwie pętle await foreach pod stołem.</p>
<pre><code class="hljs cs ">await foreach(var fileinfos in collection1_1)
{
    await foreach(var item in fileinfos)
    {
        Console.WriteLine(item.Name);
        Console.Write($" -&gt; {item.Length}\n");
    }

}</code></pre>
<p>Taki kod jest nadal w porządku.</p>
<pre><code class="hljs cs ">var collection1_2 = folders
                    .Select(f =&gt; GetFilesInfoAsync(f))
                    .Select
                    (files =&gt; files.Select
                    (file =&gt;
                        new
                        {
                            IsSus = IsSus(file),
                            Name = file.Name,
                            FullName = file.FullName,
                            Lenght = file.Length,
                        }
                    ));</code></pre>
<p>Czy tu będą problemy? W końcu wewnątrz SELECT-a robimy tym razem operację sprawdzania pliku asynchronicznie.&nbsp;</p>
<pre><code class="hljs cs ">var collection2_2 = folders
                    .Select(f =&gt; GetFilesInfoAsync(f))
                    .Select
                    (files =&gt; files.Select
                    (async file =&gt;
                        new
                        {
                            IsSus = await IsSusAsync(file),
                            Name = file.Name,
                            FullName = file.FullName,
                            Lenght = file.Length,
                        }
                    ));</code></pre>
<p>Taki kod spokojnie się skompiluje i zadziała.&nbsp;</p>
<p>Problemy zaczynają się, gdy pr&oacute;bujemy tę asynchroniczną metodę wykonać wewnątrz wyrażenia Where.</p>
<pre><code class="hljs cs ">var collection2_1 = folders
                    .Select(f =&gt; GetFilesInfoAsync(f))
                    .Select(f =&gt;
                    f.Where(async file =&gt; await IsSusAsync(file)));</code></pre>
<p>Co wtedy nam pozostaje? Zamienić metodę asynchroniczną na synchroniczną blokującą poprzez właściwość Result. W akcje desperacji możemy się podjąć takiej akcji.</p>
<pre><code class="hljs cs ">var collection2_1_1 = folders
                    .Select(f =&gt; GetFilesInfoAsync(f))
                    .Select(f =&gt; f.Where(file =&gt; IsSusAsync(file).Result));

var collection2_2_1 = folders
                    .Select(f =&gt; new DirectoryInfo(f).
                    GetFiles("*", SearchOption.TopDirectoryOnly))
                    .Select
                    (files =&gt; files.Select
                    (file =&gt; 
                        new 
                        {
                            IsSus = IsSusAsync(file).Result,
                            Name = file.Name,
                            FullName = file.FullName,
                            Lenght = file.Length,
                        }
                    ));</code></pre>
<p>A jak to możemy zrobić lepiej? Wypadałoby napisać swoją metodę WhereAwait(), kt&oacute;ra by nie miała problem&oacute;w z operacjami "async i await" wewnątrz wyrażenia lambda</p>
<p>Tylko po co coś takiego pisać, gdy zapewne jest na to paczka NuGet.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/iasyncenumerable-w-c/paczka_638059245064717959.PNG" alt="Paczka Nuget System.LINQ.Async" width="711" height="70" /></p>
<p>Oto przykład działania metody WhereAwait(). Dzięki niej cała ta operacja nadal jest asynchronicznym strumieniem.</p>
<pre><code class="hljs cs ">IAsyncEnumerable&lt;string&gt; folders = GetFolders();

var collection3_1 = folders
                    .Select( f =&gt; GetFilesInfoAsync(f))
                    .Select(f =&gt; f.WhereAwait
                    (async file =&gt; await IsSusAsync(file)));

await foreach (var fileinfos in collection3_1)
{
    await foreach (var item in fileinfos)
    {
        Console.WriteLine(item.Name);
        Console.Write($" -&gt; {item.Length}\n");
    }
}</code></pre>
<p>Paczka ta także oferuję metodę SelectAwait. Ona na tym etapie skrystalizuje nas asynchroniczny strumień.</p>
<pre><code class="hljs cs ">var collection3_2 = folders
                    .SelectAwait(async f =&gt; await GetFilesInfoAsync2(f))
                    .Select(f =&gt; f.WhereAwait
                    (async file =&gt; await IsSusAsync(file)));</code></pre>
<p>Ta sama paczka NuGet ma metodę "ToAsyncEnumerable()" , kt&oacute;ra potrafi zamieniać normalne listy na kolekcję IAsyncEnumerable.&nbsp;</p>
<p>Kiedy taka konwersja ma sens, o tym w następnym przykładzie.</p>
<pre><code class="hljs cs ">var collection3_3 = folders
                    .SelectAwait(async f =&gt; await GetFilesInfoAsync2(f))
                    .Select(f =&gt; f.ToAsyncEnumerable().WhereAwait
                    (async file =&gt; await IsSusAsync(file)));</code></pre>
<h3>Asynchronicznie sekwencyjnie, asynchronicznie wsp&oacute;łbieżnie</h3>
<p>Do następnego przykładu stworzyłem sobie listę stron internetowych.&nbsp;</p>
<pre><code class="hljs cs ">var urls = new List&lt;string&gt;()
{
    "https://cezarywalenciuk.pl/",
    "https://docs.microsoft.com/pl-pl/aspnet/core/blazor/",
    "https://angular.io/",
    "https://pl.reactjs.org/",
    "https://vuejs.org/",
    "https://svelte.dev/",
    "https://www.javascript.com/",
    "https://www.youtube.com/",
    "https://azure.microsoft.com/",
    "https://www.amazon.pl/",
    "https://zetcode.com/csharp/httpclient/",
    "https://stackoverflow.com/questions/55686928/using-stopwatch-in-c-sharp",
    "https://www.programmingwithwolfgang.com/replace-rabbitmq-azure-service-bus-queue/",
    "https://medium.com/dotnet-hub/use-azure-key-vault-with-net-or-asp-net-core-applications-read-azure-key-vault-secret-in-dotnet-fca293e9fbb3",
    "https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/attribute-mapping.html",
    "https://www.nuget.org/packages/System.Linq.Async",
    "https://github.com/dotnet/reactive",
    "https://www.udemy.com/",
    "https://jquery.com/",
    "https://www.php.net/",
    "https://www.python.org/",
    "https://go.dev/",
    "https://docs.microsoft.com/pl-pl/dotnet/csharp/"

};</code></pre>
<p>Moim zadaniem jest pobrać zawartość każdej strony internetowej z tej listy po to, aby m&oacute;gł sprawdzić potem, czy te strony internetowe zawierają słowo podane przez użytkownika.</p>
<p>Oto rozwiązania numer 1 przy użyciu IAsyncEnumerable. Jak widzisz w tym przypadku użycie "ToAsyncEnumerable()" ma sens. Przy pomocy tej metody z Linq.Async zamieniam listę stron na asynchroniczny strumień, kt&oacute;rego każdy elementy będzie wykonywał metodę GetStringAsync z klasy HttpClient.</p>
<pre><code class="hljs cs ">async Task SolutionOne()
{
    using var client = new HttpClient();
    Console.WriteLine();
    Console.WriteLine("Wpisz szukane słowo");
    var word = Console.ReadLine();
    if (string.IsNullOrEmpty(word))
        return;

    var timer = new Stopwatch(); timer.Start();

    var results = urls.ToAsyncEnumerable()
            .SelectAwait(async url =&gt;
                new {
                    Url = url,
                    Html = await client.GetStringAsync(url)
                })
            .Where(x =&gt; x.Html.Contains(word));


    await foreach (var result in results)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"Znalezione {result.Url}");
        Console.ResetColor();
    }

    timer.Stop();
    Console.WriteLine(timer.ElapsedMilliseconds);
}</code></pre>
<p>Jaki jest problem z tym rozwiązaniem. Specjalnie napisałem taki przykłady, aby pokazać, że IAsyncEnumerable nie w każdym scenariuszu jest użyteczne.</p>
<p>Strumień IAsyncEnumerable jest sekwencyjni. Znaczy to, że każda operacja asynchroniczna zapisana w taki spos&oacute;b czeka na poprzednią.</p>
<p>Być może gdybyśmy szukali pierwszej lepszej strony internetowej gdzie pada dane słowo wtedy takie rozwiązanie miało sens. Przerywalibyśmy wtedy sekwencje.</p>
<p>My jednak wiemy, że pobierzemy zawsze wszystkie strony internetowe. Jeśli zależy nam na prędkości rozwiązania to oczywiście te strony chcemy pobrać wsp&oacute;łbieżnie, czyli r&oacute;wnocześnie.</p>
<p>Oto przykład rozwiązania tego samego zadania, ale przy użyciu Task.WhenAll&nbsp;</p>
<pre><code class="hljs cs ">//Przetwarzanie sekwencji r&oacute;wnolegle
async Task SolutionTwo()
{
    using var client = new HttpClient();
    Console.WriteLine();
    Console.WriteLine("Wpisz szukane słowo");
    var word = Console.ReadLine();
    if (string.IsNullOrEmpty(word))
    return;

    var tasks = urls
        .Select(async url =&gt; new
        {
            Url = url,
            Html = await client.GetStringAsync(url)
        });

    var timer = new Stopwatch(); timer.Start();

    var results2 = await Task.WhenAll(tasks);

    foreach (var result in results2.Where(x =&gt; 
        x.Html.Contains(word)))
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"Znalezione {result.Url}");
        Console.ResetColor();
    }

    timer.Stop();
    Console.WriteLine(timer.ElapsedMilliseconds);

}</code></pre>
<p>Alternatywnie możemy skorzystać z ForEachAsync.</p>
<pre><code class="hljs cs ">//R&oacute;wnolegle
async Task SolutionThree()
{
    using var client = new HttpClient();
    Console.WriteLine();
    Console.WriteLine("Wpisz szukane słowo");
    var word = Console.ReadLine();
    if (string.IsNullOrEmpty(word))
        return;

    var parallelOptions = new ParallelOptions() 
    { MaxDegreeOfParallelism = 4 };

    var timer = new Stopwatch(); timer.Start();

    await Parallel.ForEachAsync(urls, parallelOptions,
                async (url, ct) 
                =&gt; await FindMatch(url, word, client));

    timer.Stop();
    Console.WriteLine(timer.ElapsedMilliseconds);
}

async Task FindMatch(string url, string searchTerm, HttpClient client)
{
    var html = await client.GetStringAsync(url);
    if (html.Contains(searchTerm))
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"Znalezione {url}");
        Console.ResetColor();
    }
}</code></pre>
<p>Co jest najszybsze? Na pewno nie "IAsyncEnumerable" po jest ono sekwencyjnie.&nbsp;</p>
<p>Zostają więc dwaj kandydaci Task.WhenAll i Parallel.ForEachAsync.&nbsp;</p>
<pre><code class="hljs cs ">while (true)
{
    var options = Console.ReadKey();

    if (options.KeyChar == '1')
        await SolutionOne();
    else if (options.KeyChar == '2')
        await SolutionTwo();
    else if (options.KeyChar == '3')
        await SolutionThree();
}
</code></pre>
<p>W moich testach wychodzi, że to Task.WhenAll jest szybsze. Jednakże warto zaznaczyć, że operacja pobierania strony nie jest operacją deterministyczną i z r&oacute;żnych losowych przyczyn może ona raz zadziałać szybciej lub wolniej.</p>
<p>To wszystko, co musisz wiedzieć na temat IAsyncEnumerable.</p>
<p>&nbsp;</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category></item><item><title>Blazor i Uwierzytelnienie z JSON Web Tokenem</title><link>https://cezarywalenciuk.pl/blog/programing/blazor-i-uwierzytelnienie-z-json-web-tokenem</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/blazor-i-uwierzytelnienie-z-json-web-tokenem</guid><pubDate>Tue, 12 Apr 2022 12:36:00 GMT</pubDate><description><![CDATA[<p>[metro] W poprzednim wpisie stworzyliśmy REST API z ASP.NET CORE w .NET 5/6 wraz z możliwością logowania użytkowników przez JSON WEB Tokeny.</p>
<p>Jeśli interesuje Cię ta część to proszę spójrz na ten <a href="https://cezarywalenciuk.pl/blog/programing/uwierzytelnienie-z-json-web-token-cebula-z-aspnet-core-swagger" target="_blank" title="Uwierzytelnienie z JSON Web Token, Cebula z ASP.NET CORE, Swagger" rel="noopener">wpis</a>.</p>
<p>Nasz projekt wygląda tak:[more]</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://cezarywalenciuk.pl/Posts/programing/files/2021/json-web-token-i-samuraje-cebula-z-aspnet-core--swagger-ui-i-blazor/z-11_637589995575461025.PNG" alt="Dodajemy Autoryzację: Samurai.Application.Security w Visual Studio"></p>
<p>Teraz zobaczymy jak obsłużyć REST API z JWT służące nam do uwierzytelnienia w aplikacji Blazor. </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-pr_637853723849045706.PNG" alt="Visual Studio Blazor UI Warstwa" width="292" height="397"></p>
<h3>Jakie paczki NuGet ?</h3>
<p>Od czego tutaj zacząć ? Otóż do nowego projektu trzeba dodać pewne paczki NuGet.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-blazor1_637854270704968734.PNG" alt="blazor webassembly" width="507" height="104"></p>
<p>Na pewno będziemy potrzebowali paczki, która będzie nam deserializowała JSON z odpowiedzi HTTP. Klasy, które później wygenerujemy będą korzystać z tej paczki</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-b04_637853737872176756.PNG" alt="Newtonsoft.Json paczka NuGet" width="420" height="66"></p>
<p>Będziemy także potrzebować paczki "Microsoft.Extensions.Http", aby mieć większą kontrolę nad tworzeniem klasy HTTPClient.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-b04_637853737872100226.PNG" alt="Microsoft.Extensions.Http paczka NuGet" width="780" height="79"></p>
<p>Skoro używamy autoryzacji to potrzebujemy paczki "Microsoft.AspNetCore.Authorization", która została stworzona z myślą o Blazor.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-b04_637853737872131009.PNG" alt="Microsoft.AspNetCore.Authorization paczka NuGet" width="520" height="69"></p>
<p>Przejdźmy do naszych klas modeli.</p>
<h3>ViewModels i Mapowanie</h3>
<p>Jak pamiętasz z poprzedniego przykładu nasze REST API nie jest bogate w klasy. To dobrze, ponieważ nie musimy do nich tworzyć klasy "ViewModel".</p>
<p>O co chodzi? Otóż, aby moje projekty były bardziej elastyczne i niezależne od siebie to chce, aby każda obiekt, który wyjdzie z jako odpowiedź z REST API miał swoją klasę przeznaczoną do wyświetlania tych danych tylko po stronie widoku.</p>
<p>Jeśli coś zmienię w REST API np. definicję Warrior to szanse, że coś mi eksploduje po stronie UI Blazor będą dużo bardziej zmniejszone.</p>
<p>Oto wszystkie nasze ViewModele. Jak widzisz po stronie widoku będę chciał wyświetlić Użytkownika, przekazać dane do logowania oraz wyświetlić moich wojowników.</p>
<pre><code class="hljs cs">public class LoginBlazorVM
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public string Token { get; set; }
}

public class WarriorViewModel
{
    public string Name { get; set; }
    public string ImageUrl { get; set; }
    public int Age { get; set; }
}</code></pre>
<p>Projekt jest mały, ale możemy skorzystać z AutoMapper, który automatycznie nam z mapuje nam obiekty klas, które wyjdą z REST API na nasze ViewModele.</p>
<p>Więcej o AutoMapper pisałem <a href="https://cezarywalenciuk.pl/blog/programing/automapper-z-aspnet-core" target="_blank" title="https://cezarywalenciuk.pl/blog/programing/automapper-z-aspnet-core" rel="noopener">tutaj</a></p>
<p>Instalujemy odpowiednie paczki NuGet</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-b095_637853737872309792.PNG" alt="paczka Nuget AutoMapper" width="614" height="151"></p>
<p>Tworzymy definicję mapowania naszego wojownika w obie strony.</p>
<pre><code class="hljs cs">public MappingProfile()
{
    CreateMap&lt;WarriorViewModel, Warrior&gt;().ReverseMap();
}</code></pre>
<p>A na końcu do klasy Program.cs dodajemy rejestracje naszego AutoMappera.</p>
<pre><code class="hljs cs">public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add&lt;App&gt;("#app");
    
    //builder.Services.AddScoped(sp =&gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());</code></pre>
<p>Teraz gdy to mamy możemy przejść do najtrudniejszej części. Czyli do tego, jak te dane będziemy pobierać z REST API.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-pro1_637854270705111586.PNG" alt="Visual Studio projekt Blazor" width="224" height="358"></p>
<h3>A może tak wygenerować sobie metody do HTTPClient</h3>
<p>Gdy pisałem ten projekt na potrzeby prelekcji na Warszawskie Dni Informatyki naprawdę nie chciało mi się ręcznie pisać wywołań HTTPClinet do mojego REST API w aplikacji Blazor.</p>
<p>Na szczęście możemy wygenerować taki kod, ponieważ nasze REST API używa Swagger do dokumentacji i testu.</p>
<p>Na pomoc przychodzi NSwag narzędzie, które pozwoli nam wygenerować API </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-slajd_637853723849140871.png" alt="Co to jest NSwag ? Generator kodu" width="600" height="338"></p>
<p>Instalator do tego programu znajdziesz tutaj : <a href="https://github.com/RicoSuter/NSwag/wiki/NSwagStudio" target="_blank" rel="noopener">https://github.com/RicoSuter/NSwag/wiki/NSwagStudio</a></p>
<p>Korzystanie z tego narzędzia jest bardzo proste. Podaje w odpowiednim miejscu definicję JSON swojego Swaggera, a po drugiej stronie mogę zobaczyć wygenerowane wywołanie HTTPClient do tych metod w moim REST API</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-AA_637853723849295339.png" alt="NSwagStudio program" width="750" height="469"></p>
<p>Wygenerowana klasa jednak nie wyręcza mnie we wszystkim. Muszę ją trochę przerobić, aby JSON WEB Tokeny działały. Tego fragmentu kodu nie jestem w stanie wygenerować.</p>
<p>Do wygenerowanego interfejsu  IClient muszę dodać właściwość, która wystawi mi do użytku instancje HttpClienta</p>
<pre><code class="hljs cs">public partial interface IClient
{
    System.Net.Http.HttpClient HttpClient { get; }</code></pre>
<p>...</p>
<p>Potem w wygenerowanej klasie muszę w konstruktorze dodać wstrzyknięcie tego HTTPClienta tak, aby miał nad nim większą kontrolę. To jest moja modyfikacja wygenerowanego kodu.</p>
<pre><code class="hljs cs">public partial class Client : IClient
{
    private string _baseUrl = "";
    private System.Net.Http.HttpClient _httpClient;
    private System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt; _settings;
    
    public Client(string baseUrl, System.Net.Http.HttpClient httpClient)
    {
        BaseUrl = baseUrl; 
        _httpClient = httpClient; 
        _settings = new System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt;(CreateSerializerSettings);
    }</code></pre>
<p>Dodatkowo z konstruktora wywalam baseUrl, gdyż tak naprawdę utrudnia mi to operację wstrzykiwania zależności. </p>
<pre><code class="hljs cs">[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.10.8.0 (NJsonSchema v10.3.11.0 (Newtonsoft.Json v12.0.0.0))")]
public partial class Client : IClient
{
    private string _baseUrl = "";
    private System.Net.Http.HttpClient _httpClient;
    private System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt; _settings;
    
    public Client(System.Net.Http.HttpClient httpClient)
    {
    
        _httpClient = httpClient;
        _settings = new System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt;(CreateSerializerSettings);
    }</code></pre>
<p>Cały wygenerowany kod wygląda tak.</p>
<pre><code class="hljs cs">namespace Samurai.UI.Services
{
    using System = global::System;

    [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.10.8.0 (NJsonSchema v10.3.11.0 (Newtonsoft.Json v12.0.0.0))")]
    public partial interface IClient
    {
        System.Net.Http.HttpClient HttpClient { get; }

        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;AuthenticationResponse&gt; AuthenticateAsync(AuthenticationRequest body);

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;AuthenticationResponse&gt; AuthenticateAsync(AuthenticationRequest body, System.Threading.CancellationToken cancellationToken);

        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;RegistrationResponse&gt; RegisterAsync(RegistrationRequest body);

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;RegistrationResponse&gt; RegisterAsync(RegistrationRequest body, System.Threading.CancellationToken cancellationToken);

        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;System.Collections.Generic.ICollection&lt;Warrior&gt;&gt; GetAllSamuraisAsync();

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        System.Threading.Tasks.Task&lt;System.Collections.Generic.ICollection&lt;Warrior&gt;&gt; GetAllSamuraisAsync(System.Threading.CancellationToken cancellationToken);

    }

    [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.10.8.0 (NJsonSchema v10.3.11.0 (Newtonsoft.Json v12.0.0.0))")]
    public partial class Client : IClient
    {
        private string _baseUrl = "";
        private System.Net.Http.HttpClient _httpClient;
        private System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt; _settings;

        public Client(System.Net.Http.HttpClient httpClient)
        {

            _httpClient = httpClient;
            _settings = new System.Lazy&lt;Newtonsoft.Json.JsonSerializerSettings&gt;(CreateSerializerSettings);
        }
        public System.Net.Http.HttpClient HttpClient
        {
            get { return _httpClient; }
        }

        private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
        {
            var settings = new Newtonsoft.Json.JsonSerializerSettings();
            UpdateJsonSerializerSettings(settings);
            return settings;
        }

        public string BaseUrl
        {
            get { return _baseUrl; }
            set { _baseUrl = value; }
        }

        protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }

        partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);


        partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
        partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
        partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public System.Threading.Tasks.Task&lt;AuthenticationResponse&gt; AuthenticateAsync(AuthenticationRequest body)
        {
            return AuthenticateAsync(body, System.Threading.CancellationToken.None);
        }

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public async System.Threading.Tasks.Task&lt;AuthenticationResponse&gt; AuthenticateAsync(AuthenticationRequest body, System.Threading.CancellationToken cancellationToken)
        {
            if (body == null)
                throw new System.ArgumentNullException("body");

            var urlBuilder_ = new System.Text.StringBuilder();
            urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Login/authenticate");

            var client_ = _httpClient;
            var disposeClient_ = false;
            try
            {
                using (var request_ = new System.Net.Http.HttpRequestMessage())
                {
                    var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
                    content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
                    request_.Content = content_;
                    request_.Method = new System.Net.Http.HttpMethod("POST");
                    request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));

                    PrepareRequest(client_, request_, urlBuilder_);

                    var url_ = urlBuilder_.ToString();
                    request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);

                    PrepareRequest(client_, request_, url_);

                    var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
                    var disposeResponse_ = true;
                    try
                    {
                        var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ =&gt; h_.Key, h_ =&gt; h_.Value);
                        if (response_.Content != null &amp;&amp; response_.Content.Headers != null)
                        {
                            foreach (var item_ in response_.Content.Headers)
                                headers_[item_.Key] = item_.Value;
                        }

                        ProcessResponse(client_, response_);

                        var status_ = (int)response_.StatusCode;
                        if (status_ == 200)
                        {
                            var objectResponse_ = await ReadObjectResponseAsync&lt;AuthenticationResponse&gt;(response_, headers_, cancellationToken).ConfigureAwait(false);
                            if (objectResponse_.Object == null)
                            {
                                throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                            }
                            return objectResponse_.Object;
                        }
                        else
                        {
                            var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                            throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                        }
                    }
                    finally
                    {
                        if (disposeResponse_)
                            response_.Dispose();
                    }
                }
            }
            finally
            {
                if (disposeClient_)
                    client_.Dispose();
            }
        }

        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public System.Threading.Tasks.Task&lt;RegistrationResponse&gt; RegisterAsync(RegistrationRequest body)
        {
            return RegisterAsync(body, System.Threading.CancellationToken.None);
        }

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public async System.Threading.Tasks.Task&lt;RegistrationResponse&gt; RegisterAsync(RegistrationRequest body, System.Threading.CancellationToken cancellationToken)
        {
            if (body == null)
                throw new System.ArgumentNullException("body");

            var urlBuilder_ = new System.Text.StringBuilder();
            urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Login/register");

            var client_ = _httpClient;
            var disposeClient_ = false;
            try
            {
                using (var request_ = new System.Net.Http.HttpRequestMessage())
                {
                    var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value));
                    content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
                    request_.Content = content_;
                    request_.Method = new System.Net.Http.HttpMethod("POST");
                    request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));

                    PrepareRequest(client_, request_, urlBuilder_);

                    var url_ = urlBuilder_.ToString();
                    request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);

                    PrepareRequest(client_, request_, url_);

                    var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
                    var disposeResponse_ = true;
                    try
                    {
                        var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ =&gt; h_.Key, h_ =&gt; h_.Value);
                        if (response_.Content != null &amp;&amp; response_.Content.Headers != null)
                        {
                            foreach (var item_ in response_.Content.Headers)
                                headers_[item_.Key] = item_.Value;
                        }

                        ProcessResponse(client_, response_);

                        var status_ = (int)response_.StatusCode;
                        if (status_ == 200)
                        {
                            var objectResponse_ = await ReadObjectResponseAsync&lt;RegistrationResponse&gt;(response_, headers_, cancellationToken).ConfigureAwait(false);
                            if (objectResponse_.Object == null)
                            {
                                throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                            }
                            return objectResponse_.Object;
                        }
                        else
                        {
                            var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                            throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                        }
                    }
                    finally
                    {
                        if (disposeResponse_)
                            response_.Dispose();
                    }
                }
            }
            finally
            {
                if (disposeClient_)
                    client_.Dispose();
            }
        }

        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public System.Threading.Tasks.Task&lt;System.Collections.Generic.ICollection&lt;Warrior&gt;&gt; GetAllSamuraisAsync()
        {
            return GetAllSamuraisAsync(System.Threading.CancellationToken.None);
        }

        /// &lt;param name="cancellationToken"&gt;A cancellation token that can be used by other objects or threads to receive notice of cancellation.&lt;/param&gt;
        /// &lt;returns&gt;Success&lt;/returns&gt;
        /// &lt;exception cref="ApiException"&gt;A server side error occurred.&lt;/exception&gt;
        public async System.Threading.Tasks.Task&lt;System.Collections.Generic.ICollection&lt;Warrior&gt;&gt; GetAllSamuraisAsync(System.Threading.CancellationToken cancellationToken)
        {
            var urlBuilder_ = new System.Text.StringBuilder();
            urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Samurai");

            var client_ = _httpClient;
            var disposeClient_ = false;
            try
            {
                using (var request_ = new System.Net.Http.HttpRequestMessage())
                {
                    request_.Method = new System.Net.Http.HttpMethod("GET");
                    request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain"));

                    PrepareRequest(client_, request_, urlBuilder_);

                    var url_ = urlBuilder_.ToString();
                    request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);

                    PrepareRequest(client_, request_, url_);

                    var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
                    var disposeResponse_ = true;
                    try
                    {
                        var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ =&gt; h_.Key, h_ =&gt; h_.Value);
                        if (response_.Content != null &amp;&amp; response_.Content.Headers != null)
                        {
                            foreach (var item_ in response_.Content.Headers)
                                headers_[item_.Key] = item_.Value;
                        }

                        ProcessResponse(client_, response_);

                        var status_ = (int)response_.StatusCode;
                        if (status_ == 200)
                        {
                            var objectResponse_ = await ReadObjectResponseAsync&lt;System.Collections.Generic.ICollection&lt;Warrior&gt;&gt;(response_, headers_, cancellationToken).ConfigureAwait(false);
                            if (objectResponse_.Object == null)
                            {
                                throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                            }
                            return objectResponse_.Object;
                        }
                        else
                        {
                            var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                            throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                        }
                    }
                    finally
                    {
                        if (disposeResponse_)
                            response_.Dispose();
                    }
                }
            }
            finally
            {
                if (disposeClient_)
                    client_.Dispose();
            }
        }

        protected struct ObjectResponseResult&lt;T&gt;
        {
            public ObjectResponseResult(T responseObject, string responseText)
            {
                this.Object = responseObject;
                this.Text = responseText;
            }

            public T Object { get; }

            public string Text { get; }
        }

        public bool ReadResponseAsString { get; set; }

        protected virtual async System.Threading.Tasks.Task&lt;ObjectResponseResult&lt;T&gt;&gt; ReadObjectResponseAsync&lt;T&gt;(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary&lt;string, System.Collections.Generic.IEnumerable&lt;string&gt;&gt; headers, System.Threading.CancellationToken cancellationToken)
        {
            if (response == null || response.Content == null)
            {
                return new ObjectResponseResult&lt;T&gt;(default(T), string.Empty);
            }

            if (ReadResponseAsString)
            {
                var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                try
                {
                    var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject&lt;T&gt;(responseText, JsonSerializerSettings);
                    return new ObjectResponseResult&lt;T&gt;(typedBody, responseText);
                }
                catch (Newtonsoft.Json.JsonException exception)
                {
                    var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
                    throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
                }
            }
            else
            {
                try
                {
                    using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    using (var streamReader = new System.IO.StreamReader(responseStream))
                    using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
                    {
                        var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
                        var typedBody = serializer.Deserialize&lt;T&gt;(jsonTextReader);
                        return new ObjectResponseResult&lt;T&gt;(typedBody, string.Empty);
                    }
                }
                catch (Newtonsoft.Json.JsonException exception)
                {
                    var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
                    throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
                }
            }
        }

        private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
        {
            if (value == null)
            {
                return "";
            }

            if (value is System.Enum)
            {
                var name = System.Enum.GetName(value.GetType(), value);
                if (name != null)
                {
                    var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
                    if (field != null)
                    {
                        var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
                            as System.Runtime.Serialization.EnumMemberAttribute;
                        if (attribute != null)
                        {
                            return attribute.Value != null ? attribute.Value : name;
                        }
                    }

                    var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
                    return converted == null ? string.Empty : converted;
                }
            }
            else if (value is bool)
            {
                return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
            }
            else if (value is byte[])
            {
                return System.Convert.ToBase64String((byte[])value);
            }
            else if (value.GetType().IsArray)
            {
                var array = System.Linq.Enumerable.OfType&lt;object&gt;((System.Array)value);
                return string.Join(",", System.Linq.Enumerable.Select(array, o =&gt; ConvertToString(o, cultureInfo)));
            }

            var result = System.Convert.ToString(value, cultureInfo);
            return result == null ? "" : result;
        }
    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.3.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class AuthenticationRequest
    {
        [Newtonsoft.Json.JsonProperty("email", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Email { get; set; }

        [Newtonsoft.Json.JsonProperty("password", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Password { get; set; }


    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.3.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class AuthenticationResponse
    {
        [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Id { get; set; }

        [Newtonsoft.Json.JsonProperty("userName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string UserName { get; set; }

        [Newtonsoft.Json.JsonProperty("email", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Email { get; set; }

        [Newtonsoft.Json.JsonProperty("token", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Token { get; set; }


    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.3.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class RegistrationRequest
    {
        [Newtonsoft.Json.JsonProperty("firstName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string FirstName { get; set; }

        [Newtonsoft.Json.JsonProperty("lastName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string LastName { get; set; }

        [Newtonsoft.Json.JsonProperty("email", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Email { get; set; }

        [Newtonsoft.Json.JsonProperty("userName", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string UserName { get; set; }

        [Newtonsoft.Json.JsonProperty("password", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Password { get; set; }


    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.3.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class RegistrationResponse
    {
        [Newtonsoft.Json.JsonProperty("userId", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string UserId { get; set; }


    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.3.11.0 (Newtonsoft.Json v12.0.0.0)")]
    public partial class Warrior
    {
        [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public string Name { get; set; }

        [Newtonsoft.Json.JsonProperty("age", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
        public int Age { get; set; }


    }

    [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.10.8.0 (NJsonSchema v10.3.11.0 (Newtonsoft.Json v12.0.0.0))")]
    public partial class ApiException : System.Exception
    {
        public int StatusCode { get; private set; }

        public string Response { get; private set; }

        public System.Collections.Generic.IReadOnlyDictionary&lt;string, System.Collections.Generic.IEnumerable&lt;string&gt;&gt; Headers { get; private set; }

        public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary&lt;string, System.Collections.Generic.IEnumerable&lt;string&gt;&gt; headers, System.Exception innerException)
            : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length &gt;= 512 ? 512 : response.Length)), innerException)
        {
            StatusCode = statusCode;
            Response = response;
            Headers = headers;
        }

        public override string ToString()
        {
            return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
        }
    }

    [System.CodeDom.Compiler.GeneratedCode("NSwag", "13.10.8.0 (NJsonSchema v10.3.11.0 (Newtonsoft.Json v12.0.0.0))")]
    public partial class ApiException&lt;TResult&gt; : ApiException
    {
        public TResult Result { get; private set; }

        public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary&lt;string, System.Collections.Generic.IEnumerable&lt;string&gt;&gt; headers, TResult result, System.Exception innerException)
            : base(message, statusCode, response, headers, innerException)
        {
            Result = result;
        }
    }

}</code></pre>
<p>W projekcie do pobranie zostawiłem też plik konfiguracyjny dla programu NSwag.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/wygenerowany kod_637853737872217249.PNG" alt="wygenerowany kod w Visual Studio 2022" width="277" height="230"></p>
<p>Tyle, jeśli chodzi o wywołania naszego REST API. Teraz jak będziemy przesyłać JSON WEB Tokeny do naszego REST API?</p>
<h3>Jak będziemy dodawać tokeny do zapytania ?</h3>
<p>Najpierw stworze sobie serwis, którego zadaniem będzie dodać do instancji wygenerowanego IClient i wewnątrz niego HTTPClient token autoryzujący. </p>
<pre><code class="hljs cs">public interface IAddBearerTokenService
{
    Task AddBearerToken(IClient client);
}</code></pre>
<p>Jego implementacja wygląda tak.</p>
<pre><code class="hljs cs">public class AddBearerTokenService : IAddBearerTokenService
{
    private readonly ILocalStorageService _localStorage;


    public AddBearerTokenService(ILocalStorageService localStorage)
    {
        _localStorage = localStorage;
    }

    public async Task AddBearerToken(IClient client)
    {
        if (await _localStorage.ContainKeyAsync("token"))
            client.HttpClient.DefaultRequestHeaders.Authorization
                = new AuthenticationHeaderValue("Bearer", await _localStorage.GetItemAsync&lt;string&gt;("token"));
    }
}</code></pre>
<p>Skąd wzięło się "ILocalStorageService". Otóż ten interfejs pochodzi z paczki NuGet : "Blazored.LocalStorage".</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-nuget_637853737872246468.PNG" alt="Paczka NuGet Blazored.LocalStorage" width="436" height="73"></p>
<p>Blazored.LocalStorage pozwoli nam przechowywać różne dane w trakcie działania aplikacji Blazor w tym właśnie nasz token autoryzujący. Ten token zostanie zapisany w przeglądarce i gdy użytkownik uruchomi naszą aplikację Blazor ponownie to nadal będzie on zalogowany.</p>
<p>Aby użytkownik mógł się wylogować to musi ten token skasować ze swojego lokalnego zapisu w przeglądarce.</p>
<p>[cloud-green Co to jest LocalStorage ?]Zapisane w localStorage obiekty służą do długotrwałego przechowywania danych i pozostają na dysku po zamknięciu przeglądarki.</p>
<p>W przeciwieństwie do cookies, obiekty z localStorage nie są automatycznie odczytywane przez serwer przy połączeniu</p>
<p>Firefox, Chrome i Opera rezerwują 10 MB na domenę, a Internet Explorer 100 MB na wszystkie przechowywane dane, przy czym wartość te można zmienić na maksymalnie 20 GB. Dane przechowywane są jako tablice asocjacyjne, których kluczem i wartością jest łańcuch znaków. </p>
<p>LocalStorage pojawił się wraz ze standardem HTML 5.</p>
<p>[cg]</p>
<p>Pamiętaj, że każda aplikacja internetowa pisana JavaScript także ma dostęp do "localStorage"</p>
<p>Teraz stworzę serwis, który będzie wyciągał moich samurajów wojowników.</p>
<pre><code class="hljs cs">public interface ISamuraiService
{
    Task&lt;List&lt;WarriorViewModel&gt;&gt; GetAllWarriors();
}</code></pre>
<p>Jego implementacja wygląda tak</p>
<pre><code class="hljs cs">public class SamuraiService : ISamuraiService
{
    private readonly IMapper _mapper;
    private IClient _client;
    private IAddBearerTokenService _addBearerTokenService;

    public SamuraiService(IClient client, IMapper mapper,
        IAddBearerTokenService addBearerTokenService)
    {
        _mapper = mapper;
        _client = client;
        _addBearerTokenService = addBearerTokenService;
    }

    public async Task&lt;List&lt;WarriorViewModel&gt;&gt; GetAllWarriors()
    {
        await _addBearerTokenService.AddBearerToken(_client);

        try
        {
            var allPosts = await _client.GetAllSamuraisAsync();
            var mappedPosts = _mapper.Map&lt;ICollection&lt;WarriorViewModel&gt;&gt;(allPosts);
            return mappedPosts.ToList();
        }
        catch (ApiException ex)
        {
            return new List&lt;WarriorViewModel&gt;();
        }
    }
}</code></pre>
<p>Jak widzisz przed wywołaniem odpowiedniej metody z wygenerowanego kodu IClient spróbuje dodać wcześniej token autoryzujący. Jak pamiętasz mój serwis "IAddBearerTokenService" doda ten token do instancji HTTPClient tak, aby mógł on zostać wysłany wraz z zapytaniem HTTP. </p>
<h3>Jak będziemy się logować i pobierać ten token ?</h3>
<p>A jak ten token pobierać ? To za chwilę, bo mamy ważniejsze pytanie.</p>
<p>Jak w Blazor przedstawić użytkownika, aby można było skorzystać z pewnych specjalnych opcji i ról w wewnątrz tej aplikacji. Nie chcemy takie weryfikacji od zera.  Zobaczmy co Blazor nam oferuje.</p>
<p>Do tej zabawy musimy stworzyć swoją implemtnację klasy abstrakcyjnej "AuthenticationStateProvider". Wygląda ona tak:</p>
<pre><code class="hljs cs">public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ILocalStorageService _localStorage;

    public CustomAuthenticationStateProvider(ILocalStorageService localStorage)
    {
        _localStorage = localStorage;
    }

    public override async Task&lt;AuthenticationState&gt; GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync&lt;string&gt;("token");

        if (string.IsNullOrWhiteSpace(savedToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseTokenClaims(savedToken), "jwt")));
    }

    public void SetUserAuthenticated(string email)
    {
        var authUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authUser));
        NotifyAuthenticationStateChanged(authState);
    }

    public void SetUserLoggedOut()
    {
        var anonUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonUser));
        NotifyAuthenticationStateChanged(authState);
    }

    private IEnumerable&lt;Claim&gt; ParseTokenClaims(string jwt)
    {
        var claims = new List&lt;Claim&gt;();
        var payload = jwt.Split('.')[1];
        var jsonBytes = ParseBase64WithoutPadding(payload);
        var keyValuePairs = JsonSerializer.Deserialize&lt;Dictionary&lt;string, object&gt;&gt;(jsonBytes);

        keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

        if (roles != null)
        {
            if (roles.ToString().Trim().StartsWith("["))
            {
                var parsedRoles = JsonSerializer.Deserialize&lt;string[]&gt;(roles.ToString());

                foreach (var parsedRole in parsedRoles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, parsedRole));
                }
            }
            else
            {
                claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
            }

            keyValuePairs.Remove(ClaimTypes.Role);
        }

        claims.AddRange(keyValuePairs.Select(kvp =&gt; new Claim(kvp.Key, kvp.Value.ToString())));

        return claims;
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
        switch (base64.Length % 4)
        {
            case 2: base64 += "=="; break;
            case 3: base64 += "="; break;
        }
        return Convert.FromBase64String(base64);
    }
}</code></pre>
<p>W metodzie przeciążonej "GetAuthenticationStateAsync" zwracam stan zalogowanego użytkownika wraz z jego rolami.</p>
<pre><code class="hljs cs">public override async Task&lt;AuthenticationState&gt; GetAuthenticationStateAsync()
{
    var savedToken = await _localStorage.GetItemAsync&lt;string&gt;("token");

    if (string.IsNullOrWhiteSpace(savedToken))
    {
        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
    }

    return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseTokenClaims(savedToken), "jwt")));
}</code></pre>
<p>Pierwsza metoda pozwoli nam ustawić użytkownika w aplikacji Blazor, gdy go zweryfikujemy.</p>
<pre><code class="hljs cs">public void SetUserAuthenticated(string email)
{
    var authUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
    var authState = Task.FromResult(new AuthenticationState(authUser));
    NotifyAuthenticationStateChanged(authState);
}

public void SetUserLoggedOut()
{
    var anonUser = new ClaimsPrincipal(new ClaimsIdentity());
    var authState = Task.FromResult(new AuthenticationState(anonUser));
    NotifyAuthenticationStateChanged(authState);
}
</code></pre>
<p>Z drugiej metody skorzystamy, gdy będziemy chcieli użytkownika wylogować. Informując przy tym aplikację Blazor, że status się zmienił poprzez wywołanie metody NotifyAuthenticationStateChanged</p>
<p>A jak wygląda parsowanie JSON WEB Tokenu. Token jest zapisany specyficznie w Base64. Potem gdy uzyskam z tego tablice bajtów to mogę to z deserializować na słownik klucz / wartość.</p>
<pre><code class="hljs cs">private IEnumerable&lt;Claim&gt; ParseTokenClaims(string jwt)
{
    var claims = new List&lt;Claim&gt;();
    var payload = jwt.Split('.')[1];
    var jsonBytes = ParseBase64WithoutPadding(payload);
    var keyValuePairs = JsonSerializer.Deserialize&lt;Dictionary&lt;string, object&gt;&gt;(jsonBytes);

    keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

    if (roles != null)
    {
        if (roles.ToString().Trim().StartsWith("["))
        {
            var parsedRoles = JsonSerializer.Deserialize&lt;string[]&gt;(roles.ToString());

            foreach (var parsedRole in parsedRoles)
            {
                claims.Add(new Claim(ClaimTypes.Role, parsedRole));
            }
        }
        else
        {
            claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
        }

        keyValuePairs.Remove(ClaimTypes.Role);
    }

    claims.AddRange(keyValuePairs.Select(kvp =&gt; new Claim(kvp.Key, kvp.Value.ToString())));

    return claims;
}

private byte[] ParseBase64WithoutPadding(string base64)
{
    switch (base64.Length % 4)
    {
        case 2: base64 += "=="; break;
        case 3: base64 += "="; break;
    }
    return Convert.FromBase64String(base64);
}</code></pre>
<p>Tak też wyciągam rolę zapisane w tym JSON WEB Tokenie.</p>
<p>Teraz oddalam się od tej wymaganej klasy przez Blazor i tworzę swój serwis autoryzujący z takimi metodami.</p>
<pre><code class="hljs cs">public interface IBlazorAuthenticationService
{
    Task&lt;bool&gt; Authenticate(string email, string password);
    Task&lt;bool&gt; Register(string firstName, string lastName, string userName, string email, string password);
    Task Logout();
}</code></pre>
<p>Oto jego implementacja.</p>
<pre><code class="hljs cs">public class BlazorAuthenticationService : IBlazorAuthenticationService
{
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    protected readonly ILocalStorageService _localStorage;

    protected IClient _client;

    public BlazorAuthenticationService(IClient client, ILocalStorageService localStorage,
        AuthenticationStateProvider authenticationStateProvider)
    {
        _authenticationStateProvider = authenticationStateProvider;
        _client = client;
        _localStorage = localStorage;
    }

    public async Task&lt;bool&gt; Authenticate(string email, string password)
    {
        try
        {
            AuthenticationRequest authenticationRequest = new AuthenticationRequest() { Email = email, Password = password };
            var authenticationResponse = await _client.AuthenticateAsync(authenticationRequest);

            if (authenticationResponse.Token != string.Empty)
            {
                await _localStorage.SetItemAsync("token", authenticationResponse.Token);
                ((CustomAuthenticationStateProvider)_authenticationStateProvider).SetUserAuthenticated(email);
                _client.HttpClient.DefaultRequestHeaders.Authorization
                    = new AuthenticationHeaderValue("bearer", authenticationResponse.Token);
                return true;
            }
            return false;
        }
        catch
        {
            return false;
        }
    }

    public async Task&lt;bool&gt; Register(string firstName, string lastName, string userName, string email, string password)
    {
        RegistrationRequest registrationRequest = new RegistrationRequest() { FirstName = firstName, LastName = lastName, Email = email, UserName = userName, Password = password };
        var response = await _client.RegisterAsync(registrationRequest);

        if (!string.IsNullOrEmpty(response.UserId))
        {
            return true;
        }
        return false;
    }

    public async Task Logout()
    {
        await _localStorage.RemoveItemAsync("token");
        ((CustomAuthenticationStateProvider)_authenticationStateProvider).SetUserLoggedOut();
        _client.HttpClient.DefaultRequestHeaders.Authorization = null;
    }
}</code></pre>
<p>Jak wyciągamy Token z REST API ?</p>
<p>Korzystam z pewnej wygenerowanej metody "AuthenticateAsync" przez NSwag i potem zapisuje ten token w LocalStorage. </p>
<pre><code class="hljs cs">public async Task&lt;bool&gt; Authenticate(string email, string password)
{
    try
    {
        AuthenticationRequest authenticationRequest = new AuthenticationRequest() { Email = email, Password = password };
        var authenticationResponse = await _client.AuthenticateAsync(authenticationRequest);

        if (authenticationResponse.Token != string.Empty)
        {
            await _localStorage.SetItemAsync("token", authenticationResponse.Token);
            ((CustomAuthenticationStateProvider)_authenticationStateProvider).SetUserAuthenticated(email);
            _client.HttpClient.DefaultRequestHeaders.Authorization
                = new AuthenticationHeaderValue("bearer", authenticationResponse.Token);
            return true;
        }
        return false;
    }
    catch
    {
        return false;
    }
}</code></pre>
<p>Wylogowanie wygląda tak. Usuwam token z LocalStorage, a potem mój CustomAuthenticationStateProvider informuje Blazor o zmianie statusu użytkownika.</p>
<pre><code class="hljs cs">public async Task Logout()
{
    await _localStorage.RemoveItemAsync("token");
    ((CustomAuthenticationStateProvider)_authenticationStateProvider).SetUserLoggedOut();
    _client.HttpClient.DefaultRequestHeaders.Authorization = null;
}</code></pre>
<p>Rejestracja to tylko wywołanie odpowiedniej wygenerowanej metody przez NSwag</p>
<pre><code class="hljs cs">public async Task&lt;bool&gt; Register(string firstName, string lastName, string userName, string email, string password)
{
    RegistrationRequest registrationRequest = new RegistrationRequest() { FirstName = firstName, LastName = lastName, Email = email, UserName = userName, Password = password };
    var response = await _client.RegisterAsync(registrationRequest);

    if (!string.IsNullOrEmpty(response.UserId))
    {
        return true;
    }
    return false;
}</code></pre>
<p>Tak obecnie prezentuje się nasz projekt Blazor.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-xcxcx_637854300175049594.PNG" alt="Nasz projekt Blazor w Visual Studio" width="324" height="246"></p>
<p>Korzystając z kontenera wstrzykiwania zależności zarejestrujmy te wszystkie mechanizmy w naszej aplikacji Blazor.</p>
<p>Jak widzisz ustawiamy tutaj także adres naszego REST API. Tę aplikację do testów uruchomię po localhost i pod portem 5001.</p>
<pre><code class="hljs cs">public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add&lt;App&gt;("#app");

        //builder.Services.AddScoped(sp =&gt; new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
        builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
        builder.Services.AddBlazoredLocalStorage();

        builder.Services.AddSingleton(new HttpClient
        {
            BaseAddress = new Uri("https://localhost:5001")
        });

        builder.Services.AddHttpClient&lt;IClient, Client&gt;
        (client =&gt; client.BaseAddress = new Uri("https://localhost:5001"));

        builder.Services.AddScoped&lt;ISamuraiService, SamuraiService&gt;();

        //Auth
        builder.Services.AddScoped&lt;IBlazorAuthenticationService,
        BlazorAuthenticationService&gt;();
        builder.Services.AddScoped&lt;AuthenticationStateProvider,
        CustomAuthenticationStateProvider&gt;();
        builder.Services.AddScoped&lt;IAddBearerTokenService,
        AddBearerTokenService&gt;();</code></pre>
<p>Teraz możemy przejść do naszych strony pisanych w Razor, czyli składni, która pozwala nam mieszać kod HTML z C# </p>
<h3>Razor, czyli strony</h3>
<p>Oto mojego wszystkie strony.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-pro_637853749518470460.PNG" alt="Strony Razor w naszej aplikacji Blazor" width="248" height="178"></p>
<p>Razor strony logującej wygląda tak. Jak widzisz pola InputText są powiązane, z jaką właściwością "@LoginViewModel.Password". </p>
<p>Samo wywołanie formularza wywoła jakąś metodę "HandleValidSubmit". Gdzie ona jest ? Jest ona tak zwanym CodeBehind.</p>
<pre><code class="hljs razor">@page "/login"

&lt;div class="mt-5 row"&gt;
    &lt;div class="card col-12 col-lg-6 mr-auto ml-auto p-3"&gt;
        &lt;h3 class="card-title"&gt;Zaloguj się Samuraju&lt;/h3&gt;
        &lt;div class="card-body"&gt;
            &lt;EditForm Model="@LoginViewModel" OnValidSubmit="HandleValidSubmit"&gt;
                &lt;div class="input-group row mt-2"&gt;
                    &lt;label class="col-12 col-md-4 p-0" for="userName"&gt;Email:&lt;/label&gt;
                    &lt;InputText id="userName" class="col-12 col-md-8 login-field" @bind-Value="LoginViewModel.Email"&gt;&lt;/InputText&gt;
                &lt;/div&gt;
                &lt;div class="input-group row mt-2"&gt;
                    &lt;label class="col-12 col-md-4 p-0" for="password"&gt;Password:&lt;/label&gt;
                    &lt;InputText type="password" id="password" class="col-12 col-md-8 login-field" @bind-Value="@LoginViewModel.Password"&gt;&lt;/InputText&gt;
                &lt;/div&gt;
                &lt;div class="input-group row mt-2"&gt;
                    &lt;div class=""&gt;
                        &lt;button class="m-4 p-2"&gt;Zaloguj&lt;/button&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/EditForm&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;span class="label label-error"&gt;@Message&lt;/span&gt;</code></pre>
<p>Kod poboczny, który obsłuży logikę tej strony wygląda następująco. Jak widzisz w kodzie pobocznym nie możemy zrobić wstrzykiwania zależności poprzez konstruktor.</p>
<p>Na pomoc przychodzi nam atrybut "[Inject]", który to za nas zrobi.</p>
<pre><code class="hljs cs">public partial class Login
{
    public LoginBlazorVM LoginViewModel { get; set; }

    [Inject]
    public NavigationManager NavigationManager { get; set; }
    public string Message { get; set; }

    [Inject]
    private IBlazorAuthenticationService AuthenticationService { get; set; }

    public Login()
    {

    }

    protected override void OnInitialized()
    {
        LoginViewModel = new LoginBlazorVM();
    }

    protected async void HandleValidSubmit()
    {
        if (await AuthenticationService.Authenticate
            (LoginViewModel.Email, LoginViewModel.Password))
        {
            NavigationManager.NavigateTo("adminoptions");
        }
        Message = "Username/password Coś jest źle";
    }
}</code></pre>
<p>Nasza metoda "HandleValidSubmit" wykona zapytanie HTTP do naszego REST API i poprosi o token autoryzujący. Jeśli wszystko poszło zgodnie z planem to zostaniemy przekierowani na stronę "adminoptions".</p>
<h3>Kontrola ról użytkowników</h3>
<p>W Blazor istnieje także możliwość kontroli ról użytkowników. Próbowałem napisać taki kod i przynajmniej jestem w stanie Ci zagwarantować, że działa sprawdzenie, czy użytkownik w ogóle jest zalogowany.</p>
<p>Mam pewne problemy ze sprawdzeniem konkretnych ról i mam nadzieje, że kiedyś znajdę przyczynę tego błędu w kodzie</p>
<pre><code class="hljs cs">public class Program
{
    public static async Task Main(string[] args)
    {

        ...
        builder.Services.AddAuthorizationCore(config =&gt;
        {
            config.AddPolicy(Policies.IsAdmin, Policies.IsUserLogged());
            config.AddPolicy(Policies.IsUserLog, Policies.IsUserLogged());
            config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
            config.AddPolicy(Policies.IsClaim, Policies.IsClaimed());
        });

        await builder.Build().RunAsync();
    }
}</code></pre>
<p>Warto stworzyć sobie taką klasę statyczną i weryfikować użytkowników w widoku Razor.</p>
<pre><code class="hljs cs">public static class Policies
{
    public const string IsAdmin = "IsAdmin";
    public const string IsUserLog = "IsUserLog";
    public const string IsUser = "IsUser";
    public const string IsClaim = "IsClaim";

    public static AuthorizationPolicy IsAdminPolicy()
    {
        return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()

                                               .RequireRole("adminEdu")
                                               .Build();
    }

    public static AuthorizationPolicy IsUserLogged()
    {
        return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()

                                               .Build();
    }

    public static AuthorizationPolicy IsClaimed()
    {
        return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
            .RequireClaim("MyCos", "MyValue")
            .Build();
    }

    public static AuthorizationPolicy IsUserPolicy()
    {
        return new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
                                               .RequireRole("User")
                                               .Build();
    }
}</code></pre>
<h3>Wracamy do stron Razor</h3>
<p>Teraz skorzystam z tej klasy statycznej "Policies". Oto kod Razor "NavMenu.razor"</p>
<pre><code class="hljs razor">&lt;div class="top-row pl-4 navbar navbar-dark"&gt;
    &lt;a class="navbar-brand" href=""&gt;Samurai.UI&lt;/a&gt;
    &lt;button class="navbar-toggler" @onclick="ToggleNavMenu"&gt;
        &lt;span class="navbar-toggler-icon"&gt;&lt;/span&gt;
    &lt;/button&gt;
&lt;/div&gt;

&lt;div class="@NavMenuCssClass" @onclick="ToggleNavMenu"&gt;
    &lt;ul class="nav flex-column"&gt;
        &lt;li class="nav-item px-3"&gt;
            &lt;NavLink class="nav-link" href="" Match="NavLinkMatch.All"&gt;
                &lt;span class="oi oi-home" aria-hidden="true"&gt;&lt;/span&gt; Home
            &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li class="nav-item px-3"&gt;
            &lt;NavLink class="nav-link" href="login"&gt;
                &lt;span class="oi oi-list-rich" aria-hidden="true"&gt;&lt;/span&gt; Login
            &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li class="nav-item px-3"&gt;
            &lt;NavLink class="nav-link" href="samurailist"&gt;
                &lt;span class="oi oi-list-rich" aria-hidden="true"&gt;&lt;/span&gt; Listy Samurajów
            &lt;/NavLink&gt;
        &lt;/li&gt;
        &lt;li class="nav-item px-3" Policy="@Policies.IsAdmin"&gt;
            &lt;NavLink class="nav-link" href="adminoptions"&gt;
                &lt;span class="oi oi-list-rich" aria-hidden="true"&gt;&lt;/span&gt; Admin
            &lt;/NavLink&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass =&gt; collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}</code></pre>
<p>Jak widzisz próbuje w ten sposób ukryć jeden z linku w tej pasku nawigującym. </p>
<pre><code class="hljs razor">&lt;li class="nav-item px-3" Policy="@Policies.IsAdmin"&gt;
    &lt;NavLink class="nav-link" href="adminoptions"&gt;
        &lt;span class="oi oi-list-rich" aria-hidden="true"&gt;&lt;/span&gt; Admin
    &lt;/NavLink&gt;
&lt;/li&gt;</code></pre>
<p>W widoku można skorzystać z budowanych tagów w Razor, które ukryją odpowiednio zawartość strony przed nie zalogowany użytkownikiem.</p>
<p>Oto kod strony "AdminOptions.razor". Tutaj wyświetlę sobie zawartość całego tokenu JSON.</p>
<pre><code class="hljs razor">@page "/adminoptions"

&lt;AuthorizeView Policy="@Policies.IsAdmin"&gt;
    &lt;Authorized&gt;

        &lt;p class="back-to-catalog mt-3"&gt;
            &lt;a class="back-link" @onclick="@NavigateToList"&gt;---- Zobacz listę samurajów ----&lt;/a&gt;
        &lt;/p&gt;
    &lt;/Authorized&gt;
    &lt;NotAuthorized&gt;
        &lt;p&gt;Proszę się zalogować&lt;/p&gt;
    &lt;/NotAuthorized&gt;
&lt;/AuthorizeView&gt;
&lt;br /&gt;

&lt;AuthorizeView Policy="@Policies.IsClaim"&gt;
    &lt;Authorized&gt;
        &lt;p&gt;@Policies.IsClaim : true&lt;/p&gt;
    &lt;/Authorized&gt;
    &lt;NotAuthorized&gt;
        &lt;p&gt;@Policies.IsClaim : false&lt;/p&gt;
    &lt;/NotAuthorized&gt;
&lt;/AuthorizeView&gt;

Zawartość tokenu JSON
&lt;p&gt;
    @Data
&lt;/p&gt;

&lt;p class="back-to-catalog mt-3"&gt;
    &lt;a class="back-link" @onclick="@DeleteToken"&gt;---- Skasuj token ----&lt;/a&gt;
&lt;/p&gt;
</code></pre>
<p>Kod poboczny strony wygląda tak. W trakcie ładowania strony wyświetlę sobie zawartość tokenu. Przy okazji oto prosty przykład jak w Blazor tworzyć dynamicznie kod HTML poprzez użycie klasy "MarkupString"</p>
<pre><code class="hljs cs">public partial class AdminOptions
{
    [Inject]
    public AuthenticationStateProvider _authenticationStateProvider { get; set; }

    [Inject]
    protected ILocalStorageService _localStorage { get; set; }

    public AdminOptions()
    {

    }

    [Parameter]
    public MarkupString Data { get; set; } = new MarkupString("");

    protected async override Task OnInitializedAsync()
    {
        var use = (await _authenticationStateProvider.GetAuthenticationStateAsync());

        StringBuilder sb = new StringBuilder();
        sb.AppendLine(use.User.Identity.Name);
        sb.Append("&lt;br /&gt;");

        sb.AppendLine(" &lt;ul&gt; ");
        foreach (var item in use.User.Claims)
        {
            sb.Append(" &lt;li&gt; ");
            sb.Append(" &lt;ol&gt; ");
            sb.Append(" &lt;li&gt; ");
            sb.AppendLine(item.Type);
            sb.Append(" &lt;/li&gt; ");
            sb.Append(" &lt;li&gt; ");
            sb.AppendLine(item.Value);
            sb.Append(" &lt;/li&gt; ");
            sb.Append(" &lt;/ol&gt; ");
            sb.Append(" &lt;/li&gt; ");
        }
        sb.AppendLine(" &lt;/ul&gt; ");
        sb.Append("&lt;br /&gt; Jest Adminiem");
        sb.AppendLine(use.User.IsInRole("Admin").ToString());
        sb.Append("&lt;br /&gt; Ma MyValue");
        sb.AppendLine(use.User.Claims.Any(p =&gt; p.Value == "MyValue").ToString());
        sb.Append("&lt;br /&gt; Ma Cos");
        sb.AppendLine(use.User.Claims.Any(p =&gt; p.Type == "MyCos").ToString());


        Data = new MarkupString(sb.ToString());
    }


    [Inject]
    public NavigationManager NavigationManager { get; set; }

    protected void DeleteToken()
    {
        _localStorage.RemoveItemAsync("token");
        NavigationManager.NavigateTo("/");
    }

    protected void NavigateToList()
    {
        NavigationManager.NavigateTo("/samurailist");
    }

}</code></pre>
<p>Tak wygląda kod strony "SamuraiList.razor". Jej celem jest wyświetlenie wszystkich samurajów.</p>
<pre><code class="hljs razor">&lt;AuthorizeView Policy="@Policies.IsAdmin"&gt;
    &lt;Authorized&gt;

        &lt;h3&gt;Samuraje&lt;/h3&gt;

        @if (Warriors == null)
        {
            &lt;p&gt;&lt;em&gt;Loading...&lt;/em&gt;&lt;/p&gt;
        }
        else
        {
            &lt;table&gt;
                &lt;thead&gt;
                    &lt;tr&gt;
                        &lt;th&gt;Nazwa&lt;/th&gt;
                        &lt;th&gt;Wiek&lt;/th&gt;
                        &lt;th&gt;&lt;/th&gt;
                        &lt;th&gt;&lt;/th&gt;
                    &lt;/tr&gt;
                &lt;/thead&gt;
                &lt;tbody&gt;
                    @foreach (var p in Warriors)
                    {
                        &lt;tr&gt;

                            &lt;td class="post-title"&gt;@p.Name &lt;/td&gt;
                            &lt;td class="post-title"&gt;@p.Age &lt;/td&gt;
                            &lt;td class="post-title"&gt;@p.ImageUrl&lt;/td&gt;
                        &lt;/tr&gt;
                    }
                &lt;/tbody&gt;
            &lt;/table&gt;
        }
    &lt;/Authorized&gt;
    &lt;NotAuthorized&gt;
        &lt;p&gt;Proszę się zalogować. Samuraju&lt;/p&gt;
    &lt;/NotAuthorized&gt;
&lt;/AuthorizeView&gt;</code></pre>
<p>Oto kod poboczny tej strony. Pobieram samurajów w trakcie ładowania strony i potem przekazuje to co pobrałem do parametru.</p>
<pre><code class="hljs cs">public partial class SamuraiList
{
    [Inject]
    public ISamuraiService SamuraiService { get; set; }

    [Inject]
    public NavigationManager NavigationManager { get; set; }

    public ICollection&lt;WarriorViewModel&gt; Warriors { get; set; }

    protected async override Task OnInitializedAsync()
    {
        Warriors = await SamuraiService.GetAllWarriors();
    }

}</code></pre>
<p>To wszystko, zanim uruchomimy naszą aplikację Blazor warto zmienić jej port. Zakładamy, że nasze REST API będzie działać pod portem 5001. To niech nasza aplikacja Blazor działa pod portem 5005.</p>
<pre><code class="hljs json">{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:50456",
      "sslPort": 44375
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
    },
    "Samurai.UI": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "dotnetRunMessages": "true",
      "applicationUrl": "https://localhost:5005;http://localhost:5006",
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
    }
  }
}</code></pre>
<p>Oto chwila prawdy.</p>
<h3>Zobaczmy jak to działa ?</h3>
<p>Strona startowa wygląda tak:</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518575121.PNG" alt="Strona startowa Blazor" width="519" height="281"></p>
<p>Jak widać nie możemy zobaczyć listy samurajów przed zalogowaniem więc blokady nasze działają.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518610120.PNG" alt="Strona lista samurajów jest zablokowana ponieważ nie jesteśmy zalogowani" width="421" height="248"></p>
<p>W panelu administratora jak widzisz nie mamy żadnych informacji.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518652088.PNG" alt="panelu administratora jest pusty" width="372" height="326"></p>
<p>Uruchamiamy nasze REST API, które stworzyliśmy w poprzednim wpisie.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-aadd_637853751327787857.PNG" alt="REST API metody" width="487" height="268"></p>
<p>Logujemy się. Mamy użytkownika eve@gmail.com z hasłem 12345</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518688225.PNG" alt="Logujemy się w naszej aplikacji Blazor" width="539" height="370"></p>
<p>Możemy wyświetlić sobie zawartość naszego JSON WEB Tokenu.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518780405.PNG" alt="Zawartość naszego JSON WEB Tokena" width="465" height="826"></p>
<p>Teraz gdy jesteśmy zalogowani to możemy zobaczyć listę samurajów.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/blazor-i-uwierzytelnienie-z-json-web-tokenem/00-sam-08_637853749518814561.PNG" alt="Lista Samurajów w Blazor" width="547" height="263"></p>
<p>To wszystko. Kod można pobrać tutaj <a href="https://github.com/PanNiebieski/example-JsonWebToken-with-Blazor-Samurai">PanNiebieski/example-JsonWebToken-with-Blazor-Samurai (github.com)</a></p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category></item><item><title>Architekt Programista? 6 pytań do przyszłego Ciebie</title><link>https://cezarywalenciuk.pl/blog/programing/architekt-programista-6-pytan-do-przyszlego-ciebie</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/architekt-programista-6-pytan-do-przyszlego-ciebie</guid><pubDate>Tue, 12 Apr 2022 11:28:00 GMT</pubDate><description><![CDATA[<p>[metro]Każdy chce szybko z programisty stać się architektem, aby oczywiście mieć więcej wyborów i pieniędzy.</p>
<p>Zanim jednak tym architektem zostaniesz warto odpowiedzieć sobie na te pytanie.</p>
<p>Oto 6 pytań dla przyszłych architektów oprogramowania.</p>
<p>[more]</p>
<h3>1. Czy w programowaniu istnieje srebrna kula ?</h3>
<p>Nie ma srebrnej kuli w oprogramowaniu. Mógłbyś dojść do wniosku, że po 63 latach po wynalezieniu COBOL-a nasze oprogramowanie powinno dojść do fazy, w której istnieje idealne rozwiązanie. Tak jednak nie jest. </p>
<p>Z mojego doświadczenia techniki programistyczne można podzielić na dwie kategorię:</p>
<ul>
<li>Fatalne</li>
<li>i dobre, ale to zależy. No właśnie "to zależy". </li>
</ul>
<p>Warto o tym pamiętać, gdy będziesz miał obsesje nad swoim młotkiem i będziesz nim próbował montować kafelki, bo dla Ciebie wszystko jest teraz gwoździem. </p>
<h3>2. Czy kwestionujesz Hype ?</h3>
<p>Ten problem dotyka całego obszaru IT i został on nazwany już wieloma śmiesznymi skrótami jak:</p>
<ul>
<li>"Hype Driven Development",</li>
<li>"Conference Driven Development",</li>
<li>"Loudest Guy Driven Descisions"</li>
<li>i ostatnie najważniejsze "Resume Driven Development",</li>
</ul>
<p>...czyli to jak nasze dążenie do dodawania fajnych nowych technologi do CV wpływa na tworzenie aplikacji w korporacjach. </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/architekt-programista-6-pytan-do-przyszłego-ciebie/00-qq_637853689486511158.png" alt="Parodie książek" width="770" height="266"></p>
<p>Każdy chce dodać nową technologię do CV, aby zwiększyć swoją wartość, ale aby to zrobić z czystym sumieniem, to trzeba taką aplikację stworzyć w swojej obecnej firmie. Znaczy to, że marketingowe kręcenie pewnych rozwiązań technologicznych pociąga wiele sznurków w tym trendy w CV i  potem te trendy wypluwają nie koniecznie dobre programistyczne twory.</p>
<p>To, że coś jest nowe, nie znaczy automatycznie, że jest to lepsze. To, że  coś jest popularne, nie znaczy, że automatycznie, że jest to perfekcyjne narzędzie, które rozwiąże twój konkretny problem. Tworzymy oprogramowanie, aby rozwiązywać problemy, a nie po to, aby dopisać sobie technologię do CV.</p>
<p>Dlatego warto kwestionować każde rozwiązanie, zwłaszcza jeśli to rozwiązanie jest dopiero w swojej fazie "rozgłosu" i nie zostało tak naprawdę sprawdzone na dużą skalę, a to zajmuje zazwyczaj danej technologi 3-4 lata. Nie dziw się, więc jeśli twój architekt nie podziela entuzjazmu twojego nowym rozwiązaniem, bo ma do tego swoje powody.</p>
<h3>3. Czy kwestionujesz wzorce projektowe ?</h3>
<p>Gdy zaczynałem programować z 10 lat temu, to byłem przekonany, że wzorce projektowe są kluczem do czystego, czytelnego kodu i znajomość tych wzorcu będzie mi otwierało możliwości zabłyśnięcia na rozmowach kwalifikacyjnych. </p>
<p>Gdy już pracowałem, to kupiłem sobie książkę za 300 zł i myślałem, że tę cenę kupuje wręcz święty Gral programistyczny. Ta książka nie tylko opisywała wszystkie wzorce projektowe z "Gang of Four", ale także pokazała ich użycie w poszczególnych warstwach aplikacji ASP.NET Web Forms. </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/architekt-programista-6-pytan-do-przyszłego-ciebie/00-ks_637853689486764931.png" alt="00-ks.png" width="234" height="312"></p>
<p>Po krótkiej analizie książki doszedłem do wniosku, że połowa tych wzorców projektowych jest albo beznadziejna, albo bardzo sytuacyjna, albo już ktoś wymyślił coś lepszego. Potem lata mijały w mojej karierze programisty i najczęściej używanym wzorcem projektowym był tylko dekorator.</p>
<p>Mogłem, też zobaczyć jak pewien architekt zrobił bardzo nieczytelny kod, ponieważ użył wzorca projektowego "State", który jest w porządku, dopóki logika nie jest zarządzana przez 30 oddzielnych klas, które mają reprezentować, jak sama nazwa wzorca wskazuje, oddzielny stan i akcje dla danego elementu w tym stanie.</p>
<p>Ja jednak by wolał jeden plik, gdzie mogę wzrokiem zrozumieć, co ja właściwie robię, gdy takie i takie stany danego elementu występują. Niby to antywzorzec, ale przynajmniej bym wiedział, co ja właściwie robię. Teraz też wiem, że dobrą alternatywą dla wzorca "State" jest maszyna stanów, która nie była opisana w wielu książkach. </p>
<p>To mnie nauczyło kwestionować rozwiązania programistyczne nawet te poparte autorytetem i ceną książki. </p>
<h3>4. Czy znasz prawo Conway'a ?</h3>
<p>Jako programista zauważyłeś, że ostatnio bardzo stały się popularne mikroserwisy  oraz mikro-frontendy. Tak jak mówiłem wcześniej to, że coś jest popularne, to automatycznie nie znaczy, że są to rozwiązania dla Ciebie.</p>
<p>Ich popularność można tłumaczyć "Prawem Conway'a", które nam mówi, że naszego oprogramowanie będzie dążyć do struktury, jaką mamy w naszych zespołach w firmie. </p>
<p>Skoro mamy małe zespoły po 3-5 osób to nasze oprogramowanie też chce dążyć do małych aplikacji backend i frontend.</p>
<p>Nie dlatego, że jest to uniwersalnie dobre, ale dlatego, że takie mamy podziały zespołów.</p>
<p>Prawa Conway's nie da się oszukać. Jest jak prawo grawitacji.  Jeśli mamy złą komunikację w zespołach albo struktura zespołów  jest jak przyciśnięcie wzajemnie do siebie pistoletów do głowy, to te wszystkie problemy wyjdą w oprogramowaniu. </p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/architekt-programista-6-pytan-do-przyszłego-ciebie/00-ilu_637853689486854176.png" alt="00-ilu.png" width="770" height="433"></p>
<p style="text-align: center;"><em>Przykład zbyt dużego zespołu. Jak myślisz działa on wydajnie ?</em></p>
<p>Dlatego architekci nie tylko robią diagramy projektów z kodem, ale także robią mapę kontekstów i zespołów, które w jakiś sposób ze sobą muszą się komunikować, aby firma jako cały puzzel lub organizm jakoś funkcjonowała.</p>
<p>Fachowo mówi się na to "Context Map".</p>
<p>Ciekawe, że można znaleźć problem w oprogramowaniu nie w kodzie, ale w relacjach między zespołami i być może w niedalekiej przyszłości jako architekt twoim zadaniem będzie włożyć kij w mrowisko polityki firmy, aby przyszłe oprogramowania był lepsze.</p>
<h3>5. Czy to się skaluje ?</h3>
<p>Gdy już znasz najlepsze z najlepszych praktyk w architekturze w twojej głowie na nie jednym spotkaniu pojawi się pytanie "<strong>Czy to się skaluje ?</strong>".</p>
<p>No cóż, żeby, to zweryfikować to trzeba napisać prototyp albo mieć już doświadczenie z daną techniką.</p>
<p>Ostatnio jednak pojawiło się wiele alarmów o odwrotnym problemie. Nie każdy z nas będzie tworzył drugiego Facebooka, to znaczy, że wiele genialnych praktyk będzie przerostem formy nad treścią naszego rozwiązania.</p>
<p>Mówi się na to " <strong>Overengineering</strong>". </p>
<p>Dlatego, zamiast zadawać sobie pytanie ", Czy to się skaluje?" warto także zadać sobie pytanie "<strong>A co jeśli tyle mi wystarczy?</strong>".</p>
<p>Dodatkowo warto zaznaczyć, że genialna technika to jedno, ale jej dobra implementacja to drugie.  Wiesz mi, każdy programista wolałby aplikację CRUD niż źle napisaną aplikację CQRS z Event Sourcing. Dlatego skup się na jak najprostszych rozwiązaniach dopasowanych do skali swojego problemu.</p>
<h3>6. Czy masz swoje zdanie ?</h3>
<p>Warto weryfikować każdą technikę i uważać, aby nie stać się papugą, która powtarza to, co usłyszała na konferencji albo od innego kolesia, który głośnio krzyczy.</p>
<p style="text-align: left;">Ciągle się uczymy i niestety nic nie zastąpi twojej weryfikacji danej techniki z prawdziwym projektem biznesowym.</p>
<p style="text-align: left;">TL;DR Zawsze możesz też obejrzeć filmik na YT na ten temat :)</p>
<p style="text-align: center;"><iframe src="https://www.youtube.com/embed/xe5aEvFkoiM" title="YouTube video player" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe>;</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>filozofia</category></item><item><title>Excel VBA : Tworzenie, kopiowanie i nazywanie dynamiczne arkuszy</title><link>https://cezarywalenciuk.pl/blog/programing/excel-vba-tworzenie-kopiowanie-i-nazywanie-dynamiczne-arkuszy</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/excel-vba-tworzenie-kopiowanie-i-nazywanie-dynamiczne-arkuszy</guid><pubDate>Thu, 24 Feb 2022 12:14:36 GMT</pubDate><description><![CDATA[<p>[metro] Kolejne wyzwanie Visual Basic For Application dla Excela mnie spotkało. Czy można w Excelu dynamicznie wygenerować arkusze, kt&oacute;re są kopią istniejącego arkusza? Oczywiście, że tak.</p>
<p>Czy można nadać nazwę według pewnego wzoru tym arkuszom? Tak</p>
<p>Oto zadanie, kt&oacute;re miałem wykonać. Mam więc arkusz do kopiowania wygląda on tak. [more]</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba--tworzenie-i-nazywanie-dynamiczne-arkuszy/arkusz-02_637813016761892648.PNG" alt="Arkusz do kopiowania w Excel 2019" width="341" height="202" /></p>
<p>Każda kopia takiego arkusza musi mieć swoją nazwę według pewnego wzoru. Oto moja tabelka.&nbsp;</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba--tworzenie-i-nazywanie-dynamiczne-arkuszy/arkusz-02_637813016762473354.PNG" alt="Kod do arkuszy w Excel 2019" width="738" height="255" /></p>
<p>Zanim więc przejdziemy do generowania takich arkuszy musi najpierw określić kod nazwy. Jego wz&oacute;r jest następujący&nbsp;</p>
<p>"4 pierwsze litery z miasta" + "_" + "Ostatnia cyfra z Numer1" + "_" + "Pierwsza cyfra z Numer 2" + "_" + "Kod Regionu"</p>
<p>Kod VBA, kt&oacute;ry takie napisy wygeneruje wygląda tak:</p>
<pre><code class="hljs  vbnet ">Sub DajKodDoArkuszu()

For x = 2 To 9
    
    Dim cellcity, cellnumber1, cellnumeber2, cellregion As String
    
    cellcity = "B" &amp; x
    cellnumber1 = "C" &amp; x
    cellnumeber2 = "D" &amp; x
    cellregion = "E" &amp; x
    
    Dim vcity, vnumber1, vnumber2, vregion As String
    
    vcity = Range(cellcity).Value
    vnumber1 = Range(cellnumber1).Value
    vnumber2 = Range(cellnumeber2).Value
    vregion = Range(cellregion).Value
       
    Dim lenghtcity
    lenghtcity = Len(vcity)
    
    If lenghtcity &gt; 4 Then
        vcity = Left(vcity, 4)
    End If
    
    vnumber1 = Right(vnumber1, 1)
    vnumber2 = Left(vnumber2, 1)
    
    Dim result As String
    
    result = "cit_" &amp; vcity &amp; "_" &amp; vnumber1 &amp; "_" &amp; vnumber2 &amp; "_" &amp; vregion
    result = LCase(result)
    
    Range("A" &amp; x).Value = result

Next x

End Sub</code></pre>
<p>Wbudowana funkcja Len określi mi ilość znak&oacute;w w danym napisie.&nbsp;</p>
<pre><code class="hljs  vbnet ">Dim lenghtcity
lenghtcity = Len(vcity)

If lenghtcity &gt; 4 Then
    vcity = Left(vcity, 4)
End If</code></pre>
<p>Korzystając z funkcji Left jestem w stanie wyciągnąć z danego napisu pierwsze 4 znaki.</p>
<pre><code class="hljs  vbnet ">vnumber1 = Right(vnumber1, 1)
vnumber2 = Left(vnumber2, 1)</code></pre>
<p>Istnieje też podobna funkcja Right, kt&oacute;ra wyciągnie mi znaki od końca mojego napisu lub numeru.</p>
<pre><code class="hljs  vbnet ">result = "cit_" &amp; vcity &amp; "_" &amp; vnumber1 &amp; "_" &amp; vnumber2 &amp; "_" &amp; vregion
result = LCase(result)
Range("A" &amp; x).Value = result</code></pre>
<p>Na koniec pozostało nam te napisy złączyć i umieścić ich zawartość do kolumny "A".</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba--tworzenie-i-nazywanie-dynamiczne-arkuszy/arkusz-04_637813016762515156.PNG" alt="Kod do arkuszy rezultat działania metody VBA" width="722" height="256" /></p>
<p>To był fragment naszego zadania. Teraz przechodzimy do generowania arkuszy przy użyciu VBA.</p>
<p>Cały ten wcześniejszy kod możemy przerobić na funkcje, kt&oacute;ra nam zwr&oacute;ci tablice wygenerowanych napis&oacute;w. Ta tablica będzie nam potrzebna do generacji szablon&oacute;w.</p>
<pre><code class="hljs  vbnet ">Function DajTabliceKodow() As String()

Dim arr(7) As String
Dim index As Integer
index = 0

For x = 2 To 9
    
    Dim cellcity, cellnumber1, cellnumeber2, cellregion As String
    
    cellcity = "B" &amp; x
    cellnumber1 = "C" &amp; x
    cellnumeber2 = "D" &amp; x
    cellregion = "E" &amp; x
    
    Dim vcity, vnumber1, vnumber2, vregion As String
    
    vcity = Range(cellcity).Value
    vnumber1 = Range(cellnumber1).Value
    vnumber2 = Range(cellnumeber2).Value
    vregion = Range(cellregion).Value
       
    Dim lenghtcity
    lenghtcity = Len(vcity)
    
    If lenghtcity &gt; 4 Then
        vcity = Left(vcity, 4)
    End If
    
    vnumber1 = Right(vnumber1, 1)
    vnumber2 = Left(vnumber2, 1)
    
    Dim result As String
    
    result = "cit_" &amp; vcity &amp; "_" &amp; vnumber1 &amp; "_" &amp; vnumber2 &amp; "_" &amp; vregion
    result = LCase(result)
    
    arr(index) = result
    index = index + 1
    
Next x

DajTabliceKodow = arr

End Function</code></pre>
<p>Do działania będziemy też potrzebować innych pomocniczych funkcji. Potrzebujemy funkcji, kt&oacute;ra zwr&oacute;ci nam wielkość danej tablicy</p>
<pre><code class="hljs  vbnet ">Public Function GetArrLength(a As Variant) As Integer
   If IsEmpty(a) Then
      GetArrLength = 0
   Else
      GetArrLength = UBound(a) - LBound(a) + 1
   End If
End Function</code></pre>
<p>Przyda nam się też funkcja, kt&oacute;ra sprawdzi dla bezpieczeństwa czy nie istnieje już taki arkusz o takiej nazwie.</p>
<pre><code class="hljs  vbnet ">Function Sheet_Exists(WorkSheet_Name As String) As Boolean
Dim Work_sheet As Worksheet
 
Sheet_Exists = False
 
For Each Work_sheet In ThisWorkbook.Worksheets
 
    If Work_sheet.Name = WorkSheet_Name Then
        Sheet_Exists = True
    End If
 
Next
 
End Function</code></pre>
<p>Jak można dodać arkusz w VBA? Wystarczy taki fragment kodu</p>
<pre><code class="hljs  vbnet ">Sheets.Add.Name = "NowyArkusz"
</code></pre>
<p>Jeśli chcesz dodać arkusz przed lub po jakiś arkuszu to kod trochę ulega zmianie.</p>
<pre><code class="hljs  vbnet ">Sheets.Add After:=Sheets("Arkusz1") 'Dodaj po Arkuszu1'
Sheets.Add(After:=Sheets("Arkusz1")).Name = "NowyArkusz" 'Dodaj po Arkuszu1 i zmień nazwe'

Sheets.Add(Before:=Sheets(1)).Name = "FirstSheet 'Dodaj na początku'
</code></pre>
<p>Nas jednak interesuje kopiowanie i umieszczanie każdego kolejno takiego szablonu na końcu.</p>
<p>Kod generujący wygląda tak</p>
<pre><code class="hljs  vbnet ">Sub UtworzArkusze()

Dim arr() As String
arr = DajTabliceKodow()

Dim sizeArray
sizeArray = GetArrLength(arr)

For y = 0 To sizeArray - 1

    Dim Sheet_Name As String
    
    Sheet_Name = arr(y)
    
    If (Sheet_Exists(Sheet_Name) = False) Then
    
        Sheets("ArkuszDoKopiowania").Copy After:=Sheets(Sheets.Count)
        ActiveSheet.Name = Sheet_Name
        
    End If
Next y

End Sub</code></pre>
<p>Jak widzisz dosyć łatwo jest utworzyć dynamicznie arkusze w VBA w Excelu.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba--tworzenie-i-nazywanie-dynamiczne-arkuszy/arkusz-05_637813016762535465.PNG" alt="arkusz-05.PNG" width="764" height="37" /></p>
<p>Najbardziej interesuje cię zapewne ten fragment kodu. Metoda Copy utworzy kopię, a funkcja After ustawi nowo wygenerowany tak szablon na samym końcu.</p>
<pre><code class="hljs  vbnet ">Sheets("ArkuszDoKopiowania").Copy After:=Sheets(Sheets.Count)
ActiveSheet.Name = Sheet_Name</code></pre>
<p>Gdy ten szablon się generuje to jest on w tym momencie aktywnym szablonem.&nbsp; To daje nam możliwość odwołania się do niego i wtedy mam szanse zmienić jego nazwę.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba--tworzenie-i-nazywanie-dynamiczne-arkuszy/arkusz-03_637813034516713707.PNG" alt="Uruchamianie makra w Excel 2019 " width="495" height="443" /></p>
<p>To wszystko. Pamiętaj tylko o tym, aby aktywować/zaznaczyć odpowiedni arkusz, gdy uruchamiasz takie makro w VBA.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>excel-vba</category></item><item><title>Excel VBA : Dodanie formuły, która sumuje wartości z arkuszy</title><link>https://cezarywalenciuk.pl/blog/programing/excel-vba-dodanie-formuly-ktora-sumuje-wartosci-z-arkuszy</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/excel-vba-dodanie-formuly-ktora-sumuje-wartosci-z-arkuszy</guid><pubDate>Wed, 23 Feb 2022 09:58:52 GMT</pubDate><description><![CDATA[<p>[metro] O ile ten blog jest gł&oacute;wnie na temat C#, to czasem pojawiają się wyzwania, kt&oacute;re sprawdzają jak szybko jestem w stanie rozwiązać jakiś problem, gdy o danym języku programowania lub narzędziu nie wiele wiem.</p>
<p>Dzisiaj dostałem takie wyzwanie związane z Excelem. Czy można w Excelu napisać makro w VBA, kt&oacute;re za Ciebie wygeneruje wyrażenie "SUMA=" i do niego doklei za Ciebie wszystkie arkusze, jakie masz w danym zeszycie?</p>
<p>Oczywiście, że się da. Po napisaniu Makra w VBA nawet zdałem sobie sprawę, że ten problem można rozwiązać zapewne lepiej przy pomocy menadżera nazw oraz potęgi klawisza SHIFT do zaznaczania wielu arkuszy.</p>
<p>Ja jednak ponieważ jestem programistą to pokaże Ci jak szybko od podstaw&nbsp; można nauczyć się pisania makr VBA&nbsp; Excelu, tak jak ja to zrobiłem w 15 minut.[more]</p>
<p>Na początku w programie Excel musisz dodać do wstążki całą zakładkę Deweloper. Bez niej nic nie zrobisz.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/excel-01_637812071322352890.png" alt="Dostosowanie wstążki w Excel 2019 aby dodać możliwość Visual Basic" width="779" height="465" /></p>
<p>Następnie z tego nowego pola we wstążce wybierz "Visual Basic".</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/excel-05_637812133915395918.png" alt="Przycisk Visual Basic w wstążce w programie Excel 2019" width="766" height="163" /></p>
<p>W tym oknie teraz możesz deklarować nowe moduły. Do nich będziemy dodawać kod. Ten kod będziemy potem uruchamiać w specyficznym arkuszu w Excelu.</p>
<p>Dodawanie modułu wygląda tak</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/excel-04_637812133915523482.png" alt="Dodawanie module z kodem VBA w programie Microsoft Visual Basic for Applications" width="552" height="461" /></p>
<p>Pora napisać naszą pierwszą metodę w naszym module. Sub deklaruje nam funkcje o nazwie "GenerowanieSumy1".</p>
<pre><code class="hljs vbnet ">Sub GenerowanieSumy1()
    Dim x, Formula
    Formula = "=SUM(" 'Formuła zaczyna się od =SUM('
    
    For x = 1 To (Sheets.Count - 1)
        Formula = Formula &amp; Sheets(x).Name &amp; "!A1," 'Dodaj Nazwę Arkusza i Kom&oacute;rkę i Przecinek'
    Next x
    
    Formula = Left(Formula, Len(Formula) - 1) &amp; ")" 'Usuwanie przecinka i dodawanie nawias&oacute;w'
    Range("A1").Formula = Formula 'Gdzie chcesz umieścić tą formułę?'
End Sub</code></pre>
<p>"Dim" oznacza deklarację zmiennej o danej nazwie. Potem w pętli FOR przejdę po wszystkich arkuszach opr&oacute;cz ostatniego stąd -1 i wygeneruje się odpowiedni ciąg do formuły. Będzie to wyglądać ostatecznie tak:</p>
<p>=SUMA(Arkusz1!A1;Arkusz2!A1;Arkusz3!A1;Arkusz4!A1)</p>
<p>Gdzie przykładowo "akrusz5" będzie arkuszem ostatnim i do niego będę chciał dodać tą wygenerowaną formułę przez VBA. Na razie napisałem na sztywno kom&oacute;rkę "A1". Zobaczymy co możemy z tym zrobić dalej.</p>
<p>Jak tej metody użyć.</p>
<p>Wchodzimy na "arkusz5" czyli nasz ostatni arkusz i wciskamy ten guzik.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/excel-065_637812133915604518.png" alt="Przycisk makra w wstążce w programie Excel 2019" width="766" height="163" /></p>
<p>Teraz pojawi Ci się okno ze wszystkimi dostępnymi makrami. Ja mam ich więcej, ponieważ już dodałem do swojego modułu kolejne metody.&nbsp;</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/excel-07_637812133915674365.PNG" alt="Wybieramy swoje makro w oknie" width="378" height="377" /></p>
<p>Oto jak nasze makro działa w praktyce.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/GIF 23.02.2022 12-25-40_637812133915929795.gif" alt="Pokaz naszej funkcji w programie Excel 2019" width="629" height="316" /></p>
<p>Oczywiście jest jeden problem z naszym makrem.</p>
<p>Na sztywno do niego dodaliśmy kom&oacute;rkę A1.</p>
<p>Co, jeśli chcielibyśmy tak generować sumy dla każdej kom&oacute;rki?</p>
<p>Możemy przerobić naszą metodę VBA tak, aby przyjmowała ona informacje o kom&oacute;rce jako napis String</p>
<pre><code class="hljs  vbnet  ">Sub GenerowanieSumyZParametrem(CELL As String)<br />Dim x, Formula
Formula = "=SUM(" 'Formuła zaczyna się od =SUM('
For x = 1 To (Sheets.Count - 1)
    Formula = Formula &amp; Sheets(x).Name &amp; "!" &amp; CELL &amp; "," 'Dodaj Nazwę Arkusza i Kom&oacute;rkę i Przecinek'
Next x
Formula = Left(Formula, Len(Formula) - 1) &amp; ")" 'Usuwanie przecinka i dodawanie nawias&oacute;w'
Range(CELL).Formula = Formula 'Gdzie chcesz umieścić tą formułę?'
End Sub</code></pre>
<p>Niestety nie ma możliwości uruchomienia makra z parametrem bez tworzenia do niego jakieś formatki z przyciskiem.</p>
<p>Tworzenie też takich funkcji per kom&oacute;rka, też mija się z celem. Warto zaznaczyć, że jak uruchamiasz funkcje wewnątrz innej funkcji to najpierw musisz podać nazwę modułu tej funkcji. U mnie moduł nazywa się "GenerowanieSumy".&nbsp;</p>
<p>Przeciwnym wypadku dostaniesz błąd : Expected Variable or Procedure, not Module.</p>
<pre><code class="hljs  vbnet ">Sub GenerowanieSumyA2()
    GenerowanieSumy.GenerowanieSumyZParametrem("A2")<br />End Sub

Sub GenerowanieSumyA1()
    Call GenerowanieSumy.GenerowanieSumyZParametrem(CELL:="A1")<br />End Sub</code></pre>
<p>Na szczęście do problemu można podejść bardzo prymitywnie, ale też i skutecznie.</p>
<p>Przykładowo chce mieć sumy wartości kom&oacute;rek od C3 do N40. Jak to zrobić? Utworzyłem sobie tablice "CellsName", kt&oacute;ra zawiera nazwy kom&oacute;rek od C do N. Chociaż zaraz Ci pokaże jak to zrobić bez tworzenia takiej tablic.</p>
<pre><code class="hljs  vbnet  ">Sub GenerowanieSumyC3toN40()
    
    Dim CellsName(1 To 12) As String
    CellsName(1) = "C"
    CellsName(2) = "D"
    CellsName(3) = "E"
    CellsName(4) = "F"
    CellsName(5) = "G"
    CellsName(6) = "H"
    CellsName(7) = "I"
    CellsName(8) = "J"
    CellsName(9) = "K"
    CellsName(10) = "L"
    CellsName(11) = "M"
    CellsName(12) = "N"
       
    For x = 3 To 40
        For y = 1 To 12
             Dim CELL As String
             CELL = CellsName(y) &amp; x
             
             Call GenerowanieSumy.GenerowanieSumyZParametrem(CELL)<br />
        Next y
    Next x

End Sub</code></pre>
<p>Potem wykonuje pętle w pętli i tak dla każdej kom&oacute;rki od "C3 do N40" wykonam funkcję "GenerowanieSumyZParametrem".</p>
<p>Oto rezultat działanie tej metody</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/excel-vba-dodanie-formuły-ktora-sumuje-wartosci-z-arkuszy/GIF 23.02.2022 12-39-04_637812133916463562.gif" alt="Tworzenie sumy dla wielu kom&oacute;rek dzięki VBA" width="629" height="737" /></p>
<p>Jak widzisz funkcje nie są skomplikowane. Nawet taki prymitywnym podejściem można rozwiązać skomplikowany problem.</p>
<p>Visual Basic for Applications nie jest aż taki straszny</p>
<p>Czy można napisać ten kod inaczej? Mały tutorial. Oto jak możesz ustawić wartość kom&oacute;rki A2.&nbsp;</p>
<pre><code class="hljs  ">Cells(2,1).Value = 1
Range("A2").Value = 1</code></pre>
<p>W metodzie Cells w drugim parametrze podajesz kolumnę w formacie cyfrowej. Te dwie linki kodu robią dokładnie to samo</p>
<pre><code class="hljs  vbnet  ">Cells("A1").Formula = Formula 'Gdzie chcesz umieścić tą formułę?'
Cells(1, 1).Formula = Formula 'Gdzie chcesz umieścić tą formułę?'</code></pre>
<p>Wiedząc to możemy teraz zmodyfikować funkcje, kt&oacute;ra będzie generować te formuły. Jednakże do tego problemu możemy podejść jeszcze inaczej bez modyfikowania istniejącej już funkcji, kt&oacute;ra tworzy nam formułę</p>
<p>Do szczęścia potrzebujemy pomocniczej funkcję, kt&oacute;ra zamieni nam zapis cyfrowy kolumny na napis alfabetyczny.</p>
<pre><code class="hljs  vbnet  ">Function Col_Letter(lngCol) As String
    Dim vArr
    vArr = Split(Cells(1, lngCol).Address(True, False), "$")
    Col_Letter = vArr(0)
End Function</code></pre>
<p>Dzięki temu teraz stworzyć lepszą wersję metody, kt&oacute;ra zadziała dla kom&oacute;rek od C3 do N40.&nbsp;</p>
<pre><code class="hljs  vbnet  ">Sub GenerowanieSumy2C3toN40()
    
    For x = 3 To 40
        For y = 1 To 12
             ColumnLetter = GenerowanieSumy.Col_Letter(y)
            
             Dim CELL As String
             CELL = ColumnLetter &amp; CStr(x)
             Call GenerowanieSumy.GenerowanieSumyWithParameter(CELL)

        Next y
    Next x

End Sub</code></pre>
<p>To wszystko.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>excel-vba</category></item><item><title>ImageGlass : Najlepsze przeglądanie zdjęć</title><link>https://cezarywalenciuk.pl/blog/programing/imageglass-spider-najlepsze-przegladanie-zdjec</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/imageglass-spider-najlepsze-przegladanie-zdjec</guid><pubDate>Mon, 21 Feb 2022 10:39:00 GMT</pubDate><description><![CDATA[<p>[metro] Nie podoba Ci się systemowa przeglądarka zdjęć w systemie Windows. Witaj w klubie. Są rzeczy, które mnie denerwują w domyślnej przeglądarce zdjęć jak brak możliwości przejścia do następnego zdjęcia, gdy mam powiększone obecne. </p>
<p>Brakuje mi też możliwość szybkiego obrotu poziomowego/pionowego zdjęcia oraz możliwości wycinania interesującego mnie fragmentu.</p>
<p>To nie są zaawansowane funkcje, ale nie chce otwierać edytora graficznego ("Paint.NET") , aby zrobić takie podstawowe operacje.</p>
<p>Próbowałem znaleźć program, który by mnie zadowolił.</p>
<p>InfraView to prawie jest to co chce, ale irytują mnie jego ikony, które są potem podpięte do każdego pliku z obrazkiem. Taki mam z nim problem chyba od 2008 roku.</p>
<p>Wiele innych programów odpadło, ponieważ mają one też sporą listę rzeczy, których <strong>nie chce mieć w takim programie</strong>. [more]</p>
<ul>
<li>Dodatkowych pod okien
<ul>
<li>Z informacją o tym, w jakim folderze obecnie się znajduje</li>
</ul>
</li>
<li>Zbyt rażących ikon</li>
<li>Zbyt wielu przycisków, z których i tak nie skorzystam</li>
</ul>
<p>Śmiechem, żartem kiedyś w akcji desperacji wygrzebałem program ACDSee Viewer, z którego korzystał jeszcze mój brat, gdy miał Windows 98. Potem jednak go zgubiłem po którymś formacie systemu. </p>
<p>Postanowiłem dać szanse programowi  ImageGlass i sprawdziłem, czy spełnia on dla mnie wszystkie te warunki.</p>
<p>Oto jak wygląda przeglądanie na nim pliku przezroczystych plików .png</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/imageglass-spider--najlepsze-przegladanie-zdjec/program-01_637810411847213907.png" alt="Działanie programu ImageGlass" width="760" height="530"></p>
<p>Warto zaznaczyć, że tę karuzelę (wszystkich obrazków w danym folderze) na dole można schować. Domyślnie jej nie ma.</p>
<p>Jest tylko obrazek. Powiększanie i przechodzenie do następnej ilustracji żaden sposób siebie nie blokuje. </p>
<p>Gdy robiłem skany swoich notatek to sprawdziłem jak wygodnie jest obracać o 90 stopni obrazki i zapisać  te wprowadzone zmiany bez otwierania kolejnych edytorów graficznych. To samo dotyczy się wycinania.</p>
<p>Oficjalnie ten program dołączam do swoich programów. Program można pobrać tutaj <a href="https://imageglass.org/" target="_blank" rel="noopener">https://imageglass.org/</a></p>
<p>Jakieś wady programu. Najlepiej przegląda się zdjęcia w trybie pełno ekranowym, gdyż powiększanie w trybie okna powoduje zmianę wielkość okna programu. Te zmiany wielkości dla niektórych może wyglądać jak migotanie. Nie wiem, czy jakiś inny program może obejść ten problem, czyli zmieniać swój rozmiar tak szybko, że nie widać tej operacji na monitorze.</p>
<p>Kolejna wada programu też polega na tym, że mimo iż istnieje w nim tryb "pokazów slajdów" to nigdzie w opcjach nie widzę możliwość ustawienia co ile sekund ma się zmienić następny obrazek.</p>
<p>Jeśli nie podobają Ci się ikony programu to możesz to zmienić instalując odpowiedni Theme. Mnie akurat te szare ikony się podobają. </p>
<p>Gdybyś chciał zmienić skróty klawiszowe programu, jak i myszki to też masz taką możliwość,</p>
<p>A jak wyglądają ikony tego programu?</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/imageglass-spider-najlepsze-przegladanie-zdjec/program-02_637810424573411414.PNG" alt="imageglass icons are cool" width="243" height="197"></p>
<p>Jak widzisz ikony odpowiednio oznaczają typy obrazków. Jeśli więc potrzebujesz prostej przeglądarki do zdjęć to polecam ten program.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>triki-z-windows</category></item><item><title>Visitor, Odwiedzający : Wzorce projektowe C#</title><link>https://cezarywalenciuk.pl/blog/programing/visitor-odwiedzajacy-wzorce-projektowe-c</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/visitor-odwiedzajacy-wzorce-projektowe-c</guid><pubDate>Thu, 17 Feb 2022 13:11:09 GMT</pubDate><description><![CDATA[<p>[metro] Drogi czytelniku om&oacute;wiliśmy prawie wszystkie wzorce projektowe z "<strong>Gang of Four</strong>". Do skończenia tej kolekcji wpis&oacute;w pozostało nam om&oacute;wić ostatni wzorzec projektowy, a jest nim wzorzec projektowy "<strong>Visitor</strong>".</p>
<p>Jak najlepiej wyjaśnić ten wzorzec?</p>
<p>Najlepiej jest od razu przeskoczyć do przykładu.</p>
<p>Powiedzmy, że mamy następujące wyrażenie matematyczne, kt&oacute;re dla ułatwienia składa się tylko z liczb (możliwie ułamkowych) i operatora odejmowania.</p>
<p>Oto przykład takiego wyrażenia : <strong>(1.0 - (2.0 - 3.0))</strong></p>
<p>Chcemy teraz zapisać te wyrażenie matematyczne w spos&oacute;b obiektowy. [more]</p>
<p>Na początku stw&oacute;rzmy klasę abstrakcyjną, kt&oacute;ra będzie nam określała wszystkie możliwe wyrażenia.&nbsp;</p>
<pre><code class="hljs cs ">public abstract class Expression {  } 
//na razie puste</code></pre>
<p>Teraz tworzymy klasę, kt&oacute;ra reprezentuje wyrażenie liczbowe.</p>
<pre><code class="hljs cs ">public class DoubleExpression : Expression
{
    private double value;
    public DoubleExpression(double value) { this.value = value; }
}</code></pre>
<p>Oto klasa, kt&oacute;ra określa wyrażenie odejmowania. Przyjmuje ona wartości po lewej i po prawej stronie.</p>
<pre><code class="hljs cs ">public class SubtractionExpression : Expression
{
    private Expression left, right;
    public SubtractionExpression
        (Expression left, Expression right)
    {
        this.left = left;
        this.right = right;
    }
}</code></pre>
<p>Wiemy, do czego chcemy dążyć. Interesują nasz teraz dwie rzeczy:</p>
<ul>
<li>Jak wydrukować te wyrażenie obiektowe jako tekst?</li>
<li>Jak wykonać dane wyrażenie&nbsp; obiektowe, aby otrzymać jego wynik</li>
</ul>
<p>Istnieje wiele sposob&oacute;w na rozwiązanie tych problem&oacute;w.</p>
<h3>Natrętny odwiedzający inaczej&nbsp;Intrusive Visitor</h3>
<p>Najłatwiej by było dodać metodę drukującą do każdego wyrażenia. Tworząc metodę abstrakcyjną wymusimy takie zachowanie na każdym wyrażeniu zapisanym przy pomocy klas.</p>
<pre><code class="hljs cs ">public abstract class Expression 
{
    public abstract void Print(StringBuilder sb);
} 
</code></pre>
<p>Pozostało nam teraz do klas dodać implementacje tej metod.</p>
<pre><code class="hljs cs ">public class DoubleExpression : Expression
{
    public override void Print(StringBuilder sb)
    {
        sb.Append(value.ToString());
    }

    private double value;
    public DoubleExpression(double value) { this.value = value; }
}</code></pre>
<p>Rekurencyjnie i poliformicznie wywołamy metodę "Print()" kt&oacute;ra ostatecznie stworzy nam kompletne wyrażenie w formie tekstowej.</p>
<pre><code class="hljs cs ">public class SubtractionExpression : Expression
{
    public override void Print(StringBuilder sb)
    {
        sb.Append(value: "(");
        left.Print(sb);
        sb.Append(value: "-");
        right.Print(sb);
        sb.Append(value: ")");
    }

    private Expression left, right;
    public SubtractionExpression
        (Expression left, Expression right)
    {
        this.left = left;
        this.right = right;
    }
}</code></pre>
<p>Sprawdźmy jak nasz kod działa.</p>
<pre><code class="hljs cs ">var expression = new SubtractionExpression(
    left: new DoubleExpression(1),
    right: new SubtractionExpression(
    left: new DoubleExpression(2),
    right: new DoubleExpression(3)));

var sb = new StringBuilder();
expression.Print(sb);
Console.WriteLine(sb);</code></pre>
<p>Wydaje się to łatwe, ale wyobraź sobie, że w tej hierarchii wyrażenia masz z 10 klas, kt&oacute;re albo dziedziczą po sobie, albo są mniejszą częścią większego wyrażenia. Każda modyfikacji w tych klasach może wywołać reakcję łańcuchową, kt&oacute;ra przejdzie na wszystkie inne klasy co łamię zasadę "Otwarte-Zamknięte"&nbsp; .</p>
<p>To jednak niejedyny problem</p>
<p>Kolejnym problemem jest złamana zasada "Pojedynczej odpowiedzialności" . W sumie każda klasa teraz&nbsp; odpowiada za drukowanie całego wyrażenia.</p>
<p>Powinniśmy przedstawić oddzielną klasę, kt&oacute;ra skupi się tylko na drukowaniu. Potem też możemy stworzyć oddzielną klasę, kt&oacute;ra skupi się na rozwiązaniu całego wyrażenia.</p>
<h3>Drukarka w stylu Reflective</h3>
<p>Mamy więc swoje wyrażenie w postaci klasy abstrakcyjnej. Tym razem ona nic w sobie nie zawiera.</p>
<pre><code class="hljs cs ">public abstract class Expression2
{

}
</code></pre>
<p>Nasze klasy wyglądają tak samo jak w pierwszym przykładzie.</p>
<pre><code class="hljs cs ">public class SubtractionExpression : Expression2
{
    public Expression2  Right {get;set;}

    public Expression2 Left { get; set; }

    public SubtractionExpression
        (Expression2 left, Expression2 right)
    {
        this.Left = left;
        this.Right = right;
    }
}</code></pre>
<pre><code class="hljs cs ">public class DoubleExpression : Expression2
{
    public double Value { get; set; }
    public DoubleExpression(double value) { this.Value = value; }
}</code></pre>
<p>Gdybyś chciał napisać swoją implementację drukowania to zapewne zrobiłbyś to tak</p>
<pre><code class="hljs cs ">public static class ExpressionPrinter
{
    public static void Print(DoubleExpression e, StringBuilder sb)
    {
        sb.Append(e.Value);
    }
    public static void Print(SubtractionExpression ae, StringBuilder sb)
    {
        sb.Append("(");
        Print(ae.Left, sb); // to się nie kompiluje
        sb.Append("-");
        Print(ae.Right, sb); // to się nie kompiluje
        sb.Append(")");
    }
}</code></pre>
<p>Aby jednak ten kod działał musielibyśmy ustalić, że wewnątrz wyrażenia odejmowania zawsze będą liczby ułamkowe. Co, jeśli będę chciał rozwijać sw&oacute;j program i będę tam w tym wyrażeniu odejmowania umieszczał jeszcze inne wyrażenia.</p>
<p>Nie możemy więc zmodyfikować klasy "SubtractionExpression"</p>
<p>Możemy stworzyć metodę drukującą wszystko i w niej będziemy sprawdzać, z jakim typem wyrażenia mamy do czynienia.&nbsp;</p>
<pre><code class="hljs cs ">public static class ExpressionPrinter
{
    public static void Print(Expression2 e, StringBuilder sb)
    {
        if (e is DoubleExpression de)
        {
            sb.Append(de.Value);
        }
        else if (e is SubtractionExpression ae)
        {
            sb.Append("(");
            Print(ae.Left, sb);
            sb.Append("+");
            Print(ae.Right, sb);
            sb.Append(")");
        }
    }
}</code></pre>
<p>Jest to jakieś rozwiązanie.</p>
<pre><code class="hljs cs ">var expression = new SubtractionExpression(
    left: new DoubleExpression(1),
    right: new SubtractionExpression(
    left: new DoubleExpression(2),
    right: new DoubleExpression(3)));

var sb = new StringBuilder();
ExpressionPrinter.Print(expression, sb);
Console.WriteLine(sb);</code></pre>
<p>Te rozwiązanie oczywiście ma swoją wadę. Bez użycia refleksji w sumie nie masz jak sprawdzić, czy wszystkie możliwe warunki if-else dla wszystkich typ&oacute;w wyrażenia w programie biorą udział w drukowaniu.</p>
<p>Jeśli dojdzie nowe wyrażenie to oczywiście klasę "ExpressionPrinter" musisz zmodyfikować.</p>
<p>Czy można to zrobić lepiej?</p>
<h3>Dynamiczny Odwiedzający : Dynamic Visitor</h3>
<p>Na ratunek przychodzi słowo kluczowe "<strong>dynamic</strong>", kt&oacute;re sprawia, że dany fragment kodu&nbsp; zostanie sprawdzony, dopiero gdy ten kod się uruchomi.</p>
<p>Tradycyjnie sprawdzanie typ&oacute;w odbywa się w trakcie kompilacji programu.</p>
<p>Ten fragment kodu działa.</p>
<pre><code class="hljs cs ">public class ExpressionPrinter2
{
    public void Print(SubtractionExpression se, StringBuilder sb)
    {
        sb.Append("(");
        Print((dynamic)se.Left, sb);
        sb.Append("+");
        Print((dynamic)se.Right, sb);
        sb.Append(")");
    }

    public void Print(DoubleExpression de, StringBuilder sb)
    {
        sb.Append(de.Value);
    }
}</code></pre>
<p>Dalej już nic nie musimy zmieniać.</p>
<pre><code class="hljs cs ">var sb = new StringBuilder();
ExpressionPrinter2 expressionPrinter2 
    = new ExpressionPrinter2();

expressionPrinter2.Print(expression, sb);

Console.WriteLine(sb);</code></pre>
<p>Jakie są problemy z tym rozwiązaniem ?</p>
<ul>
<li>Program działa trochę wolniej niż wcześniej wynika to z działania kodu spos&oacute;b dynamiczny</li>
<li>Jeśli danej metody lub właściwości nie ma to oczywiście dostaniesz wyjątek RunTime Exception</li>
<li>Mogą pojawić się problemy, gdy jeszcze nam dojdzie mechanizm dziedziczenia</li>
</ul>
<p>Dynamiczny odwiedzający ma sens, gdy wiesz, że twoje wyrażenia nie będą aż tak skomplikowane oraz gdy wiesz, że dana metoda jak "Print" nie będzie wywoływana za często.</p>
<p>Przeciwnym wypadku tw&oacute;j system może mieć zbyt duże czkawki.</p>
<h3>Klasyczny Odwiedzający : Classic Visitor</h3>
<p>Jak wygląda klasyczne podejście do wzorca "Visitor". Ot&oacute;ż potrzebne są następujące metody</p>
<ul>
<li>Metoda Visit() wykona operacje wydruku albo liczenia. Nie chcemy mieć tej metod w każdym elemencie wyrażenie. Trzeba to jakoś wydzielić i zaraz Ci pokaże jak</li>
<li>Metoda Accept() do sprawdzenia, czy metoda Visit() powinna się uruchomić. Ta metoda powinna się znajdować w każdym elemencie wyrażenia.</li>
</ul>
<p>Tworzymy więc kolejną wersję naszej klasy abstrakcyjnej.</p>
<p>Tym razem każde wyrażenie musi mieć metodę "Accept()", kt&oacute;ra przyjmuje do siebie klasę, kt&oacute;ra będzie implementować interfejs "IExpressionVisitor".</p>
<pre><code class="hljs cs ">public abstract class Expression3
{
    public abstract void Accept(IExpressionVisitor visitor);
}</code></pre>
<p>Ten interfejs będzie miał w swoim kontrakcie wszystkie metody odwiedzające dla każdego wyrażenia.</p>
<pre><code class="hljs cs ">public interface IExpressionVisitor
{
    void Visit(DoubleExpression de);
    void Visit(SubtractionExpression se);
}</code></pre>
<p>Oto użycie naszej klasy abstrakcyjnej</p>
<pre><code class="hljs cs ">public class SubtractionExpression : Expression3
{
    public Expression3 Right { get; set; }

    public Expression3 Left { get; set; }

    public SubtractionExpression
        (Expression3 left, Expression3 right)
    {
        this.Left = left;
        this.Right = right;
    }

    public override void Accept(IExpressionVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class DoubleExpression : Expression3
{
    public double Value { get; set; }
    public DoubleExpression(double value) { this.Value = value; }

    public override void Accept(IExpressionVisitor visitor)
    {
        visitor.Visit(this);
    }
}</code></pre>
<p>O ile kod się powtarza teraz to też mamy możliwość dodania logiki blokującej wizytę danego elementu.</p>
<p>Teraz pozostało napisać nam trzecią wersję naszej drukarki, kt&oacute;ra będzie implementować interfejs "IExpressionVisitor"</p>
<pre><code class="hljs cs ">public class ExpressionPrinter3 : IExpressionVisitor
{
    StringBuilder sb = new StringBuilder();

    public void Visit(DoubleExpression de)
    {
        sb.Append(de.Value);
    }
    public void Visit(SubtractionExpression se)
    {
        sb.Append("(");
        se.Left.Accept(this);
        sb.Append("+");
        se.Right.Accept(this);
        sb.Append(")");
    }
    public override string ToString() =&gt; sb.ToString();

}</code></pre>
<p>Nie musimy też teraz korzystać ze słowa kluczowego dynamic, dzięki potędze słowa "this", kt&oacute;ra przekaże instancje naszej drukarki do innych metod.</p>
<pre><code class="hljs cs ">var expression = new SubtractionExpression(
new DoubleExpression(1),
new SubtractionExpression(
new DoubleExpression(2),
new DoubleExpression(3)));

var ep = new ExpressionPrinter3();
Console.WriteLine($"{ep}");</code></pre>
<h3>Visitor / Odwiedzający do kalkulacji matematycznej</h3>
<p>Zrobiliśmy przed chwilą drukowanie, a co z liczeniem danego wyrażenia? Nic nie stoi na przeszkodzie, aby napisać innego odwiedzającego, kt&oacute;ry właśnie to zrobi.</p>
<pre><code class="hljs cs ">public class ExpressionCalculator : IExpressionVisitor
{
    public double Result;

    public void Visit(DoubleExpression de)
    {
        Result = de.Value;
    }
    public void Visit(SubtractionExpression se)
    {
        //za chwile
    }
}</code></pre>
<p>Dla odejmowania zrobimy operacje odejmowania. Metoda Visit niczego nigdy nie powinna zwracać więc rezultat odejmowania idzie do innej właściwości.</p>
<pre><code class="hljs cs ">public void Visit(SubtractionExpression se)
{
    se.Left.Accept(this);
    var a = Result;
    se.Right.Accept(this);
    var b = Result;
    Result = a - b;
}</code></pre>
<p>Oto przykład naszego odwiedzającego, kt&oacute;ry umie interpretować wyrażenia matematyczne.</p>
<pre><code class="hljs cs ">var expression = new SubtractionExpression(
new DoubleExpression(1),
new SubtractionExpression(
new DoubleExpression(2),
new DoubleExpression(3)));

var ep = new ExpressionPrinter3();

var calc = new ExpressionCalculator();
calc.Visit(expression);
Console.WriteLine($"{ep} = {calc.Result}");</code></pre>
<p>Teraz możesz stworzyć według tego wzoru kolejnych odwiedzających, kt&oacute;rzy będą operować na tych samym cegiełkach danego wyrażenia.</p>
<p>Co ciekawe każdy nowy odwiedzający nie będzie robił zmian w tych klasach reprezentujących dany elementy wyrażenia.</p>
<p>W ten spos&oacute;b wiemy ,że zasada "Otwarte-Zamknięte" oraz zasada "Pojedynczej odpowiedzialności" jest spełniona. Co czyni kod czytelniejszym&nbsp; i łatwym do zrozumienia.</p>
<h3>Acykliczny Odwiedzający : Acyclic Visitor</h3>
<p>Wzorzec projektowy Visitor, czyli odwiedzający ma jeszcze swoje dwa odłamy.&nbsp;</p>
<ul>
<li>Cykliczny odwiedzający : polega na technice przeciążania metody/funkcji. Z tej techniki skorzystaliśmy do tej pory. Jednakże ten styl ma sens, gdy mamy styczność z hierarchią, kt&oacute;ra jest stabilna i się nie zmienia.</li>
<li>Acykliczny odwiedzający : polega na rzutowaniu. Limitacje związane ze znajomością typu odwiedzającego znikają, ale idzie za tym koszt wydajności.</li>
</ul>
<p>Zobaczmy jak możemy napisać Acyklicznego odwiedzającego:</p>
<p>W naszej drukarce nie potrzebujemy już metody Visit dla każdego typu. Możemy zrobić jedną metodę Visit, kt&oacute;ra rozmnoży się na inne metody generyczne w zależności od typu wyrażenia.</p>
<pre><code class="hljs cs ">public interface IVisitor&lt;TVisitable&gt;
{
    void Visit(TVisitable obj);
}</code></pre>
<p>Potrzebujemy też pusty interfejs, kt&oacute;ry określi nam to, że działamy ze wzorcem odwiedzającego. Poza oznaczeniem ten Interfejs nie robi nic.</p>
<pre><code class="hljs cs ">public interface IVisitor { }</code></pre>
<p>Tworzymy czwartą wersję klasy abstrakcyjne dla wszystkich wyrażeń. Jak widzisz poniższa metoda Accept jest wirtualna, a nie abstrakcyjna. Znaczy, to ,że patrzymy na domyślną implementację tej metody, chociaż możemy to zmienić poprzez przeciążenie tej metody</p>
<pre><code class="hljs cs ">public abstract class Expression4
{
    public virtual void Accept(IVisitor visitor)
    {
        if (visitor is IVisitor&lt;Expression4&gt; typed)
            typed.Visit(this);
    }
}</code></pre>
<p>Co tutaj się dzieje w tej nowej metodzie Accept ? Nasz parametr w tej metodzie musi implementować interfejs og&oacute;lny <strong>IVisitor</strong>.&nbsp;</p>
<p>P&oacute;źniej ten parametr spr&oacute;bujemy z rzutować na interfejs IVisitor&lt;T&gt;, gdzie T będzie obecnym typem, w kt&oacute;rym się znajdujemy.</p>
<p>Jeśli rzutowanie wykona się poprawnie wtedy wywołamy na tym typie metodę Visit().</p>
<p>Nasze elementy wyglądają tak samo. Pamiętaj, że każdy z nich ma tę domyślną implementację metody Visit() z możliwością przeciążenia.</p>
<pre><code class="hljs cs ">public class SubtractionExpression : Expression4
{
    public Expression4 Right { get; set; }

    public Expression4 Left { get; set; }

    public SubtractionExpression
        (Expression4 left, Expression4 right)
    {
        this.Left = left;
        this.Right = right;
    }

}

public class DoubleExpression : Expression4
{
    public double Value { get; set; }
    public DoubleExpression(double value) { this.Value = value; }
}</code></pre>
<p>Do sklejenia tego podejścia "Acyklicznego" potrzebujmy jeszcze drukarki, kt&oacute;ra nam wyświetli te wyrażenie w formie tekstowej.&nbsp;</p>
<pre><code class="hljs cs ">public class ExpressionPrinter : IVisitor,
IVisitor&lt;Expression4&gt;,
IVisitor&lt;DoubleExpression&gt;,
IVisitor&lt;SubtractionExpression&gt;
{
    StringBuilder sb = new StringBuilder();
    public void Visit(DoubleExpression de) {  }
    public void Visit(SubtractionExpression ae) {  }
    public void Visit(Expression4 obj)
    {
        // domyślne zachowanie if i else
    }
    public override string ToString() =&gt; sb.ToString();
}</code></pre>
<p>Nasza drukarka musi implementować "IVisitor" i wszystkie generyczne pod typy interfejsu "IVisitor&lt;T&gt;" dla każdego wyrażenia, kt&oacute;re istnieje systemu.</p>
<p>Niestety i tutaj pojawia się problem, z tym że nasze złożone wyrażenia zawierają po prostu definicję wyrażenia og&oacute;lnego "Expression4".</p>
<pre><code class="hljs cs ">public class ExpressionPrinter4 : IVisitor,
IVisitor&lt;Expression4&gt;,
IVisitor&lt;DoubleExpression&gt;,
IVisitor&lt;SubtractionExpression&gt;
{
    StringBuilder sb = new StringBuilder();
    public void Visit(Expression4 obj)
    {
        if (obj is DoubleExpression)
            Visit(obj as DoubleExpression);
        if (obj is SubtractionExpression)
            Visit(obj as SubtractionExpression);
    }

    public void Visit(DoubleExpression de) 
    {
        sb.Append(de.Value);
    }
    public void Visit(SubtractionExpression se) 
    {
        sb.Append("(");
        se.Left.Accept(this);
        sb.Append("-");
        se.Right.Accept(this);
        sb.Append(")");
    }
    public override string ToString() =&gt; sb.ToString();
}</code></pre>
<p>Znowu albo napiszemy if i else, aby obsłużyć taki problem ,albo skorzystam ze słowa kluczowego dynamic kosztem wydajności.</p>
<pre><code class="hljs cs ">public void Visit(Expression4 obj)
{
    Visit((dynamic)obj);
}</code></pre>
<p>Sprawdźmy, czy podejście acykliczne działa pisząc taki kod:</p>
<pre><code class="hljs cs ">var expression4 = new SubtractionExpression(
    left: new DoubleExpression(1),
    right: new SubtractionExpression(
    left: new DoubleExpression(2),
    right: new DoubleExpression(3)));

ExpressionPrinter4 expressionPrinter4
    = new ExpressionPrinter4();

expressionPrinter4.Visit(expression4);
Console.WriteLine(expressionPrinter4);</code></pre>
<p>Pamiętaj też, że w metodzie og&oacute;lnej Visit() w naszej drukarce możemy także wyrzucać wyjątki, gdy trafi się metoda Visit, kt&oacute;rą nie obsługujemy.&nbsp;</p>
<h3>Podsumowanie</h3>
<p>Wzorzec projektowy Visitor pozwala ci dodawać unikatowe zachowania dla każdego elementu danego wyrażenia, kt&oacute;re też znajduje się w jakieś hierarchii.&nbsp;</p>
<p>Oto na ile sposob&oacute;w możesz skorzystać z tego wzorca :</p>
<ul>
<li>Intrusive : Dodajemy metodę do każdego elementu określającego cegiełki naszego wyrażenia. P&oacute;źniej zgodnie z hierarchią wykonasz zbi&oacute;r operacji na tych cegiełkach. Te podejście łamie zasadę otwarte-zamknięte oraz zasadę pojedynczej odpowiedzialności.</li>
<li>Reflective : Dodajemy oddzielną klasę w tym przypadku klasę operacji drukowania tak, aby nie robić zmian w naszych element. Takich oddzielnych klas możesz mieć wiele. Przykładowo stworzyliśmy inną klasę, kt&oacute;ra wykonywała operacje matematyczne na naszym wyrażeniu.</li>
<li>Dynamic : Zamiast pisać if i else możesz rzutować dany pod element na typ dynamiczny. To wszystko się stanie kosztem szybkości działania programu.</li>
<li>Classic : Cała hierarchia element&oacute;w ulega zmianie tylko raz.&nbsp; Każdy element ma metodę Accept(), kt&oacute;ra przyjmuje danego odwiedzającego.&nbsp;</li>
<li>Acyclic : Daje nam możliwość bardziej elastycznej relacji pomiędzy odwiedzającym a odwiedzanym.&nbsp; Dzięki temu możemy mieć zbi&oacute;r odwiedzających względem jednego zadania.</li>
</ul>
<p>Wzorzec projektowy Visitor łączy się ze wzorcem Interpreter. Dlatego ta dwa wzorce zostawiłem sobie na koniec tego cyklu.&nbsp;</p>
<p>Warto poznać podstawy tego wzorca, zanim skoczysz do wzorca Visitor stworzonych dla kompilatora Roslyn, kt&oacute;ry potrafi analizować składnie napisanego kodu, jak i ją modyfikować.</p>
<p>Ja mogę sobie gratulować, ponieważ w tym cyklu właśnie om&oacute;wiliśmy wszystkie wzorce projektowe z Gang of Four. Pozostało mi przygotować jedno wielkie repozytorium GitHub z tymi wszystkim przykładami.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category><category>wzorce-projektowe</category></item><item><title>Interpreter, Interpretator : Wzorce projektowe C#</title><link>https://cezarywalenciuk.pl/blog/programing/interpreter-interpretator-wzorce-projektowe-c</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/interpreter-interpretator-wzorce-projektowe-c</guid><pubDate>Wed, 16 Feb 2022 10:34:05 GMT</pubDate><description><![CDATA[<p>[metro] Celem wzorca "Interpreter" jest zinterpretować dane wyjściowe zazwyczaj w formacie tekstowym, tak abyśmy mogli wykonać specyficzne akcje. Jednakże dane wyjściowymi nie muszą być koniecznie w formacie tekstowym.&nbsp;&nbsp;</p>
<p>"Interpreter" jest powiązany z kompilatorem. Warto zaznaczyć, że oba pojęcia nie m&oacute;wią dokładnie o tym samym, chociaż można ich używać zamiennie.&nbsp; Przypadku język&oacute;w programowania r&oacute;żnice są takie: [more]</p>
<ul>
<li>Kompilator : kompiluje język programowania do jakiegoś kodu maszynowego. Tak działają języki bazujące na C i C++ jak Java czy C#. W kompilatorze więc zawiera się Interpreter, bo w końcu coś musi zrozumieć składnie kodu, aby go przetransformować.</li>
<li>Interpreter : nie przekształca kodu, tylko go uruchamia interpretując go. Tak działa Perl,Python, Matlab</li>
</ul>
<p>Być może na studiach zostałeś zmuszony napisać prymitywny kompilator, aby zaliczyć przedmiot.&nbsp; Tak pisanie kodu w innym języku programowania, aby potem dostać kod maszynowy w wyniku kompilacji, jest też formą wzorca "Interpreter".</p>
<p>"Interpreter" też może się ukrywać pod inną nazwą jak:</p>
<ul>
<li>"Parsers" tłumaczonych potocznie na polski parsery.</li>
<li>oraz Analizatory składniowe</li>
</ul>
<p>Jakie przykładowe parsery mamy w .NET i og&oacute;lnie w programowaniu:</p>
<p><strong>Wyrażenia regularne (Regular expressions)</strong> : Co robimy, aby znaleźć specyficzne fragmenty tekstu ze skomplikowanymi zasadami.Przykładowo jakbyś napisał kod, kt&oacute;ry by wyszukał wszystkie wystąpienia adres&oacute;w e-mail w tekście. Na pomoc przychodzą wyrażenia regularne, kt&oacute;re mają swoja składnie i specyficzne zasady. Każde wyrażenie jak na przykład te...</p>
<pre><code class="hljs  ">(?:[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])</code></pre>
<p>...potrzebuje swojego interpretera.</p>
<p><strong>Struktury danych jak CSV, XML, JSON, YAML</strong> wymagają odpowiedniego interpretera, aby mogły być odczytane i użyte.</p>
<p><strong>Wyrażenia drzewiaste</strong> są kompilowane na wyrażenia lambda, czyli mają swojego interpretera.&nbsp;</p>
<p>Każdy język programowania ma sw&oacute;j <strong>kompilator</strong>, kt&oacute;ry interpretuje kod i go transmituje na coś innego. Przykładowo TypeScript może być przetransformowany na język JavaScript poprzez interpretacje.&nbsp;</p>
<p>Na potrzeby tego wpisu. Stw&oacute;rzmy więc sw&oacute;j własny interpretator. Będziemy analizować tekst i zamieniać go na tokeny. Ta część programu nazywa się "Lexer".&nbsp;</p>
<p>P&oacute;źniej te tokeny zamienimy na poszczeg&oacute;lne wartości, czyli będziemy "Parsować".</p>
<h3>Co budujemy?</h3>
<p>Naszym celem jest napisać program, kt&oacute;ry przetłumaczy tekst pisany w stylu "<strong>/u/l/{tekst</strong>" na wyrażenie HTML.</p>
<p>Oto dwa przykłady tego, jak ma m&oacute;j program ma działać.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/interpreter-interpretator--wzorce-projektowe-c/inter-01_637806121764367596.PNG" alt="Działanie naszego programu Interpreter" width="272" height="228" /></p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/interpreter-interpretator--wzorce-projektowe-c/inter-02_637806121764491581.PNG" alt="Działanie naszego programu Interpreter" width="304" height="224" /></p>
<p>Przykład nie jest idealny, ale jeśli będziesz budował sw&oacute;j interpreter oto cegiełki do niego.</p>
<h3>Lexing</h3>
<p>Pierwsza operacja, kt&oacute;ra nas interesuje będzie polegać na zmianie polecenia tekstowego na zbi&oacute;r token&oacute;w.</p>
<p>Z czego powinien składać się nasz token. Powinien mieć informację o swoim typie oraz o przetrzymywanej wartości, kt&oacute;ra będzie oznacza r&oacute;żne rzeczy w zależności od kontekstu czy swojego typu.</p>
<pre><code class="hljs cs ">public class Token
{
    public enum Type
    {
        Ul, Li, Text, 
    }

    public Type MyType;
    public string Text;
    public Token(Type type, string text)
    {
        MyType = type;
        Text = text;
    }
    public override string ToString()
    {
        return $"`{Text}`";
    }
}</code></pre>
<p>Teraz musimy mieć metodę, kt&oacute;ra przetłumaczy te wyrażenie tekstowe na zbi&oacute;r token&oacute;w</p>
<pre><code class="hljs cs ">IReadOnlyList&lt;Token&gt; Lex(string input)
{
    string[] words = input.Split("/");
    var result = new List&lt;Token&gt;();

    foreach (var word in words)
    {
        string text = "";
        if (word.Length &gt; 1)
            text = word.Substring(1);
        if (word.Length == 0)
            continue;

        Token t;
        if (word[0] == 'u')
             t = new(Token.Type.Ul, text);
        else if(word[0] == 'l')
            t = new(Token.Type.Li, text);
        else if(word[0] == '{')
            t = new(Token.Type.Text, text);
        else
            t = new(Token.Type.Text, "");

        result.Add(t);
    }

    return result;
}
</code></pre>
<p>Logika jest następująca. Jeśli pierwszy znak to 'u' to wtedy uznaje, że chce wstawić element html &lt;ul&gt; gdzie dalszy tekst jest określeniem jego klasy CSS.</p>
<p>Analogicznie postępuje dla znaku 'l', kt&oacute;ry określa mi, że chce wstawić element html &lt;li&gt;.</p>
<p>Natomiast nawias klamrowy '{' m&oacute;wi, że teraz mam wstawić token tekstowy. M&oacute;głbym umieścić więcej typ&oacute;w token&oacute;w, ale my chcemy prosty przykład, kt&oacute;ry pomoże ci w przyszłości napisać podobny kod.</p>
<h3>Parsowanie</h3>
<p>Parsowanie będzie polegało na transformacji uniwersalnych token&oacute;w na dokładniejsze klasy, kt&oacute;re określą nam potem ostateczny wynik tekstowy danego wyrażenia.</p>
<p>Pisząc taki interfejs łamię zasadę "Interface Segregation Principle".</p>
<p>Nie każda właściwość będzie używana przez klasy implementujące ten interfejs , ale tak m&oacute;wiłem ten przykład nie jest idealny.</p>
<pre><code class="hljs cs ">public interface IElement
{
    string Value { get; }

    string AfterValue { get; }

    public IElement Child { get; set; }
}</code></pre>
<p>Teraz stw&oacute;rzmy klasy, kt&oacute;re będą określać zachowanie moich poszczeg&oacute;lnych element&oacute;w wyrażenia.</p>
<p>Tak wygląda element tekstowy:</p>
<pre><code class="hljs cs ">public class TextElement : IElement
{
    public TextElement(string value)
    {
        Value = "    "+ value;
    }
    public string Value { get; }

    public IElement Child { get; set; }

    public string AfterValue =&gt; "";
}</code></pre>
<p>Tak będzie wyglądać element &lt;li&gt; i &lt;ul&gt;.</p>
<pre><code class="hljs cs ">public class UlElement : IElement
{
    public UlElement(string cssclass)
    {
        if (!string.IsNullOrWhiteSpace(cssclass))
            Value = $"&lt;ul class=\"{cssclass}\"&gt;";
        else
            Value = $"&lt;ul&gt;";

        AfterValue = "&lt;/ul&gt;";
    }
    public string Value { get; }


    public IElement Child { get; set; }

    public string AfterValue { get; }
}

public class LiElement : IElement
{
    public LiElement(string cssclass)
    {
        if (!string.IsNullOrWhiteSpace(cssclass))
            Value = $"&lt;li class=\"{cssclass}\"&gt;";
        else
            Value = $"&lt;li&gt;";

        AfterValue = "&lt;/li&gt;";
    }
    public string Value { get; }

    public IElement Child { get; set; }

    public string AfterValue { get; }
}</code></pre>
<p>Potrzebuje też klasy rodzica, kt&oacute;ry będzie mi przetrzymywać te wszystkie elementy, a potem całe te wyrażenia przetworzy na ostateczny tekst.</p>
<p>Ponieważ mamy element w elemencie to nie obędzie się bez rekurencji.</p>
<pre><code class="hljs cs ">public class HtmlOperation : IElement
{
    public IElement Child { get; set; }
    public string Value
    {
        get
        {
            StringBuilder sb = new StringBuilder();

            sb = Rec(sb,Child);

            return sb.ToString();
        }
    }

    public string AfterValue =&gt; "";

    private StringBuilder Rec(StringBuilder sb, IElement e)
    {
        sb.AppendLine(e.Value);

        if (e.Child != null)
            Rec(sb, e.Child);

        if (e.AfterValue != null &amp;&amp; e.AfterValue != "")
            sb.AppendLine(e.AfterValue);

        return sb;
    }
}</code></pre>
<p>Teraz gdy mamy wszystkie elementy układanki to pozostało nam to napisać metodę, kt&oacute;ra zamieni te tokeny na nasze konkretne cegiełki HTML.</p>
<pre><code class="hljs cs ">static HtmlOperation Parse(IReadOnlyList&lt;Token&gt; tokens)
{
    HtmlOperation root = new HtmlOperation(); 
    bool haveLHS = false;
    for (int i = 0; i &lt; tokens.Count; i++)
    {
        var token = tokens[i];
        // look at the type of token
        switch (token.MyType)
        {
            case Token.Type.Text:
                Insert(root,new TextElement(token.Text));
                break;
            case Token.Type.Ul:
                Insert(root, new UlElement(token.Text));
                break;
            case Token.Type.Li:
                Insert(root, new LiElement(token.Text));
                break;
        }
    }
    return root;
}</code></pre>
<p>Do tworzenia mojej struktury znowu będzie potrzebna rekurencja.</p>
<pre><code class="hljs cs ">static IElement Insert(IElement root, IElement nextchild)
{
    if (root.Child != null)
        return Insert(root.Child, nextchild);
    else
    {
        root.Child = nextchild;
        return root;
    }
}</code></pre>
<p>Program ten nie jest idealny. Po pierwsze nie łapie wszystkim możliwych nieprawidłowych wartości. Czy ten przykład można rozwijać? Oczywiście, że tak można do niego dodać kolejne tagi HTML i inne skr&oacute;towe oznaczenia, kt&oacute;re wydrukują się specyficznie w tekście końcowym.</p>
<h3>Użycie naszego Parsera i Lexera.&nbsp;</h3>
<p>Oto użycie naszego Parsera i Lexera. Program działać jak na obrazkach powyżej.</p>
<pre><code class="hljs cs ">var input = "/ucssklaska/ltotezklaska/{tekst";

var tokens = Lex(input);

var parsed = Parse(tokens);

Console.WriteLine($"Ten zapis :\n");
Console.WriteLine($"{input} = \n");
Console.WriteLine($"Daje to :\n");
Console.WriteLine($"{parsed.Value}");</code></pre>
<h3>Podsumowanie</h3>
<p>Wzorzec projektowy "Interpreter" jest bardzo rzadki. Pisanie swoich parser&oacute;w zazwyczaj dotyczy jakiś błahych przykład&oacute;w więc nie trzeba pisać tylu klas, aby wykonać swoje zadanie.</p>
<p>Nawet ten prosty przykład wzorca można było napisać po prostu dużą grupą warunk&oacute;w if i else. Chociaż z drugiej strony, jeśli planuje swojego Interpretera rozwijać to rozbicie tego kodu na tokeny i elementy do parsowania ma moim zdaniem sens.</p>
<p>Jeśli interesuje Cię tematyka pisania własnych kompilator&oacute;w to trochę źle trafiłeś. Mogę Cię odesłać do framework&oacute;w jak<strong> "Lex/Yacc" i "ANTLR".</strong></p>
<p>Swoją przygodę też możesz zacząć od pisania plugin&oacute;w do analizy kodu w samym Visual Studio.</p>
<p>Kompilator Roslyn to też temat rzeka, kt&oacute;ry nawet mnie kiedyś wciągnął do generowania dynamicznego całych projekt&oacute;w z kodem w Visual Studio.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category><category>wzorce-projektowe</category></item><item><title>Proxy : Wzorzec projektowy C#</title><link>https://cezarywalenciuk.pl/blog/programing/proxy-wzorzec-projektowy-c</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/proxy-wzorzec-projektowy-c</guid><pubDate>Tue, 15 Feb 2022 08:57:13 GMT</pubDate><description><![CDATA[<p>[metro] We wzorcu projektowy "Dekorator" widzieliśmy jak można dodawać kolejne funkcjonalności bez zmiany oryginalnego zachowania. Wzorzec Proxy pr&oacute;buje osiągnąć to samo tylko gorzej. Warto zaznaczyć, że ten wzorzec nie ma jednej słusznej implementacji. Wiele os&oacute;b podchodzi do tego wzorca na wiele sposob&oacute;w.&nbsp;</p>
<p>Gdyby pada słowo "Proxy" to zazwyczaj m&oacute;wimy o pośredniku komunikacyjnym między serwerami, kt&oacute;re gadają do siebie.</p>
<p>Wzorzec projektowy Proxy też jest takim pośrednikiem między obiektami i jego rola polega na <strong>zabezpieczeniu,rozszerzeniu, zmodyfikowaniu</strong> jakieś innej funkcji systemu, kt&oacute;ra jest&nbsp; pod nim.</p>
<p>W zależności od celu wzorzec ten będzie miał inną implementację. Dlatego nie ma on jednego dobrego podejścia.</p>
<p>Sp&oacute;jrz więc na te r&oacute;żne podejścia do tego wzorca. [more]:</p>
<ul>
<li>Protection Proxy,</li>
<li>Property Proxy,</li>
<li>Virtual Proxy&nbsp;</li>
<li>Communication Proxy</li>
<li>i wiele innych, kt&oacute;re możesz znaleźć w sieci</li>
</ul>
<h3>Protection Proxy</h3>
<p>Idea "Protection proxy" jak sama nazwa wskazuje polega na zabezpieczeniu pewnego obiektu przed jakimś warunkiem biznesowym.</p>
<p>Mam więc komputer, kt&oacute;ry potrafi uruchamiać kod.</p>
<pre><code class="hljs cs ">public class Computer : IComputer 
{
    public void RunCode()
    {
        Console.WriteLine("WriteCode");
    }
}</code></pre>
<p>P&oacute;źniej stwierdziłem, że nie każdy programista przed komputerem będzie m&oacute;gł sw&oacute;j kod uruchomić. Wymyśliłem taką zasadę biznesową, że jeśli jesteś programistą XML lub HTML to niestety komputer powinien odm&oacute;wić uruchomienia twojego kodu.</p>
<p>Z jakiegoś powodu nie mogę zmienić już swojej klasy Computer, aby to zrobić. Przyzwyczaja się do tego, bo zazwyczaj przez to, że nie możemy czegoś zmienić tworzymy klasy Proxy w każdym przykładzie w tym wpisie.</p>
<p>Postanowiłem napisać interfejs, kt&oacute;ry w żaden spos&oacute;b nie modyfikuje już istniejącej klasy Computer.</p>
<pre><code class="hljs cs ">public interface IComputer
{
    void RunCode();<br />}</code></pre>
<p>Nasze proxy będzie zależne od "programisty". Ma on informację o tym, w czym on programuje.</p>
<pre><code class="hljs cs ">public class Programer
{
    public PrograminLanguage Language { get; set; }
    public Programer(PrograminLanguage programinLanguage)
    {
        Language = programinLanguage;
    }
}

public enum PrograminLanguage
{
    JavaScript,
    CSharp,
    Java,
    HTML,
    XML
}</code></pre>
<p>Jak wygląda nasze proxy? Jak widzisz pobieram sobie w konstruktorze instancje danego programisty.&nbsp;</p>
<pre><code class="hljs cs ">public class ComputerProxy : IComputer
{
    private Computer computer = new Computer();
    private Programer _programer;

    public ComputerProxy(Programer programer)
    {
        _programer = programer;
    }

    public void RunCode()<br />    {
        if (_programer.Language == PrograminLanguage.HTML)
        {
            Console.WriteLine("Html nie jest językiem programowania więc tego nie uruchamiam");
            return;
        }
        if (_programer.Language == PrograminLanguage.XML)
        {
            Console.WriteLine("XML nie jest językiem programowania więc tego nie uruchamiam");
            return;
        }

        computer.RunCode();
    }
}</code></pre>
<p>P&oacute;źniej w zależności od tego, w czym nasz programista programuje to albo uruchomimy kod, albo wyświetliły wiadomość o tym właśnie naszym zabezpieczeniu przed programistami XML i HTML.</p>
<p>Oto jak wygląda użycie naszego proxy.</p>
<pre><code class="hljs cs ">IComputer com = new ComputerProxy
    (new Programer(PrograminLanguage.HTML));
com.ExecuteCode();</code></pre>
<p>Czy ten wzorzec jest dobry? Wiesz, że lubię zadawać takie pytania. Największy problem tego wzorca leży w tym, że o ile może się wydawać, że klasy Computer i ComputerProxy są podobne to mają zupełnie inne konstruktory.</p>
<p>Czyli jeśli ComputerProxy ma służy za rozszerzenie istniejącej już funkcjonalności to ciężko tobię będzie zastąpić istniejące już użycia klasy Computer w systemie.</p>
<p>Zwłaszcza jeśli nie korzystałeś ze wstrzykiwania zależności i musi teraz taką poprawę zrobić w całym systemie.</p>
<p>Ten wzorzec ma sens, jeśli masz kontener wstrzykiwania zależności w przeciwnym wypadku niczego tak naprawdę nie rozwiązujesz i tak naprawdę tworzysz sobie kolejny problem na przyszłość.</p>
<h3>Property Proxy</h3>
<p>W C# korzysta z właściwości, kt&oacute;re tak naprawdę są dobrze opakowanymi metodami GET i SET dla danego pola.</p>
<p>Czasami chcesz, aby twoje właściwości robiły coś jeszcze, gdy następuje proces przypisania wartość lub jej pobierania.</p>
<p>Dla tego przykładu chcemy zablokować przypisanie wartości do naszej właściwości, gdy pr&oacute;bujemy ustawić jej wartość na domyślną albo na null.</p>
<p>Korzystając z tego faktu możemy także w konsoli wyświetlić jakie wartości są w og&oacute;le przypisywane do naszej właściwości.</p>
<p>Moglibyśmy skorzystać z normalnych właściwości, aby osiągnąć ten cel, ale my chcemy zobaczyć użycie wzorca Proxy, kt&oacute;ry nazywa się Property Proxy.</p>
<p>Property Proxy opakuje nam każdą właściwości, abyśmy od razu mieli takie zachowanie. Potem będziemy mogli skorzystać z naszej klasy "Property Proxy" w wielu miejscach.</p>
<p>To oczywiście odbije się kosztem wydajności, ale zał&oacute;żmy, że nagle musimy do 1000 właściwości w r&oacute;żnych klasach takie zachowanie dopisać i musisz tę zmianę w kodzie jakoś zautomatyzować do dalszych poprawek.</p>
<p>Oto jak wygląda nasza klasa, kt&oacute;ra stworzy nam "Property Proxy".</p>
<pre><code class="hljs cs ">public class Property&lt;T&gt; where T : new()
{
    private T value;
    private readonly string name;
    public T Value
    {
        get =&gt; value;
        set
        {
            if (Equals(this.value, value)) return;
            if (value == null || value.Equals(default(T))) return;

            Console.WriteLine($"Assigning {value} to {name}");
            this.value = value;
        }
    }
    public Property() : this(default(T)) { }
    public Property(T value, string name = "")
    {
        this.value = value;
        this.name = name;
    }</code></pre>
<p>Zwr&oacute;ć uwagę na blokadę uzupełniania wartości w tej klasie.</p>
<pre><code class="hljs cs ">set
{
    if (Equals(this.value, value)) return;
    if (value == null || value.Equals(default(T))) return;

    Console.WriteLine($"Assigning {value} to {name}");
    this.value = value;
}</code></pre>
<p>...</p>
<p>Musi on zawierać jawną konwersję na typ, kt&oacute;ry będziemy opakowywać.</p>
<pre><code class="hljs cs ">public static implicit operator T(Property&lt;T&gt; property)
{
    return property.Value; 
}
public static implicit operator Property&lt;T&gt;(T value)
{
    return new Property&lt;T&gt;(value); 
}</code></pre>
<p>Pozostało nam już użyć tego wzorca:</p>
<pre><code class="hljs cs ">public class Programer
{
    public Property&lt;int&gt; Luck
      = new Property&lt;int&gt;(10, nameof(Luck));

    public Property&lt;int&gt; Wisdom
        = new Property&lt;int&gt;(6, nameof(Wisdom));

    public Property&lt;int&gt; Intelligence
        = new Property&lt;int&gt;(7, nameof(Intelligence));
}</code></pre>
<p>Niestety takie podejście nie działa. Ten kod nie zrobi nic.</p>
<pre><code class="hljs cs ">var p = new Programer();
p.Wisdom = 12;
p.Luck = 12;
p.Intelligence = 0;</code></pre>
<p>Problem leży w jawnej konwersji naszego proxy, kt&oacute;ra niestety nie potrafi modyfikować istniejącej właściwości. Taki kod zadziała, ale nie o to nam chodzi:</p>
<pre><code class="hljs cs ">var p1 = new Programer();
p1.Wisdom.Value = 12;
p1.Luck.Value = 12;
p1.Intelligence.Value = 0;</code></pre>
<p>Rozwiązanie polega na stworzeniu p&oacute;l, kt&oacute;re będą opakowane naszym proxy, a te pola będą referować się do istniejących już właściwości&nbsp;</p>
<pre><code class="hljs cs ">public class Programer2
{
    public readonly Property&lt;int&gt; luck
    = new Property&lt;int&gt;(10, nameof(luck));

    public readonly Property&lt;int&gt; wisdom = 
        new Property&lt;int&gt;(6, nameof(wisdom));

    public readonly Property&lt;int&gt; intelligence =
    new Property&lt;int&gt;(7, nameof(intelligence));

    public int Luck
    {
        get =&gt; luck.Value;
        set =&gt; luck.Value = value;
    }

    public int Wisdom
    {
        get =&gt; wisdom.Value;
        set =&gt; wisdom.Value = value;
    }

    public int Intelligence
    {
        get =&gt; intelligence.Value;
        set =&gt; intelligence.Value = value;
    }
}</code></pre>
<p>Teraz ten kod zadziała.&nbsp;</p>
<pre><code class="hljs cs ">var p2 = new Programer2();
p2.Wisdom = 12;
p2.Luck = 12;
p2.Intelligence = 0;</code></pre>
<p>Ten wzorzec wygląda kiepsko, ponieważ trzeba stworzyć oddzielną klasę do tego wzorca, jak i zmodyfikować istniejący już kod, a tego właśnie chcieliśmy uniknąć używając wzorca Proxy.</p>
<p>Te problemy mogą wynikać z ograniczeń języka C#, ale ciężko jest obronić ten wzorzec. Zmodyfikowaliśmy lekko kod, aby w teorii nie pisać jeszcze więcej kodu, kt&oacute;ry będzie się powtarzać, ale to od Ciebie zależy decyzja czy ten wzorzec ma sens.</p>
<h3>Virtual Proxy</h3>
<p>Virtual Proxy już istnieje i nazywa się Lazy&lt;T&gt;.</p>
<p>Lazy&lt;T&gt; utworzy Ci instancje obiektu, gdy ten będzie potrzebny. Chodzi o to, aby uniknąć czkawki przy pierwszym uruchomieniu całego systemu, gdy wszystkie obiekty się tworzą.</p>
<p>Na potrzeby wpisu szybko utworzę swoje Wirtualne Proxy.</p>
<pre><code class="hljs cs ">interface IFile
{
    void Show();
}
</code></pre>
<p>Rola "Virtual Proxy" polega na użyciu oryginalnego API tak, aby stworzyć iluzję jego instancji. A tak naprawdę to oryginalne API zostanie utworzone, dopiero gdy będzie ono potrzebne.</p>
<p>Oto klasa, kt&oacute;ra odczyta plik tekstowy. Jak widzisz operację odczytu pliku zrobi się już w konstruktorze.</p>
<pre><code class="hljs cs ">class TextFile : IFile
{
    private readonly string _path;

    private readonly List&lt;string&gt; _texts;

    private int _lines;
    public TextFile(string path)
    {
        this._path = path;
        _texts = new List&lt;string&gt;();

        using (StreamReader file = new StreamReader(_path))
        {
            int counter = 0;
            string ln;

            while ((ln = file.ReadLine()) != null)
            {
                _texts.Add(ln);
                counter++;
            }
            file.Close();
            _lines = counter;

        }
    }

    public void Show()
    {
        foreach (var item in _texts)
        {
            Console.WriteLine("===========");
            Console.WriteLine(item);
        }
        Console.WriteLine($"How many lines : {_lines}");
    }
}</code></pre>
<p>Chcielibyśmy jednak aby ten konstruktor nie pr&oacute;bował otworzyć pliku natychmiast.</p>
<pre><code class="hljs cs ">TextFile file = new TextFile(@"D:\text.txt");</code></pre>
<p>Zał&oacute;żmy, że nie możemy zmieniać tej istniejącej klasy, czyli musimy napisać swoje proxy, kt&oacute;re nam zmodyfikuje takie zachowanie.</p>
<p>Oto nasze "Virtual Proxy"</p>
<pre><code class="hljs cs ">class LazyTextFile : IFile
{
    private readonly string _path;

    private TextFile _textFile;

    public LazyTextFile(string path)
    {
        this._path = path;
    }

    public void Show()
    {
        if (_textFile == null)
            _textFile = new TextFile(this._path);

        _textFile.Show();
    }
}</code></pre>
<p>Ta klasa stworzy instancje "TextFile" gdy ten będzie potrzebny, gdy skorzystamy z metody "Show()".</p>
<p>Obie klasy korzystają z tego samego interfejsu i mają taki sam konstruktor. Znaczy to, że łatwo możesz podmienić implementacje.</p>
<pre><code class="hljs cs ">LazyTextFile file = new LazyTextFile(@"D:\text.txt");

file.Show();</code></pre>
<h3>Communication Proxy</h3>
<p>Zadaniem tego proxy jest zasymulować zachowanie pewnego serwisu i jego metody, gdy ten znajduje się gdzieś w innym zasobie sieciowym.</p>
<p>Oto prosty przykład takiego proxy. Mamy interfejs, kt&oacute;ry sprawdza, czy dany serwis działa.</p>
<pre><code class="hljs cs ">interface ICheck
{
    string Check(string message);
}
</code></pre>
<p>W aplikacji ASP.NET Core te sprawdzenie wyglądałoby tak.</p>
<pre><code class="hljs cs ">[Route("api/[controller]")]
public class HomeController : ICheck
{
    [HttpGet("{message}")]
    public string Check(string message)
    {
        return message + "pong";
    }
}
</code></pre>
<p>W innej aplikacji chcemy uruchomić tę metodę. Tylko dla niej te API istnieje jako zas&oacute;b sieciowy, kt&oacute;ry żyje pod jakiś adresem.</p>
<pre><code class="hljs cs ">class RemoteHomeCheck : ICheck
{
    public string Check(string message)
    {
        string uri = "http://localhost:7149/api/home/" + message;
        return new WebClient().DownloadString(uri);
    }
}</code></pre>
<p>Nasze "Communication Proxy" ten problem rozwiąże i z punktu widzenia użytkownika tworzymy iluzję tego, że ta usługa jest w naszej aplikacji.&nbsp;</p>
<p>Chociaż tak naprawdę wysyłamy zapytanie HTTP, aby otrzymać rezultat oryginalnego API.&nbsp;</p>
<h3>Podsumowanie</h3>
<p>W tym wpisie pokazałem Ci wiele r&oacute;żnych proxy. W przeciwieństwie do dekoratora Proxy nie wystawia publicznie oryginalnego API, kt&oacute;re on przykrywa.</p>
<p>Zazwyczaj nie wymaga on modyfikacji oryginalnego&nbsp;kodu, ale jak sam się przekonałeś nie zawsze tak jest.&nbsp;</p>
<p>Analogicznie możemy napisać "Logging Proxy", kt&oacute;ry jak się domyślasz przykrywa oryginalne API tylko po to, aby dodać informację o logowaniu.</p>
<p>Istnieje mn&oacute;stwo wzorc&oacute;w "Proxy". Istnieje szansa, że sam napisałeś w jakimś celu Proxy i nawet o tym nie wiedziałeś, że jest to oficjalny wzorzec projektowy.&nbsp;</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category><category>wzorce-projektowe</category></item><item><title>Flyweight, Pyłek : Wzorce projektowe C#</title><link>https://cezarywalenciuk.pl/blog/programing/flyweight-pylek--wzorce-projektowe-c</link><guid isPermaLink="true">https://cezarywalenciuk.pl/blog/programing/flyweight-pylek--wzorce-projektowe-c</guid><pubDate>Mon, 14 Feb 2022 11:55:53 GMT</pubDate><description><![CDATA[<p>[metro]Flyweight czy Pyłek jest to tymczasowy komponent, kt&oacute;ry jest sprytną referencją do czegoś. Ten wzorzec projektowy najczęściej jest używany, gdy masz dużą ilość podobnych do siebie obiekt&oacute;w i chcesz ograniczyć ilość zajmowanej pamięci poprzez wydzielenie powtarzających się wartości do jednego obiektu, do kt&oacute;rego wszystkie inne obiekty będą się referować.</p>
<p>Sp&oacute;jrz na poniższy przykład [more]</p>
<h3>Miliard kontrakt&oacute;w z wartościami, kt&oacute;re będą się powtarzać</h3>
<p>Wyobraź sobie, że mamy aplikację, w kt&oacute;rej mamy milion użytkownik&oacute;w i każdy z nich ma po 20 r&oacute;żnych kontrakt&oacute;w. Już na podstawie tej informacji wiemy, że pewne informacje będą się powtarzać.&nbsp;</p>
<p>Imiona i nazwiska jak "<em>Jan Kowalski</em>" są bardzo popularne, więc i tu będą powt&oacute;rzenia, nawet gdy m&oacute;wimy o osobnych użytkownikach względem wszystkich kontrakt&oacute;w.</p>
<p>Ciągle też dochodzą inne typy um&oacute;w więc ich wszystkich nigdy nie zdefiniowaliśmy, ale wiemy, że się powtarzają wśr&oacute;d wszystkich użytkownik&oacute;w.</p>
<p>Co możemy z tym zrobić? Pomyśl w teorii obecnie istnieje nawet szansa powt&oacute;rzenia kontraktu, dla kt&oacute;rego klient ma imie i nazwisko "<em>Jan Kowalsk</em>i" i ma typ um&oacute;w "Q<em>WERT SMALL 1289</em>".</p>
<p>Oczywiście, żeby wyr&oacute;żnić unikatowych kontrakt dla konkretnego klient, to powinniśmy mieć jakiś jeszcze unikatowy identyfikator jak PESEL.</p>
<p>My jednak m&oacute;wimy tutaj o problemie związany z tym, że te wszystkie wartości, kt&oacute;re się powtarzają będą zajmować miejsce w pamięci.</p>
<p>Tradycyjnie taką klasę byśmy napisali tak:</p>
<pre><code class="hljs cs ">public class Contract
{
    public string CustomerFullName { get;  }

    public string TypeDeal { get; }

    public Contract(string fullname, string typedeal)
    {
        CustomerFullName = fullname;
        TypeDeal = typedeal;
    }
}
</code></pre>
<p>Musimy taką klasę napisać inaczej.&nbsp;</p>
<pre><code class="hljs cs ">public class Contract2
{
    private static List&lt;string&gt; _cachedNames = new List&lt;string&gt;();
    private int[] flyweightsNames;

    private static List&lt;string&gt; _cachedTypeDeal = new List&lt;string&gt;();
    private int[] flyweightsTypeDeal;</code></pre>
<p>Możemy wszystkie imiona i typy um&oacute;w, kt&oacute;re będą latać w naszej aplikacji przechowywać w statycznej liście. Co więcej, my chcemy zachować poszczeg&oacute;lne wyraz w imionach i nazwiskach oraz typach um&oacute;w i szukać tam duplikat&oacute;w.</p>
<p>Znaczy to, że będziemy mogli przechowywać w jednym miejscu w pamięci wszystkich Jan&oacute;w i Dark&oacute;w niezależnie od tego, jakie mają nazwisko.</p>
<p>To samo tyczy się typ&oacute;w um&oacute;w. Dla typu kontraktu "QWERT SMALL 1289" i "HALO SMALL" będziemy mogli wsp&oacute;łdzielić wyraz "SMALL".</p>
<p>Czym jest "<strong>Flyweight</strong>" w tym kontekście?</p>
<p>Jest nim tablica indeks&oacute;w, kt&oacute;ra będzie referować się do specyficznych wartości w tych statycznych listach. Tablica indeks&oacute;w typu Int32 będzie zajmować dużo mniej pamięci niż całe wyrazy wewnątrz naszego konkretnego kontraktu.</p>
<pre><code class="hljs cs ">public Contract2(string fullname, string typedeal)
{
    int getOrAdd(string s, List&lt;string&gt; cache)
    {
        int idx = cache.IndexOf(s);
        if (idx != -1) return idx;
        else
        {
            cache.Add(s);
            return cache.Count - 1;
        }
    }

    flyweightsNames = fullname.Split(' ')
        .Select(s =&gt; getOrAdd(s, _cachedNames)).ToArray();

    flyweightsTypeDeal = typedeal.Split(' ')
        .Select(s =&gt; getOrAdd(s, _cachedTypeDeal)).ToArray();
}</code></pre>
<p>W trakcie tworzenia naszego kontraktu będziemy umieszczać wydzielone imiona i nazwiska do naszej listy statycznej.</p>
<p>Nasze poprzednie właściwości będą teraz wyciągać imiona i nazwiska oraz typy um&oacute;w z tych statycznych list.</p>
<pre><code class="hljs cs ">public string CustomerFullName =&gt; string.Join(" ",
    flyweightsNames.Select(i =&gt;_cachedNames[i]));

public string TypeDeal =&gt; string.Join(" ",
flyweightsTypeDeal.Select(i =&gt; _cachedTypeDeal[i]));</code></pre>
<p>To wszystko. Rodzi się oczywiście pytanie, czy to rzeczywiście zajmuje mniej pamięci.</p>
<p>Pr&oacute;bowałem paru metod mierzenia pamięci, ale ostatecznie musiałem skorzystać z płatnego narzędzia dotMemory w wersji Trial.</p>
<p>Napisałem taki kod, aby sprawdzić, kt&oacute;ra z wersji będzie zajmować mniej pamięci.&nbsp;</p>
<pre><code class="hljs cs ">List&lt;Contract&gt; list;
{
    List&lt;string&gt; firstNames = new List&lt;string&gt;();

    List&lt;string&gt; surNames = new List&lt;string&gt;();

    List&lt;string&gt; typeDealPart1 = new List&lt;string&gt;();

    List&lt;string&gt; typeDealPart2 = new List&lt;string&gt;();

    list = new List&lt;Contract&gt;();

    for (int i = 0; i &lt; 10; i++)
    {
        var a1 = RandomString();
        var a2 = RandomString();
        var a3 = RandomString();
        var a4 = RandomString();

        for (int j = 0; j &lt; 10; i++)
        {
            list.Add(new Contract
                ($"{a1} {a2}"
                , $"{a3} {a4}"));
        }
        Console.WriteLine(i);
    }

    Console.WriteLine();
}
Console.WriteLine("Done");</code></pre>
<p>Każdy napis będzie składać się z 10 losowych znak&oacute;w. Dla imion i nazwisk oraz typ&oacute;w kontrakt&oacute;w będziemy mieć przynajmniej 10 powt&oacute;rzeń.</p>
<pre><code class="hljs cs ">string RandomString()
{
    Random rand = new Random();
    return new string(
    Enumerable.Range(0, 10)
    .Select(i =&gt; (char)('a' + rand.
    Next(26))).ToArray());
}</code></pre>
<p>Pozostało uruchomić ten program w wersji Trial i zobaczyć, jakie będą wyniki.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/rehsarper-01_637805093200796711.png" alt="Użycie dotmemory w Visual Studio 2022" width="750" height="211" /></p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/dotmemor-01_637805093200922895.PNG" alt="DotMemory" width="508" height="185" /></p>
<p>Parametry testu są takie : mamy p&oacute;ł miliona kontrakt&oacute;w i mamy 10 powt&oacute;rzeń wartości.Czyli co 50.000 kontrakt ma podobne parametry.</p>
<p>Dla tradycyjnego kontraktu wynik wygląda tak.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/dot-01_637805093200953023.PNG" alt="Wyniki dla Contract bez wzorca" width="489" height="51" /></p>
<p>Natomiast wynik dla wzorca FlyWeight wygląda tak.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/a-05_637805093200975186.PNG" alt="Wyniki dla Contract z wzorcem FlyWeight" width="578" height="74" /></p>
<p>Jak widać nas wzorzec dla takich parametr&oacute;w zajął tyle samo pamięci.</p>
<p>Ten niby zysk pamięci wynikający z ilość powt&oacute;rzeń jakoś został odjęty od ilość pamięci, kt&oacute;ra jest potrzebna do stworzenia tablicy liczb całkowitych, kt&oacute;re referują się do przetrzymywanych powtarzających się wartości.</p>
<p>Co trzeba zrobić , aby zobaczyć pozytywny rezultat tego wzorca ? Trzeba zrobić więcej potworzeń wartości.</p>
<p>Warto zaznaczyć, że kod ze wzorcem FlyWeight jest dużo wolniejszy od tradycyjnego podejścia. Śmiałoby powiedział, że ten kod jest 5 razy wolniejszy od tradycyjnego podejścia, chociaż nie mierzyłem tego ze stoperem.</p>
<p>Jak widać użycie tego wzorca musi być przemyślane.</p>
<pre><code class="hljs cs ">List&lt;Contract2&gt; list;
{
    List&lt;string&gt; firstNames = new List&lt;string&gt;();

    List&lt;string&gt; surNames = new List&lt;string&gt;();

    List&lt;string&gt; typeDealPart1 = new List&lt;string&gt;();

    List&lt;string&gt; typeDealPart2 = new List&lt;string&gt;();

    list = new List&lt;Contract2&gt;();

    for (int i = 0; i &lt; 50; i++)
    {
        var a1 = RandomString();
        var a2 = RandomString();
        var a3 = RandomString();
        var a4 = RandomString();

        for (int j = 0; j &lt; 100; j++)
        {
            list.Add(new Contract2
                ($"{a1} {a2}"
                , $"{a3} {a4}"));
        }
        Console.WriteLine(i);
    }

    Console.WriteLine("Done");
    Console.Read();
}</code></pre>
<p>Patrząc na poprzedni przykład możemy zauważyć, że wyniki w Megabajtach&nbsp; nam dużo nie m&oacute;wią dlatego zmniejszy rozmiar generowanej kolekcji, abyśmy mogli zobaczyć r&oacute;żnice wynik&oacute;w na podstawie poszczeg&oacute;lnych bajt&oacute;w.</p>
<p>Poza tym testowanie p&oacute;ł milionowej kolekcji dla wzorca FlyWeight trwało dobre 45 minut. Taka informacja dla Ciebie, gdybyś chciał testować podobny kod.</p>
<p>Parametry testu są takie : mamy 5000 kontrakt&oacute;w i mamy 100 powt&oacute;rzeń wartości. Czyli co 50 kontrakt ma podobne parametry.</p>
<p>Teraz taki wynik mamy dla kontraktu.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/a-04_637805093200992239.PNG" alt="Gorsze wyniki dla Contract bez wzorca" width="517" height="25" /></p>
<p>A taki dla naszego wzorca FlyWeight</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="/Posts/programing/files/2022/flyweight-pyłek--wzorce-projektowe-c/a-04_637805093201009111.PNG" alt="Lepsze wyniki dla Contract z wzorce FlyWeight" width="492" height="35" /></p>
<p>Czego się więc nauczyliśmy? Faktu, że trzeba używać tego wzorca z głową, gdy masz pewność, że liczba powtarzających się wartości w całym systemie przekroczy przynajmniej liczbę 10.</p>
<p>Dodatkowo wzorzec FlyWeight m&oacute;głby zajmować jeszcze więcej pamięci niż tradycyjne podejście, jeślibyśmy nie mielibyśmy potworzeń.</p>
<p>FlyWeight ma sens na pewno, gdy wiesz, że tw&oacute;j wskaźnik zajmuje mniej pamięci niż cały obiekt, do kt&oacute;rego on się referuje.</p>
<p>Wzorzec FlyWeight też ma inne zastosowanie.</p>
<h3>FlyWeight jako token modyfikujący<span style="font-size: 14px;">&nbsp;:&nbsp;</span>Przykład z formatowaniem tekstu</h3>
<p>Wzorzec FlyWeight wymaga często głębokiej analizy, jakie wartości możemy ztokenizować tak, aby nie tworzyć kolejnych instancji pewnego obiektu, aby osiągnąć trochę inny cel przy niewielkiej modyfikacji parametr&oacute;w.</p>
<p>Wyobraź sobie obiekt, kt&oacute;ry przechowuje tekst, kt&oacute;ry ma z milion znak&oacute;w. W nim także mamy meta informację :</p>
<ul>
<li>O wielkości znak&oacute;w w danym obszarze napisu. Przykładowo określamy, że znaki od 15 do 20 w napisie mają być pisane wielką literą.</li>
<li>O kolorze napisu w danym obszarze tego napisu. Przykładowo określamy, że znaki od 15 do 20&nbsp;w napisie&nbsp;mają być pisane na czerwono.</li>
</ul>
<p>W czym jest problem? Sp&oacute;jrz na tą klasę, kt&oacute;ra reprezentuje taki obiekt.</p>
<pre><code class="hljs cs ">public class FormattedText
{
    private string plainText;

    public FormattedText(string plainText)
    {
        this.plainText = plainText;
        _capitalize = new bool[plainText.Length];
        _colorize = new bool[plainText.Length];
    }
    public void Capitalize(int start, int end)
    {
        for (int i = start; i &lt;= end; ++i)
            _capitalize[i] = true;
    }

    public void Colorize(int start, int end)
    {
        for (int i = start; i &lt;= end; ++i)
            _colorize[i] = true;
    }

    public void SetColor(string c)
    {
        _color = c;
    }

    private bool[] _capitalize;
    private bool[] _colorize;
    private string _color;</code></pre>
<p>Tak będziemy generować napis.</p>
<pre><code class="hljs cs ">public void Write()
{
    for (var i = 0; i &lt; plainText.Length; i++)
    {
        var c = plainText[i];

        if (_capitalize[i])
        {
            c = char.ToUpper(c);
        }
        if (_colorize[i])
        {
            if (_color == "Red")
                Console.ForegroundColor = ConsoleColor.Red;
            if (_color == "Green")
                Console.ForegroundColor = ConsoleColor.Green;     
        }

        Console.Write(c);
        Console.ResetColor();
    }
}</code></pre>
<p>Teraz chce zmienić identyczny napis na dwa r&oacute;żne sposoby. Niestety z tego powodu muszę stworzyć dwie instancje swojej klasy. Co oczywiście zajmuje więcej pamięci.</p>
<pre><code class="hljs cs ">var ft = new FormattedText
    ("In the land of the blind, the one-eyed man is king\n");
ft.Capitalize(19, 24);
ft.Colorize(19, 24);
ft.SetColor("Red");
ft.Write();

var ft2 = new FormattedText
    ("In the land of the blind, the one-eyed man is king\n");
ft2.Colorize(19, 24);
ft2.SetColor("Green");
ft2.Write();
</code></pre>
<p>Co możemy z tym zrobić?</p>
<p>Tablica znak&oacute;w, czyli string wypadałoby wydzielić od informacji o tym, jak tekst powinien być w modyfikowany w danych obszarach.</p>
<p>To dam możliwość wielokrotnej modyfikacji tego samego napisu bez tworzenia kolejny instancji podobnych obiekt&oacute;w.</p>
<p>FlyWeight daje nam moc takich token&oacute;w, kt&oacute;re modyfikują część mechanizmu bez tworzenia go całkowicie od nowa jeszcze raz.</p>
<p>Oto nasz obiekt, kt&oacute;ry da nam możliwość modyfikacji formatowania.</p>
<pre><code class="hljs cs ">public class TextRange
{
    public int Start, End;
    public bool Capitalize;
    public bool Colorize;
    public string Color;
    public bool ChooseRange(int position)
    {
        return position &gt;= Start &amp;&amp; position &lt;= End;
    }
}</code></pre>
<p>Nasza klasa też się zmieniła. Mamy teraz możliwość dodania wielu takich modyfikacji obszarowych na raz dla jednego napisu.</p>
<pre><code class="hljs cs ">public class BetterFormattedText
{
    private readonly string plainText;
    private readonly List&lt;TextRange&gt; formatting
    = new List&lt;TextRange&gt;();

    public BetterFormattedText(string plainText)
    {
        this.plainText = plainText;
    }
    public TextRange GetRange(int start, int end)
    {
        var range = new TextRange { Start = start, End = end };
        formatting.Add(range);
        return range;
    }</code></pre>
<p>Tak teraz wygląda metoda Write().</p>
<pre><code class="hljs cs ">public void Write()
{
    for (var i = 0; i &lt; plainText.Length; i++)
    {
        var c = plainText[i];
        foreach (var range in formatting)
        {
            
            if (range.ChooseRange(i) &amp;&amp; range.Capitalize)
            {
                c = char.ToUpper(c);
            }
            if (range.ChooseRange(i) &amp;&amp; range.Colorize)
            {
                if (range.Color == "Red")
                    Console.ForegroundColor = ConsoleColor.Red;
                if (range.Color == "Green")
                    Console.ForegroundColor = ConsoleColor.Green;        
            }

            Console.Write(c);
            Console.ResetColor();
        }
    }
}</code></pre>
<p>A tak wygląda użycie naszej klasy. Jak widzisz dla drugiej modyfikacji nie muszę tworzy od nowa całego obiektu, czyli tworzyć napisu dwa razy. Co jest istotne, gdybyśmy m&oacute;wili o tekście, kt&oacute;ry ma milion znak&oacute;w.</p>
<pre><code class="hljs cs ">var bft = new BetterFormattedText
    ("In the land of the blind, the one-eyed man is king\n");
var range = bft.GetRange(19, 24);
range.Color = "Green";
range.Capitalize = true;
range.Colorize = true;

bft.Write();
range.Color = "Red";
range.Capitalize = false;
range.Colorize = true;
bft.Write();</code></pre>
<p>Przykład może nie jest idealny, ale chciałem Ci zobrazować jak tokenizacja pewnych mechanizm&oacute;w może Ci oszczędzić miejsce w pamięci.</p>
<h3>Podsumowanie:</h3>
<p>Flyweight jest to technika, kt&oacute;rej celem jest oszczędność pamięci komputera. Czasem jego użycie jest jawne. Przykładowo aplikacja zwraca Ci token, kt&oacute;ry p&oacute;źniej pozwoli ci modyfikować cokolwiek jest do tego tokenu podpięte. Zrobiliśmy taki trik z naszym edytorem tekstowym.</p>
<p>Gdy Flyweight jest niejawne to wtedy klient nawet nie wie, że taki wzorzec jest użyty. Ten trik zobaczyłeś, gdy tworzyliśmy Kontrakty.</p>
<p>W frameworku .NET taki wzorzec już istnieje i jest nim typ generyczny Span&lt;T&gt;. Span&lt;T&gt; przechowuje w sobie informacje o punkcie początkowym, jak i długość pewnych tablic.</p>
<p>Jest to bardzo zaawansowana technika, ale rzeczywiście, jeśli myślisz o optymalizacji swojego programu gdzie krążą napisy lub inne duże obiekty to warto poczytać o Span&lt;T&gt;.</p>]]></description><author>walenciukcezary@gmail.com (Cezary Walenciuk)</author><category>c#</category><category>wzorce-projektowe</category></item></channel></rss>