<feed xml:base="https://tomasherceg.com/" xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Tomáš Herceg</title>
  <subtitle type="text">Latest blog posts</subtitle>
  <id>uuid:c8e663f9-7eba-40eb-859a-613440597c31;id=6</id>
  <updated>2026-04-11T09:01:20Z</updated>
  <link href="https://tomasherceg.com/" />
  <entry>
    <id>https://tomasherceg.com/blog/post/setting-path-in-dockerfile-for-windows-containers</id>
    <title type="text">Setting PATH in Dockerfile for Windows containers</title>
    <published>2025-08-12T15:07:56+02:00</published>
    <updated>2025-08-12T15:09:21+02:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/setting-path-in-dockerfile-for-windows-containers" />
    <content type="text">&lt;p&gt;At &lt;a href="https://www.riganti.cz/"&gt;RIGANTI&lt;/a&gt;, we use Azure DevOps to run our CI/CD workloads, and because of various requirements from each project, we &lt;strong&gt;run the builds in our custom agents&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Some older project still rely on .NET Framework or the old Visual Studio Build tools, so the cleanest way to run these workloads is to run Azure DevOps agent in a Windows container. You can easily install any dependency you need in the operating system, and the nice thing is that NuGet and npm are cached, which significantly decreases the build times.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;I needed to install PowerShell 7 because the old Windows PowerShell is missing some features and some scripts in our projects require the new stuff. Our Dockerfile to build the agent container installs Powershell 7 by calling &lt;strong&gt;choco install pwsh&lt;/strong&gt;, but this recently stopped adding the location of &lt;strong&gt;pwsh.exe &lt;/strong&gt;to &lt;strong&gt;PATH&lt;/strong&gt;. So I decided to add it myself, and it showed to be not that trivial as it may seem.&lt;/p&gt;&lt;p&gt;ChatGPT advised to use this in a first shot, which doesn’t work in Windows containers as environment variables work differently on Windows:&lt;/p&gt;&lt;pre class="brush:text"&gt;&lt;code class="language-TEXT"&gt;ENV PATH="C:\\MyTool\\bin;${PATH}"&lt;/code&gt;&lt;/pre&gt;&lt;code class="language-TEXT"&gt;&lt;/code&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;After more searching, I found out another solution:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;RUN setx path "C:\MyTool\bin;%path%"&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;This works if you use &lt;strong&gt;cmd &lt;/strong&gt;as your SHELL, but the &lt;strong&gt;setx&lt;/strong&gt; utility has a limit on variable length set to 1024. Windows supports up to 32kB, but it cannot be achieved using this utility.&lt;/p&gt;&lt;p&gt;I found the only working way to switch SHELL to powershell and call &lt;strong&gt;[Environment]::SetEnvironmentVariable&lt;/strong&gt;. It involved some dealing with double quotes, because the Docker tries to split the clause behind the RUN command to arguments using its own algorithm, but you need to pass everything as a single argument to &lt;strong&gt;powershell -command xxx&lt;/strong&gt;.&lt;/p&gt;&lt;pre class="brush:text"&gt;&lt;code class="language-TEXT"&gt;RUN "[Environment]::SetEnvironmentVariable('PATH', 'C:\Program Files\PowerShell\7'+$env:PATH, [EnvironmentVariableTarget]::Machine)"&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice the double quotes around the entire RUN argument, and the single quotes inside the Powershell command. Quite counter-intuitive, but it works.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/my-first-vibe-coding-experience-with-github-copilot-agent-mode</id>
    <title type="text">My first vibe-coding experience with GitHub Copilot Agent mode</title>
    <published>2025-05-28T17:23:16+02:00</published>
    <updated>2025-05-28T17:24:38+02:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/my-first-vibe-coding-experience-with-github-copilot-agent-mode" />
    <content type="text">&lt;p&gt;&lt;a href="https://www.tomasherceg.com/blog/posts/files/bebe8eab-3144-4729-a745-566c45e8bf8a.png"&gt;&lt;img width="1052" height="650" title="mcp-server-cat" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="mcp-server-cat" src="https://www.tomasherceg.com/blog/posts/files/026d9bfe-e153-4bd2-80d6-4e5a7200ce45.png" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;For my upcoming conference talk, I wanted to build a simple demo application that can draw things on a canvas and expose its functionality as an MCP server, a protocol that enables AI agents to get data or execute various tasks. I've created many demos like that in the past, so I had quite a clear idea of what I needed. I was using Visual Studio 17.14 with the new GitHub Copilot Agent mode. Here is how it went.&lt;h3&gt; Beginner's luck&lt;/h3&gt;&lt;p&gt;I created an empty console app and asked Copilot to add an API (using the Minimal API approach) that would enable users to add lines and circles on my canvas. I explicitly requested not to add any database logic and to keep everything in memory, as it's just a demo. This worked surprisingly well. Copilot added the API endpoints and stored the shapes in an in-memory collection. It even added an endpoint to list all existing shapes, which I didn't request, but I would have needed it anyway.&lt;h3&gt;️ First hick-ups&lt;/h3&gt;&lt;p&gt;I noticed that the type of collection for storing the shapes was List&amp;lt;object&amp;gt;, which is not a great practice in a strongly typed language. The objects would not even serialize correctly if they were sent to the client. Additionally, the project I created was a console app, and it didn't have references to some ASP.‎NET Core types for Minimal APIs. It was my mistake - I should've created an empty ASP.‎NET Core app instead. &lt;p&gt;Copilot overlooked that. Normally, it compiles the code after each step, so in my case, it eventually discovered the problem and started working on it. It added a reference to a NuGet package Microsoft.AspNetCore.App (it asked before), but it was not the correct solution. The package is outdated and should no longer be used. The code would compile, but the application would crash on start. I had to explicitly tell Copilot to change the project SDK to Microsoft.NET.Sdk.Web explicitly. &lt;h3&gt; Trial and error &lt;/h3&gt;&lt;p&gt;Next, I instructed Copilot to define strong types for the shapes. Copilot understood what I meant and wanted to create a class to represent the Line and Circle objects. However, because all my code was in one file and I was using top-level statements, Copilot received a compilation error when it added the new classes at the beginning of the file. &lt;p&gt;The classes themselves were fine, but the code couldn't compile, so Copilot rewrote the code several times without success. It tried adding and removing namespaces and renaming things but didn't figure out that the order was wrong. Only after the 4th attempt to change something and compile the app did it try to move the new classes into a separate file. This finally removed the error. Thus, Copilot ultimately succeeded, but it took approximately 1 minute to converge on a working solution.&lt;h3&gt; Serialization issues&lt;/h3&gt;&lt;p&gt;ASP.NET Core uses System.Text.Json as a serializer, but it does not automatically serialize properties of polymorphic types. I knew that, so I had to request the addition of the JsonDerivedType attributes. I intentionally skipped this instruction to see if Copilot would figure it out automatically, but it didn't. &lt;h3&gt;✔️ Generating front-end&lt;/h3&gt;&lt;p&gt;At this point, I had the APIs for adding and listing the shapes. Now, for the front-end part - I asked to create an HTML page that would draw the shapes on canvas. This worked well. Copilot knew that it needed to add UseStaticFiles to Program.cs, and it generated an index.html page with a simple JavaScript that called the API endpoint and drew the shapes. &lt;p&gt;I clarified my previous instruction that the page should reload the shapes and refresh the drawing every 5 seconds. Copilot made a minimal change to make the page behave in this way. That was nice.&lt;h3&gt; It just doesn't work&lt;/h3&gt;&lt;p&gt;However, when I ran the project and sent some shapes to the API from Postman, nothing was displayed on the page. The problem was again in JSON serialization - the server was using camel case property names, but the client code expected Pascal case. &lt;p&gt;When I asked Copilot to fix it (without specifying exactly how), it didn't find the correct place in Program.cs to configure the contract resolver. Instead, it added a configuration that would work for controller-based APIs, but not for the minimal ones. I had to explicitly mention which function in the Program.cs I want to update. However, we finally got both the front-end and API to work.&lt;h3&gt; No web search means hallucination&lt;/h3&gt;&lt;p&gt;The biggest issue came when I asked to expose the API as an MCP server. The Agent mode in Visual Studio apparently cannot use web search yet (it only uses it in Ask mode), so it was unaware of MCP at all. It was hallucinating all the time, creating entirely new API endpoints that were precisely the same as the current ones, except for the "/mcp" prefix in the URL. That's not good.&lt;p&gt;I instructed it that there must be some NuGet package to do that, but it didn't know about it because of a missing web search capability. It thought the package is called MCP, but the actual name is ModelContextProtocol.AspNetCore. I had to manually find a GitHub repo with an example of how to use the package. I gave Copilot the URL of the repository, but it didn't help - Copilot offered to clone the repository, which I didn't want, as it was unclear where it would clone it. &lt;p&gt;Instead, I found the two files that had what I needed in the MCP samples repo, and pasted them to Copilot as an example. Copilot understood that it should take the API endpoints and build the MCP server tool around them, but it also deleted the original API endpoints. Additionally, it somehow disrupted access to the collection of shapes - it attempted to move it to a separate class, but this broke the code that was already using it.&lt;h3&gt; It's a trap&lt;/h3&gt;&lt;p&gt;From then on, it only got worse. Copilot didn't determine exactly where the MCP server should be registered. It registered the services in Program.cs after the builder.Build() was called, which compiles, but doesn't work. Then, it moved half of Program.cs to a separate class while keeping the incorrect order of registrations. I gave up and fixed that manually because there was such a mess that it would be too hard to explain how it should be fixed.&lt;p&gt;I finished the MCP server manually and wanted to try it from VS Code. VS Core also features Copilot Agent mode, along with additional capabilities. For example, you can register a custom MCP server in the configuration so the Copilot can use it. &lt;p&gt;I struggled with that for some time due to CORS and certificate-related issues (for some reason, the custom MCP server only worked with HTTP, not HTTPS). But after a few minutes of fighting, I was able to draw a cat using GitHub Copilot Agent in VS Code using my MCP server. Also, I had to use the SSE protocol explicitly. Otherwise, I wasn't able to establish a connection. &lt;p&gt;Overall, it was a very interesting experience, and I will continue to experiment with it. The fact that Copilot can manipulate more files in the project, run the compilation, and iteratively improve its solution is quite useful. It creates checkpoints, allowing you to revert previous edits if they are not satisfactory. &lt;h3&gt;❓ Will it ever be useful?&lt;/h3&gt;&lt;p&gt;It is still early, and sometimes it is like betting on red in a roulette. You can be sure it will come eventually, but you never know how many attempts will be necessary.&lt;p&gt;However, I believe that we'll find many scenarios where we can confidently apply this agentic approach. Simple changes, such as adding a new column to a database entity and showing it in the UI, can be a good candidate. Real-world projects are full of such easy tasks.</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/open-source-monetization-is-hard</id>
    <title type="text">Open-source monetization is hard</title>
    <published>2025-01-16T19:13:39+01:00</published>
    <updated>2025-03-07T13:13:59+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/open-source-monetization-is-hard" />
    <content type="text">&lt;p&gt;The .NET community has just seen &lt;strong&gt;another open-source drama&lt;/strong&gt;. &lt;a href="https://fluentassertions.com/"&gt;&lt;strong&gt;FluentAssertions&lt;/strong&gt;&lt;/a&gt;, a popular library that provides a natural and easy-to-read syntax for unit tests, has suddenly changed its license. &lt;strong&gt;Starting with version 8, you must pay $130 per developer&lt;/strong&gt; if you use it in commercial projects.&lt;p&gt;&lt;img width="768" height="768" title="" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="Group of people deciding how to monetize open-source project without making another drama" src="https://www.tomasherceg.com/blog/posts/files/62c669aa-e271-48ab-ae3a-5afb52618420.png" border="0"&gt;&lt;p&gt;Reading the related discussions on &lt;a href="https://github.com/fluentassertions/fluentassertions/pull/2943"&gt;GitHub&lt;/a&gt; and Reddit is painful. Most people only complain about paying for something that has always been free, completely ignoring the effort the authors had to put into the library, and any constructive critique is very rare.&lt;p&gt;&lt;strong&gt;Every open-source project needs significant investment to ensure it will work long-term.&lt;/strong&gt; Keeping the project healthy and reliable requires thousands of hours of its maintainers. It is perfectly OK to want to get paid for the work. The key question is how to do it without losing trust.&lt;p&gt;We saw what happened when the author of Moq published an update that collected the e-mail addresses of developers using the library. I don't think the original intent was to do any harm. Still, the idea of a library that hooks in the build pipeline, collects information from developer machines, and sends them somewhere was far behind the red line for most users.&lt;p&gt;With FluentAssertions, we can see a similar story. The NuGet package name is the same, and you may not even notice that version 8 uses a different license when upgrading. Some people reference package versions with wildcards, which upgrades the dependencies even without making a single change in the code. Yes, people can stay on version 7 forever, but the risk of accidentally upgrading the package is quite high. Don't tell me you never clicked on the "upgrade all NuGet packages in the solution" option. &lt;p&gt;&lt;strong&gt;I think that making such a fundamental change deserves publishing NuGet package with a different name.&lt;/strong&gt; Also, I believe that the change was not announced early enough, and it came out as a surprise to most of the community. Whether we like it or not, &lt;strong&gt;paying for using open-source projects is a sensitive topic, and communicating things clearly and in advance would significantly reduce the frustration&lt;/strong&gt;.&lt;p&gt;I still think that the only viable business model for OSS is to have an open-source core component surrounded by commercial add-ons. The core component must be free, and it must be clearly promised it will be free forever. No catches, no tricks. This is the only way to avoid losing the trust of the users. &lt;strong&gt;We took this strategy with &lt;/strong&gt;&lt;a href="https://www.dotvvm.com"&gt;&lt;strong&gt;DotVVM&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; 10 years ago&lt;/strong&gt; and I never regretted it. We managed to acquire many loyal customers over the years, and the commercial products helped the project become sustainable. &lt;p&gt;If you maintain an open-source project, I understand you need to get paid for that. I need it, too. But &lt;strong&gt;please be careful when transitioning to a commercial model&lt;/strong&gt;. Every drama like this with FluentAssertions damages the overall trust in all open-source libraries.</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/beware-of-dashes-in-connection-string-names-in-azure-app-service</id>
    <title type="text">Beware of dashes in connection string names in Azure App Service</title>
    <published>2025-01-04T20:25:36+01:00</published>
    <updated>2025-01-04T20:26:52+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/beware-of-dashes-in-connection-string-names-in-azure-app-service" />
    <content type="text">&lt;p&gt;Today, I ran into an unusual behavior of ASP.NET Core application deployed to Azure App Service. It could not find the connection string, even though it was present on the &lt;em&gt;Connection Strings&lt;/em&gt; pane of the App Service in the Azure portal.&lt;/p&gt;&lt;p&gt;This is how the screen looked like:&lt;/p&gt;&lt;p&gt;&lt;img width="1024" height="387" title="" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="Screenshot of the connection string in Azure portal" src="https://www.tomasherceg.com/blog/posts/files/5ed3c1fb-323e-42c2-b9d8-2b903ebf647d.png" border="0"&gt;&lt;/p&gt;&lt;p&gt;The application was accessing the connection string using the standard ASP.NET Core configuration API, as shown in the following code snippet:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;sevices.AddNpgsqlDataSource(configuration.GetConnectionString("aisistentka-db"));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Naturally, everything works as expected locally, but when I deployed the app to Azure, it did not start, with the exception “Host can’t be null.”&lt;/p&gt;&lt;p&gt;When diagnosing this kind of issues, it is a good idea to start with the Kudu console (located at &lt;strong&gt;https://your-site-name.scm.azurewebsites.net&lt;/strong&gt;). A quick check of the environment variables usually shows what is wrong.&lt;/p&gt;&lt;p&gt;Every connection string should be passed to the application as an environment variable. Normally, the ASP.NET Core’s &lt;strong&gt;GetConnectionString &lt;/strong&gt;method should look for the &lt;strong&gt;ConnectionStrings:connectionStringName&lt;/strong&gt; configuration key (which is usually in the &lt;strong&gt;appsettings.json &lt;/strong&gt;file or in User Secrets). Since environment variables cannot contain colons, they can be replaced with double underscores – the .NET configuration system treats these separators as equal. &lt;/p&gt;&lt;p&gt;However, the type field in the Azure portal (you can see it in the picture at the beginning of the article) provides a special behavior and somehow controls how the environment variable names are composed. In the case of PostgreSQL, the resulting variable name is &lt;strong&gt;POSTGRESQLCONNSTR_aisistentkadb&lt;/strong&gt;. As you can see, instead of &lt;strong&gt;ConnectionStrings__&lt;/strong&gt; prefix, the prefix is &lt;strong&gt;POSTGRESQLCONNSTR_&lt;/strong&gt;, and the dash from the connection string name is removed. &lt;/p&gt;&lt;p&gt;This was a bit unexpected for me. The &lt;strong&gt;GetConnectionString &lt;/strong&gt;method cannot see the variable, but when I use the type “SQL Server”, the same approach works (though, the dashes in connection string names do not). How is this possible?&lt;/p&gt;&lt;p&gt;I looked in the &lt;a href="https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Configuration.EnvironmentVariables/src/EnvironmentVariablesConfigurationProvider.cs#L15"&gt;source code of .NET&lt;/a&gt; and found out that there is a special treatment of these Azure prefixes, but not all of them are included. It only supports SQL Server, SQL Azure, MySQL, and Custom types. All other options will produce an incorrect name of environment variable that the application will not find.&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;    /// &amp;lt;summary&amp;gt;
    /// Provides configuration key-value pairs that are obtained from environment variables.
    /// &amp;lt;/summary&amp;gt;
    public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
    {
        private const string MySqlServerPrefix = "MYSQLCONNSTR_";
        private const string SqlAzureServerPrefix = "SQLAZURECONNSTR_";
        private const string SqlServerPrefix = "SQLCONNSTR_";
        private const string CustomConnectionStringPrefix = "CUSTOMCONNSTR_";
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The solution was to use the &lt;strong&gt;Custom &lt;/strong&gt;type and remove the dash from the connection string name.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/llm-in-c-in-200-lines-hold-my-beer</id>
    <title type="text"> LLM in C# in 200 lines? Hold my beer...</title>
    <published>2024-12-18T09:07:37+01:00</published>
    <updated>2024-12-18T09:07:37+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/llm-in-c-in-200-lines-hold-my-beer" />
    <content type="text">&lt;p&gt;I once heard, “&lt;em&gt;If you fear something, learn about it, disassemble it to the tiniest pieces, and the fear will just go away.&lt;/em&gt;”&lt;/p&gt;&lt;p&gt;Well, it didn’t work. I read a &lt;a href="https://www.manning.com/books/build-a-large-language-model-from-scratch"&gt;book about building LLM from scratch&lt;/a&gt;, which helped me understand the model's architecture and how it works inside. However, I am still concerned about the power of AI models and the risks our world may face in the future. Although we still don't understand many fundamental concepts of how the human brain works, and some scientists say we are not even close to getting to human-level intelligence, I am still a bit worried about the scale and speed the new technologies emerge. Many inventions of science were not achieved by logical thinking or inference but by mistake or trial and error. Spawning millions of model instances and automating them to make “random” experiments to discover something new doesn’t seem that impossible to me.&lt;/p&gt;&lt;p&gt;The book shows how to build the smallest version of GPT-2 in Python and preload model weights published by OpenAI. By the way, GPT-3 has the same architecture, but the model is scaled to a larger number of parameters.&lt;/p&gt;&lt;p&gt;I was curious if this could be done in C#, and I found the &lt;a href="https://github.com/dotnet/TorchSharp"&gt;TorchSharp&lt;/a&gt; library. It is a wrapper for native libraries used by PyTorch. The API was intentionally kept to be as close to Python as possible, so the code does not look like .NET at all. But it makes the library easy to learn and use, since a vast majority of examples are in Python. What surprised me is that the actual LLM implementation in C# has only about 200 lines of code. All the magic is in the model weights. PyTorch/TorchSharp provides a very nice abstraction over the primitives from which deep neural networks are composed.&lt;/p&gt;&lt;p&gt;I was wondering if it makes sense to do a session about it, for example, at our &lt;a href="https://www.meetupdate.cz"&gt;&lt;strong&gt;MeetUpdate&lt;/strong&gt;&lt;/a&gt;. The problem is that I am not an AI scientist, and the topic is hard. I think I understand all the crucial aspects and will be able to explain what is going on. But still, there are many things I have practically no experience with. Second, understanding the session requires at least some knowledge of how neural networks work and the basics of linear algebra. I am not sure what the experience of the audience would be. And finally, I would be speaking about something that is not my creation at all - it would be merely a description of things others have invented, and my added value would only be in trying to explain it in a short meetup session.&lt;/p&gt;&lt;p&gt;On the other hand, playing with it was really fun, and maybe it can motivate someone to start learning more about ML and neural networks.&lt;/p&gt;&lt;p&gt;Shall I do it?&lt;/p&gt;&lt;p&gt;&lt;img width="480" height="480" title="Crazy developer explains LLM internals to the students" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="Crazy developer explains LLM internals to the students" src="https://www.tomasherceg.com/blog/posts/files/9880c7f2-a9b9-41e0-a5e5-e97c958c51e1.png" border="0"&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/new-book-newsletter</id>
    <title type="text"> New book newsletter</title>
    <published>2024-12-09T10:41:59+01:00</published>
    <updated>2024-12-09T10:42:19+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/new-book-newsletter" />
    <content type="text">&lt;p&gt;&lt;strong&gt;My new book, “&lt;/strong&gt;&lt;a href="https://www.modernizationbook.com"&gt;&lt;strong&gt;Modernizing .NET Web Applications&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,” is finally out &lt;/strong&gt;- available for purchase in both printed and digital versions. If you are interested in getting a copy, see the &lt;a href="https://www.modernizationbook.com"&gt;new book’s website&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href="https://www.modernizationbook.com"&gt;&lt;img width="640" height="480" title="A pile of copies of my new book Modernizing .NET Web Applications" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="A pile of copies of my new book Modernizing .NET Web Applications" src="https://www.tomasherceg.com/blog/posts/files/d8915461-125f-401e-b66e-6a659d6bd90e.jpg" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It was a challenging journey for me, but I liked every moment of it, and it is definitely not my last book. I just need to bump into another topic and get enthusiastic about it (which seems not to be that hard).&lt;/p&gt;&lt;p&gt;I finished the manuscript on the last day of June, but some things had already changed before the book was published. For example, the &lt;em&gt;System.Data.SqlClient &lt;/em&gt;package became deprecated. Additionally, all samples in the book used .NET 8, but .NET 9 is just around the corner, and there will be many new and interesting features and performance improvements. Despite the fact that they do not primarily target the area of modernization, they are highly relevant - they constitute one of the strongest arguments for moving away from legacy frameworks. Chapter 2 is dedicated to the benefits of the new versions of .NET, and it is one of the longest chapters of the book. Why else would you modernize if not to use the new platform features?&lt;/p&gt;&lt;p&gt;To ensure you can stay updated, I’ve &lt;a href="https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7260987519008149504"&gt;&lt;strong&gt;set up a newsletter&lt;/strong&gt;&lt;/a&gt; where I’ll regularly post all the news concerning the modernization of .NET applications. &lt;/p&gt;&lt;p&gt;The book had an official celebration and announcement at &lt;a href="https://www.updateconference.net"&gt;Update Conference Prague&lt;/a&gt;, and I’d like to thank all the people around me who helped the book to happen.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/first-time-in-chicago-at-microsoft-ignite</id>
    <title type="text">First time in Chicago at Microsoft Ignite</title>
    <published>2024-11-25T20:54:46+01:00</published>
    <updated>2024-11-25T20:55:05+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/first-time-in-chicago-at-microsoft-ignite" />
    <content type="text">&lt;p&gt;I just returned from Chicago from &lt;strong&gt;Microsoft Ignite &lt;/strong&gt;- the largest tech conference about Microsoft technology. Unsurprisingly, it was mainly about AI, and it was a terrific experience.&lt;/p&gt;&lt;p&gt;I got the chance to be part of the Expert Meetup zone. For each of the ten main areas of interest, there were several booths where the attendees could ask any questions. I was at the booth focused on &lt;strong&gt;Serverless in Azure&lt;/strong&gt;, so we discussed &lt;strong&gt;Azure Functions&lt;/strong&gt;, &lt;strong&gt;Azure Container Apps&lt;/strong&gt;, and &lt;strong&gt;.NET Aspire&lt;/strong&gt;, my favorite dev tool of recent months. I met many great people from the product groups who stand behind these technologies.&lt;/p&gt;&lt;p&gt;Since Ignite is not primarily a developer conference, most sessions were a bit further from my technical interest. However, being the &lt;strong&gt;CEO of &lt;/strong&gt;&lt;a href="https://www.riganti.cz"&gt;&lt;strong&gt;RIGANTI&lt;/strong&gt;&lt;/a&gt;, I want to understand how enterprises around the world implement AI, which use-cases are relevant to them, and what challenges they face. These insights greatly help us steer our company strategy and give better advice to our clients about integrating AI into their businesses.&lt;/p&gt;&lt;p&gt;I also attended an interesting session about the modernization of legacy systems, a topic similar to &lt;a href="https://www.modernizationbook.com"&gt;my recently published book&lt;/a&gt;. When speaking about this topic, I always specialized purely in .NET Framework applications, for this is what I meet most of the time. However, this session went much further in the past - most of it was about mainframes and ancient languages like COBOL or Smalltalk. It showed how to use AI to analyze or reverse-engineer the original intent of ancient code routines, how to extract business rules, build missing or incomplete documentation, and how to generate test cases to ensure the new code will provide the same behavior and functionality. This was a very different approach in contrast to my strictly deterministic and non-AI-based approach presented in the book, and I got plenty of new ideas.&lt;/p&gt;&lt;p&gt;Another very interesting session was about using Azure Kubernetes Service at scale. The team has been adding an impressive number of features, focusing on making Kubernetes easy to use and enhancing security - for example, by container vulnerability scanning and patching. I was amazed to see so many features and improvements they delivered recently. Apparently, AKS is a very popular service, and the product group has a chance to do so many interesting things.&lt;/p&gt;&lt;p&gt;And as always, I couldn't miss &lt;a href="https://ignite.microsoft.com/en-US/sessions/BRK246?source=sessions"&gt;Mark Russinovich's session on Azure infrastructure&lt;/a&gt;. It conflicted with my Expert Meetup sessions, so I had to watch it from recording during my flight back, but it is thrilling to see the effort Microsoft puts in their data centers to make them energy efficient.&lt;/p&gt;&lt;p&gt;&lt;img width="687" height="768" title="Tomas Herceg at Microsoft Ignite in front of GitHub logo" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" alt="Tomas Herceg at Microsoft Ignite in front of GitHub logo" src="https://www.tomasherceg.com/blog/posts/files/494658a5-2301-4fce-add7-3237558dc21e.jpg" border="0"&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/two-conferences-in-a-day-no-problem</id>
    <title type="text">Two conferences in a day? No problem!</title>
    <published>2024-11-19T23:29:35+01:00</published>
    <updated>2024-11-19T23:33:19+01:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/two-conferences-in-a-day-no-problem" />
    <content type="text">&lt;p&gt;Thursday was the first day of our &lt;a href="https://www.updateconference.net"&gt;&lt;strong&gt;Update Conference Prague&lt;/strong&gt;&lt;/a&gt; - the &lt;strong&gt;largest event for .NET developers in the Czech Republic &lt;/strong&gt;with more than 500 attendees. On the same day, I also had an &lt;a href="https://youtu.be/hn2nEKquSOE?si=yT3e3HbfPGTJhE63"&gt;&lt;strong&gt;online session at .NET Conf&lt;/strong&gt;&lt;/a&gt; – &lt;strong&gt;a global online .NET conference organized by Microsoft&lt;/strong&gt;.&lt;p&gt;&lt;iframe width="560" height="315" title="YouTube video player" src="https://www.youtube.com/embed/hn2nEKquSOE?si=rD9I9r8zGNR8Ptp4" frameborder="0" allowfullscreen="" referrerpolicy="strict-origin-when-cross-origin" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"&gt;&lt;/iframe&gt;&lt;p&gt;Originally we were thinking of setting up a studio at Update from where I could stream my session, but because I was speaking at another conference the previous week, there was not enough time for testing the setup. I am lucky that the venue where Update takes place is only about 100 meters from the RIGANTI office.&lt;p&gt;Same as last year, my .NET Conf session was about &lt;strong&gt;modernizing legacy applications&lt;/strong&gt;, but this time not from the technical point of view. I focused on arguments &lt;em&gt;the tech leads or developers can use to explain the risks and benefits to managers and other non-technical stakeholders&lt;/em&gt;.&lt;p&gt;I talked about &lt;em&gt;security risks &lt;/em&gt;that are often seen in old libraries and frameworks, then about &lt;em&gt;performance improvements of the new .NET &lt;/em&gt;that can translate to significantly lower infrastructure costs. I also discussed &lt;em&gt;hiring and developer happiness &lt;/em&gt;- an important criterion in the long-term perspective. Most developers want to use the new versions to keep their knowledge up-to-date, and there is an increasingly large group of developers who started with the new .NET Core and never touched the old stuff.&lt;p&gt;Then I focused on &lt;em&gt;planning and estimation&lt;/em&gt;, and recommended to &lt;strong&gt;prepare a “business plan” &lt;/strong&gt;- evaluate not only &lt;em&gt;risks coming from DOING the migration&lt;/em&gt;, but also from &lt;em&gt;NOT DOING it&lt;/em&gt;. You can lead a few experiments to get the idea of complexity and make rough estimates based on the results.&lt;p&gt;The last important thing I mentioned was communication. In many cases, &lt;em&gt;being predictable is way more valuable than deliver on time&lt;/em&gt;, for many legacy applications are only internal tools and the deadlines are often only virtual. Some developers complain that spending a few hours making a presentation about the current progress is a waste of time, and use the time to write code in a hope that the deadline will be saved. In reality, these few hours will probably not save the day, and the stakeholders will face an unexpected surprise. Clear and intensive communication can prevent this.&lt;p&gt;&lt;strong&gt;You can find more information on this topic in my new book &lt;/strong&gt;&lt;a href="https://www.modernizationbook.com"&gt;&lt;strong&gt;Modernizing .NET Web Applications&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;. It contains a detailed guide for migrating from ASP.NET Web Forms, ASP.NET MVC, WCF, SignalR, Entity Framework 6, and other .NET Framework technologies to their equivalents in .NET 8 and beyond, and much more.&lt;/strong&gt;&lt;p&gt;I got several requests to &lt;strong&gt;publish the slide deck&lt;/strong&gt;. You can find it below:&lt;div class="wlWriterEditableSmartContent" id="scid:6E335E69-603A-4F1E-AA4B-726D584133B4:8628f188-2bb3-445f-beee-c6c18597118c" style="margin: 0px; padding: 0px; float: none; display: inline;"&gt;&lt;a href="https://www.tomasherceg.com/blog/posts/files/4780c28a-8c53-4ffd-bc16-bbd307e1d998.pdf"&gt;Talking to managers about modernizing .NET apps&lt;/a&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/dotvvm-maui-integration</id>
    <title type="text">DotVVM + MAUI integration</title>
    <published>2022-03-27T12:57:58+02:00</published>
    <updated>2022-03-27T12:57:58+02:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/dotvvm-maui-integration" />
    <content type="text">&lt;p&gt;I’ve seen various demos on &lt;strong&gt;hosting Blazor applications inside MAUI apps&lt;/strong&gt;, and I’ve been wondering about the use cases – on the first look it didn’t seem so practical to me. However, a bit later I noticed that having such thing few years ago, it would save us quite a lot of work. &lt;/p&gt;&lt;p&gt;At &lt;a href="https://www.riganti.cz/en"&gt;RIGANTI&lt;/a&gt;, we’ve been building several desktop applications for our customers. One of the application contained quite complex reservation system. The application was built using WPF as it needed to interact with various hardware devices (RFID readers, receipt printers, credit card terminals, and so on), and so the reservation calendar (a complex component) was built using WPF.&lt;/p&gt;&lt;p&gt;Later we found that we need a similar calendar on the web – the project has another public facing site which would take advantage of the reservation system, and thus we had to rebuild similar UI on the web. &lt;em&gt;A hybrid app would help us to save a lot of time.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;How to integrate web app into MAUI app&lt;/h2&gt;&lt;p&gt;I’ve looked at the &lt;a href="https://github.com/dotnet/maui/tree/main/src/BlazorWebView"&gt;BlazorWebView&lt;/a&gt; control implementation and found out that it’s not complicated – basically it is a wrapper over the &lt;strong&gt;WebView2 component&lt;/strong&gt;. It would be nice to offer a similar feature to &lt;a href="https://www.dotvvm.com"&gt;&lt;strong&gt;DotVVM&lt;/strong&gt;&lt;/a&gt; users. DotVVM apps use the MVVM pattern, same as in MAUI, so it may be even more interesting – &lt;strong&gt;both desktop and web UI can use the same Model-View-ViewModel approach&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;The implementation in Blazor directly invokes the components to render the HTML. It is not invoking the entire HTTP request pipeline in ASP.NET Core, which can be more performant, however the application may not take advantage of some parts of ASP.NET Core, e. g. the authorization.&lt;/p&gt;&lt;p&gt;Also, there are some custom mechanisms to serve static files to the web app. I haven’t explored the details on Android an iOS yet, so it may be necessary to do it that way, but the static pages middleware in ASP.NET Core could do the same job if it gets the correct implementation of &lt;strong&gt;IFileProvider&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Since the DotVVM runtime has a lot of dependencies on ASP.NET Core services, I’ve found that the easiest way would be to invoke directly the &lt;strong&gt;RequestDelegate &lt;/strong&gt;to handle the entire HTTP request using ASP.NET Core pipeline. &lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Same as in BlazorWebView, the app is running on a special origin 0.0.0.0 – it’s not a valid IP address, but WebView thinks that it is fine, and doesn’t do any DNS resolution. We could use localhost, but if the WebView would need to call something on the local machine, there could be conflicts or some problems with sharing cookies. This seems more feasible.&lt;/p&gt;&lt;p&gt;The crucial part for integration is to intercept the HTTP request sent by the WebView and provide the response without performing any networking operation.&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;_webview.CoreWebView2.AddWebResourceRequestedFilter($"{AppOrigin}*", CoreWebView2WebResourceContext.All);

_webview.CoreWebView2.WebResourceRequested += async (s, eventArgs) =&amp;gt;
{
    …
};&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The request is handled like this:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public async Task&amp;lt;DotvvmResponse&amp;gt; ProcessRequest(Uri requestUri, string method, IEnumerable&amp;lt;KeyValuePair&amp;lt;string, string&amp;gt;&amp;gt; headers, Stream contentStream)
{
    using var scope = serviceScopeFactory.CreateScope();
    var httpContext = new DefaultHttpContext()
    {
        Request =
        {
            Scheme = requestUri.Scheme,
            Host = new HostString(requestUri.Host, requestUri.Port),
            Path = requestUri.PathAndQuery,
            Method = method
        },
        Response =
        {
            Body = new MemoryStream()
        },
        ServiceScopeFactory = serviceScopeFactory,
        RequestServices = scope.ServiceProvider
    };
    foreach (var header in headers)
    {
        httpContext.Request.Headers.Add(header.Key, header.Value);
    }            
    if (contentStream != null)
    {
        httpContext.Request.Body = contentStream;
    }
    await requestDelegate(httpContext);

    return new DotvvmResponse(
        httpContext.Response.StatusCode,
        httpContext.Response.Headers.SelectMany(h =&amp;gt; h.Value.Select(v =&amp;gt; new KeyValuePair&amp;lt;string, string&amp;gt;(h.Key, v))),
        (MemoryStream)httpContext.Response.Body);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can obtain the &lt;strong&gt;RequestDelegate &lt;/strong&gt;using the standard ASP.NET Core dependency injection – it is registered there.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;Communication with the host&lt;/h2&gt;&lt;p&gt;The hosted web app will probably need to communicate with the MAUI app. Since both MAUI and DotVVM runs in the same process, it is theoretically possible to directly call methods in MAUI/DotVVM viewmodels, but DotVVM was designed as stateless and thus its C# viewmodel exists only for a short period of time – only during execution of a particular HTTP request. Most of the time, the viewmodel is represented by the JavaScript object in the page itself.&lt;/p&gt;&lt;p&gt;Therefore, the &lt;strong&gt;DotvvmWebView &lt;/strong&gt;implementation contains several useful methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;GetViewModelSnapshot&lt;/strong&gt; – this method obtains a snapshot of the viewmodel and returns it as dynamic. I’ve been thinking about a strongly-typed way of obtaining the viewmodel, but there are many problems – using the same viewmodel class is not a good idea since it has many dependencies (on HttpContext and other services) – there would have been some interface. Dynamic might be good enough.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;PatchViewModel&lt;/strong&gt; – the MAUI app can send patches to the page viewmodel. Again, this is fully dynamic API, but since we are working with JavaScript, it’s probably not such a big deal.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;CallNamedCommand – &lt;/strong&gt;this function allows to call commands declared using &lt;strong&gt;&amp;lt;dot:NamedCommand&amp;gt;&lt;/strong&gt; control in the page. Currently, you need to specify the viewId, which is a DotVVM internal ID of the associated JS module – I want to introduce some API that will get the viewId using a CSS selector – you would be able to specify CSS selector for a control that contains the named command.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;DotVVM can send events to the MAUI app by calling &lt;strong&gt;_webview.SendNotification(methodName, arg1, arg2…)&lt;/strong&gt; in a static command – this will invoke the &lt;strong&gt;PageNotificationReceived&lt;/strong&gt; event on &lt;strong&gt;DotvvmWebView&lt;/strong&gt;. Using this mechanism, you can respond to the events in DotVVM pages.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;I’ve implemented the prototype of &lt;a href="https://github.com/riganti/dotvvm-maui"&gt;&lt;strong&gt;DotVVM + MAUI integration&lt;/strong&gt;&lt;/a&gt; on GitHub. Currently, it supports only Windows UWP host, but I plan to add support for more platforms. There are some further challenges in making this component production-ready – DotVVM compiles its pages on the first access, which will be slow on mobile devices – we’ll probably need some way of pre-compilation.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://tomasherceg.com/blog/post/building-controls-with-parameterized-binding-expressions-in-dotvvm</id>
    <title type="text">Building controls with parameterized binding expressions in DotVVM</title>
    <published>2020-10-10T18:30:47+02:00</published>
    <updated>2020-10-10T18:33:33+02:00</updated>
    <author>
      <name />
    </author>
    <link rel="alternate" href="https://tomasherceg.com/blog/post/building-controls-with-parameterized-binding-expressions-in-dotvvm" />
    <content type="text">&lt;p&gt;Recently, we got a question on our &lt;a href="https://gitter.im/riganti/dotvvm"&gt;Gitter chat&lt;/a&gt; about building markup controls that use binding expressions. &lt;/p&gt;&lt;p&gt;Basically, the requirement was to have a breadcrumb navigation control that would be used this way:&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;cc:Breadcrumb DataSource="{value: ListNavParent}" 
               ItemActiveBinding="{value: ItemActive}" 
               PublicUrlBinding="{value: PublicUrl}" 
               TitleBinding="{value: Title}" /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first parameter is &lt;strong&gt;DataSource - &lt;/strong&gt;a hierarchy of pages in the navigation (&lt;em&gt;Section 1 &amp;gt; Subsection A &amp;gt; Page C&lt;/em&gt;). The remaining parameters are binding expressions that would be evaluated on every item in the data source and provide the URL and title of the item and the information whether the item is active.&lt;/p&gt;&lt;p&gt;DotVVM has several controls which work this way – &lt;strong&gt;ComboBox &lt;/strong&gt;for example. There is also the &lt;strong&gt;DataSource&lt;/strong&gt; property as well as &lt;strong&gt;ItemTextBinding&lt;/strong&gt;, &lt;strong&gt;ItemValueBinding&lt;/strong&gt; and so on. &lt;/p&gt;&lt;p&gt;However, it is not trivial to build a control like that. In this article, I’d like to suggest some ways how to do it.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;Rule 1: Use DataContext wherever possible&lt;/h2&gt;&lt;p&gt;Writing such control can be much simpler if you don’t try to deal with properties like &lt;strong&gt;DataSource&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Something&lt;/em&gt;Binding&lt;/strong&gt;. Since the control just renders the breadcrumb navigation (a bunch of links) and does nothing else, and the collection of links will probably be stored in the viewmodel anyway (you need to pass it in the &lt;strong&gt;DataSource&lt;/strong&gt; property somehow), you can get rid of the &lt;strong&gt;DataSource &lt;/strong&gt;property and pass the collection of links to the control via the &lt;strong&gt;DataContext &lt;/strong&gt;property. Inside the control, all bindings will be evaluated just on the control’s &lt;strong&gt;DataContext&lt;/strong&gt;. &lt;/p&gt;&lt;p&gt;And because the &lt;strong&gt;DataContext &lt;/strong&gt;will be a collection of some objects of a known type, making the Breadcrumb control would be easy. If you want to use the control in a more generic way (for different kinds of objects), feel free to use and interface. &lt;/p&gt;&lt;p&gt;First, let’s create the object which would represent the individual link in the navigation:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public class BreadcrumbLink
{
    public string Title { get; set; }

    public string Url { get; set; }

    public bool IsActive { get; set; }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;It would be nice if we could display also a home icon in the control – the control will need a URL for the home icon as well as the collection of links. &lt;/p&gt;&lt;p&gt;If you have multiple things you need to pass to the control, there are two options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Pass one of the things (probably the main one) as the DataContext and create control properties for the other values. &lt;/li&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;cc:Breadcrumb DataContext="{value: Links}" HomeUrl="/" /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;li&gt;Create a tiny viewmodel object that holds all information the control needs. It depends how “coupled” the values are to each other – if you need to aggregate various data from different places in the viewmodel, the first option would work better. If all the values are tied together and worked with from one place, this approach is better.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let’s create a tiny viewmodel object for the entire control:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public class BreadcrumbViewModel
{

    public string HomeUrl { get; set; }

    public List&amp;lt;BreadcrumbLink&amp;gt; Links { get; set; }

}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Now we can define the control like this:&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;@viewModel RepeaterBindingSample.Controls.BreadcrumbViewModel, RepeaterBindingSample

&amp;lt;ol class="breadcrumb"&amp;gt;
    &amp;lt;li&amp;gt;&amp;lt;a href="{value: HomeUrl}"&amp;gt;&amp;lt;span class="glyphicon glyphicon-home" /&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;dot:Repeater DataSource="{value: Links}" RenderWrapperTag="false"&amp;gt;
        &amp;lt;ItemTemplate&amp;gt;
            &amp;lt;li class-active="{value: IsActive}"&amp;gt;
                &amp;lt;a href="{value: Url}"&amp;gt;{{value: Title}}&amp;lt;/a&amp;gt;
            &amp;lt;/li&amp;gt;
        &amp;lt;/ItemTemplate&amp;gt;
    &amp;lt;/dot:Repeater&amp;gt;
&amp;lt;/ol&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;In this case, the control doesn’t need to have a code-behind file as it won’t need any control properties. All we need to do is to prepare the &lt;strong&gt;BreadcrumbViewModel &lt;/strong&gt;object in the page viewmodel (nesting of viewmodels is a very powerful thing btw) and pass it to the control. The &lt;strong&gt;@viewModel &lt;/strong&gt;directive of the control enforces that the control can be used only when its &lt;strong&gt;DataContext &lt;/strong&gt;is &lt;strong&gt;BreadcrumbViewModel&lt;/strong&gt;. If you use the control on any other place, you’ll get an error – bindings in DotVVM are strongly-typed.&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;cc:Breadcrumb DataContext="{value: Breadcrumb}" /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since this control will probably be on all pages of the app, it can be used in the master page. In addition to that, the master page can require all pages to provide items for the breadcrumb navigation using an abstract method:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public abstract class MasterPageViewModel : DotvvmViewModelBase
{

    [Bind(Direction.ServerToClientFirstRequest)]
    public BreadcrumbViewModel Breadcrumb { get; } = new BreadcrumbViewModel()
    {
        HomeUrl = "/"
    };

    public override Task Init()
    {
        if (!Context.IsPostBack)
        {
            Breadcrumb.Links = new List&amp;lt;BreadcrumbLink&amp;gt;(ProvideBreadcrumbLinks());
        }

        return base.Init();
    }

    protected abstract IEnumerable&amp;lt;BreadcrumbLink&amp;gt; ProvideBreadcrumbLinks();
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The conclusion is – pass data to controls using &lt;strong&gt;DataContext &lt;/strong&gt;and avoid creating code behind for the controls if you can.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;But what if I really want to work with custom binding expressions?&lt;/h2&gt;&lt;p&gt;Building of custom binding expressions is tricky, but very powerful. Before we start, let’s review how binding works in DotVVM.&lt;/p&gt;&lt;p&gt;The DotVVM controls define their properties using this awful boilerplate syntax:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public string HomeUrl
{
    get { return (string)GetValue(HomeUrlProperty); }
    set { SetValue(HomeUrlProperty, value); }
}
public static readonly DotvvmProperty HomeUrlProperty
    = DotvvmProperty.Register&amp;lt;string, Breadcrumb2&amp;gt;(c =&amp;gt; c.HomeUrl, string.Empty);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Why is that? Because the property can contain either its value, or a data-binding expression (which is basically an object implementing the &lt;strong&gt;IBinding&lt;/strong&gt; interface). DotVVM controls have a dictionary of property values-or-bindings, and offer the &lt;strong&gt;GetValue &lt;/strong&gt;and &lt;strong&gt;SetValue &lt;/strong&gt;methods that can work with the property values (including evaluation of the binding expression). The only important thing here is the &lt;em&gt;static readonly &lt;/em&gt;field &lt;strong&gt;HomeUrlProperty &lt;/strong&gt;which is a DotVVM property descriptor. It can specify the default value, it supports inheritance from parent controls (if you set DataContext property on some control, its value will propagate to the child controls - unless the child control sets DataContext to something else), and there are more features to that.&lt;/p&gt;&lt;p&gt;There are several types of bindings – the most frequent is the &lt;strong&gt;value &lt;/strong&gt;and &lt;strong&gt;command &lt;/strong&gt;bindings. But how to construct the binding object?&lt;/p&gt;&lt;p&gt;The easiest way is to have it constructed by the DotVVM – it means specify it somewhere in a DotHTML file and have it compiled by the DotVVM view engine. It happens when you declare the code-behind for your markup control:&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;@viewModel System.Object
@baseType RepeaterBindingSample.Controls.Breadcrumb2, RepeaterBindingSample&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public class Breadcrumb2 : DotvvmMarkupControl
{

    public string HomeUrl
    {
        get { return (string)GetValue(HomeUrlProperty); }
        set { SetValue(HomeUrlProperty, value); }
    }
    public static readonly DotvvmProperty HomeUrlProperty
        = DotvvmProperty.Register&amp;lt;string, Breadcrumb2&amp;gt;(c =&amp;gt; c.HomeUrl, string.Empty);

}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you use the control on some page and look what’s in the &lt;strong&gt;HomeUrl &lt;/strong&gt;property (e. g. using &lt;strong&gt;GetValueRaw(HomeUrlProperty)&lt;/strong&gt;), you’ll get the constructed binding object. &lt;/p&gt;&lt;p&gt;There are several things inside the binding object – the bare minimum are these:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;DataContextStack &lt;/strong&gt;– this structure represents the hierarchy of types in the &lt;strong&gt;DataContext&lt;/strong&gt; path – if the binding is in the root scope of the page, the stack has only &lt;em&gt;PageViewModel&lt;/em&gt;; if it’s nested in a repeater, there will be PageViewModel and BreadcrumbLink types. Please note that these are only types, not the actual values. The binding must know of that type is &lt;strong&gt;_this&lt;/strong&gt;, &lt;strong&gt;_parent &lt;/strong&gt;and&amp;nbsp; &lt;strong&gt;_root&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;LINQ expression that represents the binding itself. It is strongly-typed, it can be compiled to IL so it’s fast to evaluate, and it can be quite easily translated to JS because it’s an expression tree. The expression is basically a lambda with one parameter of object[] – this is a stack of &lt;strong&gt;DataContexts&lt;/strong&gt; (not types but the actual values – they are passed to the binding so it can evaluate). The element 0 is &lt;strong&gt;_this&lt;/strong&gt;, the element 1 is &lt;strong&gt;_parent&lt;/strong&gt;, the last one is &lt;strong&gt;_root&lt;/strong&gt;.&amp;nbsp; &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There can be other things, like translated Knockout expression of the binding, original binding string and so on, but they are either optional or will be generated automatically when needed. If you want to construct your own binding, you need at least the &lt;strong&gt;DataContextStack&lt;/strong&gt; and the expression.&lt;/p&gt;&lt;p&gt;To create a binding, you can use something like this:&lt;/p&gt;&lt;p&gt;In order to build a binding, you need an instance of &lt;strong&gt;BindingCompilationService&lt;/strong&gt;, the expression and the &lt;strong&gt;DataContextStack&lt;/strong&gt;. &lt;/p&gt;&lt;p&gt;You can obtain the &lt;strong&gt;BindingCompilationService&lt;/strong&gt; from dependency injection – just request it in the control constructor.&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;DataContextStack&lt;/strong&gt; can be obtained by calling &lt;strong&gt;control.GetDataContextType()&lt;/strong&gt;. &lt;/p&gt;&lt;p&gt;An finally, you need the expression – you can define it yourself like this:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;Expression&amp;lt;Func&amp;lt;object[], string&amp;gt;&amp;gt; expr = contexts =&amp;gt; ((BreadcrumbViewModel)contexts[0]).HomeUrl;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that the expression is always &lt;strong&gt;Func&amp;lt;object[], ResultType&amp;gt;&lt;/strong&gt;. The &lt;strong&gt;contexts &lt;/strong&gt;variable is the array of data context – the first element (0) is &lt;strong&gt;_this&lt;/strong&gt;. The code snippet above is equivalent to &lt;strong&gt;{value: _this.HomeUrl}&lt;/strong&gt; or just &lt;strong&gt;{value: HomeUrl}&lt;/strong&gt;. The cast to specific type is needed just because &lt;strong&gt;contexts &lt;/strong&gt;is array of objects.&lt;/p&gt;&lt;p&gt;To build the expression and pass it for instance to the &lt;strong&gt;Literal &lt;/strong&gt;control, you need something like this:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;// build the binding for literal yourself
Expression&amp;lt;Func&amp;lt;object[], string&amp;gt;&amp;gt; expr = contexts =&amp;gt; ((BreadcrumbViewModel)contexts[0]).HomeUrl;
var dataContextStack = this.GetDataContextType();
var binding = ValueBindingExpression.CreateBinding(bindingCompilationService, expr, dataContextStack);

// create the literal control and add it as a child
var literal = new Literal();
literal.SetBinding(Literal.TextProperty, binding);
Children.Add(literal);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So now you know how to work with bindings – if you’d like to create a control with a property that accepts a binding, or if you need to compose your own binding, that’s how this is done.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;h2&gt;Now how would I create a control as described at the beginning?&lt;/h2&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;If you really want to create a control which is used in the following way, there is some extra things that need to be done.&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;p&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;cc:Breadcrumb DataSource="{value: ListNavParent}" 
               ItemActiveBinding="{value: ItemActive}" 
               PublicUrlBinding="{value: PublicUrl}" 
               TitleBinding="{value: Title}" /&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;The &lt;strong&gt;DataSource&lt;/strong&gt; property contains a binding that is evaulated in the same &lt;strong&gt;DataContextStack&lt;/strong&gt; in which the control lives. If you use this control in the root scope of the page, its &lt;strong&gt;DataContext&lt;/strong&gt; will be just &lt;em&gt;PageViewModel&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;But the remaining properties will actually be evaluated on the individual items in the &lt;strong&gt;DataSource &lt;/strong&gt;collection. They need to have the &lt;strong&gt;DataContextStack&lt;/strong&gt; equal to the &lt;em&gt;(PageViewModel, BreadcrumbLink)&lt;/em&gt; pair. Also, the IntelliSense in Visual Studio will need to know that so it could offer you the property names. &lt;/p&gt;&lt;p&gt;That’s why these properties have to be decorated with &lt;strong&gt;DataContextChange &lt;/strong&gt;attributes. These attributes can stack on top of each other and basically they tell how to change the &lt;strong&gt;DataContextStack&lt;/strong&gt; in order to get the result.&lt;/p&gt;&lt;p&gt;The TitleBinding property will be declared like this:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;[ControlPropertyTypeDataContextChange(order: 0, propertyName: nameof(DataSource))]
[CollectionElementDataContextChange(order: 1)]
public IValueBinding&amp;lt;string&amp;gt; TitleBinding
{
    get { return (IValueBinding&amp;lt;string&amp;gt;)GetValue(TitleBindingProperty); }
    set { SetValue(TitleBindingProperty, value); }
}
public static readonly DotvvmProperty TitleBindingProperty
    = DotvvmProperty.Register&amp;lt;IValueBinding&amp;lt;string&amp;gt;, Breadcrumb2&amp;gt;(nameof(TitleBinding));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The first attribute says “let’s take the type of the value assigned to the &lt;strong&gt;DataSource &lt;/strong&gt;property”. The second says “let’s take that type; it should be a collection or an array, so let’s take the element type of this and use it as a second level in the &lt;strong&gt;DataContextStack&lt;/strong&gt;”. &lt;/p&gt;&lt;p&gt;So, if you assign a collection of type &lt;strong&gt;List&amp;lt;BreadrumbLink&amp;gt;&lt;/strong&gt; in the &lt;strong&gt;DataSource&lt;/strong&gt; property, the &lt;strong&gt;TitleBinding &lt;/strong&gt;property stack will be &lt;em&gt;(PageViewModel, BreadcrumbLink)&lt;/em&gt;. &lt;/p&gt;&lt;p&gt;Notice that the type of the property is &lt;strong&gt;IValueBinding&amp;lt;string&amp;gt;&lt;/strong&gt; – this hints everyone that they shouldn’t fin the “actual value” in the property. &lt;/p&gt;&lt;p&gt;Also, if you use the binding generated in this property, make sure you use it in the correct &lt;strong&gt;DataContextStack&lt;/strong&gt;.&lt;/p&gt;&lt;h2&gt;How to use binding object in DotHTML?&lt;/h2&gt;&lt;p&gt;Unfortunately, right now you can’t. I think it is possible to write an attached property that would do that (I will try it), but DotVVM doesn’t ship with any option to do that.&amp;nbsp; &lt;/p&gt;&lt;p&gt;However, it is possible to generate the controls from the C# code. Theoretically, you can combine markup controls and code-only approach of tampering with bindings, but sometimes it gets even more complicated, so I prefer using code-only control in these cases.&lt;/p&gt;&lt;p&gt;Let’s start by defining the &lt;strong&gt;Breadcrumb2 &lt;/strong&gt;control and its properties:&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;public class Breadcrumb2 : ItemsControl 
{

    public string HomeUrl
    {
        get { return (string)GetValue(HomeUrlProperty); }
        set { SetValue(HomeUrlProperty, value); }
    }
    public static readonly DotvvmProperty HomeUrlProperty
        = DotvvmProperty.Register&amp;lt;string, Breadcrumb2&amp;gt;(c =&amp;gt; c.HomeUrl, string.Empty);
    
    [ControlPropertyTypeDataContextChange(order: 0, propertyName: nameof(DataSource))]
    [CollectionElementDataContextChange(order: 1)]
    public IValueBinding ItemActiveBinding
    {
        get { return (IValueBinding)GetValue(ItemActiveBindingProperty); }
        set { SetValue(ItemActiveBindingProperty, value); }
    }
    public static readonly DotvvmProperty ItemActiveBindingProperty
        = DotvvmProperty.Register&amp;lt;IValueBinding, Breadcrumb2&amp;gt;(nameof(ItemActiveBinding));

    [ControlPropertyTypeDataContextChange(order: 0, propertyName: nameof(DataSource))]
    [CollectionElementDataContextChange(order: 1)]
    public IValueBinding PublicUrlBinding
    {
        get { return (IValueBinding)GetValue(PublicUrlBindingProperty); }
        set { SetValue(PublicUrlBindingProperty, value); }
    }
    public static readonly DotvvmProperty PublicUrlBindingProperty
        = DotvvmProperty.Register&amp;lt;IValueBinding, Breadcrumb2&amp;gt;(nameof(PublicUrlBinding));

    [ControlPropertyTypeDataContextChange(order: 0, propertyName: nameof(DataSource))]
    [CollectionElementDataContextChange(order: 1)]
    public IValueBinding TitleBinding
    {
        get { return (IValueBinding)GetValue(TitleBindingProperty); }
        set { SetValue(TitleBindingProperty, value); }
    }
    public static readonly DotvvmProperty TitleBindingProperty
        = DotvvmProperty.Register&amp;lt;IValueBinding, Breadcrumb2&amp;gt;(nameof(TitleBinding));


}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The class inherits from &lt;strong&gt;ItemsControl&lt;/strong&gt;, a handy base class that is common for all controls which have the &lt;strong&gt;DataSource &lt;/strong&gt;property. Building your own &lt;strong&gt;DataSource &lt;/strong&gt;property is not that easy if you want it to support all the nice things like the &lt;strong&gt;_index &lt;/strong&gt;variable – you can look in the &lt;a href="https://github.com/riganti/dotvvm/blob/master/src/DotVVM.Framework/Controls/ItemsControl.cs"&gt;ItemsControl&lt;/a&gt; source code how it’s done.&lt;/p&gt;&lt;p&gt;Otherwise, the code snippet above is not complicated – it is just long. It declares the &lt;strong&gt;HomeUrl &lt;/strong&gt;property as well as the &lt;strong&gt;ItemActiveBinding&lt;/strong&gt;, &lt;strong&gt;PublicUrlBinding &lt;/strong&gt;and &lt;strong&gt;TitleBinding&lt;/strong&gt; with the &lt;strong&gt;DataContextChange &lt;/strong&gt;attributes.&lt;/p&gt;&lt;p&gt;Now, let’s build the contents of the control. Since it doesn’t depend on the actual data passed to the control, we can do this in the &lt;strong&gt;Init &lt;/strong&gt;phase – in general, the earlier the better.&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;protected override void OnInit(IDotvvmRequestContext context)
{
    var ol = new HtmlGenericControl("ol");
    ol.Attributes["class"] = "breadcrumb";
    ol.Children.Add(BuildHomeItem());
    ol.Children.Add(BuildRepeater());
    Children.Add(ol);

    base.OnInit(context);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;The &lt;strong&gt;BuildHomeItem&lt;/strong&gt; method just builds the first piece of markup:&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;li&amp;gt;&amp;lt;a href="{value: HomeUrl}"&amp;gt;&amp;lt;span class="glyphicon glyphicon-home" /&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;private HtmlGenericControl BuildHomeItem()
{
    var homeLi = new HtmlGenericControl("li");
    {
        var homeLink = new HtmlGenericControl("a");
        {
            homeLink.Attributes["href"] = GetValueRaw(HomeUrlProperty);     // hard-coded value or binding

            var homeSpan = new HtmlGenericControl("span");
            {
                homeSpan.Attributes["class"] = "glyphicon glyphicon-home";
            }
            homeLink.Children.Add(homeSpan);
        }
        homeLi.Children.Add(homeLink);
    }
    return homeLi;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Btw, I am used to nest the child control creation in the inner blocks – it helps me to orient in large hierarchies of the controls.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Creation of the &lt;strong&gt;Repeater&lt;/strong&gt; involves implementation of the &lt;strong&gt;ItemTemplate&lt;/strong&gt; class:&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&amp;lt;dot:Repeater DataSource="{value: Links}" RenderWrapperTag="false"&amp;gt;
    &amp;lt;ItemTemplate&amp;gt;
        &amp;lt;li class-active="{value: IsActive}"&amp;gt;
            &amp;lt;a href="{value: Url}"&amp;gt;{{value: Title}}&amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    &amp;lt;/ItemTemplate&amp;gt;
&amp;lt;/dot:Repeater&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;private Repeater BuildRepeater()
{
    var repeater = new Repeater();
    repeater.RenderWrapperTag = false;
    repeater.ItemTemplate = new BreadcrumbLinkTemplate(this);
    repeater.SetBinding(Repeater.DataSourceProperty, GetValueBinding(DataSourceProperty));

    return repeater;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;pre class="brush:csharp"&gt;&lt;code class="language-CSHARP"&gt;internal class BreadcrumbLinkTemplate : ITemplate
{
    private Breadcrumb2 parent;

    public BreadcrumbLinkTemplate(Breadcrumb2 parent)
    {
        this.parent = parent;
    }

    public void BuildContent(IDotvvmRequestContext context, DotvvmControl container)
    {
        var li = new HtmlGenericControl("li");
        {
            li.CssClasses.AddBinding("active", parent.GetValueBinding(Breadcrumb2.ItemActiveBindingProperty));

            var link = new HtmlGenericControl("a");
            {
                link.Attributes["href"] = parent.GetValueBinding(Breadcrumb2.PublicUrlBindingProperty);
                link.SetBinding(HtmlGenericControl.InnerTextProperty, parent.GetValueBinding(Breadcrumb2.TitleBindingProperty));
            }
            li.Children.Add(link);
        }
        container.Children.Add(li);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;And we’re done – you can find the &lt;a href="https://github.com/riganti/dotvvm-samples-control-bindings"&gt;complete code on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;As you can see, building the control the second way is much more complicated. Partly, it’s because you are trying to build a very universal control that is agnostic to the type of items passed to the &lt;strong&gt;DataSource &lt;/strong&gt;collection, and accepts binding expressions as parameters. Thanks to the strongly-typed nature of DotVVM, you need to deal with bindings, data context stacks and changes, and all these things you wouldn’t have in the dynamic world. And finally, we didn’t have much time to make this simple – we focused on other areas than simplifying control development, although it is an important one too. &lt;/p&gt;&lt;p&gt;We had some ideas and &lt;a href="https://github.com/riganti/dotvvm/pull/535"&gt;we even have an experimental implementation&lt;/a&gt;, but we didn’t have the time to finalize the pull request and make it a real framework feature. &lt;/p&gt;&lt;pre class="brush:dothtml"&gt;&lt;code class="language-DOTHTML"&gt;&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
</feed>