<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: itysu tur</title>
    <description>The latest articles on DEV Community by itysu tur (@dotnetwithai).</description>
    <link>https://dev.to/dotnetwithai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3948884%2F2c4659c7-2392-4b63-996b-158777dcb6c1.png</url>
      <title>DEV Community: itysu tur</title>
      <link>https://dev.to/dotnetwithai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dotnetwithai"/>
    <language>en</language>
    <item>
      <title>AI-Assisted TDD vs. Pure TDD: I Picked the Iterative Dance</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Fri, 05 Jun 2026 11:34:16 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/ai-assisted-tdd-vs-pure-tdd-i-picked-the-iterative-dance-chb</link>
      <guid>https://dev.to/dotnetwithai/ai-assisted-tdd-vs-pure-tdd-i-picked-the-iterative-dance-chb</guid>
      <description>&lt;h1&gt;
  
  
  AI-Assisted TDD vs. Pure TDD: I Picked the Iterative Dance
&lt;/h1&gt;

&lt;p&gt;How do you maintain a true test-first discipline when an AI assistant is constantly itching to write the implementation before you've even finished the test? That question gnawed at me for months.&lt;/p&gt;

&lt;p&gt;I've been a TDD advocate for years, but the rise of AI coding assistants like GitHub Copilot and Claude Sonnet 4.6 threw a wrench in my process. My usual rhythm of red-green-refactor felt constantly disrupted. I needed a way to leverage the AI without losing the benefits of TDD, especially on a new .NET 9 project I was kicking off. I wanted a true &lt;code&gt;ai tdd dotnet&lt;/code&gt; workflow, not just AI generating code &lt;em&gt;around&lt;/em&gt; my tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Initial Missteps and the 'Premature Implementation' Trap
&lt;/h2&gt;

&lt;p&gt;Honestly, my first attempts at integrating AI into my TDD cycle were a mess. I'd typically prompt something like, "Write a C# 13 method to reverse a string, then write a unit test for it." And almost every time, the AI—whether it was Copilot's inline suggestion or a full response from Claude Opus 4.7 in Cursor 0.42—would spit out the &lt;em&gt;implementation first&lt;/em&gt;, then a test that simply confirmed its own code. It wasn't TDD; it was "implementation-driven development with a side of AI-generated validation."&lt;/p&gt;

&lt;p&gt;This felt like cheating, or at best, missing the point. The value of TDD, for me, lies in defining behavior &lt;em&gt;before&lt;/em&gt; coding, letting the tests guide the design. When the AI gives you the answer upfront, that crucial design step is skipped. I might be wrong about this, but for me, the magic of TDD is in the constraint, the thinking it forces. The AI's eagerness to complete the thought was a constant hurdle.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of what I kept getting, and what I was actively trying to avoid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// My initial prompt (mental or explicit): "I need a method to safely parse an int from a string."&lt;/span&gt;
&lt;span class="c1"&gt;// AI's eager response, often before I've even thought of edge cases:&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringParser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;ParseIntOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// And then, a test for it, which is useful but didn't guide the *design*:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringParserTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ParseIntOrDefault_ValidInput_ReturnsParsedValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StringParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseIntOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ParseIntOrDefault_InvalidInput_ReturnsDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StringParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseIntOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation was often fine, but it bypassed my brain's TDD muscle memory. I was constantly running into this "premature implementation" trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rhythm I Found: Test-First Prompts
&lt;/h2&gt;

&lt;p&gt;It took me an embarrassing amount of time to figure out: I needed to treat the AI like a highly capable, but literal, junior developer who needed &lt;em&gt;very&lt;/em&gt; specific instructions. The key was to constrain the AI to &lt;strong&gt;only&lt;/strong&gt; generate the test, and &lt;em&gt;then&lt;/em&gt;, once I'd seen it fail, to ask for the implementation. This became my core &lt;code&gt;tdd ai pair&lt;/code&gt; strategy.&lt;/p&gt;

&lt;p&gt;What I ended up with was a two-stage prompting approach, primarily using Cursor 0.42's chat for the initial test generation and then Visual Studio 2026 with Copilot for quick completions on the implementation.&lt;/p&gt;

&lt;p&gt;First, I'd prompt for the test, often specifying C# 13 and .NET 9 context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="s"&gt;"I'm working in C# 13 with .NET 9. I need a unit test for a new static method `GuidUtils.IsValidGuid(string s)`.
&lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="n"&gt;GUID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Focus&lt;/span&gt; &lt;span class="n"&gt;ONLY&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s"&gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Sonnet 4.6 would then give me something wonderfully focused like this, which I'd drop into my test project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GuidUtilsTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsValidGuid_NullString_ReturnsFalse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GuidUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsValidGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsValidGuid_EmptyString_ReturnsFalse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GuidUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsValidGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsValidGuid_InvalidFormat_ReturnsFalse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GuidUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsValidGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not-a-guid"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;IsValidGuid_ValidGuid_ReturnsTrue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GuidUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsValidGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd run the tests, watch them all fail (red!), and &lt;em&gt;then&lt;/em&gt; I'd prompt the AI for the implementation, often feeding it the failing test code as context, or just referencing the method signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="s"&gt;"Implement the `GuidUtils.IsValidGuid` method in C# 13 to make the above tests pass. Keep it efficient."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, the AI would provide the minimal code to get to green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GuidUtils&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsValidGuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This felt like genuine &lt;code&gt;test driven ai&lt;/code&gt;. I was still driving the behavior definition, and the AI was an incredible accelerator for getting to the green state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm Still Tweaking: Refactoring and Edge Cases
&lt;/h2&gt;

&lt;p&gt;So, where am I now? I'm firmly in the camp of &lt;code&gt;ai tdd pair&lt;/code&gt; as a viable, productive workflow. My test suites are growing faster, and I'm catching more edge cases upfront because the AI is so good at suggesting test scenarios. It’s particularly strong for utility methods and well-defined algorithms.&lt;/p&gt;

&lt;p&gt;However, the journey isn't over. Refactoring, for instance, is still a tricky area. While I can prompt the AI to "refactor this C# method to improve readability," it often suggests a full rewrite based on patterns rather than an incremental, behavior-preserving refactor that I'd typically do with TDD. I find myself doing more of the refactoring manually, using the AI more for brainstorming &lt;em&gt;ideas&lt;/em&gt; for refactorings rather than the actual code changes. Also, for complex domain logic with subtle business rules, I still often write the first few tests myself to truly embed my understanding. Your mileage may vary, but for me, the &lt;em&gt;test generation&lt;/em&gt; part remains the biggest win.&lt;/p&gt;

&lt;p&gt;The biggest remaining challenge for me is leveraging AI for complex domain modeling during the &lt;em&gt;design&lt;/em&gt; phase, before any code or tests are written.&lt;/p&gt;




&lt;p&gt;If you've tried to pair TDD with AI in a .NET context, especially for refactoring existing codebases, I'd love to hear what broke or what surprising workflows you discovered.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Agents Cut My .NET Debug Time by 30% — But Not How I Expected</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:42:20 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/ai-agents-cut-my-net-debug-time-by-30-but-not-how-i-expected-15h2</link>
      <guid>https://dev.to/dotnetwithai/ai-agents-cut-my-net-debug-time-by-30-but-not-how-i-expected-15h2</guid>
      <description>&lt;h1&gt;
  
  
  AI Agents Cut My .NET Debug Time by 30% — But Not How I Expected
&lt;/h1&gt;

&lt;p&gt;I closed the laptop at 2am after my third failed migration attempt, staring at a &lt;code&gt;NullReferenceException&lt;/code&gt; that just &lt;em&gt;shouldn't&lt;/em&gt; be there. Debugging legacy .NET 6 code after a .NET 9 upgrade, especially with a few tricky interop layers, has been draining. I'd been hearing about the new wave of &lt;code&gt;ai agents dotnet&lt;/code&gt; tools, specifically the multi-step reasoning capabilities, and honestly, I was cynical. My prior attempts with "AI assistants" often felt like glorified search engines. But that particular Tuesday night, something had to give. I needed a different approach to untangle these gnarly dependency issues.&lt;/p&gt;

&lt;p&gt;My goal wasn't to replace myself, but to offload the tedious, repetitive "check this, then check that" debugging loops that eat up hours. I wanted an &lt;code&gt;agentic ai 2026&lt;/code&gt; solution that could actually &lt;em&gt;act&lt;/em&gt; on code, not just suggest it. The idea of a small, focused agent capable of inspecting project files, running tests, and even modifying code based on a goal, sounded like a pipe dream. Yet, the promise of automating the initial diagnostic steps for complex &lt;code&gt;csharp ai agents&lt;/code&gt; interactions kept nagging at me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agentic "Aha!" Moment: Context is King
&lt;/h2&gt;

&lt;p&gt;My initial attempts involved throwing entire solution folders at large language models via the new Copilot for Workspaces in Visual Studio 2026. Predictably, it was a mess. The models got lost, hallucinated file paths, or just gave generic advice. It wasn't until I started thinking like a human debugger – focusing on specific files and immediate context – that things changed. I needed an agent that could &lt;em&gt;reason&lt;/em&gt; about its environment, not just consume a massive blob of text.&lt;/p&gt;

&lt;p&gt;What finally clicked for me was using a specialized agent running on Claude Opus 4.7, orchestrated via a simple C# 13 console app. This agent's primary tool was a &lt;code&gt;FileInspector&lt;/code&gt; which could read specific files and a &lt;code&gt;DotNetRunner&lt;/code&gt; that could execute &lt;code&gt;dotnet test&lt;/code&gt; or &lt;code&gt;dotnet build&lt;/code&gt; commands. The prompt was crucial: I didn't ask it to "fix the bug," but to "investigate the &lt;code&gt;NullReferenceException&lt;/code&gt; in &lt;code&gt;LegacyService.cs&lt;/code&gt; related to &lt;code&gt;DependencyX&lt;/code&gt; after the .NET 9 upgrade." This gave it a concrete starting point.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of the tool definition I gave my agent, using the emerging Model Context Protocol (MCP) for C# tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified representation of an agent tool definition&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileInspectorTool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ToolDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reads the content of a specified file."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ParameterDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The path to the file to read."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"Error: File not found at &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DotNetRunnerTool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ToolDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Executes a dotnet CLI command and returns its output."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;RunDotNetCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ParameterDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The dotnet command arguments (e.g., 'test', 'build --no-restore')."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// In a real scenario, this would execute a process and capture stdout/stderr&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"Simulated: dotnet &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; output..."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent would then autonomously decide to &lt;code&gt;ReadFile&lt;/code&gt; on &lt;code&gt;LegacyService.cs&lt;/code&gt;, see the usage of &lt;code&gt;DependencyX&lt;/code&gt;, then perhaps &lt;code&gt;RunDotNetCommand("build --no-restore")&lt;/code&gt; to check for compilation errors, and so on. This iterative, tool-driven approach was &lt;strong&gt;game-changing&lt;/strong&gt; for narrowing down issues. It wasn't perfect, but it dramatically cut down the initial legwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Overhead and The "Gotcha": Orchestration Complexity
&lt;/h2&gt;

&lt;p&gt;While the concept of &lt;code&gt;ai agents dotnet&lt;/code&gt; proved powerful, the journey wasn't without its bumps. My biggest "gotcha" wasn't the AI itself, but the sheer complexity of agent orchestration. Initially, I tried to make a single, monolithic agent that could do everything. This led to decision paralysis for the model and slow, expensive interactions.&lt;/p&gt;

&lt;p&gt;The key learning for me was to embrace specialized agents. Instead of one "super-agent," I ended up with a small team:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Issue Triage Agent:&lt;/strong&gt; (Claude Haiku 4.5) Quick, cheap, and good at scanning logs or error messages, then deciding which specialized agent to hand off to.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Code Analysis Agent:&lt;/strong&gt; (Claude Sonnet 4.6) Deeper code inspection, understanding design patterns, suggesting refactors.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Test Execution Agent:&lt;/strong&gt; (Custom C# agent) Focused solely on running specific &lt;code&gt;dotnet test&lt;/code&gt; commands and reporting results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This multi-agent architecture, though more complex to set up, provided far superior results. My local setup, primarily in Visual Studio 2026 and Rider 2026, involves a small C# orchestrator that manages tool registration for each agent and routes requests. The biggest challenge here was ensuring proper context flow between agents – passing relevant file paths, error messages, or previous analysis results without overwhelming the next agent. I also found that giving agents a "scratchpad" (a temporary file they could write notes to) significantly improved their multi-step reasoning, as they could externalize their thoughts. It took me an embarrassing amount of time to figure out that just dumping all prior conversational turns into the next agent's prompt was a recipe for disaster.&lt;/p&gt;

&lt;p&gt;For instance, if the Triage Agent spotted a potential EF Core issue, it would hand off to the Code Analysis Agent with a prompt like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AGENT_MESSAGE]
Context: A NullReferenceException was found in LegacyService.cs.
Suspected Area: Entity Framework Core query in DataAccessLayer.cs.
Goal: Analyze DataAccessLayer.cs for potential EF Core lazy-loading or include-related issues causing the NRE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This explicit context passing, rather than relying on a shared history, made all the difference in getting reliable &lt;code&gt;agentic ai 2026&lt;/code&gt; behavior. Your mileage may vary, but for my specific .NET ecosystem, this separation of concerns was crucial.&lt;/p&gt;




&lt;p&gt;I'm still figuring out the best ways to integrate these &lt;code&gt;ai agents dotnet&lt;/code&gt; workflows into my daily &lt;code&gt;.NET 9&lt;/code&gt; development. The initial setup had a steep learning curve, and the cost of powerful models like Claude Opus 4.7 can add up if you're not careful with prompt engineering. Right now, I'm focusing on refining the orchestration for more complex refactoring tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you've built specialized C# agents for automated debugging or refactoring, especially in a distributed microservices environment, I'd love to hear what surprising limitations or breakthroughs you've encountered.
&lt;/h2&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>How I Stopped Fighting AI on Legacy .NET Code</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Wed, 03 Jun 2026 11:32:09 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/how-i-stopped-fighting-ai-on-legacy-net-code-4abd</link>
      <guid>https://dev.to/dotnetwithai/how-i-stopped-fighting-ai-on-legacy-net-code-4abd</guid>
      <description>&lt;h1&gt;
  
  
  How I Stopped Fighting AI on Legacy .NET Code
&lt;/h1&gt;

&lt;p&gt;My workflow before modern AI tools felt like a constant cycle of context switching. I’d spend half my day digging through solution explorer, jumping between files to understand dependencies, and manually writing boilerplate. Debugging meant stepping through code line by line, often guessing at the root cause of an issue in a complex .NET Framework 4.8 service that’s been around for over a decade.&lt;/p&gt;

&lt;p&gt;Now, with tools like GitHub Copilot for Workspaces and Claude Opus 4.7 integrated into Visual Studio 2026 and Rider 2026, things are different. Boilerplate code, initial test drafts, even explaining obscure C# 13 syntax – AI handles it. The friction of starting a new feature or understanding a small, isolated module has drastically reduced. Yet, this new efficiency introduced a different kind of friction, a subtle battle against a tool that often &lt;em&gt;thinks&lt;/em&gt; it knows more than it does about my specific codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI's Blind Spot for Architectural Nuance
&lt;/h2&gt;

&lt;p&gt;I quickly learned that AI, even the latest models like Claude Sonnet 4.6, struggles significantly with deep architectural context. It's fantastic at localized code generation and refactoring within a single file or a tightly coupled pair. But ask it to understand a cross-cutting concern in a multi-project solution, or to refactor a service layer that has evolved over years, and its suggestions often fall flat. For me, this was particularly evident when dealing with our primary legacy .NET 9 service, which uses a custom event-driven architecture that predates many modern patterns.&lt;/p&gt;

&lt;p&gt;Last Tuesday, I was trying to refactor a &lt;code&gt;PaymentProcessor&lt;/code&gt; interface and its implementations across three different projects. I fed the entire solution structure and relevant files into Cursor 0.42+, expecting it to grasp the implicit domain knowledge. It generated a clean, modern C# 13 interface and abstract base class, but completely missed the mark on how our custom message bus integrated with the &lt;code&gt;PostProcess&lt;/code&gt; method, suggesting a direct dependency injection where a &lt;code&gt;MediatR&lt;/code&gt; notification was expected. It was syntactically perfect, but architecturally &lt;strong&gt;wrong&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AI's initial suggestion, trying to refactor PaymentProcessor&lt;/span&gt;
&lt;span class="c1"&gt;// This looks clean, but completely misses our internal message bus integration&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StripePaymentProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPaymentProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StripePaymentProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// AI missed that we don't directly inject IMessageBus here, &lt;/span&gt;
    &lt;span class="c1"&gt;// but rather publish events via a MediatR notification handler.&lt;/span&gt;
    &lt;span class="c1"&gt;// private readonly IMessageBus _messageBus; &lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;StripePaymentProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StripePaymentProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="cm"&gt;/*, IMessageBus messageBus */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// _messageBus = messageBus;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;PaymentResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Processing Stripe payment for {Amount}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ... Stripe API calls ...&lt;/span&gt;
        &lt;span class="c1"&gt;// AI suggested: await _messageBus.Publish(new PaymentProcessedEvent(request.OrderId));&lt;/span&gt;
        &lt;span class="c1"&gt;// What we actually needed was a MediatR notification:&lt;/span&gt;
        &lt;span class="c1"&gt;// await _mediator.Publish(new PaymentProcessedNotification(request.OrderId));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaymentResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Stripe payment successful"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue isn't that AI can't generate code; it's that it lacks the deep, implicit understanding of &lt;em&gt;why&lt;/em&gt; certain design decisions were made years ago. It doesn't know the historical context, the trade-offs, or the specific business rules that shaped the architecture. This is where ai csharp limitations truly show. I've found it helps to provide extremely verbose context about architectural patterns and internal conventions, but even then, it's a constant battle of correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging the Unseen: Environment-Specific Failures
&lt;/h2&gt;

&lt;p&gt;Another area where I consistently hit ai coding limits is debugging environment-specific issues. AI models, even those with advanced MCP (Model Context Protocol) capabilities, are trained on vast datasets of common code patterns and public documentation. They are fantastic at identifying syntax errors, suggesting common exceptions, or even pinpointing potential race conditions in well-known frameworks. But when a bug manifests only in a specific Kubernetes cluster running .NET 10 Preview, or due to a subtle interaction with a third-party library's native dependency, AI often provides generic, unhelpful advice.&lt;/p&gt;

&lt;p&gt;I spent three days chasing a &lt;code&gt;System.Net.Http.HttpRequestException&lt;/code&gt; last month that only occurred when our service was deployed to our staging environment. Copilot Edits kept suggesting network connectivity issues or incorrect base URLs. Claude Opus 4.7, when fed the stack trace and relevant code, would hallucinate about proxy configurations that didn't exist in our setup. It took me an embarrassing amount of time to figure out it was a specific TLS 1.3 negotiation issue with an outdated load balancer firmware version in staging – something completely external to the code itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# My desperate prompt to Claude Opus 4.7 after 2 days&lt;/span&gt;
&lt;span class="c"&gt;# Claude's response was a generic list of HTTP client best practices.&lt;/span&gt;
&lt;span class="c"&gt;# It couldn't 'see' the environment.&lt;/span&gt;
You are debugging an ASP.NET Core 9.0 service.
It throws System.Net.Http.HttpRequestException: &lt;span class="s1"&gt;'The SSL connection could not be established'&lt;/span&gt;
This ONLY happens &lt;span class="k"&gt;in &lt;/span&gt;our staging Kubernetes cluster, not &lt;span class="nb"&gt;local &lt;/span&gt;dev or UAT.
The target is an external vendor API.
I&lt;span class="s1"&gt;'ve verified DNS, firewall rules, and API key.
What are less obvious causes for this specific environment-dependent SSL failure?

# Snippet of the actual error log from K8s, which AI struggled to interpret accurately.
# It often focused on app code, not infrastructure.
---
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---&amp;gt; System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
    ---&amp;gt; System.Security.Cryptography.X509Certificates.X509Chain+ChainAix.BuildChain(X509Certificate2 certificate, X509Certificate2Collection extraStore, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, X509VerificationFlags verificationFlags, DateTime verificationTime, X509ChainStatus[]&amp;amp; chainStatus)
    at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ReadOnlySpan`1 alert)
    at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean is renegotiation, CancellationToken cancellationToken)
    at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
---
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where ai fails dotnet developers who work on complex, distributed systems. The AI lacks the "eyes" to observe the runtime environment, the network conditions, or the specific configuration of a particular deployment. It can only reason about the code it sees, not the complex interplay of factors outside the repository. Your mileage may vary, but for me, environment-specific debugging remains a deeply human task.&lt;/p&gt;

&lt;p&gt;I'm still figuring out the optimal dance between letting AI take the lead and knowing when to trust my own intuition and experience. It's clear that AI is a powerful accelerator for many tasks, but its understanding of architectural intent and unseen environmental factors is still nascent.&lt;/p&gt;




&lt;p&gt;If you've encountered similar limitations with AI tools when tackling legacy .NET code or subtle environment-specific bugs, I'd love to hear what strategies you've found effective.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>5 Surprises Migrating .NET Framework to .NET 9 with Claude Opus</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Tue, 02 Jun 2026 10:25:42 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/5-surprises-migrating-net-framework-to-net-9-with-claude-opus-39fp</link>
      <guid>https://dev.to/dotnetwithai/5-surprises-migrating-net-framework-to-net-9-with-claude-opus-39fp</guid>
      <description>&lt;h1&gt;
  
  
  5 Surprises Migrating .NET Framework to .NET 9 with Claude Opus
&lt;/h1&gt;

&lt;p&gt;My workflow before this little experiment was pretty standard for a .NET Framework to .NET 9 migration: open Visual Studio 2026, create a new SDK-style project, copy over files, then hit a wall of red squiggles. I'd spend hours manually updating &lt;code&gt;csproj&lt;/code&gt; files, hunting down modern equivalents for &lt;code&gt;System.Web&lt;/code&gt; or &lt;code&gt;ConfigurationManager&lt;/code&gt;, and constantly flipping between documentation, Stack Overflow, and my IDE. It was a grind of context switching and often, just plain guessing.&lt;/p&gt;

&lt;p&gt;Now, my initial pass looks wildly different. I'm still in Visual Studio 2026 or Rider 2026, but the first step involves feeding the entire legacy project structure and relevant code files into Claude Opus 4.7. I'm prompting it to generate the new &lt;code&gt;csproj&lt;/code&gt;, suggest initial code changes, and even outline a migration plan. The red squiggles still appear, but they're a &lt;em&gt;starting point&lt;/em&gt; for refinement, not a blank canvas of despair. It’s like having a hyper-competent, slightly over-confident junior dev do the grunt work, and I get to be the architect, reviewing and correcting.&lt;/p&gt;

&lt;p&gt;Honestly, I was skeptical. Our team has a decent-sized .NET Framework 4.8 web service – nothing ancient, but definitely showing its age with some custom configuration and a reliance on &lt;code&gt;System.Web&lt;/code&gt; for various utility functions. The goal was to bring it to .NET 9 (with C# 13, naturally) to leverage modern performance and features. I figured this was a prime candidate to see if &lt;code&gt;ai migration dotnet&lt;/code&gt; could actually deliver, or if it was just hype.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Opus 4.7 Got Right (and Wrong) About the Initial Lift
&lt;/h2&gt;

&lt;p&gt;My first real test was feeding Claude Opus 4.7 an entire solution structure, including a &lt;code&gt;csproj&lt;/code&gt; file and a few core C# files, asking for a &lt;code&gt;net framework to net 9&lt;/code&gt; conversion. What surprised me was how well it handled the basic &lt;code&gt;csproj&lt;/code&gt; upgrade. It correctly identified target frameworks, package references, and even most of the &lt;code&gt;ItemGroup&lt;/code&gt; changes. Honestly, I expected this to fail completely on the first pass, but the generated SDK-style project file was a solid foundation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Initial prompt to Claude Opus 4.7 --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;prompt&amp;gt;&lt;/span&gt;
I have a .NET Framework 4.8 C# project. Please convert this csproj to an SDK-style .NET 9 csproj. 
Also, review the provided C# code files and suggest initial changes for .NET 9 compatibility, focusing on common System.Web and ConfigurationManager replacements.

&lt;span class="nt"&gt;&amp;lt;csproj_content&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;ToolsVersion=&lt;/span&gt;&lt;span class="s"&gt;"15.0"&lt;/span&gt; &lt;span class="na"&gt;DefaultTargets=&lt;/span&gt;&lt;span class="s"&gt;"Build"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Configuration&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(Configuration)' == '' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Debug&lt;span class="nt"&gt;&amp;lt;/Configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Platform&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(Platform)' == '' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AnyCPU&lt;span class="nt"&gt;&amp;lt;/Platform&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ProjectGuid&amp;gt;&lt;/span&gt;{ABCDEF-1234-5678-90AB-CDEF12345678}&lt;span class="nt"&gt;&amp;lt;/ProjectGuid&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Library&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;AppDesignerFolder&amp;gt;&lt;/span&gt;Properties&lt;span class="nt"&gt;&amp;lt;/AppDesignerFolder&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;RootNamespace&amp;gt;&lt;/span&gt;MyLegacyService&lt;span class="nt"&gt;&amp;lt;/RootNamespace&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;AssemblyName&amp;gt;&lt;/span&gt;MyLegacyService&lt;span class="nt"&gt;&amp;lt;/AssemblyName&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;TargetFrameworkVersion&amp;gt;&lt;/span&gt;v4.8&lt;span class="nt"&gt;&amp;lt;/TargetFrameworkVersion&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;FileAlignment&amp;gt;&lt;/span&gt;512&lt;span class="nt"&gt;&amp;lt;/FileAlignment&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"System"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"System.Configuration"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"System.Web"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- ... other references ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"MyService.cs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- ... other compiles ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildToolsPath)\Microsoft.CSharp.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/csproj_content&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;file_content&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"MyService.cs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  using System.Configuration;
  using System.Web;
  using System.Collections.Generic;

  namespace MyLegacyService
  {
      public class MyService
      {
          public string GetConfigValue()
          {
              return ConfigurationManager.AppSettings["MyKey"];
          }
          public Dictionary&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; GetRequestHeaders()
          {
              var headers = new Dictionary&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;();
              if (HttpContext.Current != null)
              {
                  foreach (string key in HttpContext.Current.Request.Headers.AllKeys)
                  {
                      headers.Add(key, HttpContext.Current.Request.Headers[key]);
                  }
              }
              return headers;
          }
      }
  }
&lt;span class="nt"&gt;&amp;lt;/file_content&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/prompt&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Claude's (edited for brevity) initial response for MyService.cs --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
csharp&lt;br&gt;
// Claude's suggested MyService.cs changes&lt;br&gt;
// Note: This is a starting point, manual integration with IConfiguration and DI is needed.&lt;br&gt;
using Microsoft.Extensions.Configuration; // Added&lt;br&gt;
using Microsoft.AspNetCore.Http; // For HttpContext replacement (assuming ASP.NET Core)&lt;br&gt;
using System.Collections.Generic;&lt;/p&gt;

&lt;p&gt;namespace MyLegacyService // Namespace retained, assuming project structure&lt;br&gt;
{&lt;br&gt;
    public class MyService&lt;br&gt;
    {&lt;br&gt;
        // Placeholder for IConfiguration injection&lt;br&gt;
        private readonly IConfiguration _configuration; &lt;br&gt;
        private readonly IHttpContextAccessor _httpContextAccessor; // For HttpContext access&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public MyService(IConfiguration configuration, IHttpContextAccessor httpContextAccessor) // Added constructor
    {
        _configuration = configuration;
        _httpContextAccessor = httpContextAccessor;
    }

    public string GetConfigValue()
    {
        // Replaced ConfigurationManager.AppSettings
        return _configuration["MyKey"]; 
    }

    public Dictionary&amp;lt;string, string&amp;gt; GetRequestHeaders()
    {
        var headers = new Dictionary&amp;lt;string, string&amp;gt;();
        if (_httpContextAccessor.HttpContext != null)
        {
            foreach (var header in _httpContextAccessor.HttpContext.Request.Headers)
            {
                headers.Add(header.Key, header.Value.ToString());
            }
        }
        return headers;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where it stumbled, predictably, was on the deeper integration patterns. It correctly identified `ConfigurationManager.AppSettings` needed to go but replaced it with a direct `IConfiguration` access without considering dependency injection or how that `IConfiguration` instance would actually *get* there in a new ASP.NET Core project. Similarly, for `HttpContext.Current`, it pointed to `IHttpContextAccessor`, which is the right path, but again, didn't provide the full DI setup. It was a strong hint, but not a drop-in solution. The **context of *how* an ASP.NET Core app is structured** was missing from its initial understanding, which is fair given I only fed it code, not architecture.

## The Iterative Dance: Prompting for Nuances and Dependencies

This is where the real `claude migration` magic, or rather, the iterative process, began. Instead of asking for a full conversion, I started focusing on specific problem areas. For instance, a common pattern in our legacy code was accessing `ConfigurationManager` in static helper classes. I couldn't just inject `IConfiguration` into a static class. Took me an embarrassing amount of time to figure out the right prompt to get Claude to suggest a factory pattern or a service locator *only for that specific legacy scenario* that I could then refactor later.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
prompt&lt;br&gt;
My .NET 9 project uses Dependency Injection. I have a static utility class 'LegacyConfigHelper' that currently accesses ConfigurationManager.AppSettings. How can I refactor this static class to use IConfiguration from DI, or provide a suitable pattern for accessing configuration in a static context without violating DI principles in a modern ASP.NET Core 9 application? Provide a code example.&lt;br&gt;
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Claude's suggested pattern for static configuration access (simplified)&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;MyLegacyService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyConfigHelper&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// This method should be called once at application startup (e.g., in Program.cs)&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetLegacySetting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_configuration&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegacyConfigHelper has not been initialized. Call Initialize() at startup."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In Program.cs (or similar startup file for an ASP.NET Core app):&lt;/span&gt;
&lt;span class="c1"&gt;// var builder = WebApplication.CreateBuilder(args);&lt;/span&gt;
&lt;span class="c1"&gt;// // ... other services ...&lt;/span&gt;
&lt;span class="c1"&gt;// var app = builder.Build();&lt;/span&gt;
&lt;span class="c1"&gt;// LegacyConfigHelper.Initialize(app.Configuration); // Call this after build, before run&lt;/span&gt;
&lt;span class="c1"&gt;// app.Run();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern, while not ideal long-term, gave me a bridge. It allowed me to get the application compiling and running with &lt;code&gt;dotnet build&lt;/code&gt; quickly, deferring a full refactor of those static helpers. I found that providing a small, focused code block and a very specific problem description yielded far better results than dumping large files. Your mileage may vary, but for me, this granular approach to &lt;code&gt;ai migration dotnet&lt;/code&gt; was key. I also started using Copilot Edits within Visual Studio 2026 for smaller, in-file refactors after Claude had done the heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup I Settled On for Debugging and Refinement
&lt;/h2&gt;

&lt;p&gt;My final setup involved Visual Studio 2026 as the primary IDE for debugging and running tests, especially with the new C# 13 features. For larger refactors or when I needed to give Claude more context than a single file, I'd use Cursor 0.42+. Cursor's ability to "chat with your codebase" by indexing multiple files and providing a larger context window (leveraging MCP, Model Context Protocol, for Claude Opus 4.7) was invaluable. I could ask it to find all usages of a particular &lt;code&gt;System.Web&lt;/code&gt; method and suggest replacements across the entire project, which was far more efficient than manual searching.&lt;/p&gt;

&lt;p&gt;When I had a chunk of code that Claude had transformed, my process was to paste it into Visual Studio, let Copilot Edits suggest minor syntax fixes, and then immediately run the existing unit tests (which I was also migrating and updating with Claude's help).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example of running tests to validate a migration step&lt;/span&gt;
&lt;span class="c"&gt;# Assuming you've created a test project for your migrated service&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;MyMigratedService.Tests
dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"Category=MigrationTests &amp;amp; Priority=High"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rapid feedback loop was critical. I screwed this up by initially trusting Claude's output too much without immediate validation. Last Tuesday, I was debugging a seemingly correct &lt;code&gt;IConfiguration&lt;/code&gt; access that was always returning null, only to realize Claude had suggested a &lt;code&gt;GetSection&lt;/code&gt; call that didn't match my appsettings structure. A quick &lt;code&gt;dotnet test&lt;/code&gt; would have caught that immediately if I'd written a test for it (which I then did). I'm still figuring out the best way to leverage Copilot for Workspaces alongside Claude's larger refactors, but the combination of broad strokes from Claude and fine-grained assistance from Copilot is proving powerful.&lt;/p&gt;

&lt;p&gt;The service is now mostly running on .NET 9, albeit with a few &lt;code&gt;LegacyConfigHelper&lt;/code&gt; instances I'm still planning to eliminate. The biggest remaining challenge is a complex custom serialization library that relies heavily on reflection patterns that changed between .NET Framework and .NET 9.&lt;/p&gt;




&lt;p&gt;If you've tried using AI models like Claude or Copilot for Workspaces to migrate a substantial .NET Framework project to .NET 9, I'd love to hear about your biggest gotchas or unexpected wins.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>3 Unexpected Ways Copilot Edges CodeWhisperer for .NET 9 Devs</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Mon, 01 Jun 2026 10:31:02 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/3-unexpected-ways-copilot-edges-codewhisperer-for-net-9-devs-dea</link>
      <guid>https://dev.to/dotnetwithai/3-unexpected-ways-copilot-edges-codewhisperer-for-net-9-devs-dea</guid>
      <description>&lt;h1&gt;
  
  
  3 Unexpected Ways Copilot Edges CodeWhisperer for .NET 9 Devs
&lt;/h1&gt;

&lt;p&gt;I processed over 2,000 lines of legacy C# last month, untangling years of technical debt in a crucial microservice. It was a grind, and honestly, the thought of doing it without some AI assistance felt like going back to the stone age. My team’s been using GitHub Copilot for Workspaces for a while, and it’s become indispensable for our .NET 9 projects. But with all the buzz around Amazon CodeWhisperer's recent C# model updates and its deep integration into AWS services, I started to wonder if we were missing out. For a couple of weeks, I decided to run both in parallel in Visual Studio 2026 and Rider 2026, specifically looking at their &lt;code&gt;ai code completion csharp&lt;/code&gt; capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Initial Jitters and Basic Completion
&lt;/h2&gt;

&lt;p&gt;Setting up both was straightforward enough. Copilot for Workspaces was already active, and CodeWhisperer's plugin for Visual Studio 2026 (or Rider 2026) installed without a hitch. My first impression, especially when writing new methods or simple classes, was that Copilot felt more organic. It would often suggest completions that extended naturally from my variable names or comments. CodeWhisperer, on the other hand, sometimes felt a little &lt;strong&gt;too eager&lt;/strong&gt; to complete an entire method or class with boilerplate that wasn't quite what I needed, often requiring more backspacing than actual coding.&lt;/p&gt;

&lt;p&gt;For example, when I was creating a simple DTO, Copilot would usually give me property names that made sense in the context of the surrounding code. CodeWhisperer, especially in a fresh file, would sometimes just dump a generic &lt;code&gt;public class MyClass { public int Id { get; set; } ... }&lt;/code&gt; which was rarely useful. I found myself hitting &lt;code&gt;Esc&lt;/code&gt; more often with CodeWhisperer. It took me an embarrassing amount of time to realize I needed to be much more explicit with my initial code or comments for CodeWhisperer to grasp the intent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Scenario: Creating a new DTO for a user profile update request&lt;/span&gt;
&lt;span class="c1"&gt;// Copilot often suggests relevant properties based on common patterns.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfileUpdateRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;UserId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Copilot often suggests LastName, Email right after&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// CodeWhisperer, without much context, might be more generic or verbose initially.&lt;/span&gt;
&lt;span class="c1"&gt;// It often felt like I had to type more of the structure before it "understood".&lt;/span&gt;
&lt;span class="c1"&gt;// Example of a prompt I'd type, then CodeWhisperer might try to complete:&lt;/span&gt;
&lt;span class="c1"&gt;// public class UserProfileUpdateRequest&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//     public string Username { get; set; }&lt;/span&gt;
&lt;span class="c1"&gt;//     public string Password { get; set; } // Less relevant for a *profile update*&lt;/span&gt;
&lt;span class="c1"&gt;//     // ...&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where Contextual Understanding Made the Difference
&lt;/h2&gt;

&lt;p&gt;This is where the &lt;code&gt;copilot vs codewhisperer&lt;/code&gt; debate really started to resolve for me. I spend a lot of time in existing, often sprawling, C# applications. Copilot for Workspaces, especially with its expanded context window, felt like it had a much better grasp of the surrounding files and the overall project structure. When I was refactoring a particularly gnarly service class, Copilot would offer suggestions that actually adhered to the existing naming conventions and patterns within that specific codebase, even if they were a bit idiosyncratic.&lt;/p&gt;

&lt;p&gt;Last Tuesday, I was debugging a 500-line method (yes, really) that handled complex business logic. My goal was to extract a few helper methods. Copilot consistently suggested method signatures and implementations that fit the existing data flow and object types. CodeWhisperer, powered by its latest C# model, often proposed entirely new approaches or introduced types that weren't present, forcing me to re-type or discard its suggestions. It felt like CodeWhisperer was trying to give me "best practice" code rather than "this project's practice" code. Honestly, I expected CodeWhisperer to be better at this, given Amazon's scale, but for legacy code, it just wasn't as effective &lt;strong&gt;for me&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Original problematic code snippet (simplified for brevity)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... many lines of validation and data fetching ...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSpecialOrder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Complex logic for special orders, ~100 lines&lt;/span&gt;
        &lt;span class="c1"&gt;// Copilot would suggest: "Extract SpecialOrderValidation(request)"&lt;/span&gt;
        &lt;span class="c1"&gt;// CodeWhisperer often suggested: "Create new SpecialOrderProcessor.Process(request)"&lt;/span&gt;
        &lt;span class="c1"&gt;// which wasn't quite what I was going for.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Regular order processing&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... more logic ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Copilot's helpful suggestion for extraction within the existing class:&lt;/span&gt;
&lt;span class="c1"&gt;// (User types 'private bool IsValidSpecialOrder(OrderRequest request)' and Copilot fills)&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsValidSpecialOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check specific conditions for special orders&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Ecosystem Play and My Final Setup
&lt;/h2&gt;

&lt;p&gt;Beyond raw code completion, the ecosystem integration was a significant factor. As a heavy GitHub user, Copilot for Workspaces integrates seamlessly with my pull requests, code reviews, and even suggests changes based on my team's coding standards pulled from our repositories. I'm still figuring out how to fully leverage Copilot Edits for multi-file changes, but the potential is huge. CodeWhisperer, naturally, shines when you're deeply embedded in the AWS ecosystem. If I were building a new serverless application on AWS Lambda with .NET 9, I could see CodeWhisperer's direct integration with AWS SDKs and services being a massive time-saver. It automatically suggests code for connecting to S3, DynamoDB, and other services, which is incredibly powerful if that's your stack.&lt;/p&gt;

&lt;p&gt;For my current work, which involves a mix of legacy on-prem and modern Azure-hosted microservices, Copilot’s broader contextual understanding and integration with GitHub felt more aligned. Your mileage may vary, especially if AWS is your primary cloud provider. What I ended up doing was keeping Copilot for Workspaces as my primary &lt;code&gt;ai code completion csharp&lt;/code&gt; tool, mainly because its suggestions required less correction in my daily workflow. I still have CodeWhisperer installed, but I disable it by default and only enable it when I'm specifically working on a new AWS-centric project where I know its specialized knowledge will be an advantage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# How I toggle CodeWhisperer in Visual Studio 2026 (simplified representation)&lt;/span&gt;
&lt;span class="c"&gt;# In reality, this is done via Extensions -&amp;gt; Manage Extensions, or a specific CodeWhisperer menu.&lt;/span&gt;
&lt;span class="c"&gt;# This shell command is illustrative of the intent.&lt;/span&gt;
&lt;span class="c"&gt;# My preference is to have Copilot active by default.&lt;/span&gt;

&lt;span class="c"&gt;# Example: Disable CodeWhisperer globally for general .NET development&lt;/span&gt;
&lt;span class="c"&gt;# (simulated via CLI, actual UI interaction in VS/Rider)&lt;/span&gt;
&lt;span class="c"&gt;# aws codewhisperer disable-extension --profile my-dev-profile&lt;/span&gt;

&lt;span class="c"&gt;# Example: Enable CodeWhisperer when starting a new AWS-specific project&lt;/span&gt;
&lt;span class="c"&gt;# aws codewhisperer enable-extension --profile my-dev-profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm currently running Copilot for Workspaces alongside C# 13 in Visual Studio 2026. While CodeWhisperer has certainly improved, for now, Copilot provides a smoother, more context-aware experience across the diverse C# projects I juggle. I’m still figuring out how to best integrate AI tools for high-level architectural suggestions rather than just code completion.&lt;/p&gt;




&lt;p&gt;If you've run both GitHub Copilot and Amazon CodeWhisperer on complex legacy C# codebases, I'd love to hear what specific challenges or triumphs you encountered.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Is AI-Native .NET Development Actually Happening in 2026?</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Sun, 31 May 2026 12:48:02 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/is-ai-native-net-development-actually-happening-in-2026-2pj6</link>
      <guid>https://dev.to/dotnetwithai/is-ai-native-net-development-actually-happening-in-2026-2pj6</guid>
      <description>&lt;h1&gt;
  
  
  Is AI-Native .NET Development Actually Happening in 2026?
&lt;/h1&gt;

&lt;p&gt;Honestly, I'd been dipping my toes into AI for coding for a while, mostly with basic Copilot completions, but it felt more like a novelty. Then, last Tuesday, my teammate Mark mentioned how he'd been using &lt;code&gt;Copilot Edits&lt;/code&gt; in Visual Studio 2026 to refactor some gnarly .NET Framework 4.8 code into modern .NET 9. I was skeptical. I mean, really? An AI doing proper legacy refactoring? But he insisted it saved him hours on a particularly stubborn module. That's what finally pushed me to dedicate two weeks to really integrating AI into my daily .NET workflow. I wanted to see if this &lt;code&gt;ai dotnet 2026&lt;/code&gt; buzz was just hype or if it was genuinely changing how we build software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The IDE as a new copiloted workspace
&lt;/h2&gt;

&lt;p&gt;What I quickly learned was that the landscape has shifted dramatically since I last paid close attention. It's no longer just about generating a single line of code. Both Visual Studio 2026 and Rider 2026 have deeply integrated &lt;code&gt;Copilot for Workspaces&lt;/code&gt; and &lt;code&gt;Copilot Edits&lt;/code&gt;, making the IDE itself feel like a collaborative partner. For me, the biggest win was using Copilot Edits for C# 13 language feature adoption. I'd highlight a block of older code, hit &lt;code&gt;Alt+C&lt;/code&gt;, and prompt for "Convert to C# 13 primary constructor and use &lt;code&gt;ArgumentOutOfRangeException.ThrowIfNegativeOrZero&lt;/code&gt;." It wasn't perfect every time; sometimes it would miss an edge case or suggest something overly complex, but it got me 80% of the way there, often better than I'd have done manually in the same time.&lt;/p&gt;

&lt;p&gt;Here’s a quick example of a common scenario where Copilot Edits helped me out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before Copilot Edits&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProductRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Bad practice!&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid product ID: {Id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product ID must be positive."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After Copilot Edits prompt: "Fix repository instantiation, use C# 13 ThrowIfNegativeOrZero"&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Injected repository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNegativeOrZero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// C# 13 helper&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wasn't just about syntax; it was about moving towards a more idiomatic &lt;code&gt;future of dotnet&lt;/code&gt; codebase, nudging me to fix dependency injection issues I might have overlooked. &lt;strong&gt;Your mileage may vary&lt;/strong&gt;, but I found it incredibly useful for cleaning up debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the IDE: Custom AI agents for .NET 9
&lt;/h2&gt;

&lt;p&gt;Where things got really interesting for me was using external AI models, specifically Claude Sonnet 4.6 (and sometimes Opus 4.7 for more strategic tasks), to generate entire feature prototypes. My team often needs to spin up new minimal API endpoints for .NET 9 services, and the boilerplate can be tedious. I'm still figuring out the best way to prompt, but what I ended up with was a structured approach using a custom agent that understands our existing architectural patterns. It took me an embarrassing amount of time to figure out how to give it enough context without overwhelming it.&lt;/p&gt;

&lt;p&gt;My process involved defining our common interfaces and data models, then asking Claude to generate an endpoint. The key was using the &lt;code&gt;Model Context Protocol (MCP)&lt;/code&gt; to explicitly define the context schema, so I could feed it &lt;code&gt;IProductRepository&lt;/code&gt; and &lt;code&gt;ProductDto&lt;/code&gt; definitions directly.&lt;/p&gt;

&lt;p&gt;Here’s an example of a prompt I used and a snippet of the response for a new product endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt for Claude Sonnet 4.6:

"Generate a C# 13 minimal API endpoint using .NET 9.
The endpoint should handle POST requests to '/products'.
It needs to:
1. Accept a `ProductDto` from the request body.
2. Inject `IProductRepository` and `ILogger&amp;lt;ProductEndpoint&amp;gt;` via the primary constructor.
3. Validate `ProductDto.Name` for null/whitespace.
4. Map `ProductDto` to a `Product` entity.
5. Use `IProductRepository` to add the new `Product`.
6. Return a `Results.CreatedAtRoute` response, linking to a hypothetical 'GetProductById' route.
7. Include `.WithName` and `.Produces` for OpenAPI documentation.

Assume the following types are available:
public record ProductDto(string Name, decimal Price);
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
public interface IProductRepository { Task AddProductAsync(Product product); }
"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Claude Sonnet 4.6 Response (snippet for app.MapPost)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProductDto&lt;/span&gt; &lt;span class="n"&gt;productDto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductEndpoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product creation failed due to empty name."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Product name cannot be empty."&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Created new product with ID: {ProductId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreatedAtRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetProductById"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&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;product&lt;/span&gt;&lt;span class="p"&gt;.&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;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CreateProduct"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status201Created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wasn't just about saving typing; it was about ensuring consistency with our existing patterns and quickly generating a fully-fledged, runnable starting point. It's a significant shift in the &lt;code&gt;csharp future&lt;/code&gt; workflow.&lt;/p&gt;

&lt;p&gt;After these two weeks, I'm genuinely impressed. The &lt;code&gt;future of dotnet&lt;/code&gt; development, at least for me, involves a lot more AI collaboration than I anticipated. I'm still trying to figure out how to integrate these custom AI agents into our CI/CD pipeline for automated code generation, but the potential is clear.&lt;/p&gt;




&lt;p&gt;If you've managed to integrate AI agents into your CI/CD pipeline for .NET 9, especially for generating and validating code, I'd love to hear about your setup and any roadblocks you hit.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>How Fixing My AI Coding Pitfalls Rescued My .NET Development Time</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Sat, 30 May 2026 07:26:25 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/how-fixing-my-ai-coding-pitfalls-rescued-my-net-development-time-19f9</link>
      <guid>https://dev.to/dotnetwithai/how-fixing-my-ai-coding-pitfalls-rescued-my-net-development-time-19f9</guid>
      <description>&lt;h1&gt;
  
  
  How Fixing My AI Coding Pitfalls Rescued My .NET Development Time
&lt;/h1&gt;

&lt;p&gt;Everyone talks about AI tools as a productivity superpower, but for me, integrating them into my daily .NET development initially felt like trying to code with one hand tied behind my back. I'd spend more time correcting &lt;code&gt;copilot mistakes&lt;/code&gt; and refactoring &lt;code&gt;Claude Opus 4.7&lt;/code&gt;'s suggestions than I saved writing new code. Honestly, I was pretty close to giving up on the whole idea, convinced these tools just weren't mature enough for serious enterprise .NET work. It took me an embarrassing amount of time to realize the problem wasn't the tools; it was my approach to using them. I was making some fundamental &lt;code&gt;ai coding mistakes&lt;/code&gt; that turned every interaction into a time sink.&lt;/p&gt;

&lt;p&gt;I had jumped in expecting magic, hoping &lt;code&gt;Visual Studio 2026&lt;/code&gt; and &lt;code&gt;Rider 2026&lt;/code&gt;'s AI integrations would instantly churn out perfect &lt;code&gt;C# 13&lt;/code&gt; code for my &lt;code&gt;.NET 9&lt;/code&gt; and &lt;code&gt;.NET 10&lt;/code&gt; projects. My goal was to accelerate boilerplate, unit test generation, and refactoring. Instead, I found myself battling irrelevant suggestions and subtly broken code. These &lt;code&gt;dotnet ai pitfalls&lt;/code&gt; cost me hours, until I started to reflect on &lt;em&gt;how&lt;/em&gt; I was interacting with the AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Blind Spot: Underestimating Context
&lt;/h2&gt;

&lt;p&gt;My biggest initial &lt;code&gt;ai coding mistake&lt;/code&gt; was treating AI like a mind-reader. I'd provide vague prompts, assuming the tool, whether &lt;code&gt;Copilot Edits&lt;/code&gt; or &lt;code&gt;Claude Opus 4.7&lt;/code&gt;, would infer everything it needed from my open files. It turns out, that's a recipe for generic, often incorrect, code. While &lt;code&gt;Copilot for Workspaces&lt;/code&gt; and its integration with the Model Context Protocol (MCP) in &lt;code&gt;Visual Studio 2026&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; help by providing a wider context, it’s not omniscient. I learned the hard way that explicit context is king.&lt;/p&gt;

&lt;p&gt;For example, asking for "a new service" would get me a barebones class. But asking for "a new &lt;code&gt;IOrderService&lt;/code&gt; implementation that uses &lt;code&gt;MediatR&lt;/code&gt; for domain events, logs with &lt;code&gt;Serilog&lt;/code&gt;, and interacts with an &lt;code&gt;IOrderRepository&lt;/code&gt; for a &lt;code&gt;.NET 9&lt;/code&gt; application" yielded significantly better results. I found &lt;code&gt;Claude Opus 4.7&lt;/code&gt; especially responsive to this level of detail when I was building out new feature skeletons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// My initial, naive prompt to Claude Opus 4.7
Please write a service to handle orders.

// What I learned to do for better results
Please generate an implementation for `IOrderService` in a .NET 9 C# 13 project.
It needs to:
1. Be registered as a scoped service.
2. Inject `IOrderRepository` and `ILogger&amp;lt;OrderService&amp;gt;`.
3. Have a `PlaceOrder` method that takes an `OrderDto`, maps it to an `Order` entity, saves it via the repository, and publishes an `OrderPlacedEvent` using `IMediator`.
4. Include basic error handling and logging.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The "Looks Good" Trap: Trusting Too Fast
&lt;/h2&gt;

&lt;p&gt;Another significant pitfall was my tendency to blindly accept suggestions from &lt;code&gt;Copilot Edits&lt;/code&gt; or &lt;code&gt;Cursor 0.42+&lt;/code&gt; without thorough review. The code &lt;em&gt;looked&lt;/em&gt; right, it compiled, and often passed basic tests. But under the hood, I was accumulating &lt;code&gt;copilot mistakes&lt;/code&gt; that introduced subtle bugs, performance regressions, or deviations from our team's established &lt;code&gt;.NET 9&lt;/code&gt; coding standards.&lt;/p&gt;

&lt;p&gt;Last Tuesday, I was debugging a strange entity state issue with &lt;code&gt;EF Core&lt;/code&gt; when I traced it back to a &lt;code&gt;Copilot Edits&lt;/code&gt; suggestion I'd accepted weeks prior. It had "optimized" a LINQ query, but in doing so, created an N+1 problem that only manifested under specific load conditions. It was a perfect example of a &lt;code&gt;dotnet ai pitfall&lt;/code&gt; where the AI provided syntactically correct but semantically flawed code. Now, I always run a quick mental check, especially with complex LINQ or &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; patterns. &lt;strong&gt;Never hit "Accept" without a glance.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Original, performant query&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrdersWithItemsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Eager loading&lt;/span&gt;
                         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Copilot's "optimization" that introduced N+1 if OrderItems were accessed outside the context&lt;/span&gt;
&lt;span class="c1"&gt;// (I accepted this without thinking, assuming it was smarter)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrdersWithItemsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;
                               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                               &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Fetches orders&lt;/span&gt;
    &lt;span class="c1"&gt;// Later, if I iterated 'orders' and accessed 'order.OrderItems', it would cause a separate query per order&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Misaligned Expectations: One AI Tool Does Not Fit All
&lt;/h2&gt;

&lt;p&gt;My final, and perhaps most frustrating, initial mistake was trying to force a general-purpose LLM to do highly specific, domain-heavy .NET tasks. I'd try to get &lt;code&gt;Claude Sonnet 4.6&lt;/code&gt; to generate complex &lt;code&gt;ASP.NET Core&lt;/code&gt; middleware configurations or intricate &lt;code&gt;EF Core&lt;/code&gt; query projections. While it could give me &lt;em&gt;something&lt;/em&gt;, it often required significant correction or entirely missed nuanced &lt;code&gt;.NET 9&lt;/code&gt; idioms or &lt;code&gt;C# 13&lt;/code&gt; features.&lt;/p&gt;

&lt;p&gt;I learned that for in-line, localized code completion and suggestions within an existing codebase, &lt;code&gt;Visual Studio 2026&lt;/code&gt;'s built-in &lt;code&gt;Copilot&lt;/code&gt; features are incredibly powerful. For larger architectural patterns, brainstorming, or detailed problem-solving &lt;em&gt;outside&lt;/em&gt; the immediate code context, &lt;code&gt;Claude Opus 4.7&lt;/code&gt; is fantastic – but only if I feed it enough of my project's structure and existing code. Trying to use it for a quick, specific &lt;code&gt;dotnet&lt;/code&gt; snippet often led to more &lt;code&gt;ai coding mistakes&lt;/code&gt; than solutions. Your mileage may vary, but for me, understanding each tool's strength was key.&lt;/p&gt;




&lt;p&gt;I'm still figuring out the best ways to integrate these tools, but by being more deliberate about context, critically reviewing output, and using the right AI for the right job, my productivity has genuinely improved. If you've also wrestled with initial &lt;code&gt;dotnet ai pitfalls&lt;/code&gt; and found specific strategies to overcome them in production, I'd love to hear what worked for you.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I Finally Got an MCP Server Running with .NET 9</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Fri, 29 May 2026 10:20:28 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/how-i-finally-got-an-mcp-server-running-with-net-9-8p0</link>
      <guid>https://dev.to/dotnetwithai/how-i-finally-got-an-mcp-server-running-with-net-9-8p0</guid>
      <description>&lt;h1&gt;
  
  
  How I Finally Got an MCP Server Running with .NET 9
&lt;/h1&gt;

&lt;p&gt;Last Tuesday, a Slack DM from our DevOps lead about "context window exhaustion" got me thinking. We've been hitting the limits with direct API calls to Claude Opus 4.7 for some of our internal workflow automation tools. The sheer volume of domain-specific context we need to feed was becoming unwieldy, and frankly, expensive. That's when I remembered hearing whispers about the Model Context Protocol (MCP) and how it's designed to give you more granular control over context management. I figured, why not try to build a simple &lt;code&gt;mcp server csharp&lt;/code&gt; implementation myself?&lt;/p&gt;

&lt;p&gt;Honestly, I was skeptical. Another protocol, another layer of abstraction. But the idea of potentially offloading complex context assembly from the model provider's API and managing it locally, closer to our data, was too tempting to ignore. My goal wasn't a production-ready behemoth, but a proof-of-concept for a &lt;code&gt;model context protocol dotnet&lt;/code&gt; server that could accept context, make internal tool calls, and then pass a refined request to an actual LLM. I spent the next couple of weeks chipping away at it in &lt;code&gt;Visual Studio 2026&lt;/code&gt; using &lt;code&gt;C# 13&lt;/code&gt; and &lt;code&gt;dotnet 9&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Stumble: Deconstructing the Protocol
&lt;/h2&gt;

&lt;p&gt;The initial hurdle was simply understanding the MCP specification itself. It's not overly complex, but translating its concepts into idiomatic C# took a few tries. I kept getting stuck on how tools were supposed to be defined and then invoked. My first attempt at defining the core &lt;code&gt;Message&lt;/code&gt; and &lt;code&gt;ToolDefinition&lt;/code&gt; structures felt clunky, and I spent an embarrassing amount of time over-engineering the serialization. I envisioned a complex hierarchy, but the protocol is surprisingly flat.&lt;/p&gt;

&lt;p&gt;What I ended up with, after several refactors and some help from Claude Sonnet 4.6 (more on that later), were simple record types. The key insight was realizing that &lt;code&gt;ToolDefinition&lt;/code&gt; objects are just JSON schemas, and &lt;code&gt;ToolCall&lt;/code&gt; objects are essentially method calls with arguments. Keeping it simple was &lt;strong&gt;crucial&lt;/strong&gt;. This pattern holds true across the protocol: everything is a structured message, whether it's user input, assistant output, or a tool invocation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified core MCP types I settled on&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&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;ToolCall&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ToolCall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ToolResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ToolCallId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ToolDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSchema&lt;/span&gt; &lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// JsonSchema from a library&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Setup I Settled On: A Minimal .NET 9 Host
&lt;/h2&gt;

&lt;p&gt;After wrestling with the data structures, the server implementation itself was surprisingly straightforward, thanks to &lt;code&gt;ASP.NET Core Minimal APIs&lt;/code&gt; in .NET 9. I wanted something lean, without a lot of boilerplate, and Minimal APIs fit the bill perfectly. My &lt;code&gt;Program.cs&lt;/code&gt; became the central hub for defining the MCP endpoints.&lt;/p&gt;

&lt;p&gt;I decided to encapsulate the core logic for handling invoke requests within a simple &lt;code&gt;IMCPService&lt;/code&gt; interface. This allowed me to swap out implementations easily and kept my &lt;code&gt;Program.cs&lt;/code&gt; clean. I found it helps to think of the MCP server as a proxy or an orchestrator. It receives a request, potentially performs some local actions (like invoking an internal tool), and then crafts a new request for the actual LLM if needed.&lt;/p&gt;

&lt;p&gt;Here’s a snippet of my &lt;code&gt;Program.cs&lt;/code&gt; showing how I mapped the primary &lt;code&gt;/mcp/v1/invoke&lt;/code&gt; endpoint. I used &lt;code&gt;Rider 2026&lt;/code&gt; for most of this, and its refactoring tools made quick work of moving logic around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs snippet for MCP server&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.MCP.Server.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// My custom models based on MCP spec&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Anthropic.MCP.Server.Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// My IMCPService implementation&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add services to the container.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMCPService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LocalMCPService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// My service for handling MCP logic&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// For making calls to actual LLMs&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Main MCP invoke endpoint&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/mcp/v1/invoke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IMCPService&lt;/span&gt; &lt;span class="n"&gt;mcpService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The MCP spec allows for streaming directly over HTTP/1.1 or WebSockets.&lt;/span&gt;
    &lt;span class="c1"&gt;// For simplicity in this PoC, I initially focused on non-streaming, then added SSE.&lt;/span&gt;
    &lt;span class="c1"&gt;// This example shows the initial non-streaming approach.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvokeRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; 
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid MCP InvokeRequest payload."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/event-stream"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CacheControl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Use a custom streaming writer for Server-Sent Events (SSE)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;mcpService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleInvokeStreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BodyWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Response written directly to stream&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integrating with AI: Where Copilot and Claude Shined (and Failed)
&lt;/h2&gt;

&lt;p&gt;The real power play came in connecting my MCP server to an actual model. My goal was to feed the processed context to Claude Opus 4.7. This meant my &lt;code&gt;LocalMCPService&lt;/code&gt; needed to perform two main tasks: interpreting incoming tool calls from the MCP request and then, if necessary, making an API call to Anthropic's API with the refined context.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;GitHub Copilot for Workspaces&lt;/code&gt; and &lt;code&gt;Cursor 0.42+&lt;/code&gt; became invaluable. Writing the boilerplate for &lt;code&gt;HttpClient&lt;/code&gt; calls, JSON serialization, and handling streaming responses from the Anthropic API is tedious. I prompted Copilot with things like "create a method to call the Anthropic messages API with streaming, given a list of &lt;code&gt;Anthropic.MessagesApi.Message&lt;/code&gt; objects." It generated surprisingly accurate starting points, saving me hours.&lt;/p&gt;

&lt;p&gt;Where I screwed this up initially was with the streaming responses. I tried to buffer everything before sending it back through my MCP server's SSE stream, which completely defeated the purpose. It took me a few frustrating debugging sessions to realize I needed to directly pipe the Anthropic streaming response into my own &lt;code&gt;Response.BodyWriter&lt;/code&gt; as Server-Sent Events. My server wasn't just a proxy; it was a re-streamer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified snippet from LocalMCPService.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleInvokeStreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InvokeRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PipeWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... logic to process incoming MCP messages and tool definitions ...&lt;/span&gt;

    &lt;span class="c1"&gt;// Example: If an internal tool call is detected and handled&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCall&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolCall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCall&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ToolCall&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulate internal tool execution&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;toolResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ExecuteInternalTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCall&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;WriteSseEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool_result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolResult&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="c1"&gt;// Add the tool result to the context for the LLM&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolResult&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;ToolResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolResult&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Now, forward the refined context to the actual Anthropic API&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;anthropicMessages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MapToAnthropicMessage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Mapping logic&lt;/span&gt;

    &lt;span class="c1"&gt;// Using Anthropic's official .NET client (or a custom HttpClient call)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_anthropicClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StreamMessagesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anthropicMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolDefinitions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Custom method to pipe stream&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... helper methods like MapToAnthropicMessage, WriteSseEvent, ExecuteInternalTool ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm still figuring out the best way to manage the state between multiple MCP requests for a single conversation, especially when tools are involved. Your mileage may vary, but for me, this initial dive into an &lt;code&gt;anthropic mcp tutorial&lt;/code&gt; proved that gaining control over the context pipeline is absolutely worth the effort.&lt;/p&gt;




&lt;p&gt;If you've tried building an MCP server in .NET, especially with complex tool orchestration or state management, I'd love to hear what challenges you faced and how you solved them.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Stopped Fighting AI Context: JetBrains AI vs. Copilot in Rider</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Thu, 28 May 2026 12:13:39 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/how-i-stopped-fighting-ai-context-jetbrains-ai-vs-copilot-in-rider-3lhi</link>
      <guid>https://dev.to/dotnetwithai/how-i-stopped-fighting-ai-context-jetbrains-ai-vs-copilot-in-rider-3lhi</guid>
      <description>&lt;h1&gt;
  
  
  How I Stopped Fighting AI Context: JetBrains AI vs. Copilot in Rider
&lt;/h1&gt;

&lt;p&gt;Last Tuesday, I was staring at a &lt;code&gt;System.NullReferenceException: Object reference not set to an instance of an object. (Parameter 'serviceProvider')&lt;/code&gt; in a new &lt;code&gt;Program.cs&lt;/code&gt; file, trying to boot up a .NET 9 API. The kicker? The code came almost entirely from an AI assistant. I'd been trying to leverage JetBrains AI Assistant and GitHub Copilot in Rider 2026 to speed up a legacy .NET 7 service migration, and honestly, the context dance between them was driving me a little nuts.&lt;/p&gt;

&lt;p&gt;For a while, I felt like I was spending more time debugging AI output than writing actual code. My goal was simple: use AI to offload boilerplate, understand unfamiliar patterns in a large codebase, and generally accelerate my daily work. What I ended up with initially was a chaotic mix of half-baked suggestions and context-blind refactors. It took some serious trial and error, but I think I've finally settled on a pragmatic approach that works for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context Conundrum: Where My Assumptions Broke Down
&lt;/h2&gt;

&lt;p&gt;My initial mistake was treating both JetBrains AI Assistant (powered by Claude Sonnet 4.6) and GitHub Copilot (the latest Copilot for Workspaces version) as interchangeable, all-knowing oracles. I'd ask a question in the chat window, or expect an inline completion to magically understand my entire project structure. This almost never worked.&lt;/p&gt;

&lt;p&gt;For instance, I'd ask JetBrains AI Assistant to "add a new &lt;code&gt;AuditLogService&lt;/code&gt; to this project" and get back a barebones class definition that completely ignored my existing DI setup, &lt;code&gt;appsettings.json&lt;/code&gt; conventions, or even the &lt;code&gt;ILogger&lt;/code&gt; pattern I was using everywhere else. It was technically correct C#, but utterly useless in my context. Copilot, meanwhile, would often complete a line based purely on syntax and local variables, completely missing the architectural implications. I even got a code block where &lt;code&gt;_dbContext&lt;/code&gt; was used before it was injected, leading to that nasty &lt;code&gt;NullReferenceException&lt;/code&gt; I mentioned.&lt;/p&gt;

&lt;p&gt;Here’s a simplified example of the kind of output I was getting, which compiled but didn't quite fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Asking JetBrains AI Assistant to "Add a new AuditLogService"&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLogService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"User &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; performed &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// And then Copilot suggesting usage without context&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AuditLogService&lt;/span&gt; &lt;span class="n"&gt;_auditLogService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No constructor injection&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Missing dependency&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="n"&gt;_auditLogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"current_user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"some_action"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// NRE waiting to happen&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue wasn't the AI's intelligence, but my &lt;em&gt;prompting strategy&lt;/em&gt; and expectation of its context awareness. While MCP (Model Context Protocol) support in Rider 2026 does a great job of feeding relevant code snippets, it's not a mind-reader. &lt;strong&gt;I learned that explicit context is king.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding My Workflow: A Tale of Two Assistants
&lt;/h2&gt;

&lt;p&gt;What I ended up with is a clearer division of labor, treating them less as rivals and more as complementary tools. Your mileage may vary, but this has drastically improved my productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot in Rider:&lt;/strong&gt; This is my fast-twitch muscle memory. For quick, inline completions, generating boilerplate, writing unit tests for a specific method, or even just filling out XML documentation comments, Copilot is incredibly efficient. It excels when the context is narrow and immediately visible in the current file or surrounding lines. I use it constantly for things like &lt;code&gt;if (string.IsNullOrWhiteSpace(value))&lt;/code&gt; or generating switch expressions. I've also found Copilot Edits surprisingly useful for quick refactors within a single method, like extracting a local function or simplifying a LINQ query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Using Copilot for quick completion and test generation&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync&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;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After defining IOrderService, Copilot often suggests this:&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync&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;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO: Implement order processing logic&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// If I then navigate to the test project and create a new test file:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderAsync_ValidId_ReturnsTrue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Copilot will often suggest this test setup:&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JetBrains AI Assistant (with Claude Sonnet 4.6) in Rider:&lt;/strong&gt; This is where I go for deeper understanding, architectural refactoring, or when I need more nuanced code generation. Its strength lies in its chat interface, allowing for multi-turn conversations and explicit context feeding. When I need to understand a complex legacy class, I'll paste the entire file into the chat, ask "Explain the dependencies and responsibilities of this &lt;code&gt;LegacyOrderProcessor&lt;/code&gt;," and then follow up with "Suggest a strategy to refactor this using a CQRS pattern for .NET 9." It takes more effort to craft the prompt, but the quality of the output is significantly higher for complex tasks. I've even used Claude Opus 4.7 via the assistant for particularly thorny problems, though it's slower.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prompting JetBrains AI Assistant for a refactor&lt;/span&gt;
&lt;span class="c1"&gt;// (Imagine I've pasted the entire 'LegacyOrderProcessor' class into the chat window first)&lt;/span&gt;
&lt;span class="s"&gt;"Refactor the provided `LegacyOrderProcessor` class to use an `IOrderStategy` pattern. 
&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nc"&gt;IOrderProcessingStrategy&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;least&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="n"&gt;concrete&lt;/span&gt; &lt;span class="nf"&gt;implementations&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;StandardOrderStrategy&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;PremiumOrderStrategy&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt; &lt;span class="n"&gt;Explain&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; 
&lt;span class="n"&gt;show&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s"&gt;"
&lt;/span&gt;
&lt;span class="c1"&gt;// Partial example of the kind of detailed response I get back:&lt;/span&gt;
&lt;span class="c1"&gt;// (AI Assistant would provide full code, explanations, and factory logic)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderProcessingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardOrderStrategy&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderProcessingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Standard processing logic here&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dual-tool approach means Copilot handles the small, localized tasks, while JetBrains AI Assistant tackles the bigger picture, aided by more explicit context from me.&lt;/p&gt;




&lt;p&gt;I'm still figuring out the best way to leverage Copilot for Workspaces' broader context features without feeling overwhelmed by its suggestions, especially when dealing with early .NET 10 preview features. If you've managed to integrate both JetBrains AI Assistant and GitHub Copilot effectively in Rider for large-scale .NET refactoring, I'd love to hear your strategies and any pitfalls you've encountered.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From InvalidOperationException to Insight: My AI Debugging Journey with .NET 9</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Wed, 27 May 2026 10:26:53 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/from-invalidoperationexception-to-insight-my-ai-debugging-journey-with-net-9-43n6</link>
      <guid>https://dev.to/dotnetwithai/from-invalidoperationexception-to-insight-my-ai-debugging-journey-with-net-9-43n6</guid>
      <description>&lt;h1&gt;
  
  
  From InvalidOperationException to Insight: My AI Debugging Journey with .NET 9
&lt;/h1&gt;

&lt;p&gt;Last Tuesday, I hit a familiar wall: &lt;code&gt;System.InvalidOperationException: Collection was modified; enumeration operation may not execute.&lt;/code&gt; I was trying to optimize a legacy .NET 9 endpoint, iterating a list of active connections while a background task could add new ones. Classic race condition, but in a sprawling codebase, tracing the exact timing and interplay felt like untangling wet spaghetti. My usual approach of setting breakpoints and stepping through felt like it would take hours.&lt;/p&gt;

&lt;p&gt;This particular bug pushed me over the edge. I've been cautiously optimistic about AI's role in dev workflows, but mostly for boilerplate code or documentation. Could it actually help with real, gnarly debugging? For two weeks, I decided to lean heavily into &lt;code&gt;ai debugging dotnet&lt;/code&gt; tools, specifically focusing on how Claude Opus 4.7 and Visual Studio 2026's integrated AI features could speed up my &lt;code&gt;ai bug fix&lt;/code&gt; cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Forays (and Fumbles) with Claude Debugging C
&lt;/h2&gt;

&lt;p&gt;My initial attempts at &lt;code&gt;claude debug csharp&lt;/code&gt; were, frankly, a bit clunky. I'd paste the exception, maybe a few lines of code, and get back generic advice like "use &lt;code&gt;ToList()&lt;/code&gt; before iterating" or "add locks." Useful, but not always the deep insight I needed for a specific context. My first major screw-up was feeding it too little context. I’d paste a method, but forget to include the calling code or the full stack trace. The AI, understandably, made assumptions that led me down a few rabbit holes.&lt;/p&gt;

&lt;p&gt;What I ended up doing was taking a more structured approach. When I hit that &lt;code&gt;InvalidOperationException&lt;/code&gt;, I grabbed the full stack trace, the problematic method, and any relevant class definitions. Then, I'd prompt Claude Opus 4.7 directly within Cursor 0.42+, which I’ve been using more lately for its integrated AI chat.&lt;/p&gt;

&lt;p&gt;Here’s a simplified version of a prompt that finally got me somewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm getting a System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Here's the stack trace:
   at System.Collections.Concurrent.ConcurrentQueue`1.GetEnumerator()
   at MyNamespace.ConnectionManager.ProcessConnections() in C:\...\ConnectionManager.cs:line 45
   at MyNamespace.BackgroundService.DoWork(CancellationToken cancellationToken) in C:\...\BackgroundService.cs:line 22

Here's the relevant code from ConnectionManager.cs:

public class ConnectionManager
{
    private readonly ConcurrentQueue&amp;lt;Connection&amp;gt; _activeConnections = new ConcurrentQueue&amp;lt;Connection&amp;gt;();

    public void AddConnection(Connection connection)
    {
        _activeConnections.Enqueue(connection);
    }

    public void ProcessConnections()
    {
        foreach (var connection in _activeConnections) // Line 45
        {
            // Process connection
            if (connection.ShouldBeRemoved())
            {
                // This is where the issue might be - trying to remove while iterating.
                // But ConcurrentQueue doesn't have a direct Remove.
            }
        }
    }
}

The issue occurs on line 45. The `AddConnection` method can be called from multiple threads.
How can I safely iterate and potentially remove connections without causing this exception?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude’s response was surprisingly insightful, pointing out that even &lt;code&gt;ConcurrentQueue&lt;/code&gt; iterators can throw this if the collection is modified during enumeration, and that direct removal isn't supported. It suggested a few patterns, but the one that stuck was using a snapshot or a producer-consumer pattern. This was &lt;strong&gt;a lightbulb moment&lt;/strong&gt; for me, realizing the AI understood the nuances beyond just "use &lt;code&gt;lock&lt;/code&gt;."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup I Settled On for Rapid AI Bug Fixes
&lt;/h2&gt;

&lt;p&gt;After a few days of trial and error, I found a workflow that significantly cut down my &lt;code&gt;ai bug fix&lt;/code&gt; time. Instead of just pasting code, I now treat the AI as a highly knowledgeable, slightly context-blind colleague.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Replicate (or Isolate):&lt;/strong&gt; Get a minimal reproducible example or at least the exact failure scenario.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Gather Context:&lt;/strong&gt; Full stack trace, relevant code (including dependencies or interfaces), and a clear description of &lt;em&gt;what I was trying to do&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Prompt the AI:&lt;/strong&gt; I've found that Claude Opus 4.7, particularly when given a good "System Prompt" (using MCP in Cursor or Copilot for Workspaces in VS 2026), provides the best results. I ask it to &lt;em&gt;explain the root cause first&lt;/em&gt;, then &lt;em&gt;suggest solutions&lt;/em&gt;, and finally &lt;em&gt;provide a code example&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Review and Implement:&lt;/strong&gt; This is crucial. I never just copy-paste. The AI's suggestion is a starting point. I understand &lt;em&gt;why&lt;/em&gt; it works, verify it, and then integrate it into my codebase.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For my &lt;code&gt;InvalidOperationException&lt;/code&gt;, the AI nudged me towards a simple snapshot approach for processing, acknowledging that &lt;code&gt;ConcurrentQueue&lt;/code&gt; isn't designed for mid-iteration removal. What I ended up with was something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConnectionManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_activeConnections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConcurrentQueue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_activeConnections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ProcessConnections&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Take a snapshot of the current connections&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionsToProcess&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_activeConnections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Safe snapshot&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;connectionsToProcess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Process connection&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShouldBeRemoved&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// If removal is needed, re-enqueue only the ones that should remain&lt;/span&gt;
                &lt;span class="c1"&gt;// or use a separate mechanism for removal (e.g., a "dead connections" queue)&lt;/span&gt;
                &lt;span class="c1"&gt;// For this specific bug, simply not re-adding it effectively "removes" it from the next snapshot.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// If connection is still active and needs to be processed again,&lt;/span&gt;
                &lt;span class="c1"&gt;// re-enqueue it or ensure it's handled by AddConnection if it's new.&lt;/span&gt;
                &lt;span class="c1"&gt;// My actual fix involved moving to a ConcurrentDictionary for easier ID-based removal.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// For a ConcurrentQueue, if you need to "remove" items, you typically drain and re-populate.&lt;/span&gt;
        &lt;span class="c1"&gt;// A better structure might be ConcurrentDictionary&amp;lt;Guid, Connection&amp;gt; for explicit removal.&lt;/span&gt;
        &lt;span class="c1"&gt;// The AI's core insight was "don't modify the collection you're iterating."&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This wasn't a direct "copy-paste" fix, but the AI pointed me to the fundamental issue and the common patterns. It saved me hours of stepping through code, letting me focus on the architectural implications of the fix. Your mileage may vary with the specific AI and prompt, but for &lt;code&gt;ai debugging dotnet&lt;/code&gt; in a crunch, this worked wonders for me.&lt;/p&gt;

&lt;p&gt;I'm still figuring out the best way to integrate AI into every debugging scenario, especially for subtle performance issues or highly domain-specific logic. But for common concurrency bugs or puzzling exceptions, it feels like I've gained a superpower.&lt;/p&gt;




&lt;p&gt;If you've tried using Claude Opus 4.7 or similar AI tools for real-world &lt;code&gt;ai debugging dotnet&lt;/code&gt; scenarios, especially in legacy C# projects, I'd love to hear what specific challenges you faced and how you overcame them.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code vs. Legacy .NET: I Picked the Helper, Not the Hero</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Tue, 26 May 2026 11:06:02 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/claude-code-vs-legacy-net-i-picked-the-helper-not-the-hero-1pd1</link>
      <guid>https://dev.to/dotnetwithai/claude-code-vs-legacy-net-i-picked-the-helper-not-the-hero-1pd1</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code vs. Legacy .NET: I Picked the Helper, Not the Hero
&lt;/h1&gt;

&lt;p&gt;Last Tuesday, I was staring down a &lt;code&gt;System.InvalidOperationException: 'Sequence contains no elements.' at MyLegacyProject.DataService.GetActiveUsersAsync()&lt;/code&gt; stack trace. It wasn't the AI's fault directly, but it was generated from a prompt I'd given Claude Sonnet 4.6 to "fix" a related issue in a particularly ancient .NET Framework 4.8 service. That's when I realized this wasn't going to be a simple copy-paste job.&lt;/p&gt;

&lt;p&gt;Our team, like many, has a few critical, undocumented services nobody wants to touch. The idea was to see if Claude Code, specifically the Opus 4.7 model, could accelerate our understanding of this spaghetti code, perhaps even generate some initial fixes or tests, reducing the mental load on our &lt;code&gt;ai coding team dotnet&lt;/code&gt; members. Honestly, I didn't expect much from this initially; I figured it would be another glorified search engine. I was wrong, but not in the way I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unraveling Legacy Code with Claude Opus 4.7
&lt;/h2&gt;

&lt;p&gt;My initial goal was to get Claude to explain a particularly dense, 500-line method in an old &lt;code&gt;DataService&lt;/code&gt; class. This method, &lt;code&gt;CalculateComplexBillingAsync&lt;/code&gt;, had a dozen nested &lt;code&gt;if&lt;/code&gt; statements and some truly creative LINQ queries. I copied the entire method, pasted it into the Claude Opus 4.7 web interface, and asked: "Explain this C# method, identify potential issues, and suggest how it could be refactored for readability and testability, targeting .NET 9 and C# 13 features."&lt;/p&gt;

&lt;p&gt;What I got back wasn't just a line-by-line explanation, but a high-level summary of its purpose, a breakdown of its core logic flows, and even a couple of specific edge cases I'd never considered (like a &lt;code&gt;FirstOrDefault()&lt;/code&gt; without a null check, which was the root of my &lt;code&gt;Sequence contains no elements&lt;/code&gt; error last week). It even proposed using &lt;code&gt;record&lt;/code&gt; types for some DTOs and &lt;code&gt;IAsyncEnumerable&lt;/code&gt; for streaming results, which was a nice touch for a .NET 9 target. For me, the &lt;strong&gt;contextual understanding&lt;/strong&gt; Claude provided was a game-changer. It helped me mentally model the legacy code far faster than just stepping through it in Visual Studio 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// My prompt (simplified)&lt;/span&gt;
&lt;span class="c1"&gt;// "Explain this C# method, identify potential issues, and suggest refactoring for .NET 9/C# 13."&lt;/span&gt;
&lt;span class="c1"&gt;// (Followed by 500 lines of legacy C# code)&lt;/span&gt;

&lt;span class="c1"&gt;// Claude's (simplified) response snippet:&lt;/span&gt;
&lt;span class="p"&gt;/*&lt;/span&gt;
&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;primarily&lt;/span&gt; &lt;span class="n"&gt;calculates&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt; &lt;span class="n"&gt;based&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;various&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Potential&lt;/span&gt; &lt;span class="n"&gt;Issue&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Lack&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="n"&gt;checking&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;If&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;userActivityLogs&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;matching&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Refactoring&lt;/span&gt; &lt;span class="n"&gt;Suggestion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;Replace&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
csharp&lt;br&gt;
var log = userActivityLogs.FirstOrDefault(l =&amp;gt; l.UserId == userId);&lt;br&gt;
if (log != null)&lt;br&gt;
{&lt;br&gt;
    // ... proceed with log.Property&lt;br&gt;
}&lt;br&gt;
else&lt;br&gt;
{&lt;br&gt;
    // Handle case where no log is found, e.g., default value or throw specific exception.&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integrating into the AI Coding Team DotNet Workflow (and the Handoff)
&lt;/h2&gt;

&lt;p&gt;The real challenge wasn't getting Claude to generate code or explanations, but integrating it effectively into our existing &lt;code&gt;ai coding team dotnet&lt;/code&gt; workflow. My colleagues, mostly Rider 2026 users, were initially skeptical. "Another Copilot?" was the common refrain. My personal workflow evolved: I'd use Claude for the initial understanding or boilerplate generation, then bring that into my IDE for refinement.&lt;/p&gt;

&lt;p&gt;One specific "gotcha" I hit repeatedly was Claude's tendency to generate C# 13 preview features or .NET 10 preview APIs when I wasn't explicit enough about our stable .NET 9 target. It took me an embarrassing amount of time to realize the generated code often wouldn't compile without slight modifications or specific &lt;code&gt;using&lt;/code&gt; directives, even when I thought I'd given it enough context. This meant that &lt;code&gt;claude code review&lt;/code&gt; wasn't about blindly accepting its output; it was about treating Claude's suggestions as a very smart junior developer's first draft. Human &lt;strong&gt;oversight remained critical&lt;/strong&gt;. We started treating Claude's output like any other code submitted by a new team member – it needed thorough review, especially for correctness and adherence to our coding standards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Claude generated this (hypothetically, for .NET 10 preview)&lt;/span&gt;
&lt;span class="c1"&gt;// Example of a small issue I'd often fix manually&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureToggle&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// .NET 10 preview feature (hypothetically)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;featureName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExperimentalFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;featureName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// My manual fix for .NET 9:&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureToggle&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;featureName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"FEATURE_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;featureName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a month, I'm still figuring out the best way to leverage Claude Code. It's fantastic for understanding complex codebases and generating initial drafts or unit tests, especially when dealing with legacy systems. However, it's not a set-it-and-forget-it solution. The handoff between AI generation and human refinement is where the real work happens, and it requires careful attention to detail, especially regarding target framework and language versions.&lt;/p&gt;




&lt;p&gt;If you've managed to integrate AI code generation tools into your team's .NET code review process without major headaches, I'd love to hear what broke for you and how you fixed it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI's Promise vs. My Reality: The .NET Dev Stack I Actually Use</title>
      <dc:creator>itysu tur</dc:creator>
      <pubDate>Mon, 25 May 2026 08:32:21 +0000</pubDate>
      <link>https://dev.to/dotnetwithai/ais-promise-vs-my-reality-the-net-dev-stack-i-actually-use-5eho</link>
      <guid>https://dev.to/dotnetwithai/ais-promise-vs-my-reality-the-net-dev-stack-i-actually-use-5eho</guid>
      <description>&lt;h1&gt;
  
  
  AI's Promise vs. My Reality: The .NET Dev Stack I Actually Use
&lt;/h1&gt;

&lt;p&gt;I'd heard the buzz about AI coding assistants replacing developers, and honestly, for a while, I bought into the hype. But what I’ve found after months of trying to integrate every shiny new AI into my .NET 9 projects is that the real game-changer isn't replacement, but intelligent augmentation. My personal journey has been less about delegating entire features to an LLM and more about finding that sweet spot where a few specific tools amplify my existing skills, especially when wrestling with legacy C# 13 codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Copilot I Never Expected
&lt;/h2&gt;

&lt;p&gt;My initial foray into the world of &lt;code&gt;dotnet ai tools&lt;/code&gt; started, like many, with GitHub Copilot. Honestly, I expected it to be a novelty, good for trivial boilerplate or maybe filling out a &lt;code&gt;switch&lt;/code&gt; statement. What I ended up finding was that its real strength, for me, lies in its ability to act as a &lt;strong&gt;hyper-aware rubber duck&lt;/strong&gt;. When I'm in Visual Studio 2026 or Rider 2026 and get stuck on a method signature or a common pattern, Copilot often has the right suggestion before I even finish typing.&lt;/p&gt;

&lt;p&gt;Where it truly shines for me is in generating unit tests or simple CRUD operations, especially when there's an existing codebase to learn from. However, I learned the hard way that over-reliance can introduce subtle bugs, particularly with complex &lt;code&gt;async/await&lt;/code&gt; patterns or intricate LINQ queries in C# 13. My rule now is: if it's more than a few lines, I treat Copilot's suggestion as a draft, not gospel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a .NET 9 Web API controller, Copilot often nails the boilerplate&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Copilot frequently suggests this entire block based on existing repository patterns&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ModelState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_productRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreatedAtAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetProductById&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&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;product&lt;/span&gt;&lt;span class="p"&gt;.&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;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Claude and Cursor: My Pair-Programming Power-Up
&lt;/h2&gt;

&lt;p&gt;For deeper architectural questions, complex refactoring, or simply understanding a gnarly piece of legacy code, my &lt;code&gt;developer ai stack&lt;/code&gt; leans heavily on Claude Opus 4.7, accessed primarily through Cursor 0.42+. I'm still figuring out the optimal way to prompt, but what I've found is that Claude excels at explaining &lt;em&gt;why&lt;/em&gt; a particular piece of code might be problematic or suggesting more robust patterns.&lt;/p&gt;

&lt;p&gt;One specific scenario where this combination saved me an embarrassing amount of time was debugging a tricky &lt;code&gt;ConcurrentDictionary&lt;/code&gt; race condition. I'd tried to implement an &lt;code&gt;async&lt;/code&gt; cache &lt;code&gt;GetOrAdd&lt;/code&gt; pattern myself, only to hit subtle deadlocks. Feeding the problematic C# 13 snippet to Claude via Cursor, leveraging its Model Context Protocol (MCP) awareness, yielded a robust solution. It took a few iterations, but the detailed explanation of &lt;code&gt;Lazy&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; was a game-changer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prompt to Claude Opus 4.7 via Cursor 0.42+&lt;/span&gt;
&lt;span class="n"&gt;Explain&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;potential&lt;/span&gt; &lt;span class="n"&gt;pitfalls&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;safety&lt;/span&gt; &lt;span class="n"&gt;concerns&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;particularly&lt;/span&gt; &lt;span class="n"&gt;related&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt; &lt;span class="n"&gt;modification&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="n"&gt;dictionary&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Suggest&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;safer&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
csharp&lt;br&gt;
public class MyService&lt;br&gt;
{&lt;br&gt;
    private readonly ConcurrentDictionary _cache = new();&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async Task&amp;lt;T&amp;gt; GetOrAddAsync&amp;lt;T&amp;gt;(string key, Func&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt; factory)
{
    if (_cache.TryGetValue(key, out var cachedValue))
    {
        return (T)cachedValue;
    }

    var newValue = await factory(); // Potential for multiple threads to execute factory
    _cache.TryAdd(key, newValue);
    return newValue;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
csharp&lt;br&gt;
// Claude Opus 4.7 Response (abbreviated for brevity)&lt;br&gt;
You've correctly identified a race condition. The primary issue with your proposed &lt;code&gt;GetOrAddAsync&lt;/code&gt; is that &lt;code&gt;ConcurrentDictionary.GetOrAdd&lt;/code&gt; expects a synchronous factory function. Directly passing an &lt;code&gt;async&lt;/code&gt; lambda won't properly cache the &lt;em&gt;result&lt;/em&gt; of the &lt;code&gt;Task&lt;/code&gt;, only the &lt;code&gt;Task&lt;/code&gt; object itself, leading to potential re-execution or incomplete caching.&lt;/p&gt;

&lt;p&gt;To properly handle asynchronous factory functions and ensure single execution of the factory across multiple threads, I typically lean on a pattern involving &lt;code&gt;Lazy&amp;lt;Task&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; or an &lt;code&gt;AsyncLazy&amp;lt;T&amp;gt;&lt;/code&gt; implementation. This ensures the asynchronous operation is performed once and its result is safely cached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Store Lazy&amp;lt;Task&amp;lt;object&amp;gt;&amp;gt; to ensure the async factory runs only once&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&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;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetOrAddAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&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;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lazyTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// The factory is executed inside the Lazy, ensuring single execution&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;LazyThreadSafetyMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecutionAndPublication&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Critical for thread safety&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;lazyTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Await the result of the Lazy&amp;lt;Task&amp;lt;object&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Visual Studio + Copilot Edits: The IDE's New Brain
&lt;/h2&gt;

&lt;p&gt;The biggest shift in my daily coding rhythm has been the tight integration of AI directly into my IDE. Visual Studio 2026, with its latest Copilot Edits and Copilot for Workspaces features, has become an indispensable part of my &lt;code&gt;best ai for csharp&lt;/code&gt; toolkit. It's not just auto-completion anymore; it's proactive refactoring and code generation right where I need it.&lt;/p&gt;

&lt;p&gt;For quick, localized transformations, Copilot Edits is fantastic. I can highlight a &lt;code&gt;for&lt;/code&gt; loop and prompt it to "Refactor this to use LINQ," and it usually gets it right on the first try. It handles mundane tasks like extracting methods, renaming variables consistently, or even adding XML documentation comments based on context. I'm still figuring out how to make Copilot for Workspaces truly effective for larger, multi-file refactors across our sprawling monorepo, but for focused, in-file changes, it's a significant time-saver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Scenario: Using Copilot Edits in VS 2026&lt;/span&gt;
&lt;span class="c1"&gt;// Original code selected by me:&lt;/span&gt;
&lt;span class="c1"&gt;// var activeUsers = new List&amp;lt;User&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// foreach (var user in allUsers)&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//     if (user.IsActive &amp;amp;&amp;amp; user.LastLogin &amp;gt; DateTime.UtcNow.AddDays(-30))&lt;/span&gt;
&lt;span class="c1"&gt;//     {&lt;/span&gt;
&lt;span class="c1"&gt;//         activeUsers.Add(user);&lt;/span&gt;
&lt;span class="c1"&gt;//     }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;span class="c1"&gt;// return activeUsers.OrderBy(u =&amp;gt; u.Username).ToList();&lt;/span&gt;

&lt;span class="c1"&gt;// My prompt to Copilot Edits: "Refactor this to use LINQ for filtering and ordering."&lt;/span&gt;

&lt;span class="c1"&gt;// Copilot Edits output:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;allUsers&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastLogin&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, where am I now? My current developer AI stack isn't about replacing me, but about making me a faster, more informed .NET developer. I'm still figuring out how to leverage Copilot for Workspaces effectively for cross-project context, especially with our monorepo setup. It feels like we're just scratching the surface of what's possible.&lt;/p&gt;




&lt;p&gt;If you've managed to integrate &lt;code&gt;dotnet ai tools&lt;/code&gt; into your daily workflow for complex refactoring or legacy codebase understanding, I'd genuinely love to hear about your specific wins and especially your failures. What broke for you, and how did you fix it?&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
