<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[myblog]]></title><description><![CDATA[myblog]]></description><link>https://mateusz-czernek.pl</link><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 09:03:58 GMT</lastBuildDate><atom:link href="https://mateusz-czernek.pl/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Make AI models available in your home network - second life for a gaming laptop.]]></title><description><![CDATA[Recently, I realized that I have a gaming laptop doing little more than gathering dust. It has an NVIDIA GeForce RTX 4060 with 8 GB of VRAM—sure, it’s not a monster, but it still seems a shame not to ]]></description><link>https://mateusz-czernek.pl/make-ai-models-available-in-your-home-network-second-life-for-a-gaming-laptop</link><guid isPermaLink="true">https://mateusz-czernek.pl/make-ai-models-available-in-your-home-network-second-life-for-a-gaming-laptop</guid><category><![CDATA[ollama]]></category><category><![CDATA[LLM's ]]></category><category><![CDATA[Local LLM]]></category><dc:creator><![CDATA[Mateusz Czernek]]></dc:creator><pubDate>Wed, 05 Aug 2026 17:21:23 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I realized that I have a gaming laptop doing little more than gathering dust. It has an NVIDIA GeForce RTX 4060 with 8 GB of VRAM—sure, it’s not a monster, but it still seems a shame not to get any use out of it other than playing games, for which I sadly don’t have much time anymore.</p>
<p>I thought I could use it to run some small LLMs and make them available over my local network. Why over the network? Well, most of my after-hours work on various projects takes place on my MacBook Air, so it would be nice to have the option of using some open-source models there.</p>
<p>To be completely honest, though, I was mostly curious about how this could be done - and whether it would work at all. :-) Besides, I think this setup could be useful when planning integrations with paid models and tools such as Codex or Claude Code. I could experiment locally first, refining the code and prompts without incurring additional costs or using any tokens.</p>
<p>Since I had previously used <a href="https://ollama.com">Ollama</a> to experiment with local models, I thought it would be a great candidate for this small project. After some quick research online, it became clear that I was good to go.</p>
<h2>Plan</h2>
<ul>
<li><p>Install Ollama and several models on the Windows gaming laptop.</p>
</li>
<li><p>Configure the server so that I can interact with the models over the local network.</p>
</li>
<li><p>Use Postman or cURL on the MacBook - or experiment with the AI integration available in .NET.</p>
</li>
</ul>
<h2>Execution</h2>
<p>You can download Ollama from <a href="https://ollama.com/download">here</a>. Once it is installed, launch the application and open <strong>Settings</strong>, where you will need to enable network access.</p>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/44d1e64c-6592-4d4d-92fb-19b44d7cd371.png" alt="" style="display:block;margin:0 auto" />

<p>In addition, we need to configure the firewall to accept connections from other devices. This can be done manually through Windows Defender Firewall or by running the following PowerShell command:</p>
<pre><code class="language-shell">New-NetFirewallRule `
  -DisplayName "Ollama API - Private LAN" `
  -Direction Inbound `
  -Action Allow `
  -Protocol TCP `
  -LocalPort 11434 `
  -Profile Private `
  -RemoteAddress LocalSubnet
</code></pre>
<p>Next, we have to setup the environment variable for Ollama service</p>
<pre><code class="language-shell">[Environment]::SetEnvironmentVariable(
    "OLLAMA_HOST",
    "0.0.0.0:11434",
    "User"
)
</code></pre>
<p>After creating the environment variable, restart the Ollama application. That’s all we need to do on the Windows side.</p>
<p>We can now verify that the Ollama server is accessible from another device by calling one of its API endpoints:</p>
<pre><code class="language-shell">curl http://192.168.100.202:11434/api/tags | python3 -m json.tool
</code></pre>
<p>If everything is working correctly, we should receive a JSON response containing a list of the available models and their capabilities. In my case, two models are available.</p>
<pre><code class="language-json">{
    "models": [
        {
            "name": "gemma4:12b",
            "model": "gemma4:12b",
            "modified_at": "2026-08-05T16:43:15.3260319+02:00",
            "size": 7556508396,
            "digest": "4eb23ef187e2c5462566d6a1d3bbbc2f1346d0b4327cbb66d58fffbcc9b2b05c",
            "details": {
                "parent_model": "",
                "format": "gguf",
                "family": "gemma4",
                "families": [
                    "gemma4"
                ],
                "parameter_size": "11.9B",
                "quantization_level": "Q4_K_M",
                "context_length": 262144,
                "embedding_length": 3840
            },
            "capabilities": [
                "completion",
                "tools",
                "thinking",
                "vision"
            ]
        },
        {
            "name": "qwen3.6:latest",
            "model": "qwen3.6:latest",
            "modified_at": "2026-08-05T15:56:43.4560821+02:00",
            "size": 23938333577,
            "digest": "07d35212591fc27746f0a317c975a6d68754fb38e9053d82e25f06057af28522",
            "details": {
                "parent_model": "",
                "format": "gguf",
                "family": "qwen35moe",
                "families": [
                    "qwen35moe"
                ],
                "parameter_size": "36.0B",
                "quantization_level": "Q4_K_M",
                "context_length": 262144,
                "embedding_length": 2048
            },
            "capabilities": [
                "vision",
                "completion",
                "tools",
                "thinking"
            ]
        }
    ]
}
</code></pre>
<p>Now, using the Ollama server endpoints we can start talking with the model. Complete list of endpoints can be found <a href="https://docs.ollama.com/api/introduction">here</a>.</p>
<p>For example, we can use chat endpoint to start the conversation:</p>
<pre><code class="language-json">http://192.168.100.202:11434/api/chat
</code></pre>
<p>,with following payload:</p>
<pre><code class="language-json">{
    "model": "gemma4:12b",
    "messages": [
      {
        "role": "system",
        "content": "You are an experienced Python developer."
      },
      {
        "role": "user",
        "content": "Write a function that reverses a string. Give me only the code - no additional info. Just pure code of that function."
      }
    ],
    "stream": false
}
</code></pre>
<p>Here is the response from the gemma4 model:</p>
<pre><code class="language-json">{
    "model": "gemma4:12b",
    "created_at": "2026-08-05T16:23:03.5613682Z",
    "message": {
        "role": "assistant",
        "content": "```python\ndef reverse_string(s: str) -&gt; str:\n    return s[::-1]\n```",
        "thinking": "*   Task: Write a Python function to reverse a string.\n    *   Constraint 1: Experienced Python developer persona (implies clean, idiomatic code).\n    *   Constraint 2: Only code - no additional info.\n    *   Constraint 3: Just pure code of that function.\n\n    ```python\n    def reverse_string(s: str) -&gt; str:\n        return s[::-1]\n    ```\n    Or simply:\n    ```python\n    def reverse_string(string):\n        return string[::-1]\n    ```\n\n    The user wants *only* the code. No explanation, no markdown surrounding it? Actually, usually \"just pure code\" in this context means no chatty text, but formatting as a code block is standard unless specified otherwise. I will provide just the function.\n\n    *Refining choice:*\n    `def reverse_string(s: str) -&gt; str:`\n    `    return s[::-1]`\n\n    Wait, sometimes users want it exactly as requested. \"Just pure code\".\n\n    Let's go with a standard Pythonic implementation."
    },
    "done": true,
    "done_reason": "stop",
    "total_duration": 24660633100,
    "load_duration": 321959300,
    "prompt_eval_count": 48,
    "prompt_eval_duration": 803372000,
    "eval_count": 264,
    "eval_duration": 23471301000
}
</code></pre>
<p>We can also use generate endpoint:</p>
<pre><code class="language-json">http://192.168.100.202:11434/api/generate
</code></pre>
<pre><code class="language-json">{
    "model": "gemma4:12b",
    "prompt": "Write a Python function that reverses a string. Propose different approaches (max 3) for solution. I'm expecting code only, no additional explenation.",
    "stream": false
}
</code></pre>
<p>, and receive the following response</p>
<pre><code class="language-json">{
    "model": "gemma4:12b",
    "created_at": "2026-08-05T16:30:59.3725895Z",
    "response": "```python\n# Approach 1: Slicing\ndef reverse_string_slicing(text: str) -&gt; str:\n    return text[::-1]\n\n# Approach 2: Using reversed() and join()\ndef reverse_string_builtin(text: str) -&gt; str:\n    return \"\".join(reversed(text))\n\n# Approach 3: Iterative approach\ndef reverse_string_loop(text: str) -&gt; str:\n    reversed_str = \"\"\n    for char in text:\n        reversed_str = char + reversed_str\n    return reversed_str\n```",
    "done": true,
    "done_reason": "stop",
    "context": [
        2,
        105,
        9731,
        107,
        98,
        107,
        106,
        ...// I have removed aroun 400 lines with context items
    ],
    "total_duration": 39022107700,
    "load_duration": 367006400,
    "prompt_eval_count": 49,
    "prompt_eval_duration": 406029000,
    "eval_count": 418,
    "eval_duration": 38021484000
}
</code></pre>
<p>You can also configure the IDE to use Ollama as a third party provider so that you can communicate with model directly from there. Here is the sample configuration for Jetbrains Rider:</p>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/9e7e3859-56f8-40bb-abb2-c7ae7c4c5f30.png" alt="" style="display:block;margin:0 auto" />

<p>To be fair, it’s slow - but performance will depend on your hardware and the size of the model. On the machine hosting the Ollama service, you can check how the model is distributed between the CPU and GPU by running the following command:</p>
<pre><code class="language-shell">ollama ps
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/ae18dde1-43d8-443e-ab19-0db463b09952.png" alt="" style="display:block;margin:0 auto" />

<p>Additionally, if you have NVIDIA GPU you can verify whether the Ollama server is actually using the GPU by calling this command</p>
<pre><code class="language-shell">nvidia-smi
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/b48f4001-24da-4d5d-becc-0573b1766b62.png" alt="" style="display:block;margin:0 auto" />

<p>We can also integrate the model—and, by extension, the Ollama server—with a .NET application. The integration uses the Microsoft.Extensions.AI abstractions, which should make it relatively easy to switch to another provider or client later if needed.</p>
<p>Here is a small console application that demonstrates how it works. First, install the following packages:</p>
<pre><code class="language-plaintext">Microsoft.Extensions.AI
OllamaSharp
</code></pre>
<pre><code class="language-csharp">using Microsoft.Extensions.AI;
using OllamaSharp;

var endpoint = new Uri("http://192.168.100.202:11434");
var model = "gemma4:12b";

IChatClient client = new OllamaApiClient(endpoint, model);

ChatResponse response = await client.GetResponseAsync(
[
    new ChatMessage(
        ChatRole.System,
        "You are an experienced Python developer."),

    new ChatMessage(
        ChatRole.User,
        "Write a function that reverses a string. Give me only the code - no additional info. Just pure code of that function.")
]);

Console.WriteLine(response.Text);
</code></pre>
<p>And here is the response</p>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/33d252b5-081b-450b-a8cb-4be4e0a916b1.png" alt="" style="display:block;margin:0 auto" />

<h2>Summary</h2>
<p>Clearly, this approach will not replace high-performance paid solutions - but that was never my goal. Still, as ridiculous as it may sound, this experiment was a lot of fun, and seeing one device communicate with a model running on another device in my apartment genuinely made me happy.</p>
<p>As mentioned earlier, this setup could be useful for testing AI integrations locally before switching to a paid provider once everything is ready. Now I hope to find some nice use cases for this stack - if any I will post them here.</p>
]]></content:encoded></item><item><title><![CDATA[Exam Buddy]]></title><description><![CDATA[Exam Buddy repository
This project came about after a request from my wife. She needed a simple application to help her prepare for several exams using a known set of questions and answers. I saw it a]]></description><link>https://mateusz-czernek.pl/exam-buddy</link><guid isPermaLink="true">https://mateusz-czernek.pl/exam-buddy</guid><category><![CDATA[Vertical Slice Architecture]]></category><category><![CDATA[【 Carter 】 ]]></category><category><![CDATA[MediatR]]></category><category><![CDATA[PrimeNg]]></category><dc:creator><![CDATA[Mateusz Czernek]]></dc:creator><pubDate>Sat, 20 Jun 2026 19:54:22 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://bitbucket.org/mat-czernek/exambuddy/src/main/">Exam Buddy repository</a></p>
<p>This project came about after a request from my wife. She needed a simple application to help her prepare for several exams using a known set of questions and answers. I saw it as an opportunity to get my hands dirty with Angular, work with APIs, and experiment with architecture and implementation.</p>
<p>The first version of the application was little more than a collection of ugly code - mostly because I was working under a tight deadline (<em>“I need this now…”</em>). It had no authentication, only a few tests - mainly covering question imports - and a very limited UI. Nevertheless, I managed to deliver an MVP that did the job and helped my wife achieve her goal: preparing for and passing her exams.</p>
<p>But it kept bothering me. I had something that worked, yet behind the scenes, its code, design, and architecture were far from ideal. So I decided to spend some time making it right. I had just finished reading an excellent blog post by Milan Jovanović about Vertical Slice Architecture, and I thought this small application would be the perfect candidate for trying out that approach in practice.</p>
<p>Compared with the initial version, the following features were added:</p>
<ul>
<li><p>Authentication using an email address and a verification code</p>
</li>
<li><p>Exam result history</p>
</li>
<li><p>Question categories</p>
</li>
<li><p>The ability to create an exam for a selected category</p>
</li>
<li><p>Importing questions and answers from JSON and CSV files</p>
</li>
</ul>
<p>On the API side, I used:</p>
<ul>
<li><p>Minimal APIs with Carter for endpoint registration</p>
</li>
<li><p>MediatR to practise the command and query pattern</p>
</li>
<li><p>FluentValidation for request validation</p>
</li>
<li><p>Mapster for mapping requests to commands</p>
</li>
<li><p>Entity Framework Core with PostgreSQL, and SQLite for tests</p>
</li>
</ul>
<p>On the UI side, I used:</p>
<ul>
<li>Angular with PrimeNG components and icons</li>
</ul>
<p>I also used AI - specifically Codex - to help with:</p>
<ul>
<li><p>Converting the original source of the questions and answers to JSON so that the data could be imported</p>
</li>
<li><p>Creating validation rules for commands</p>
</li>
<li><p>Styling the UI and setting up some PrimeNG components</p>
</li>
<li><p>Adding code for new contracts once TypeGen had been configured and used with the initial ones</p>
</li>
<li><p>Creating the graphic for the landing page</p>
</li>
</ul>
<p>Some screenshots from the application</p>
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/9ebd32b6-24bf-4375-b5b2-108e3bb4ed7a.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/e72f60c1-8808-4dfe-8a69-6b9977b93421.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/90180f3e-d862-47eb-9cfa-f8e7de83593e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/c31fc0c4-60fe-42a5-8d6b-114d444edc95.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/55c713c9-d6e3-4021-ab74-ce70e2875f5b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/2f817a0d-11ca-4726-9cc0-cbb389156645.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/64d3deaccda572f1d04ef00a/6174d711-dce5-440f-8a89-5121b71099ce.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Is Polish the best language for AI prompts?]]></title><description><![CDATA[Based on the University of Maryland and Microsoft, the answer seems to be yes (tak). For details, see this paper.
This is the earliest recorded sentence in the Polish language:

Daj, niech ja pomielę,]]></description><link>https://mateusz-czernek.pl/polish-language-llm-prompts</link><guid isPermaLink="true">https://mateusz-czernek.pl/polish-language-llm-prompts</guid><dc:creator><![CDATA[Mateusz Czernek]]></dc:creator><pubDate>Sun, 08 Mar 2026 18:14:21 GMT</pubDate><content:encoded><![CDATA[<p>Based on the University of Maryland and Microsoft, the answer seems to be yes (tak). For details, see <a href="https://arxiv.org/pdf/2503.01996">this paper.</a></p>
<p>This is the earliest recorded sentence in the Polish language:</p>
<blockquote>
<p>Daj, niech ja pomielę, a ty odpoczywaj.</p>
<p>Let me do the grinding, while you take your rest.</p>
</blockquote>
<p>, given the results of the research, this sentence could be expressed as:</p>
<blockquote>
<p>Let me process the load, while you sit back and relax.</p>
</blockquote>
<p>, in context of LLM prompts :-)</p>
]]></content:encoded></item><item><title><![CDATA[.Net WebApi - SignalR & Angular]]></title><description><![CDATA[Source code
Code available at GitHub
About SignalR
Full documentation available here
Fundamental concepts
SignalR acts as an abstraction layer over remote procedure calls (RPC), streamlining their use. It primarily employs the WebSocket network proto...]]></description><link>https://mateusz-czernek.pl/net-webapi-signalr-and-angular</link><guid isPermaLink="true">https://mateusz-czernek.pl/net-webapi-signalr-and-angular</guid><category><![CDATA[.net core]]></category><category><![CDATA[SignalR]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Mateusz Czernek]]></dc:creator><pubDate>Tue, 27 May 2025 20:57:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748380991129/e6cb6639-597a-45c6-a13b-03c46a6a1811.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-source-code">Source code</h2>
<p>Code available at <a target="_blank" href="https://github.com/mat-czernek/angular-signalR">GitHub</a></p>
<h2 id="heading-about-signalr">About SignalR</h2>
<p>Full documentation available <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr">here</a></p>
<h3 id="heading-fundamental-concepts">Fundamental concepts</h3>
<p>SignalR acts as an abstraction layer over remote procedure calls (RPC), streamlining their use. It primarily employs the WebSocket network protocol, but can fallback to other transport protocols like HTML5 or Comet when necessary. A key concept in SignalR is the Hub, which facilitates communication between clients and the server, allowing clients to invoke server methods and vice versa. Hubs are transient, meaning they should not store state. To call methods outside a hub, IHubContext is used. SignalR supports strongly typed hubs, reducing the risk of runtime errors - there is no need to use strings for method names. Hubs can target all connected clients, specific connections, groups, or individual users. Additionally, SignalR integrates with ASP.NET Core authentication, associating users with each connection. Authentication data is accessible within the hub via HubConnectionContext.User, similar to WebApi.</p>
<h3 id="heading-supported-platforms">Supported platforms</h3>
<p>Server side:</p>
<ul>
<li>any server platform that ASP.NET Core supports</li>
</ul>
<p>Client side:</p>
<ul>
<li><p>JavaScript/TypeScript</p>
</li>
<li><p>.NET runs on ASP.NET Core</p>
</li>
<li><p>Java</p>
</li>
<li><p>Swift</p>
</li>
</ul>
<h2 id="heading-use-cases-from-sample-project">Use cases from sample project</h2>
<h3 id="heading-retrieving-status-of-executed-tasks-with-single-client">Retrieving status of executed tasks with single client</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748289044542/21537377-9818-41c9-9fe3-d8df6285187c.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-retrieving-status-of-executed-tasks-with-two-clients-plus-run-request-that-returns-result-only-to-caller">Retrieving status of executed tasks with two clients plus run request that returns result only to caller</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748289636662/d09f4f38-3f30-4623-bdb0-ee2f5e44d596.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-client-executes-method-on-server-side-to-get-number-of-running-tasks">Client executes method on server side to get number of running tasks</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748376986654/ea8eeeca-b896-45e8-9fe9-d50754c2c460.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-requirements">Requirements</h2>
<h3 id="heading-net">.NET</h3>
<p>Install SignalR NuGet package</p>
<pre><code class="lang-plaintext">Microsoft.AspNetCore.SignalR
</code></pre>
<h3 id="heading-angular">Angular</h3>
<p>Install SignalR npm package</p>
<pre><code class="lang-plaintext">npm install @microsoft/signalr
</code></pre>
<h2 id="heading-configuration">Configuration</h2>
<p>This section describes the application setup details related specifically to SignalR - surprisingly there is not much to do in scope of configuring the app. Things like CORS, HTTPS redirection, etc. are not covered here. For sake of the sample project simplicity the authorization concept was ignored.</p>
<h3 id="heading-net-1">.NET</h3>
<blockquote>
<p>Program.cs</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-comment">// ...</span>
<span class="hljs-comment">// Add SignalR services</span>
builder.Services.AddSignalR();

<span class="hljs-comment">// ...</span>
<span class="hljs-comment">// Map incoming requests with a specific hub</span>
app.MapHub&lt;TasksHub&gt;(<span class="hljs-string">"/tasksHub"</span>);

app.Run();

<span class="hljs-comment">// ...</span>
</code></pre>
<h2 id="heading-core-parts-of-the-sample-project">Core parts of the sample project</h2>
<h3 id="heading-net-side">.NET side</h3>
<p><code>webapi/Api/TasksApi.Core/TasksHub.cs</code></p>
<p>Contract for strongly typed Hub</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ITasksStatusClient</span>
{
    <span class="hljs-comment">// Called on client-side by server</span>
    <span class="hljs-function">Task <span class="hljs-title">TasksStatuses</span>(<span class="hljs-params">IReadOnlyCollection&lt;TaskDto&gt; tasks</span>)</span>;

    <span class="hljs-comment">// Called on client-side by server</span>
    <span class="hljs-function">Task <span class="hljs-title">TaskStatus</span>(<span class="hljs-params">TaskDto task</span>)</span>;

    <span class="hljs-comment">// Called on server-side by clients</span>
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">RunningTasksCount</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> tasksCount</span>)</span>;
}
</code></pre>
<p>Hub implementation</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TasksHub</span> : <span class="hljs-title">Hub</span>&lt;<span class="hljs-title">ITasksStatusClient</span>&gt;
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITasksStatusService _taskStatusService;

    <span class="hljs-comment">// Task service injected into the Hub to fetch data from in-memory storage</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TasksHub</span>(<span class="hljs-params">ITasksStatusService taskStatusService</span>)</span>
    {
        _taskStatusService = taskStatusService ?? <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-keyword">nameof</span>(taskStatusService));
    }

    <span class="hljs-comment">// This method can be called by clients</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">RunningTasksCount</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Task.FromResult(_taskStatusService.RunningTasksCount);
    }
}
</code></pre>
<p><code>webapi/Api/TasksApi.Core/TasksStatusService.cs</code></p>
<p>Example of calling the method on client side</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Call method for all clients</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">ExecuteTask</span>(<span class="hljs-params">TaskDto task</span>)</span>
{
    <span class="hljs-keyword">return</span> ExecuteTaskInternal(task, (t) =&gt;
        _tasksStatusHubContext.Clients.All.TaskStatus(t));
}

<span class="hljs-comment">// Call method only for specific connection (caller)</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">ExecuteTask</span>(<span class="hljs-params">TaskDto task, <span class="hljs-keyword">string</span> connectionId</span>)</span>
{
    <span class="hljs-keyword">return</span> ExecuteTaskInternal(task, (t) =&gt;
        _tasksStatusHubContext.Clients.Client(connectionId).TaskStatus(t));
}

<span class="hljs-comment">// Call method for all clients</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">RemoveTask</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
{
    _taskStorage.Delete(id);
    <span class="hljs-keyword">await</span> _tasksStatusHubContext.Clients.All.TasksStatuses(_taskStorage.GetAll());
}
</code></pre>
<h3 id="heading-angular-side">Angular side</h3>
<p><code>angular/src/app/services/signalR/tasks-signalr.service.ts</code></p>
<p>Build hub connection</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.hubConnection = <span class="hljs-keyword">new</span> HubConnectionBuilder()
      .withUrl(environment.signalRBaseUrl + <span class="hljs-string">"tasksHub"</span>)
      .build();
  }
</code></pre>
<p>Establish connection with hub, executed in root component under OnInit method.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">public</span> start() {

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hubConnection == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Start Hub connection</span>
    <span class="hljs-built_in">this</span>.hubConnection
      .start()
      .then(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Connected to tasks hub.'</span>);
        <span class="hljs-comment">// We can store connection ID to make further calls with response only for caller</span>
        <span class="hljs-built_in">this</span>.connectionId = <span class="hljs-built_in">this</span>.hubConnection.connectionId ?? <span class="hljs-string">''</span>;
      })
      .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Tasks hub connection error: '</span> + error));

    <span class="hljs-comment">// Register hub method that can be called by server on client side</span>
    <span class="hljs-built_in">this</span>.hubConnection.on(<span class="hljs-string">'TasksStatuses'</span>, <span class="hljs-function">(<span class="hljs-params">tasks: TaskDto[]</span>) =&gt;</span> {
      <span class="hljs-built_in">this</span>.tasksStatusesSubject.next(tasks);
    })

    <span class="hljs-comment">// Register hub method that can be called by server on client side</span>
    <span class="hljs-built_in">this</span>.hubConnection.on(<span class="hljs-string">'TaskStatus'</span>, <span class="hljs-function">(<span class="hljs-params">task: TaskDto</span>) =&gt;</span> {
      <span class="hljs-built_in">this</span>.taskStatusSubject.next(task);
    });
}
</code></pre>
<p>Close connection to hun, executed in root component under OnDestroy method</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">public</span> stop() {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hubConnection == <span class="hljs-literal">null</span>) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-built_in">this</span>.hubConnection.stop().then(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Disconnected from tasks hub.'</span>);
    });
}
</code></pre>
<p>Call method on service-side</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">public</span> getRunningTasksCount(): Observable&lt;<span class="hljs-built_in">number</span>&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Observable&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-function">(<span class="hljs-params">subscriber</span>) =&gt;</span> {
      <span class="hljs-built_in">this</span>.hubConnection
        <span class="hljs-comment">// Provide expected return type and method name</span>
        <span class="hljs-comment">// See webapi/Api/TasksApi.Core/TasksHub.cs for Hub contract</span>
        .invoke&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-string">'RunningTasksCount'</span>)
        .then(<span class="hljs-function">(<span class="hljs-params">count</span>) =&gt;</span> {
          subscriber.next(count);
          subscriber.complete();
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          subscriber.error(err);
        });
    });
  }
</code></pre>
]]></content:encoded></item></channel></rss>