<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>.NET on Viklund</title><link>https://viklund.fi/tags/.net/</link><description>Recent content in .NET on Viklund</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 06 Nov 2026 11:36:00 +0200</lastBuildDate><atom:link href="https://viklund.fi/tags/.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Types as Proof</title><link>https://viklund.fi/posts/types-as-proof/</link><pubDate>Fri, 06 Nov 2026 11:36:00 +0200</pubDate><guid>https://viklund.fi/posts/types-as-proof/</guid><description>&lt;p&gt;Only the agent who&amp;rsquo;s assigned to a ticket may reply to it. The obvious way to&#10;enforce that is a check before the reply:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;IsAssignedTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NotAssignedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// later: trusts that someone checked&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This works until the next use case forgets the check. The check returns a&#10;bool, and the answer is thrown away. When &lt;code&gt;Reply&lt;/code&gt; runs, it gets an &lt;code&gt;AgentId&lt;/code&gt;&#10;that may or may not have been checked, and nothing in the types tells you&#10;which.&lt;/p&gt;</description></item><item><title>Bridges Ask, Events Tell</title><link>https://viklund.fi/posts/bridges-ask-events-tell/</link><pubDate>Fri, 06 Nov 2026 11:35:00 +0200</pubDate><guid>https://viklund.fi/posts/bridges-ask-events-tell/</guid><description>&lt;p&gt;The first version of this backend had one project that was allowed to know&#10;about every module. All the translation between modules lived there, which&#10;sounded tidy: one seam, one place to look. It ended up seeing every project&#10;and referencing everything, and it was hard to maintain and easy to get&#10;wrong. About three months in, we removed it.&lt;/p&gt;&#10;&lt;p&gt;What replaced it is much stricter about what each project can see, and it&#10;comes down to two moves:&lt;/p&gt;</description></item><item><title>Ports and Adapters Without Exceptions</title><link>https://viklund.fi/posts/ports-and-adapters-without-exceptions/</link><pubDate>Fri, 06 Nov 2026 11:34:00 +0200</pubDate><guid>https://viklund.fi/posts/ports-and-adapters-without-exceptions/</guid><description>&lt;p&gt;Someone needs to load a ticket. They already have its id as a Guid, so they&#10;add an overload to the port, just this once:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IForLoadingTickets&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Ticket&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TicketId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// just this once…&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Ticket&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It compiles, and every unit test still passes. The build goes red anyway:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dotnet test tests/Architecture&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;failed DrivenPortsArchitectureTests.Driven_ports_should_never_accept_a_guid_parameter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Driven ports should never accept a guid as a parameter. Offending types:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tickets.Application.Ports.Driven.IForLoadingTickets&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The backend from the &lt;a href="https://viklund.fi/posts/four-models-of-one-person/"&gt;previous post&lt;/a&gt; is&#10;built this way. Its ports and adapters are rules the build enforces, with no&#10;exceptions, not even for plain CRUD. This post covers the layout, how it got&#10;there, and what it costs.&lt;/p&gt;</description></item><item><title>Four Models of One Person</title><link>https://viklund.fi/posts/four-models-of-one-person/</link><pubDate>Fri, 06 Nov 2026 11:33:00 +0200</pubDate><guid>https://viklund.fi/posts/four-models-of-one-person/</guid><description>&lt;p&gt;In the customer service system I&amp;rsquo;ve been building at work, a support agent is&#10;four different things. To authentication, they&amp;rsquo;re a user who signs in and has&#10;roles. To the module that knows the people, they&amp;rsquo;re a profile with a name and&#10;a team. To the module that hands out work, they&amp;rsquo;re a ticket handler, someone&#10;who can be given tickets to work on. And to the module that owns the&#10;conversation, they only matter as someone who may reply and who wrote a&#10;message.&lt;/p&gt;</description></item></channel></rss>