<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://natemcmaster.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://natemcmaster.com/" rel="alternate" type="text/html" /><updated>2026-01-31T17:48:58-08:00</updated><id>https://natemcmaster.com/feed.xml</id><title type="html">Nate McMaster</title><subtitle>Thoughts on developer tools, software internals, and lessons learned from a principal software engineer.
</subtitle><author><name>Nate McMaster</name><email>hello@natemcmaster.com</email><uri>https://natemcmaster.com</uri></author><entry><title type="html">1Password is still worth it in 2026</title><link href="https://natemcmaster.com/blog/2026/01/31/1password-is-still-worth-it/" rel="alternate" type="text/html" title="1Password is still worth it in 2026" /><published>2026-01-31T00:00:00-08:00</published><updated>2026-01-31T00:00:00-08:00</updated><id>https://natemcmaster.com/blog/2026/01/31/1password-is-still-worth-it</id><content type="html" xml:base="https://natemcmaster.com/blog/2026/01/31/1password-is-still-worth-it/"><![CDATA[<p>I let my 1Password Family subscription lapse for three months to give iCloud Passwords a real shot.
Apple’s offering is free, built-in, and getting better.
After three months, I went back to 1Password.</p>

<p>This is totally a convenience purchase. You can absolutely get by without it, and maybe as Apple keeps improving, this won’t be needed.
But iCloud Passwords is a death by a thousand papercuts. Each issue is small, but they add up, and after three months I was fatigued enough to miss 1Password.</p>

<blockquote>
  <p>Note: this is not a paid post. I’m not a sponsor. I like writing about tools I enjoy using.</p>
</blockquote>

<h2 id="macos-chrome-autofill-is-painful">macOS: Chrome autofill is painful</h2>

<p>The <a href="https://chromewebstore.google.com/detail/icloud-passwords/pejdijmoenmkgeppbflobdenhhabjlaj">iCloud Passwords Chrome extension</a> has 2.3 stars on the Chrome Web Store, and I think that’s justified. I have to re-enter a 6-digit code multiple times a day just to let Chrome autofill passwords. Even when I’m already signed in, every single password fill requires re-entering my password or using Touch ID. There’s no grace period.</p>

<p>Passkeys, on the other hand, work great. Likewise, if you’re using Safari on macOS, no complaints there. But don’t get be started on Safari for macOS and its gaps….</p>

<p><img src="/assets/images/blog/chrome-icloud-autofill-sync.png" alt="iCloud Passwords verification code prompt for Chrome" /></p>

<p><img src="/assets/images/blog/chrome-auto-fill-re-enable.png" alt="Enable Password AutoFill prompt in Chrome" /></p>

<h2 id="icloud-sync-bugs">iCloud sync bugs</h2>

<p>Sync has been <em>mostly</em> reliable, but I ran into a strange issue a few weeks ago. Sync between my family member’s iPad, my phone, and laptop was broken. They all had slightly different versions of passwords. It didn’t work to logout/login again. Instead, I had to add dummy items to our password vaults to trigger a resync.</p>

<h2 id="credit-card-autofill">Credit card autofill</h2>

<p>Apple Passwords is just for passwords. Despite the increasing availability of Apple Pay, I still have to fill in credit card numbers in many places. macOS does let you save credit cards in Wallet &amp; Apple Pay, but getting to those details takes about 6 clicks through System Settings. And Wallet’s saved credit cards only autofill in Safari, not Chrome.</p>

<p>With 1Password, I can hit Cmd+Option+\ or click the browser extension to quickly search and autofill. It also recognizes credit card fields and suggests autofill automatically. It’s a much smoother experience.</p>

<p><img src="/assets/images/blog/macos-saved-credit-card-1.png" alt="Navigating to saved credit cards in macOS System Settings" /></p>

<p><img src="/assets/images/blog/macos-saved-credit-card-2.png" alt="Viewing saved credit card details" /></p>

<p><img src="/assets/images/blog/macos-saved-credit-card-3.png" alt="Manually selecting and copying credit card details" /></p>

<h2 id="iphone-where-apple-passwords-shines">iPhone: where Apple Passwords shines</h2>

<p>Integration is actually slightly better with Apple Passwords in most cases since it’s the native experience. 1Password has its own annoyance: sometimes the password suggestion doesn’t pop up above the keyboard, and I have to switch apps to copy it. Minor, but real.</p>

<h2 id="windows-sync-is-buggier-than-macos">Windows sync is buggier than macOS</h2>

<p>There is an iCloud for Windows app to make the Chrome extension work for passwords. I don’t use Windows, but my family members tell me the password autofill is buggy.</p>

<h2 id="migration-is-the-worst-part">Migration is the worst part</h2>

<p>Passkeys can’t be exported or imported, so you’re locked in. Most services let you register more than one passkey, but not all of them. De-duplicating passwords after switching between managers is also tedious. Merging two vaults means sorting through duplicates, outdated entries, and slight mismatches.</p>

<p>To make this less painful, I wrote <a href="https://gist.github.com/natemcmaster/32749f8da8dc1685ffd7e3ef11ff46d8">a script</a> that uses the <a href="https://developer.1password.com/docs/cli/get-started">1Password CLI</a> to find and merge duplicate logins. It groups items by domain and username, lets you interactively pick which to keep, and archives the rest. It also handles merging TOTP secrets, notes, and extra fields from discarded items into the keeper so you don’t lose anything. If you’re migrating back to 1Password from another manager and end up with a mess of duplicates, it might save you some time.</p>

<h2 id="verdict">Verdict</h2>

<p>iCloud Passwords is good enough for most people, especially if you’re all-in on Safari and Apple devices.
But if you use Chrome on macOS, the experience has enough friction to justify paying for 1Password.
It’s a convenience tax, and I’m okay paying it.</p>]]></content><author><name>Nate</name></author><category term="tools" /><summary type="html"><![CDATA[I let my 1Password Family subscription lapse for three months to give iCloud Passwords a real shot. Apple’s offering is free, built-in, and getting better. After three months, I went back to 1Password.]]></summary></entry><entry><title type="html">So, you want to work for Anthropic?</title><link href="https://natemcmaster.com/blog/2026/01/27/so-you-want-to-join-anthropic/" rel="alternate" type="text/html" title="So, you want to work for Anthropic?" /><published>2026-01-27T00:00:00-08:00</published><updated>2026-01-27T00:00:00-08:00</updated><id>https://natemcmaster.com/blog/2026/01/27/so-you-want-to-join-anthropic</id><content type="html" xml:base="https://natemcmaster.com/blog/2026/01/27/so-you-want-to-join-anthropic/"><![CDATA[<p><a href="/blog/2025/12/30/farewell-amazon/">I recently joined Anthropic</a> as a Member of Technical Staff, building software to support Anthropic’s network.
It sparked a flurry of people asking if they could join, too.
Whether you’re considering a job change or specifically curious about Anthropic, I’ll share how I got the job, then walk through what I weighed: income, uncertainty, values, and the allure of hype.</p>

<blockquote>
  <p>DISCLAIMER: This is a personal website, produced on my own time and solely reflecting my personal opinions. Statements on this site do not represent the views or policies of my employer, Anthropic. I am not a recruiter for Anthropic. This was published Jan. 2026. Hiring practices may have changed by the time you read this.</p>
</blockquote>

<h2 id="process-how-did-i-get-the-job">Process: how did I get the job?</h2>

<p>I applied at <a href="https://anthropic.com/jobs">https://anthropic.com/jobs</a>.
I first considered Anthropic in 2024, but didn’t apply as I was committed to my goals at AWS.
I completed my goals at AWS by mid 2025.
A posting appeared on Anthropic’s job board in late 2025 with requirements that matched me almost perfectly.
So, I created an updated resume and applied online.
A recruiter got back to me 4 days later.
It took about 2 months to go from application to starting the job.
During this time, I also interviewed with Databricks and xAI as well as explored other roles within Amazon.</p>

<p>The exact questions and content of the interviews are not something I’ll share here—I don’t think that would help anyone.
If you’re preparing, don’t expect to pass by memorizing “perfect” answers or using an AI assistant in the background.
Interviewers want to understand <em>how</em> you think, not whether you can produce a polished response.
Here’s Anthropic’s guidance on using AI to prepare and apply: <a href="https://www.anthropic.com/candidate-ai-guidance">https://www.anthropic.com/candidate-ai-guidance</a>.
I used AI to proofread my resume and work on practice questions, but during the actual interviews, it was all me.</p>

<p>If you’re using AI to prepare, a few tips:</p>

<ul>
  <li>Ask it to review your resume for clarity and gaps—and tell it to be brutally honest.</li>
  <li>Practice answering questions out loud, then ask AI to critique your reasoning, not just your phrasing.</li>
  <li>Don’t over-polish. The goal is to think clearly, not to sound rehearsed.</li>
</ul>

<h2 id="income-less-cash-but-maybe-more-equity">Income: less cash, but maybe more equity</h2>

<p>Anthropic job posts include salary ranges for the role, and will mention if the role includes equity compensation.
Read it carefully.
And if you get the job offer, take time to understand the equity offer.</p>

<p>I took a pay cut for this job—at least in cash terms.
<a href="https://news.ycombinator.com/item?id=18847856">Joining a startup is similar in ways to investing in a startup</a>, and like any investment, you should understand the risk.</p>

<p>If you’re comparing offers, here are questions worth asking:</p>

<ul>
  <li><strong>Public vs. private stock:</strong> Public company equity (like AMZN) is predictable—you can track the price and sell when it vests. Private company equity is a bet on future value. What’s your risk tolerance?</li>
  <li><strong>Cliffs and liquidity:</strong> When does equity vest? Are there buyback opportunities, or will you wait years for a liquidity event?</li>
  <li><strong>Your financial situation:</strong> Can you absorb a lower cash salary? Do you have runway if the bet doesn’t pay off?</li>
</ul>

<p>I weighed these factors against my own situation and decided to take the risk. Your calculus may be different.</p>

<h2 id="uncertainty-into-the-unknown">Uncertainty: into the unknown</h2>

<p>How do you weigh an unknown opportunity against the familiar trade-offs of your current job?
I had a job with coworkers I like, a good boss, and predictable pay.
Why walk away?</p>

<blockquote>
  <p>I don’t need something new, I’m afraid of what I’m risking</p>

  <p>— Elsa, <a href="https://open.spotify.com/track/3Z0oQ8r78OUaHvGPiDBR3W?si=f24d14d991014b2c">Into the Unknown</a></p>
</blockquote>

<p>Like many parents, I’ve seen Frozen approximately a bazillion times.
So naturally, I resonated with this song—and the tongue-in-cheek humor of <a href="https://open.spotify.com/track/1Tt7zr1yDbKbT8L4jzSZ74?si=3cff6f6c761743c3">“When I Am Older”</a>.
Elsa resists the call into the unknown at first.
I also resisted leaving Amazon, despite feeling drawn away.
I came close in 2022—I had a job offer from a startup—but stayed.
There’s wisdom in caution.
“Better the devil you know” exists for a reason.</p>

<p>If you’re feeling similar resistance, it might help to ask: what specifically am I afraid of?
Can I test those assumptions by talking to people, researching the company, or examining the offer more closely?</p>

<p>For me, what was different this time?
The Anthropic role fit my interest, needs, and goals.
This was enough to outweigh the uncertainty that comes from changing to a new job.
I covered this in more detail in <a href="/blog/2025/12/30/farewell-amazon/#what-made-you-want-to-leave">my previous post</a>.</p>

<h2 id="personal-values-and-company-alignment">Personal values and company alignment</h2>

<p>The past few years were difficult, but they transformed my worldview.
Once I came to believe AI was going to substantially change the way my children will experience life, I realized I wanted to contribute to a vision of AI aligned with my humanist values.
I suspect this is common at my stage of life.
I’ve known others who, after experiencing loss or confronting mortality, felt a pull toward work that felt more meaningful.</p>

<p>During your interviews, pay attention to what employees say about why they’re there.
Do their answers feel rehearsed or genuine?
Is the company’s stated mission reflected in the questions they ask and the goals they describe?
For me, the more people I met at Anthropic, the more I saw alignment between <a href="https://www.anthropic.com/company">their mission</a> and how people actually talked about their work.</p>

<p>If you’re weighing options, I’d encourage you to think beyond compensation and hype.
What does the company actually do, and does it matter to you?
What do employees talk about when they describe why they joined?
These questions helped me find clarity.</p>

<h2 id="popularity-hype-is-ephemeral">Popularity: hype is ephemeral</h2>

<p>Anthropic is having a moment.
I asked myself, “am I joining because of the hype?”
That played a role in attracting my attention, but tech companies <a href="https://medium.com/backchannel/how-the-tech-press-forces-a-narrative-on-companies-it-covers-5f89fdb7793e">follow predictable narrative arcs</a>—today’s darling becomes tomorrow’s cautionary tale.
I fully expect Anthropic’s time in the spotlight to fade, and I’m okay with that.
(I probably don’t have to convince you of that—I joined Microsoft after an internship on Windows Phone, perhaps the most un-cool, unpopular smartphone available to college students at the time.)
The positive press won’t last forever, but my reasons for being here aren’t built on it.</p>

<p>I should also be honest: I’ve only been here a month.
My impressions are early, and I could be wrong.
One risk I think about: any company surrounded by believers can drift into an echo chamber.
Anthropic’s mission-driven culture is a strength, but it’s also a vulnerability if it crowds out dissent or self-criticism.
I don’t know yet how well the company handles that tension.
Ask me again in a year.</p>

<hr />

<p>So, you want to work for Anthropic?
I can’t give you a step-by-step process of what to say in a resume or how to respond in interviews.
I also can’t tell you if it’s right for you—but I hope in sharing my experiences, you’ll be better equipped to chart your own course.</p>]]></content><author><name>Nate</name></author><category term="anthropic" /><category term="career" /><summary type="html"><![CDATA[I recently joined Anthropic as a Member of Technical Staff, building software to support Anthropic’s network. It sparked a flurry of people asking if they could join, too. Whether you’re considering a job change or specifically curious about Anthropic, I’ll share how I got the job, then walk through what I weighed: income, uncertainty, values, and the allure of hype.]]></summary></entry><entry><title type="html">Thoughts on leaving AWS and joining Anthropic</title><link href="https://natemcmaster.com/blog/2025/12/30/farewell-amazon/" rel="alternate" type="text/html" title="Thoughts on leaving AWS and joining Anthropic" /><published>2025-12-30T00:00:00-08:00</published><updated>2025-12-30T00:00:00-08:00</updated><id>https://natemcmaster.com/blog/2025/12/30/farewell-amazon</id><content type="html" xml:base="https://natemcmaster.com/blog/2025/12/30/farewell-amazon/"><![CDATA[<p>This month, I announced I have resigned my position as a Principal Software Engineer for Amazon Web Services (AWS) and accepted a new role as a Member of Technical Staff with Anthropic. After announcing my transition out, 34 people at AWS scheduled office hours for 1:1 interviews with me, and I got asked a lot of questions. I’m sharing some of the common questions and answers here. Whether my thoughts are actually worth sharing is a question I try not to think about too hard. (Plus, two people specifically asked if I could blog more after I leave. I’m choosing to believe they weren’t being sarcastic.)</p>

<h2 id="most-asked-questions">Most asked questions</h2>

<h3 id="what-made-you-want-to-leave">What made you want to leave?</h3>

<p>People were implying—and a few said outright—that I had a position too good to give up. I didn’t make this decision in haste. It took months of consideration, during which time I had many chats with my wife, family, managers, mentors, and friends to collect diverse viewpoints. Those conversations helped me identify my motivations.</p>

<p>Ultimately, it came down to 3 factors:</p>

<ul>
  <li>Anthropic is on the cutting edge of AI innovation, a technology that I believe is going to transform our economy and society. I’m aligned with the company’s mission and values, and I want to contribute to something meaningful. While the company is a startup and may not end up being profitable, I think their research will outlast the corporate structure. I hope the science it produces benefits humanity.</li>
  <li>At AWS, I had been on the same team for 6.5 years and was happy with the state of the projects. In 2025 specifically, I received lots of positive feedback that my focus area, a project called Barge, had delivered “best in class” tooling. I would rather leave a project in a good state than one falling apart.</li>
  <li>Anthropic’s work climate is favorable. By this I mean the combination of compensation and equity, benefits, work-from-home flexibility, in-office perks, and “vibe”. The last part, “vibe”, is important. I want to be in a space where people are excited and thrilled about their work, and not just clinging to a job, hoping they don’t get laid off to boost profits or forced out to meet a quota for unregretted attrition.</li>
</ul>

<h3 id="what-do-you-recommend-for-my-career">What do you recommend for <em>my</em> career?</h3>

<p>Many people asked me for my input. Should they stay at Amazon? Would I be able to hire them into Anthropic? How can they get promoted if they stay at Amazon?</p>

<p>While my feedback was adapted to each individual, some common themes emerged:</p>

<h4 id="sde-ii-l5-looking-for-promotion-to-senior-sde-l6">SDE II (L5) looking for promotion to senior SDE (L6)</h4>

<p>I’ll probably need to write a separate post about this one. The promotion to L6 SDE at Amazon is challenging. Many people wait years, eager for the significant bump in pay and the sense of security that comes from the title.</p>

<p>Right now, Amazon is in a period of contraction. They’ve laid off thousands in 2025 alone. In the “before times” when teams were aggressively growing, promotions to L6 seemed to be easier. Because Amazon brought in so many new college hires, it was easier for anyone with experience to guide a team and fulfill the expectations of a senior engineer. Now, however, teams are shrinking, not growing, leaving less space for people to move into a team-lead type role.</p>

<p>My advice to people asking about this was to dig deeper into the problem space. There are countless things that need solving. Perhaps your current team is too heavy on senior engineers to demonstrate next-level work in your current scope—but there are many unsolved problems. If you can find those unsolved areas, and make it clear to your managers and other leaders why those problems need solving <em>AND</em> help guide a team to deliver on it, you may find that’s a path to promotion. That said, it might not work. The other, more likely path, is that a senior/principal engineer leaves a team and you have grown in skill and expertise so you can fill their shoes.</p>

<h4 id="stay-at-amazon-or-quit">Stay at Amazon or quit?</h4>

<p>Once it came out I was leaving, people started sharing with me they were either interviewing, planning to leave, or were strongly considering it. They wanted my input on my own decision-making process to check it against their own.</p>

<p>People had many reasons for considering quitting Amazon:</p>

<ul>
  <li>Frustration with bureaucracy and politics or other team dynamics</li>
  <li>Lack of opportunities for promotion</li>
  <li>Desire for more flexibility to work hybrid/remote</li>
  <li>Looking to increase income by moving to companies that pay better</li>
  <li>Insecurity due to Amazon’s persistent layoffs, and wanting to move first instead of waiting to be cut</li>
</ul>

<p>As a side note, in my final exit interview, I shared some of these reasons anonymously with my director. He wasn’t surprised at all. I took that to mean these are all common issues a director deals with. New to me, not to him.</p>

<h3 id="who-will-replace-you">Who will replace you?</h3>

<p>Because I had stayed in the same team and position for 6.5 years, I accumulated a lot of domain knowledge and understanding of history. But I want to be clear: I’m not special. There are many engineers who know these systems well. I was never a single point of failure—though I did have a unique perspective from a breadth of knowledge across areas and deep insights in a few specific domains.</p>

<p>I don’t feel any guilt about leaving. Amazon is going to be fine. It’s a huge company with enormous resources. The people around me will get to learn and grow in ways they couldn’t have with me always present to answer questions or make calls.</p>

<p>There’s some discomfort in realizing you’re not as essential as you thought. Better to leave before Amazon figures that out too.</p>

<p>I left people a “redirect” table pointing to other managers and engineers taking on leadership of various areas of work. The machine keeps running.</p>

<h2 id="whats-next">What’s next</h2>

<h3 id="stepping-out-of-the-spotlight">Stepping out of the spotlight</h3>

<p>Amazon has a culture of high expectations for anyone with principal-level titles. For years they’ve hosted an internal talk series called “Principals of Amazon”. They record the sessions and the content is often used as reference material. In meetings, I found that the principals could often use their influence and title to break ties or make final calls. On more than one occasion, senior managers directly asked “what is your call here, Nate?”. And in my organization, my role extended beyond software engineering into people management: hiring, mentoring, and coaching.</p>

<p>Lalit Maganti <a href="https://lalitm.com/software-engineering-outside-the-spotlight/">recently wrote</a> a post about his experiences as Staff+ at Google on a developer tools and infra team. His post resonated with me because I, like Lalit, was not on a “product” team at AWS where we had direct external customers. That said, there was still some level of spotlight internally. I was involved in giving internal talks to showcase our internal services and tooling. I wrote newsletters and Slack announcements. At Amazon, this “internal marketing” is necessary to raise awareness. The company is so large and filled with so many bright, talented people, that the pace of innovation is breakneck.</p>

<p>In my next role at Anthropic, there is no “principal” in my title. I’m a “Member of Technical Staff”, the same title as all of my peers. I’m eager to see how this changes conversations—it’s been a long time since I was new anywhere. And I’ve found at Amazon that my title was often entering the meeting before me, so anything I said came with an air of authority, regardless of whether my opinion had technical merit or not. Moving out of the spotlight seems refreshing—a chance to ground myself again.</p>

<h3 id="why-anthropic-why-now">Why Anthropic, why now?</h3>

<p>I’ve been cautious about avoiding hype in making this choice. I’d been considering Anthropic, xAI, or OpenAI for over a year. I actually had the application page open in 2024 but didn’t submit—I needed time to clarify my thinking. At some point in the last year, it became clear a transition was needed. I was either going to change roles internally at AWS or leave.</p>

<p>I spent my last three months at Amazon exploring a new project space, but I’m more excited about what I’ll get to work on at Anthropic. I was able to have several interviews beyond the typical hiring loop to learn about what I’ll be doing there. I’m going to be intentionally vague about the specifics for now, but hopefully I’ll have a chance to blog more in the future.</p>

<p>I’m excited to see how AI models are actually developed. I’ve heard Dario say that a lot of the work to do science comes down to engineering. I want to be part of that.</p>

<h3 id="work-flexibility">Work flexibility</h3>

<p>I’m also excited to have more flexibility in how I work. There’s an Anthropic office in Seattle, but as I understand it, there isn’t a “badge report” system like Amazon has been using to enforce return-to-office goals—and in some cases, force out people unwilling to comply. Everything I’ve heard indicates people go to the office because they’re genuinely excited to be there, and the atmosphere is vibrant and collaborative.</p>

<p>My office at Amazon had begun to feel empty and hollow. I would drive a long commute to sit in a cubicle and join Zoom calls. I had one of the better cubicles—I had a window—but I often felt isolated from the people around me.</p>

<h3 id="back-to-coding">Back to coding</h3>

<p>Finally, I’m excited to keep coding. Coding is why I got into this profession.</p>

<p>I’ve enjoyed learning to use AI as a coding assistant. I’ve heard Dario say 90% of Anthropic’s code is written by AI. I was maybe getting 60-70% of that in my work at Amazon, but the spec-driven approach of Kiro didn’t feel quite right—too heavy for small changes. I’m curious to see what Anthropic has developed to make AI-assisted coding even more effective.</p>

<p>I believe AI will transform my profession even more than it already has, and I’m eager to contribute. There’s vast, unlocked potential that’s been too hard to achieve due to the difficult nature of coding prior to AI. I’m excited to see what we—collectively, humanity—are able to do with better technology.</p>]]></content><author><name>Nate</name></author><category term="career" /><category term="software-engineering" /><summary type="html"><![CDATA[This month, I announced I have resigned my position as a Principal Software Engineer for Amazon Web Services (AWS) and accepted a new role as a Member of Technical Staff with Anthropic. After announcing my transition out, 34 people at AWS scheduled office hours for 1:1 interviews with me, and I got asked a lot of questions. I’m sharing some of the common questions and answers here. Whether my thoughts are actually worth sharing is a question I try not to think about too hard. (Plus, two people specifically asked if I could blog more after I leave. I’m choosing to believe they weren’t being sarcastic.)]]></summary></entry><entry><title type="html">How I use AI to code</title><link href="https://natemcmaster.com/blog/2025/11/06/tenets-of-ai-coding/" rel="alternate" type="text/html" title="How I use AI to code" /><published>2025-11-06T00:00:00-08:00</published><updated>2025-11-06T00:00:00-08:00</updated><id>https://natemcmaster.com/blog/2025/11/06/tenets-of-ai-coding</id><content type="html" xml:base="https://natemcmaster.com/blog/2025/11/06/tenets-of-ai-coding/"><![CDATA[<p>I love to code. I code a lot. And I think I’m good at it - I <a href="https://github.com/natemcmaster/first-website">started coding in 2002</a> and have written many, many applications in the last 20+ years. AI has been changing the way I code, and I wanted to share my learnings (so far).</p>

<p>Over the past few years, I’ve been an eager adopter of <a href="https://cline.bot/">Cline</a>, <a href="https://roocode.com/">Roo</a>, and <a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line.html">Amazon Q CLI</a>. I’ve also been experimenting with <a href="https://kiro.dev/">Kiro</a>, <a href="https://cursor.com/">Cursor</a>, <a href="https://github.com/features/copilot">Copilot</a>, and <a href="https://www.claude.com/product/claude-code">Claude</a> on the side.</p>

<p>From these experiences, I’ve developed a few personal guidelines for using AI coding tools effectively.</p>

<h2 id="only-ask-ai-to-do-things-i-already-know-how-to-do">Only ask AI to do things I already know how to do</h2>

<p>I take ownership of any code I produce. So, I don’t ask AI to help me write code unless I could have written it myself.<br />
That said, AI is much faster at typing than I am, so if it can do something I already know how to do, I’ll absolutely let it.</p>

<p>This includes knowing how to research to <em>find</em> an answer. I use AI as a research companion to help me find resources when I need to learn something new. Then I apply that learning to guide my AI coding agents more effectively.</p>

<h2 id="no-vibe-coding">No “vibe” coding</h2>

<p>AI can generate huge volumes of code in autopilot mode. I like the mental model behind Kiro’s spec-driven coding: set a goal, think about the end state, and break the work into smaller steps.<br />
That said, I’m not a fan of Kiro’s “autopilot” mode. No matter how well I’ve written a spec, AI still needs supervision to produce results I’m happy with.</p>

<p>So, I don’t code in a “fire-and-forget” way. I tried this many times, but I ended up spending just as much time undoing or discarding code generated <em>en masse</em> by an autopilot agent.</p>

<h2 id="ask-ai-to-do-small-specific-tasks">Ask AI to do small, specific tasks</h2>

<p>As of Nov. 2025, even the best coding models (like Sonnet 4.5) still appear to perform best on small, well-scoped tasks.
I’ve found Roo’s <a href="https://docs.roocode.com/features/boomerang-tasks">boomerang tasks</a> to be a great way to break larger tasks into steps AI can manage well.</p>

<h2 id="ai-automates-but-does-not-think">AI automates but does not think</h2>

<p>I resist the temptation to delegate “thinking” to the AI. I treat AI like an autocomplete engine on steroids. It may look like it’s thinking, but it’s not capable of true critical reasoning.<br />
I use it to automate tasks, but I stay in charge and make sure it’s doing what I actually need.</p>]]></content><author><name>Nate</name></author><category term="ai" /><category term="software-engineering" /><summary type="html"><![CDATA[I love to code. I code a lot. And I think I’m good at it - I started coding in 2002 and have written many, many applications in the last 20+ years. AI has been changing the way I code, and I wanted to share my learnings (so far).]]></summary></entry><entry><title type="html">Less code is often better</title><link href="https://natemcmaster.com/blog/2023/06/18/less-code/" rel="alternate" type="text/html" title="Less code is often better" /><published>2023-06-18T00:00:00-07:00</published><updated>2023-06-18T00:00:00-07:00</updated><id>https://natemcmaster.com/blog/2023/06/18/less-code</id><content type="html" xml:base="https://natemcmaster.com/blog/2023/06/18/less-code/"><![CDATA[<p>Early in my software engineering career, a senior engineer at Microsoft told me “the best solution is one that requires no new code.”
At the time, I thought this was nonsense. Is not my role as a software engineer to write code? Why would writing <em>less</em> or <em>no</em> code be better?
More code means more bug fixes, more features, more services, and more tools. So why is more not always better?</p>

<p>Fast forward to 2023 – now I am the most senior engineer on a team, and I give the same guidance. Prefer solutions that require less or no code.</p>

<p>What led to this shift in perspective? It boils down to this: writing code is a one-time cost,
but maintenance is an ongoing cost. Maintenance of successful code will extend for years beyond your original projections because migrating
to a new thing will have its own cost. So, your code will run until the benefit of removing it outweighs the cost of refactor or migration.</p>

<p>On the one hand, this can be exciting. Your legacy could be a system the lasts for years, even decades.
But on the other hand, you may live to regret introducing it in the first place, especially if your code ends up having problems.
And more often than not, code will have problems after it is first written. Many problems appear immediately, but others creep in slowly as
the system around it changes.</p>

<p>Another thing shifted, too – I see now that software is a means to an end, but not the end itself. I was especially blind to this because
my first job was building an open-source framework at Microsoft. The code <em>was</em> the product, or so I thought. Now I realize our product was
an intermediate ingredient. The framework on its own produces no value. It gained value by helping other developers solve problems with less code
of their own to maintain.</p>

<h2 id="hidden-costs">Hidden costs</h2>

<p>Ongoing maintenance costs can be hard to understand when you are learning to build software. Often your initial tasks are to write
something new. Also, often young developers do not stay on projects long enough to see the consequences
of their work play out.</p>

<p>However, with time, most engineers end up maintaining code they or someone else created long ago. And when that happens, what was
hidden now becomes a headache.</p>

<p>We have a term for this: “technical debt”. It’s sometimes used as a dirty word to malign a system or
piece of code because its issues are more expensive than they should be or preventing you from accomplishing something.</p>

<h2 id="open-source-and-sticky-ownership">Open-source and sticky ownership</h2>

<p>It should come as no surprise that if you make code open-source, you are giving it away for free. GitHub has tried to
set up “sponsorship” programs to fund developers. I have been lucky to have some sponsors, but despite their generosity,
I have earned 10 to 15 cents per hour of effort spent.</p>

<p>In professional environments, maintenance is passed along employees rotate through a project. In personal, open-source projects,
there is almost never someone else working on it. So, ownership of your code will stick to you forever.</p>

<p>So, I add double emphasis to “write less code” if you are considering open-sourcing it. While open-source has benefits
to the world of software as a whole, most of the benefits are collected by the big companies and not individuals.
For an individual getting started, something other than money must motivate your open-source work if you want it to continue.
Consider your motivations, and whether those will change over time.</p>

<h3 id="an-example">An example</h3>

<p>Five years ago, I uploaded a project called <a href="https://github.com/natemcmaster/DotNetCorePlugins">DotNetCorePlugins</a> and
advertised in Twitter with a blog post <a href="/blog/2018/07/25/netcore-plugins/">“Introducing an API for loading .dll files”</a>.
My motivations at the time were related to my work in the <a href="https://github.com/dotnet">@dotnet</a> project at Microsoft.
I saw many developers struggling with dynamic loading. I had found one solution that was not straightforward to discover, but
once I found I could abstract some complexity of .NET in a library, it seemed like a good chance to promote my findings
(and my reputation) but posting online.</p>

<p>Since that time, however, several things occurred which I did not anticipate.</p>

<ol>
  <li>There were bugs. The project has 160 issues. I made 4 releases after the initial release to address the biggest ones, but the bug reports continued to come in.</li>
  <li>.NET added a standard library feature which filled the need, but people kept using my project.</li>
  <li>I lost interest in C# and .NET. Plus, life changed. I aged, I started a family, I developed non-computer hobbies.</li>
</ol>

<p>Now, years later, I am deprecating the project and alerting developers this has hit the end of its life. I posted the project was in maintenance-only mode
in 2020, and no one has come along to offer help or to take ownership.</p>

<h2 id="write-code-responsibly">Write code responsibly</h2>

<p>If you are about to write some code, you should also ask yourself:</p>

<ul>
  <li>Who is this code for?</li>
  <li>Who will maintain it?</li>
  <li>Is there existing code I can repurpose or use instead?</li>
</ul>

<p>Writing code is fun, so keep doing it. Just be careful not to get carried away and neglect to consider its future.</p>]]></content><author><name>Nate</name></author><category term="software-engineering" /><summary type="html"><![CDATA[Early in my software engineering career, a senior engineer at Microsoft told me “the best solution is one that requires no new code.” At the time, I thought this was nonsense. Is not my role as a software engineer to write code? Why would writing less or no code be better? More code means more bug fixes, more features, more services, and more tools. So why is more not always better?]]></summary></entry><entry><title type="html">Deep-dive into .NET Core primitives, part 3: runtimeconfig.json in depth</title><link href="https://natemcmaster.com/blog/2019/01/09/netcore-primitives-3/" rel="alternate" type="text/html" title="Deep-dive into .NET Core primitives, part 3: runtimeconfig.json in depth" /><published>2019-01-09T00:00:00-08:00</published><updated>2019-01-09T00:00:00-08:00</updated><id>https://natemcmaster.com/blog/2019/01/09/netcore-primitives-3</id><content type="html" xml:base="https://natemcmaster.com/blog/2019/01/09/netcore-primitives-3/"><![CDATA[<p>.NET Core applications contain a file named <code class="language-plaintext highlighter-rouge">&lt;something&gt;.runtimeconfig.json</code>. This file can be used to control
a variety of options. Most developers need not be concerned with it because the SDK
generates the file, but I think it’s worth understanding. The file can be used to control settings which are
not surfaced in Visual Studio, such as automatically running your app on higher .NET Core versions,
tuning thread pools and garbage collection, and more.</p>

<p>This post is part of a series:</p>

<ul>
  <li><a href="/blog/2017/12/21/netcore-primitives/">Part 1 - .deps.json, runtimeconfig.json, and dll’s</a></li>
  <li><a href="/blog/2018/08/29/netcore-primitives-2/">Part 2 - the shared framework</a></li>
  <li>Part 3 - runtimeconfig.json in depth</li>
</ul>

<h2 id="purpose-of-the-file">Purpose of the file</h2>

<p>The runtimeconfig.json file is technically optional, but for practical reasons, every real-world app has it.
The file can be hand-edited. Unlike the <a href="/blog/2017/12/21/netcore-primitives/"><code class="language-plaintext highlighter-rouge">.deps.json</code> file</a>, it is meant to be human-readable.
The purpose of the file is to define required shared frameworks (for
<a href="https://docs.microsoft.com/dotnet/core/deploying/">framework-dependency deployments</a> only),
as well as other runtime options, as <a href="#runtime-options">outlined below</a>.</p>

<h3 id="a-simple-example">A simple example</h3>

<p>A typical runtimeconfig.json file will look something like this.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"tfm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"netcoreapp2.1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"framework"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.NETCore.App"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.1.0"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I’ve written a complete schema for this file. See <a href="https://gist.github.com/natemcmaster/0bdee16450f8ec1823f2c11af880ceeb">https://gist.github.com/natemcmaster/0bdee16450f8ec1823f2c11af880ceeb</a>.</p>

<p><a id="template-json"></a></p>

<h2 id="runtimeconfigtemplatejson">runtimeconfig.template.json</h2>

<p>Some options cannot be set in your project file (.csproj).
You have two options to work around this. Hand-edit runtimeconfig.json as a post-build action, or use a file named <code class="language-plaintext highlighter-rouge">runtimeconfig.template.json</code>.
I recommend using the template if you want settings to persist.</p>

<p>On build, the SDK will augment the template with additional data from your .csproj file.
Follow these steps to use a template:</p>

<ol>
  <li>Create a new project (<code class="language-plaintext highlighter-rouge">dotnet new console -n MyApp</code>)</li>
  <li>Create a file named “runtimeconfig.template.json” into the project directory (next to your .csproj file).</li>
  <li>Set the contents of the file to this:
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"rollForwardOnNoCandidateFx"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>Run <code class="language-plaintext highlighter-rouge">dotnet build</code></li>
</ol>

<p>Voila! That’s it. Look at <code class="language-plaintext highlighter-rouge">bin/Debug/netcoreapp2.1/MyApp.runtimeconfig.json</code> to make sure it worked.</p>

<h3 id="visual-studio-intellisense">Visual Studio intellisense</h3>

<p>I’ve written <a href="https://gist.github.com/natemcmaster/0bdee16450f8ec1823f2c11af880ceeb">a JSON schema</a>, which you can use in your Visual Studio editor.
Add this line to your <code class="language-plaintext highlighter-rouge">runtimeconfig.template.json</code> file.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://gist.githubusercontent.com/natemcmaster/0bdee16450f8ec1823f2c11af880ceeb/raw/runtimeconfig.template.schema.json"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h1 id="runtime-options">Runtime options</h1>

<h2 id="frameworks-versions-and-roll-forward">Frameworks, versions, and roll-forward</h2>

<p>.NET Core <a href="/blog/2018/08/29/netcore-primitives-2/">shared frameworks</a> support installing side-by-side versions, and therefore, dotnet has to
pick one version when starting an application. The following options are used to set which shared frameworks
and which versions of those frameworks are loaded.</p>

<p>Note: the default settings generated by the SDK are usually sufficient, but they can be altered to workaround regressions in
.NET Core patches or the unfortunately common error when .NET Core fails to launch:</p>

<blockquote>
  <p>It was not possible to find any compatible framework version.
The specified framework ‘Microsoft.NETCore.App’, version ‘X.Y.Z’ was not found.</p>
</blockquote>

<h3 id="shared-frameworks">Shared framework(s)</h3>

<p>This specifies the shared framework(s) the application depends on by name. The version is treated as a <strong>minimum version</strong>.
The only way to override the minimum (without changing the file) is to use <code class="language-plaintext highlighter-rouge">dotnet exec --fx-version</code>.</p>

<p>For .NET Core &lt; 3.0, only one framework can be specified.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"framework"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.AspNetCore.App"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.2.0"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ItemGroup&gt;</span>
  <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="na">Version=</span><span class="s">"2.2.0"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/ItemGroup&gt;</span>
</code></pre></div></div>

<p>For .NET Core &gt;= 3.0, multiple shared frameworks can be used and are no longer referenced as packages.</p>

<blockquote>
  <p>Note: 3.0 is still in preview, may change.</p>
</blockquote>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"frameworks"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.AspNetCore.App"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.0.0"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.WindowsDesktop.App"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.0.0"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ItemGroup&gt;</span>
  <span class="nt">&lt;FrameworkReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;FrameworkReference</span> <span class="na">Include=</span><span class="s">"Microsoft.WindowsDesktop.App"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/ItemGroup&gt;</span>
</code></pre></div></div>

<h3 id="automatically-run-on-higher-versions">Automatically run on higher versions</h3>

<p>This option is new in .NET Core 3.0.</p>

<p>By default, .NET Core will try to find the highest patch version of the shared framework which has the same major and minor version as your app specifies. But if it can’t find that, it may roll-forward to newer versions. This option controls the roll-forward policy.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"rollForward"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Major"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span>
  <span class="nt">&lt;RollForward&gt;</span>Major<span class="nt">&lt;/RollForward&gt;</span>
<span class="nt">&lt;/PropertyGroup&gt;</span>
</code></pre></div></div>

<p>The spec for this setting can be found at <a href="https://github.com/dotnet/designs/blob/master/accepted/2019/runtime-binding.md">https://github.com/dotnet/designs/blob/master/accepted/2019/runtime-binding.md</a>. About this setting, it says:</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">RollForward</code> can have the following values:</p>

  <ul>
    <li><code class="language-plaintext highlighter-rouge">LatestPatch</code> – Roll forward to the highest patch version. This disables minor version roll forward.</li>
    <li><code class="language-plaintext highlighter-rouge">Minor</code> – Roll forward to the lowest higher minor version, if requested minor version is missing. If the requested minor version is present, then the <code class="language-plaintext highlighter-rouge">LatestPatch</code> policy is used.</li>
    <li><code class="language-plaintext highlighter-rouge">Major</code> – Roll forward to lowest higher major version, and lowest minor version, if requested major version is missing. If the requested major version is present, then the <code class="language-plaintext highlighter-rouge">Minor</code> policy is used.</li>
    <li><code class="language-plaintext highlighter-rouge">LatestMinor</code> – Roll forward to highest minor version, even if requested minor version is present.</li>
    <li><code class="language-plaintext highlighter-rouge">LatestMajor</code> – Roll forward to highest major and highest minor version, even if requested major is present.</li>
    <li><code class="language-plaintext highlighter-rouge">Disable</code> – Do not roll forward. Only bind to specified version. This policy is not recommended for general use since it disable the ability to roll-forward to the latest patches. It is only recommended for testing.</li>
  </ul>

  <p><code class="language-plaintext highlighter-rouge">Minor</code> is the default setting. See <a href="https://github.com/dotnet/designs/blob/master/accepted/2019/runtime-binding.md#configuration-precedence"><strong>Configuration Precedence</strong></a> for more information.</p>

  <p>In all cases except <code class="language-plaintext highlighter-rouge">Disable</code> the highest available patch version is selected.</p>

  <p>Note: <code class="language-plaintext highlighter-rouge">LatestMinor</code> and <code class="language-plaintext highlighter-rouge">LatestMajor</code> are intended for component hosting scenarios, for both managed and native hosts (for example, managed COM components).</p>
</blockquote>

<h3 id="automatically-run-on-higher-patch-versions-before-net-core-30">Automatically run on higher patch versions (before .NET Core 3.0)</h3>

<p>This policy is being deprecated in .NET Core 3.0 in favor of the simpler “rollForward” option, as described above.</p>

<p>By default, .NET Core runs on the highest patch version of shared frameworks installed on the machine. This can be disabled using ‘applyPatches’.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"applyPatches"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>: currently not available as an SDK option. See <a href="#template-json">above</a>.</p>

<blockquote>
  <p>Note: I couldn’t write about this without a word of caution. I would personally only use this in production when it’s 3 AM, the site is down, the phone is ringing, and the company is bleeding $$$ every minute.
Otherwise, it’s better to get the latest security patches – for obvious reasons.</p>
</blockquote>

<h3 id="automatically-run-on-higher-major-or-minor-versions-before-net-core-30">Automatically run on higher major or minor versions (before .NET Core 3.0)</h3>

<p>This policy is being deprecated in .NET Core 3.0 in favor of the simpler “rollForward” option, as described above.</p>

<p>By default, .NET Core will try to find the highest patch version of the shared framework which has the same major and minor version as your app specifies. But if it can’t find that, it may roll-forward to newer versions. This option controls the roll-forward policy.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"rollForwardOnNoCandidateFx"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>: currently not available as an SDK option. See <a href="#template-json">above</a>.</p>

<p>This can be set to 0, 1, or 2.
See the <a href="https://github.com/dotnet/core-setup/blob/92072fd9ea03740ea317b631d3894ff3c5d0854d/Documentation/design-docs/roll-forward-on-no-candidate-fx.md">design document</a> for more details.</p>

<p>For example, given framework.version == 2.1.0, this is how .NET Core uses
this setting to decided what is a ‘compatible’ version of the framework.</p>

<table>
  <thead>
    <tr>
      <th>rollForwardOnNoCandidateFx</th>
      <th>Compatible framework versions</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td><code class="language-plaintext highlighter-rouge">&gt;=2.1.0, &lt; 2.2.0</code></td>
    </tr>
    <tr>
      <td>1 (default)</td>
      <td><code class="language-plaintext highlighter-rouge">&gt;=2.1.0, &lt; 3.0.0</code></td>
    </tr>
    <tr>
      <td>2</td>
      <td><code class="language-plaintext highlighter-rouge">&gt;=2.1.0</code></td>
    </tr>
  </tbody>
</table>

<h2 id="target-framework-moniker">Target framework moniker</h2>

<p>This one is an implementation detail of the <a href="https://docs.microsoft.com/dotnet/core/deploying/runtime-store">runtime package store</a>.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"tfm"</span><span class="p">:</span><span class="w"> </span><span class="s2">"netcoreapp2.1"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span>
  <span class="nt">&lt;TargetFramework&gt;</span>netcoreapp2.1<span class="nt">&lt;/TargetFramework&gt;</span>
<span class="nt">&lt;/PropertyGroup&gt;</span>
</code></pre></div></div>

<h2 id="assembly-probing-paths">Assembly probing paths</h2>

<p>This specifies additional folders used by the host to find assemblies listed in the .deps.json file. See <a href="/blog/2017/12/21/netcore-primitives/">Part 1 of this series</a> for details on how this works.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"additionalProbingPaths"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"C:</span><span class="se">\\</span><span class="s2">Users</span><span class="se">\\</span><span class="s2">nmcmaster</span><span class="se">\\</span><span class="s2">.nuget</span><span class="se">\\</span><span class="s2">packages</span><span class="se">\\</span><span class="s2">"</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ItemGroup&gt;</span>
  <span class="nt">&lt;AdditionalProbingPath</span> <span class="na">Include=</span><span class="s">"$(USERPROFILE)\.nuget\packages"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/ItemGroup&gt;</span>
</code></pre></div></div>

<blockquote>
  <p>Note: this .csproj item will only end up in the <code class="language-plaintext highlighter-rouge">runtimeconfig.dev.json</code> file, which is only used
during development, not production. Use the <a href="#template-json">template file</a> to set values which
are required to be in the regular, production version of <code class="language-plaintext highlighter-rouge">runtimeconfig.json</code>.</p>
</blockquote>

<h2 id="runtime-settings">Runtime settings</h2>

<p>“configProperties” is a list of key-value pairs given to the runtime.
These can be used in almost any way imaginable, but there is a short list of well-defined and commonly used settings.</p>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"configProperties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"value"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="well-known-runtime-settings">Well-known runtime settings</h3>

<table>
  <thead>
    <tr>
      <th>Setting name</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>System.GC.Server</td>
      <td>boolean</td>
      <td>Enable server garbage collection.</td>
    </tr>
    <tr>
      <td>System.GC.Concurrent</td>
      <td>boolean</td>
      <td>Enable concurrent garbage collection.</td>
    </tr>
    <tr>
      <td>System.GC.RetainVM</td>
      <td>boolean</td>
      <td>Put segments that should be deleted on a standby list for future use instead of releasing them back to the OS.</td>
    </tr>
    <tr>
      <td>System.Runtime.TieredCompilation</td>
      <td>boolean</td>
      <td>Enable tiered compilation.</td>
    </tr>
    <tr>
      <td>System.Threading.ThreadPool.MinThreads</td>
      <td>integer</td>
      <td>Override MinThreads for the ThreadPool worker pool.</td>
    </tr>
    <tr>
      <td>System.Threading.ThreadPool.MaxThreads</td>
      <td>integer</td>
      <td>Override MaxThreads for the ThreadPool worker pool.</td>
    </tr>
    <tr>
      <td>System.Globalization.Invariant</td>
      <td>boolean</td>
      <td>Enabling invariant mode disables globalization behavior.</td>
    </tr>
  </tbody>
</table>

<p>Here are some documents explaining more about these:</p>

<ul>
  <li><a href="https://github.com/dotnet/coreclr/blob/v2.2.0/Documentation/project-docs/clr-configuration-knobs.md">Host Configuration Knobs</a></li>
  <li><a href="https://github.com/dotnet/corefx/blob/v2.2.0/Documentation/architecture/globalization-invariant-mode.md">Invariant globalization mode</a></li>
  <li><a href="https://blogs.msdn.microsoft.com/dotnet/2018/08/02/tiered-compilation-preview-in-net-core-2-1/">Tiered compilation</a></li>
</ul>

<p>These settings can be configured in your .csproj file. The best way to find more
is to look at the <a href="https://github.com/dotnet/sdk/blob/a9d9e0a552ee177088ded0ef09f6061a44223596/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.targets#L195-L215">Microsoft.NET.Sdk.targets</a>
file itself.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span>
  <span class="nt">&lt;ConcurrentGarbageCollection&gt;</span>true<span class="nt">&lt;/ConcurrentGarbageCollection&gt;</span>
  <span class="nt">&lt;ServerGarbageCollection&gt;</span>true<span class="nt">&lt;/ServerGarbageCollection&gt;</span>
  <span class="nt">&lt;RetainVMGarbageCollection&gt;</span>true<span class="nt">&lt;/RetainVMGarbageCollection&gt;</span>
  <span class="nt">&lt;ThreadPoolMinThreads&gt;</span>1<span class="nt">&lt;/ThreadPoolMinThreads&gt;</span>
  <span class="nt">&lt;ThreadPoolMaxThreads&gt;</span>100<span class="nt">&lt;/ThreadPoolMaxThreads&gt;</span>
  <span class="c">&lt;!-- Supported as of .NET Core SDK 3.0 Preview 1 --&gt;</span>
  <span class="nt">&lt;TieredCompilation&gt;</span>true<span class="nt">&lt;/TieredCompilation&gt;</span>
  <span class="nt">&lt;InvariantGlobalization&gt;</span>true<span class="nt">&lt;/InvariantGlobalization&gt;</span>
<span class="nt">&lt;/PropertyGroup&gt;</span>
</code></pre></div></div>

<h3 id="additional-runtime-settings">Additional runtime settings</h3>

<p>.NET Core allows you to specify your own settings. These values can be retrieved using <code class="language-plaintext highlighter-rouge">System.AppContext.GetData</code>.</p>

<blockquote>
  <p>Note: this is not a suitable alternative to <a href="https://docs.microsoft.com/aspnet/core/fundamentals/configuration">configuration builders</a>.</p>
</blockquote>

<p><strong>JSON</strong>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"configProperties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"ArbitraryNumberSetting"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ArbitraryStringSetting"</span><span class="p">:</span><span class="w"> </span><span class="s2">"red"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ArbitraryBoolSetting"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>.csproj</strong>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ItemGroup&gt;</span>
  <span class="nt">&lt;RuntimeHostConfigurationOption</span> <span class="na">Include=</span><span class="s">"ArbitraryNumberSetting"</span> <span class="na">Value=</span><span class="s">"2"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;RuntimeHostConfigurationOption</span> <span class="na">Include=</span><span class="s">"ArbitraryStringSetting"</span> <span class="na">Value=</span><span class="s">"red"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;RuntimeHostConfigurationOption</span> <span class="na">Include=</span><span class="s">"ArbitraryBoolSetting"</span> <span class="na">Value=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/ItemGroup&gt;</span>
</code></pre></div></div>

<p>In C#,</p>

<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// "red"</span>
<span class="kt">var</span> <span class="n">color</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">AppContext</span><span class="p">.</span><span class="nf">GetData</span><span class="p">(</span><span class="s">"ArbitraryStringSetting"</span><span class="p">)</span> <span class="k">as</span> <span class="kt">string</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="more-info">More info</h3>

<p>See <a href="/blog/2017/12/21/netcore-primitives/">Part 1</a> for more details about this file and how to use it. I also recommend searching
through the Markdown files in <a href="https://github.com/dotnet">https://github.com/dotnet</a> for more details on how these various
settings are used.</p>]]></content><author><name>Nate</name></author><category term="dotnet" /><summary type="html"><![CDATA[.NET Core applications contain a file named &lt;something&gt;.runtimeconfig.json. This file can be used to control a variety of options. Most developers need not be concerned with it because the SDK generates the file, but I think it’s worth understanding. The file can be used to control settings which are not surfaced in Visual Studio, such as automatically running your app on higher .NET Core versions, tuning thread pools and garbage collection, and more.]]></summary></entry><entry><title type="html">Deep-dive into .NET Core primitives, part 2: the shared framework</title><link href="https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/" rel="alternate" type="text/html" title="Deep-dive into .NET Core primitives, part 2: the shared framework" /><published>2018-08-29T00:00:00-07:00</published><updated>2018-08-29T00:00:00-07:00</updated><id>https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2</id><content type="html" xml:base="https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/"><![CDATA[<p>Shared frameworks have been an essential part of .NET Core since 1.0.
ASP.NET Core shipped as a shared framework for the first time in 2.1.
You may not have noticed if things are working smoothly, but there have been
<a href="https://github.com/aspnet/AspNetCore/issues/3241">some</a> <a href="https://github.com/aspnet/Universe/issues/1180">bumps</a> and <a href="https://github.com/aspnet/AspNetCore/issues/3292">ongoing discussion</a> about its design.
In this post, I will dive deep into the shared frameworks and talk about some common developer pitfalls.</p>

<p>This post is part of a series:</p>

<ul>
  <li><a href="/blog/2017/12/21/netcore-primitives/">Part 1 - .deps.json, runtimeconfig.json, and dll’s</a></li>
  <li>Part 2 - the shared framework</li>
  <li><a href="/blog/2019/01/09/netcore-primitives-3/">Part 3 - runtimeconfig.json in depth</a></li>
</ul>

<h1 id="the-basics">The Basics</h1>

<p>.NET Core apps run in one of two modes: framework-dependent or self-contained. On my MacBook, a
minimal <em>self-contained</em> ASP.NET Core application is 93 <strong>MB</strong> and has 350 files.
By contrast, a minimal framework-dependent app is 239 <strong>KB</strong> and has 5 files.</p>

<p>You can produce both kinds of apps with these command line instructions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dotnet new web
dotnet publish --runtime osx-x64 --output bin/self_contained_app/
dotnet publish --output bin/framework_dependent_app/
</code></pre></div></div>

<p><img src="/assets/images/blog/netcore_primitives_fdd_vs_scd.png" alt="Screenshot comparing file size of framework dependent and self-contained" /></p>

<p>When the app runs, it is functionally equivalent in both modes. So why are there
different modes? As <a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/">the docs explain well</a>:</p>

<blockquote>
  <p>framework-dependent deployment relies on the presence of a shared system-wide version of .NET Core…. [A] self-contained deployment doesn’t rely on the presence of shared components on the target system. All components…are included with the application.</p>
</blockquote>

<p><a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/">This document</a> does a great job of explaining the advantages of each mode.</p>

<h1 id="the-shared-framework">The shared framework</h1>

<p>To put it simply, a .NET Core shared framework is a folder of assemblies (*.dll files) that are not in the application folder. These assemblies version and release together.
This folder is one part of the “shared system-wide version of .NET Core”, and is usually found in <code class="language-plaintext highlighter-rouge">C:/Program Files/dotnet/shared</code>.</p>

<p>When you run <code class="language-plaintext highlighter-rouge">dotnet.exe WebApp1.dll</code>, the <strong>.NET Core host</strong> must</p>

<ol>
  <li>discover the names and versions of your app dependencies</li>
  <li>find those dependencies in common locations.</li>
</ol>

<p>These dependencies are found in a variety locations, including, but not limited to, the shared frameworks.
In a previous post, I briefly explained how the <a href="/blog/2017/12/21/netcore-primitives/">deps.json and runtimeconfig.json files</a> configure the host’s behavior. See that post for more details.</p>

<p>The .NET Core host reads the *.runtimeconfig.json file to determine which shared framework(s) to load. Its contents
may look like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"runtimeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"framework"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Microsoft.AspNetCore.App"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.1.1"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <strong>shared framework name</strong> is just that - a name. By convention, this name ends in “.App”, but it could
be anything, like “FooBananaShark”.</p>

<p>The <strong>shared framework version</strong> represents the <em>minimum</em> version. The .NET Core host will never run on
a lower version, but it may try to run on a higher one.</p>

<h3 id="which-shared-frameworks-do-i-have-installed">Which shared frameworks do I have installed?</h3>

<p>Run <code class="language-plaintext highlighter-rouge">dotnet --list-runtimes</code>. It will show the names, versions, and locations of shared frameworks.</p>

<h3 id="comparing-microsoftnetcoreapp-aspnetcoreapp-and-aspnetcoreall">Comparing Microsoft.NETCore.App, AspNetCore.App, and AspNetCore.All</h3>

<p>As of .NET Core 2.2, there are three shared frameworks.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Framework name</th>
      <th style="text-align: left">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Microsoft.NETCore.App</td>
      <td style="text-align: left">The base runtime. It supports things like <code class="language-plaintext highlighter-rouge">System.Object</code>, <code class="language-plaintext highlighter-rouge">List&lt;T&gt;</code>, <code class="language-plaintext highlighter-rouge">string</code>, memory management, file and network IO, threading, etc.</td>
    </tr>
    <tr>
      <td style="text-align: left">Microsoft.AspNetCore.App</td>
      <td style="text-align: left">The default web runtime. It imports Microsoft.NETCore.App, and adds API to build an HTTP server using Kestrel, Mvc, SignalR, Razor, and parts of EF Core.</td>
    </tr>
    <tr>
      <td style="text-align: left">Microsoft.AspNetCore.All</td>
      <td style="text-align: left">Integrations with third-party stuff. It imports Microsoft.AspNetCore.App. It adds support for EF Core + SQLite, extensions that use Redis, config from Azure Key Vault, and more. (Will be <a href="https://github.com/aspnet/Announcements/issues/314">deprecated in 3.0.</a>)</td>
    </tr>
  </tbody>
</table>

<h3 id="relationship-to-the-nuget-package">Relationship to the NuGet package</h3>

<p>The .NET Core SDK generates the <code class="language-plaintext highlighter-rouge">runtimeconfig.json</code> file. In .NET Core 1 and 2, it uses two pieces
from the project configuration to determine what goes in the framework section of the file:</p>

<ol>
  <li>the <code class="language-plaintext highlighter-rouge">MicrosoftNETPlatformLibrary</code> property. <a href="https://github.com/dotnet/sdk/blob/v2.1.300/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets#L501-L516">By default</a> this is set to <code class="language-plaintext highlighter-rouge">"Microsoft.NETCore.App"</code> for all .NET Core projects.</li>
  <li>NuGet restore results, which must include a package by the same name.</li>
</ol>

<p>The .NET Core SDK <a href="https://github.com/dotnet/sdk/blob/v2.1.300/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.DefaultItems.props#L49">adds an <strong>implicit package reference</strong> to Microsoft.NETCore.App</a> to all projects. ASP.NET Core <a href="https://github.com/aspnet/Universe/blob/2.1.3/src/Microsoft.AspNetCore.App/build/netcoreapp2.1/Microsoft.AspNetCore.App.targets#L10">overrides the default by setting</a> <code class="language-plaintext highlighter-rouge">MicrosoftNETPlatformLibrary</code> to <code class="language-plaintext highlighter-rouge">"Microsoft.AspNetCore.App"</code>.</p>

<p>The NuGet package, however, <strong>does not provide the shared framework</strong>.
I repeat: the NuGet package <strong>does not provide the shared framework</strong>. (I’ll repeat once more below.)
The NuGet package only provides a set of APIs used by the compiler and a few other SDK bits.
The shared framework files come from runtime installers found on <a href="https://aka.ms/dotnet-download">https://aka.ms/dotnet-download</a>, or bundled
in Visual Studio, Docker images, and some Azure services.</p>

<h3 id="version-roll-forward">Version roll-forward</h3>

<p>As mentioned above, runtimeconfig.json is a <em>minimum</em> version. The <em>actual</em> version used depends on a rollforward
policy <a href="https://docs.microsoft.com/en-us/dotnet/core/versions/selection#framework-dependent-apps-roll-forward">documented in great detail by Microsoft</a>.
The most common way this applies is:</p>

<ul>
  <li>If an app minimum version is 2.1.0, the highest 2.1.* version will be loaded.</li>
</ul>

<p>I’ll go into this file in more details. See <a href="/blog/2019/01/09/netcore-primitives-3/">.NET Core Primitives part 3</a>.</p>

<h3 id="layered-shared-frameworks">Layered shared frameworks</h3>

<p><em>This feature was added in .NET Core 2.1.</em></p>

<p>Shared frameworks can depend on other shared frameworks. This was introduced
to support ASP.NET Core which converted from <a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/runtime-store">a package runtime store</a> to a shared framework.</p>

<p>For example, if you look inside the <code class="language-plaintext highlighter-rouge">$DOTNET_ROOT/shared/Microsoft.AspNetCore.All/$version/</code> folder, you will see
a <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.All.runtimeconfig.json</code> file.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /usr/local/share/dotnet/shared/Microsoft.AspNetCore.All/2.1.2/Microsoft.AspNetCore.All.runtimeconfig.json
{
  "runtimeOptions": {
    "tfm": "netcoreapp2.1",
    "framework": {
      "name": "Microsoft.AspNetCore.App",
      "version": "2.1.2"
    }
  }
}
</code></pre></div></div>

<h3 id="multi-level-lookup">Multi-level lookup</h3>

<p><em>This feature was added in .NET Core 2.0.</em></p>

<p>The host will probe several locations to find a suitable shared framework. It starts by looking in the
<strong>dotnet root</strong>, which is the directory containing the <code class="language-plaintext highlighter-rouge">dotnet</code> executable. This can also be overridden
by setting the <code class="language-plaintext highlighter-rouge">DOTNET_ROOT</code> environment variable to a folder path. The first location probed is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$DOTNET_ROOT/shared/$name/$version
</code></pre></div></div>

<p>If a folder is not there, it will attempt to look in pre-defined global locations using <strong>multi-level lookup</strong>.
This can be turned off by setting the environment variable <code class="language-plaintext highlighter-rouge">DOTNET_MULTILEVEL_LOOKUP=0</code>.
The default global locations are:</p>

<table>
  <thead>
    <tr>
      <th>OS</th>
      <th>Location</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Windows</td>
      <td><code class="language-plaintext highlighter-rouge">C:\Program Files\dotnet</code> (64-bit processes) <br /> <code class="language-plaintext highlighter-rouge">C:\Program Files (x86)\dotnet</code> (32-bit processes) (<a href="https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/common/pal.windows.cpp#L203-L210">See in the source code</a>)</td>
    </tr>
    <tr>
      <td>macOS</td>
      <td><code class="language-plaintext highlighter-rouge">/usr/local/share/dotnet</code> (<a href="https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/common/pal.unix.cpp#L195">source code</a>)</td>
    </tr>
    <tr>
      <td>Unix</td>
      <td><code class="language-plaintext highlighter-rouge">/usr/share/dotnet</code> (<a href="https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/common/pal.unix.cpp#L197">source code</a>)</td>
    </tr>
  </tbody>
</table>

<p>The host will probe for directories in:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$GLOBAL_DOTNET_ROOT/shared/$name/$version
</code></pre></div></div>

<h3 id="readytorun">ReadyToRun</h3>

<p>The assemblies in the shared frameworks are pre-optimized with <a href="https://github.com/dotnet/coreclr/blob/v2.1.3/Documentation/building/crossgen.md">a tool called <code class="language-plaintext highlighter-rouge">crossgen</code></a>. This produces
<a href="https://github.com/dotnet/coreclr/blob/v2.1.3/Documentation/botr/readytorun-overview.md">“ReadyToRun” versions</a> of .dll’s which are optimized for specific operating systems and CPU architectures.
The primary performance gain is that this reduces the amount of time the JIT spends preparing code on startup.</p>

<h1 id="pitfalls">Pitfalls</h1>

<p>I think every .NET Core developer has fallen into one of these pitfalls at some point. I’ll attempt to explain
how this happens.</p>

<h3 id="http-error-5025-process-failure"><code class="language-plaintext highlighter-rouge">HTTP Error 502.5 Process Failure</code></h3>

<p><img src="/assets/images/blog/netcore_primitives_ancm.png" alt="Screenshot of HTTP 502.5 error" /></p>

<p>By far the most common pitfall when hosting ASP.NET Core in IIS or running on Azure Web Services.
This typically happens after a developer upgraded a project, or when an app is deployed to a machine which hasn’t
been updated recently. The real error is often that a shared framework cannot be found, and the .NET Core application cannot start without it. When dotnet fails to launch the app, IIS issues the HTTP 502.5 error, but does not
surface the internal error message.</p>

<h3 id="the-specified-framework-was-not-found">“The specified framework was not found”</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>It was not possible to find any compatible framework version
The specified framework 'Microsoft.AspNetCore.App', version '2.1.3' was not found.
  - Check application dependencies and target a framework version installed at:
      /usr/local/share/dotnet/
  - Installing .NET Core prerequisites might help resolve this problem:
      http://go.microsoft.com/fwlink/?LinkID=798306&amp;clcid=0x409
  - The .NET Core framework and SDK can be installed from:
      https://aka.ms/dotnet-download
  - The following versions are installed:
      2.1.1 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
      2.1.2 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
</code></pre></div></div>

<p>This error is often found lurking behind HTTP 502.5 errors or Visual Studio Test Explorer failures.</p>

<p>This happens when the runtimeconfig.json file specifies a framework name and version, and the host
cannot find an appropriate version using the <a href="#multi-level-lookup">multi-level lookup</a> and
<a href="#version-roll-forward">rollforward policies</a>, as explained above.</p>

<h3 id="updating-the-nuget-package-for-microsoftaspnetcoreapp">Updating the NuGet package for Microsoft.AspNetCore.App</h3>

<p>The NuGet package for Microsoft.AspNetCore.App does not provide the shared framework.
It only provides the APIs used by the C#/F# compiler and a few SDK bits. You must download and install the
shared framework separately. See <a href="https://aka.ms/dotnet-download">https://aka.ms/dotnet-download</a>.</p>

<p>Also, because of <a href="#version-roll-forward">rollforward policies</a>, you don’t need to update the NuGet package version to get your app to run on a new shared framework version.</p>

<p>It was probably a design mistake on the part of the ASP.NET Core team (which I’m on) to represent the shared framework as a NuGet package in the project file.
The packages which represent shared frameworks are not normal packages. Unlike most packages, they are not
self-sufficient. It is reasonable to expect that when a project uses a <code class="language-plaintext highlighter-rouge">&lt;PackageReference&gt;</code>,
NuGet is able to install everything needed, and frustrating that these special packages
deviate from the pattern. <a href="https://github.com/aspnet/AspNetCore/issues/3307">Various proposals</a>
have been made to fix this. I’m hopeful one will land soon-ish.</p>

<h3 id="packagereference-includemicrosoftaspnetcoreapp-"><code class="language-plaintext highlighter-rouge">&lt;PackageReference Include="Microsoft.AspNetCore.App" /&gt;</code></h3>

<p>New project templates and docs for ASP.NET Core 2.1 showed users that they only needed to have this line in their project.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>All other <code class="language-plaintext highlighter-rouge">&lt;PackageReference&gt;</code>’s must include a <code class="language-plaintext highlighter-rouge">Version</code> attribute. The version-less package ref
only works if the project begins with <code class="language-plaintext highlighter-rouge">&lt;Project Sdk="Microsoft.NET.Sdk.Web"&gt;</code>, and only works for the <code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.{App, All}</code> packages. The Web SDK will
automatically pick a version of these packages based on other values in the project, like <code class="language-plaintext highlighter-rouge">&lt;TargetFramework&gt;</code>
and <code class="language-plaintext highlighter-rouge">&lt;RuntimeIdentifier&gt;</code>.</p>

<p>This magic does not work if you specify a version on the package reference element,
or if you’re not using the Web SDK. It’s hard to recommend a good solution because
the best approach depends on your level of understanding and the project type.</p>

<h3 id="publish-trimming">Publish trimming</h3>

<p>When you run <code class="language-plaintext highlighter-rouge">dotnet publish</code> to create a framework-dependent app, the SDK uses the NuGet restore
result to determine which assemblies should be in the publish folder. Some will be copied from NuGet packages, and others are not because they are expected to be in the shared frameworks.</p>

<p>This can easily go wrong because ASP.NET Core is available as a shared framework <em>and</em> as NuGet packages.
The trimming attempts to do some graph math to examine transitive dependencies, upgrades, etc., and pick
the right files accordingly.</p>

<p>Take for example this project:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="na">Version=</span><span class="s">"2.1.1"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.Mvc"</span> <span class="na">Version=</span><span class="s">"2.1.9"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>MVC is actually part of Microsoft.AspNetCore.App, but when <code class="language-plaintext highlighter-rouge">dotnet publish</code> runs, it sees that your project
has decided to <strong>upgrade</strong> “Microsoft.AspNetCore.Mvc.dll” to a version which is higher than what
Microsoft.AspNetCore.App 2.1.1 includes, so it will put Mvc.dll into your publish folder.</p>

<p>This is less than ideal because your application grows in size and you don’t get a ReadyToRun optimized version
of Microsoft.AspNetCore.Mvc.dll. This can happen unintentionally if you get upgraded transitively through
a ProjectReference of via a third-party dependencies.</p>

<h3 id="confusing-the-target-framework-moniker-with-the-shared-framework">Confusing the target framework moniker with the shared framework</h3>

<p>It’s easy to think that <code class="language-plaintext highlighter-rouge">"netcoreapp2.0" == "Microsoft.NETCore.App, v2.0.0"</code>. This is not true.
A target framework moniker (aka TFM) is specified in a project using the <code class="language-plaintext highlighter-rouge">&lt;TargetFramework&gt;</code> element.
“netcoreapp2.0” is meant to be a human-friendly way to express which version of .NET Core you would like to use.</p>

<p>The pitfall of a TFM is that it is too short. It cannot express things like multiple shared frameworks,
patch-specific versioning, version rollforward, output type, and self-contained vs framework-dependent deployment.
The SDK will attempt to <em>infer</em> many of these settings from the TFM, but it cannot infer everything.</p>

<p>So, more accurately, <code class="language-plaintext highlighter-rouge">"netcoreapp2.0" implies "Microsoft.NETCore.App, at least v2.0.0"</code>.</p>

<h3 id="confusing-project-settings">Confusing project settings</h3>

<p>The final pitfall I will mention is about project settings. There are many, and the terminology and setting names don’t always line up. It’s a confusing set of terms, so this one isn’t your fault if you get them mixed up.</p>

<p>Below, I’ve listed some common project settings and what they actually mean.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span>
  <span class="nt">&lt;TargetFramework&gt;</span>netcoreapp2.1<span class="nt">&lt;/TargetFramework&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * The API set version to use when resolving compilation references from NuGet packages.
  --&gt;</span>

  <span class="nt">&lt;TargetFrameworks&gt;</span>netcoreapp2.1;net471<span class="nt">&lt;/TargetFrameworks&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * Compile for two different API version sets. This does not represent multi-layered shared frameworks.
  --&gt;</span>

  <span class="nt">&lt;MicrosoftNETPlatformLibrary&gt;</span>Microsoft.AspNetCore.App<span class="nt">&lt;/MicrosoftNETPlatformLibrary&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * The name of the top-most shared framework
  --&gt;</span>

  <span class="nt">&lt;RuntimeFrameworkVersion&gt;</span>2.1.2<span class="nt">&lt;/RuntimeFrameworkVersion&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * version of the implicit package reference to Microsoft.NETCore.App which then becomes
        the _minimum_ shared framework version.
  --&gt;</span>

  <span class="nt">&lt;RuntimeIdentifier&gt;</span>win-x64<span class="nt">&lt;/RuntimeIdentifier&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * Operating system kind + CPU architecture
  --&gt;</span>

  <span class="nt">&lt;RuntimeIdentifiers&gt;</span>win-x64;win-x86<span class="nt">&lt;/RuntimeIdentifiers&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * A list of operating systems and CPU architectures which this project _might_ run on.
        You still have to select one by setting RuntimeIdentifier.
  --&gt;</span>

<span class="nt">&lt;/PropertyGroup&gt;</span>

<span class="nt">&lt;ItemGroup&gt;</span>

  <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="na">Version=</span><span class="s">"2.1.2"</span> <span class="nt">/&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * Use the Microsoft.AspNetCore.App shared framework.
      * Minimum version = 2.1.2
  --&gt;</span>

  <span class="nt">&lt;PackageReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.Mvc"</span> <span class="na">Version=</span><span class="s">"2.1.2"</span> <span class="nt">/&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * Use the Microsoft.AspNetCore.Mvc package.
      * Exact version = 2.1.2
  --&gt;</span>

  <span class="nt">&lt;FrameworkReference</span> <span class="na">Include=</span><span class="s">"Microsoft.AspNetCore.App"</span> <span class="nt">/&gt;</span>
  <span class="c">&lt;!--
    Actual meaning:
      * Use the Microsoft.AspNetCore.App shared framework.
    (This is new and unreleased...see https://github.com/dotnet/sdk/pull/2486)
  --&gt;</span>

<span class="nt">&lt;/ItemGroup&gt;</span>
</code></pre></div></div>

<h1 id="closing">Closing</h1>

<p>The shared framework is an optional feature of .NET Core, and I think it’s a reasonable default for most users despite the pitfalls. I still think it’s good for .NET Core developers to understand what goes on under the hood, and hopefully this was a good overview of the shared frameworks feature. I tried to link to official docs and guidance
where possible so you can find more info. If you have more questions, leave a comment below.</p>

<h3 id="more-info">More info</h3>

<ul>
  <li>Packages, metapackages and frameworks: <a href="https://docs.microsoft.com/en-us/dotnet/core/packages">https://docs.microsoft.com/en-us/dotnet/core/packages</a></li>
  <li>.NET Core application deployment: <a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/">https://docs.microsoft.com/en-us/dotnet/core/deploying/</a>. Especially read the part about “Framework-dependent deployments (FDD)”</li>
  <li>Specs on runtimeconfig.json and deps.json:
<a href="https://github.com/dotnet/cli/blob/v2.1.400/Documentation/specs/runtime-configuration-file.md">https://github.com/dotnet/cli/blob/v2.1.400/Documentation/specs/runtime-configuration-file.md</a></li>
  <li>The implementation of the shared framework lookup: <a href="https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/cli/fxr/fx_muxer.cpp#L464">https://github.com/dotnet/core-setup/blob/v2.1.3/src/corehost/cli/fxr/fx_muxer.cpp#L464</a></li>
</ul>]]></content><author><name>Nate</name></author><category term="dotnet" /><category term="aspnetcore" /><summary type="html"><![CDATA[Shared frameworks have been an essential part of .NET Core since 1.0. ASP.NET Core shipped as a shared framework for the first time in 2.1. You may not have noticed if things are working smoothly, but there have been some bumps and ongoing discussion about its design. In this post, I will dive deep into the shared frameworks and talk about some common developer pitfalls.]]></summary></entry><entry><title type="html">Deep-dive into .NET Core primitives: inside a .dll file</title><link href="https://natemcmaster.com/blog/2018/07/28/runtime-vs-refs/" rel="alternate" type="text/html" title="Deep-dive into .NET Core primitives: inside a .dll file" /><published>2018-07-28T00:00:00-07:00</published><updated>2018-07-28T00:00:00-07:00</updated><id>https://natemcmaster.com/blog/2018/07/28/runtime-vs-refs</id><content type="html" xml:base="https://natemcmaster.com/blog/2018/07/28/runtime-vs-refs/"><![CDATA[<p>When I started working with C# and .NET, clicking the “Start” button in Visual Studio was magical, but also dissatisfying.
Dissatisfying – not because I want to write code in assembly – but because I didn’t know what “Start” did.
So, I started to dig. In a <a href="/blog/2017/12/21/netcore-primitives/">previous post</a>, I showed some of the important files used in a .NET Core application.
In this post, I’m going to look even closer at one particular file, <strong>the .dll</strong>.
If you’re new to .NET Core and want to peek under the hood, this is a good post for you.
If you’re already a .NET developer but wonder what actually happens with your *.dll files,
I’ll cover that, too.</p>

<p>I’m going to abandon the magic of Visual Studio and stick to command-line tools. To play with this yourself,
you’ll need the <a href="https://aka.ms/dotnet-download">.NET Core 2.1 SDK</a>. These steps were written for macOS,
but they work on Linux and Windows, too, if you adjust file paths to <code class="language-plaintext highlighter-rouge">C:\Program Files\dotnet\</code> and <code class="language-plaintext highlighter-rouge">dotnet.exe</code>.
You’ll also need to use the “ildasm” command, which is available in the Developer Command Prompt for VS 2017.
If you’re on macOS or Linux, <a href="https://www.nuget.org/packages/dotnet-ildasm/"><code class="language-plaintext highlighter-rouge">dotnet-ildasm</code></a> is a good-enough replacement.</p>

<blockquote>
  <p>See also <a href="/blog/2017/12/21/netcore-primitives/">Deep-dive into .NET Core primitives: deps.json, runtimeconfig.json, and dll’s</a>.</p>
</blockquote>

<h2 id="ldstr-hello-world">ldstr “Hello World!”</h2>

<p>C# must be compiled first before it can execute. The C# compiler (csc) turns .cs files into a .dll.
A .dll file is a <a href="https://en.wikipedia.org/wiki/Portable_Executable">portable executable</a>,
and it primarily contains something called
<a href="https://en.wikipedia.org/wiki/Common_Intermediate_Language">Common Intermediate Language</a>, or IL.</p>

<p>In C#, a simple method looks like this, and is stored in a plain text file.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The .dll contains the IL version, and is stored in a binary format.
By calling <code class="language-plaintext highlighter-rouge">ildasm Sample.dll</code> on command line, you can create a plain text representation of that binary format.
The matching IL looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World!"
  IL_0006:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ret
}
</code></pre></div></div>

<h2 id="external-api">External API</h2>

<p><a href="https://gist.github.com/natemcmaster/f0580df92ebeb0a2892e5fcda8488f40">Here is the complete IL</a>
for a “Hello World” console app. It’s only 79 lines. If you skim through the IL, you may have noticed something:
the IL does not contain the definition for <code class="language-plaintext highlighter-rouge">Console.WriteLine</code>. Instead, the IL contains this near the top:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.assembly extern System.Console
{
  .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
  .ver 4:1:1:0
}
</code></pre></div></div>

<p>This is called a <strong>reference</strong>. My assembly, Sample.dll, references another assembly named System.Console.
And to be more specific, it references System.Console, version 4.1.1.0, with a strong name public key token of
B03F5F7F11D50A3A.</p>

<p><strong>So where can I find System.Console?</strong> Trick question, sort of.</p>

<h2 id="the-compilation-reference-to-systemconsoledll">The compilation reference to System.Console.dll</h2>

<p>As discussed in more detail in <a href="/blog/2017/12/21/netcore-primitives/">Part 1</a>, the C# compiler is a console command which supports a flag
<code class="language-plaintext highlighter-rouge">-reference</code>. Visual Studio and the <code class="language-plaintext highlighter-rouge">dotnet</code> command line, through wizardry I won’t cover now, call the C# compiler
<abbr title="This is a very simplified list. The real list is a few hundred arguments">with arguments</abbr> like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/local/share/dotnet/dotnet /usr/local/share/dotnet/sdk/2.1.301/Roslyn/bincore/csc.dll \
    -reference:/Users/nmcmaster/.nuget/packages/microsoft.netcore.app/2.1.0/ref/netcoreapp2.1/System.Console.dll \
    -reference:/Users/nmcmaster/.nuget/packages/microsoft.netcore.app/2.1.0/ref/netcoreapp2.1/System.Runtime.dll \
    -out:bin/Debug/netcoreapp2.1/Sample.dll \
    Program.cs
</code></pre></div></div>

<p>The System.Console.dll in my NuGet cache is the <strong>compilation reference</strong>, which defines the System.Console assembly.
The C# compiler read this file, which is how it determined that:</p>

<ul>
  <li>the System.Console assembly is version 4.1.1.0 and has a public key token B03F5F7F11D50A3A</li>
  <li>this assembly defines a type named ‘Console’ in the ‘System’ namespace</li>
  <li>this type has a static method named ‘WriteLine’ which accepts one string argument</li>
</ul>

<p>Now, if we <code class="language-plaintext highlighter-rouge">ildasm</code> this System.Console.dll file, we’ll see something interesting. The IL for this method
looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.method public hidebysig static void  WriteLine(string 'value') cil managed
{
  // Code size       1 (0x1)
  .maxstack  8
  IL_0000:  ret
}
</code></pre></div></div>

<p>Let me translate this back to C#.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="nn">System</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">Console</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">WriteLine</span><span class="p">(</span><span class="kt">string</span> <span class="k">value</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>…hold up…how can that possibly work?</p>
</blockquote>

<p>This method is empty because the .NET Core SDK is taking advantage of an important feature of .NET: dynamic linking,
also called assembly binding. .NET Core needs to run on Windows, Linux, macOS and more.
Rather than produce a single System.Console.dll file which has to work on every possible operating system and CPU
(some which may not even exist yet), the .NET Core team creates multiple variants of <code class="language-plaintext highlighter-rouge">System.Console.dll</code>.
The one the compiler read is called the <strong>reference assembly</strong>, and its purpose is to provide the C# compiler
with the available API, but not the implementation. Think of it like a C++ header file. This assembly
has intentionally been stripped of implementation, so all methods do nothing or return null.</p>

<h2 id="the-runtime-reference-for-systemconsole">The runtime reference for System.Console</h2>

<p>When you execute a .NET Core app, a different System.Console.dll file is used. You can find its location
by running an app with this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Console</span><span class="p">).</span><span class="n">Assembly</span><span class="p">.</span><span class="n">Location</span><span class="p">);</span>
</code></pre></div></div>

<p>On my computer, this file was here:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/local/share/dotnet/shared/Microsoft.NETCore.App/2.1.1/System.Console.dll
</code></pre></div></div>

<p>This file is the runtime reference, aka the <strong>implementation assembly</strong>.</p>

<p>How did .NET Core find this file? It used some heuristics based on the <a href="/blog/2017/12/21/netcore-primitives/">deps.json file and runtimeconfig.json files</a>
that sit next to my Sample.dll file.</p>

<p>Now, if we <code class="language-plaintext highlighter-rouge">ildasm</code> the implementation version of System.Console.dll file, we’ll see that it’s actually doing
something:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.method public hidebysig static void  WriteLine(string 'value') cil managed noinlining
{
  .maxstack  8
  IL_0000:  call       class [System.Runtime.Extensions]System.IO.TextWriter System.Console::get_Out()
  IL_0005:  ldarg.0
  IL_0006:  callvirt   instance void [System.Runtime.Extensions]System.IO.TextWriter::WriteLine(string)
  IL_000b:  ret
}
</code></pre></div></div>

<h1 id="closing">Closing</h1>

<p>Assemblies are an essential primitive to understand to know how .NET Core really works. Most developers don’t really need
to know all the <em>details</em> of IL and .dlls, but it’s good to have a general understanding of why they exist and what they do.
This is only the tip of the iceberg. There are many, many more things involved in making a .dll execute
in a .NET Core app, and lots of things I would love to explain.
What happens if the compilation and runtime references are different? What’s a strong name? What’s crossgen?
Can I obfuscate IL? etc. But I’ll leave those for another post, maybe.</p>

<h1 id="more-info">More info</h1>

<ul>
  <li><a href="https://github.com/dotnet/standard/blob/429e02d34984d662a60d4bc7ab1d178a424d6b8d/docs/history/evolution-of-design-time-assemblies.md">The evolution of design-time assemblies</a>. A fascinating read on some techniques the .NET team has used over the years.</li>
  <li><a href="https://ilspy.net">ILSpy</a> - my favorite IL decompiler</li>
  <li><a href="https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies">How .NET Framework locates assemblies</a> - if you’re interested in comparing this to .NET Core</li>
  <li><a href="https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions">.NET Framework: Redirecting Assembly Versions</a> - in .NET Framework, assembly binding is much stricter about assembly versions and public key tokens that .NET Core…thanks goodness too. Hopefully, I never
have to explain this error again: “The located assembly’s manifest definition does not match the assembly reference.”</li>
</ul>]]></content><author><name>Nate</name></author><category term="dotnet" /><category term="aspnetcore" /><summary type="html"><![CDATA[When I started working with C# and .NET, clicking the “Start” button in Visual Studio was magical, but also dissatisfying. Dissatisfying – not because I want to write code in assembly – but because I didn’t know what “Start” did. So, I started to dig. In a previous post, I showed some of the important files used in a .NET Core application. In this post, I’m going to look even closer at one particular file, the .dll. If you’re new to .NET Core and want to peek under the hood, this is a good post for you. If you’re already a .NET developer but wonder what actually happens with your *.dll files, I’ll cover that, too.]]></summary></entry><entry><title type="html">.NET Core Plugins</title><link href="https://natemcmaster.com/blog/2018/07/25/netcore-plugins/" rel="alternate" type="text/html" title=".NET Core Plugins" /><published>2018-07-25T00:00:00-07:00</published><updated>2018-07-25T00:00:00-07:00</updated><id>https://natemcmaster.com/blog/2018/07/25/netcore-plugins</id><content type="html" xml:base="https://natemcmaster.com/blog/2018/07/25/netcore-plugins/"><![CDATA[<p>I recently <a href="https://nuget.org/packages/McMaster.NETCore.Plugins">published</a> a new package for .NET Core developers that want
to implement a plugin system. Dynamic assembly loading in .NET Core is difficult to get right. The API in this package
wrangles the complexity through a feature called ‘load contexts’. In this post, I’ll walk through problems that motivated the creation of this
project, and explain what the API can do. My hope is that this plugin API will let you focus more on
writing your app, and put an end to the inevitable mess of creating your own assembly loading code.</p>

<p><strong>TL;DR?</strong></p>

<ul>
  <li>the <a href="https://github.com/natemcmaster/DotNetCorePlugins">project source is visible on GitHub</a></li>
  <li>the package is on NuGet.org. <a href="https://nuget.org/packages/McMaster.NETCore.Plugins"><code class="language-plaintext highlighter-rouge">McMaster.NETCore.Plugins</code></a>, v0.1.0.</li>
  <li>a complete <a href="https://github.com/natemcmaster/DotNetCorePlugins/tree/v0.1.0/samples/aspnetcore">code sample is available here.</a></li>
</ul>

<h2 id="introducing-mcmasternetcoreplugins">Introducing McMaster.NETCore.Plugins</h2>

<p>The foundation of <code class="language-plaintext highlighter-rouge">McMaster.NETCore.Plugins</code> is <code class="language-plaintext highlighter-rouge">AssemblyLoadContext</code> (more on this below). The API in <code class="language-plaintext highlighter-rouge">McMaster.NETCore.Plugins</code> ties together the understanding of how ALC works, and how <code class="language-plaintext highlighter-rouge">dotnet.exe</code> (aka corehost) reads deps.json files and runtimeconfig.json files to find dependencies. In the end,
you should be able to use this new API with just a little bit of code.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">McMaster.NETCore.Plugins</span><span class="p">;</span>

<span class="n">PluginLoader</span> <span class="n">loader</span> <span class="p">=</span> <span class="n">PluginLoader</span><span class="p">.</span><span class="nf">CreateFromAssemblyFile</span><span class="p">(</span><span class="s">"./plugins/MyPlugin1.dll"</span><span class="p">,</span>
                        <span class="n">sharedTypes</span><span class="p">:</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ILogger</span><span class="p">)</span> <span class="p">});</span>
<span class="n">Assembly</span> <span class="n">pluginDll</span> <span class="p">=</span> <span class="n">loader</span><span class="p">.</span><span class="nf">LoadDefaultAssembly</span><span class="p">();</span>
</code></pre></div></div>

<p>Once you have an <code class="language-plaintext highlighter-rouge">Assembly</code> object, you can use reflection to initialize and run code from the plugin.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Reflection</span><span class="p">;</span>

<span class="c1">// For example, you could find and invoke a static method named Start on a type named Plugin.</span>
<span class="n">Type</span> <span class="n">pluginType</span> <span class="p">=</span> <span class="n">pluginDll</span><span class="p">.</span><span class="nf">GetTypes</span><span class="p">().</span><span class="nf">First</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">t</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">"Plugin"</span><span class="p">);</span>
<span class="n">MethodInfo</span> <span class="n">startMethod</span> <span class="p">=</span> <span class="n">pluginType</span><span class="p">.</span><span class="nf">GetMethod</span><span class="p">(</span><span class="s">"Start"</span><span class="p">);</span>
<span class="n">startMethod</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="k">new</span> <span class="kt">object</span><span class="p">[]</span> <span class="p">{</span> <span class="n">myLogger</span><span class="p">,</span> <span class="s">"arg1"</span><span class="p">,</span> <span class="s">"arg2"</span> <span class="p">});</span>
</code></pre></div></div>

<p>The plugins API provides a solution for managing common problems with assembly loading code, such as</p>

<ul>
  <li>finding dependencies of assemblies to load</li>
  <li>finding unmanaged binaries to load</li>
  <li>dealing with conflicts between different dependency versions</li>
  <li>type unification - establishing consistent type identity between plugin and host app</li>
  <li>isolation - keeping assemblies and their dependencies separated from each other and the host app</li>
</ul>

<h2 id="motivations-the-trouble-with-assemblyloadfrom">Motivations: the trouble with <code class="language-plaintext highlighter-rouge">Assembly.LoadFrom</code></h2>

<p>If you’ve ever tried to use <code class="language-plaintext highlighter-rouge">Assembly.LoadFrom</code>, you may be familiar with these issues. If you’re not, let
me give you a quick demo.</p>

<p>Let’s say you want to load a new .dll file into an app. <code class="language-plaintext highlighter-rouge">Assembly.LoadFrom</code> is a tempting choice
because it will get you part of what you want.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">pluginDll</span> <span class="p">=</span> <span class="n">Assembly</span><span class="p">.</span><span class="nf">LoadFrom</span><span class="p">(</span><span class="s">"./plugin/MyPlugin1.dll"</span><span class="p">);</span>
</code></pre></div></div>

<p>For simple plugins, it works great until…</p>

<h4 id="problem-1---locating-dependency-assemblies">Problem 1 - locating dependency assemblies</h4>

<p>Let’s say <code class="language-plaintext highlighter-rouge">MyPlugin1</code> uses JSON.NET, but the app calling Assembly.LoadFrom does not. If you try to do anything
with the <code class="language-plaintext highlighter-rouge">Assembly</code> object you get from <code class="language-plaintext highlighter-rouge">LoadFrom</code>, you’ll get</p>

<blockquote>
  <p>System.IO.FileNotFoundException: Could not load file or assembly ‘Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed’. The system cannot find the file specified.</p>
</blockquote>

<p>Even if you copy <code class="language-plaintext highlighter-rouge">Newtonsoft.Json.dll</code> into the same folder, .NET Core will not load it. Workarounds exist for this
problem, such as hooking into <a href="https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.assemblyresolve?view=netcore-2.1">assembly resolving events</a>,
but these don’t resolve the next set of issues.</p>

<h4 id="problem-2---dependency-mismatch">Problem 2 - dependency mismatch</h4>

<p>Let’s say <code class="language-plaintext highlighter-rouge">MyPlugin1</code> uses JSON.NET 11, but the app calling <code class="language-plaintext highlighter-rouge">LoadFrom</code> used JSON.NET 10. You will <em>still</em> run
into <code class="language-plaintext highlighter-rouge">System.IO.FileNotFoundException</code>.</p>

<p>You won’t always get this output when dependency versions don’t match. If the situation is reversed – MyPlugin1
depends on a <em>lower</em> version of something than the app has – you can get different errors if there were
<a href="https://github.com/dotnet/corefx/blob/v2.1.0/Documentation/coding-guidelines/breaking-changes.md">breaking changes</a>.
If you’re lucky, these surface as <code class="language-plaintext highlighter-rouge">MissingMethodException</code> or <code class="language-plaintext highlighter-rouge">TypeLoadException</code>.
If you’re unlucky, your plugin will just function in a way you don’t expect because it’s running on a different version of its dependency.</p>

<h4 id="problem-3---side-by-side-and-race-conditions">Problem 3 - side by side and race conditions</h4>

<p>If you resolved Problem 1 with some clever workarounds, you will still have issues when you need to load multiple
plugins with mixed dependencies. The first plugin to load “wins”. So, if MyPlugin1 depends on JSON.NET 1 and
MyPlugin2 depends on 11, you might get 10 or 11, but it will vary based on the order in which you called Assembly.LoadFrom.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">pluginDll1</span> <span class="p">=</span> <span class="n">Assembly</span><span class="p">.</span><span class="nf">LoadFrom</span><span class="p">(</span><span class="s">"./plugin/MyPlugin1/MyPlugin1.dll"</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">pluginDll2</span> <span class="p">=</span> <span class="n">Assembly</span><span class="p">.</span><span class="nf">LoadFrom</span><span class="p">(</span><span class="s">"./plugin/MyPlugin2/MyPlugin2.dll"</span><span class="p">);</span>
</code></pre></div></div>

<p>These problems may be resolvable if you know ahead of time all the plugins and you can merge them to use a same
version of common dependencies, but assuming this is not possible or reasonable, you can easily get into a situation
where plugins will break each other. And, you have to pick a version. Highest wins is not always right, and <code class="language-plaintext highlighter-rouge">Assembly.LoadFrom</code> doesn’t give you a way to use multiple versions of an assembly by the same name.</p>

<h4 id="problem-4---native-libraries">Problem 4 - native libraries</h4>

<p>If you need to use <code class="language-plaintext highlighter-rouge">[DllImport]</code> and <code class="language-plaintext highlighter-rouge">extern</code> to P/Invoke unmanaged code, <code class="language-plaintext highlighter-rouge">Assembly.LoadFrom</code> doesn’t work. In fact, I’m not really sure how you would do this
without AssemblyLoadContext.</p>

<h2 id="assemblyloadcontext-the-dark-horse">AssemblyLoadContext: the dark horse</h2>

<p><code class="language-plaintext highlighter-rouge">System.Runtime.Loader.AssemblyLoadContext</code>, aka ALC, provides some essential API for defining dynamic assembly loading behavior. This is one of my favorite, little-known APIs in .NET Core. This API provides:</p>

<ul>
  <li>Assembly loading in partial isolation. You can create multiple load contexts. Each context can load independent
versions of an assembly with the same name.</li>
  <li>Bring-your-own-resolution. <code class="language-plaintext highlighter-rouge">AssemblyLoadContext</code> is an abstract class with some virtual base members you can override.
This allows you to implement your own resolution for dependency look up.</li>
  <li><code class="language-plaintext highlighter-rouge">AssemblyLoadContext.LoadUnmanagedDll</code>. This is basically the only good way to load unmanaged binaries dynamically.</li>
</ul>

<p>While <code class="language-plaintext highlighter-rouge">AssemblyLoadContext</code> is a great API, it’s currently <a href="https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=netcore-2.1">lacking docs</a>.
(It’s on the <a href="https://github.com/dotnet/docs/issues/2982">TODO</a> <a href="https://github.com/dotnet/docs/issues/1711">list</a>). It’s also fairly low-level, so you have to have a certain level
of understanding to implement a load context.
By default, ALC does not provide any resolution logic. You might expect there to be <em>some</em> sort of API in .NET Core for reading .deps.json and runtime.json files, but there isn’t. This is why I called ALC a ‘dark horse’. It’s a really good API, but few know much about it.</p>

<h2 id="assemblyloadcontextbuilder-build-your-own-alc">AssemblyLoadContextBuilder: build your own ALC</h2>

<p>To make ALC easier to work with, I’ve written an API called <a href="https://github.com/natemcmaster/DotNetCorePlugins/blob/v0.1.0/src/Plugins/Loader/AssemblyLoadContextBuilder.cs"><code class="language-plaintext highlighter-rouge">McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder</code></a>.
This API creates a new
AssemblyLoadContext with resolving behavior based on information from various sources. Some of the methods available on this builder include:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SetBaseDirectory</code> - This directory is used as the starting point for loading assemblies.</li>
  <li><code class="language-plaintext highlighter-rouge">PreferLoadContextAssembly</code> / <code class="language-plaintext highlighter-rouge">PreferDefaultLoadContextAssembly</code> - specify, by assembly name, which assemblies should be resolved to a common version shared by every plugin and the app (the default load context), and which assemblies can use versions which are unique.</li>
  <li><code class="language-plaintext highlighter-rouge">AddProbingPath</code> - additional locations for finding dependencies</li>
  <li><code class="language-plaintext highlighter-rouge">AddAdditionalProbingPathFromRuntimeConfig</code> - add additional probing paths from a <code class="language-plaintext highlighter-rouge">runtimeconfig.json</code> file</li>
  <li><code class="language-plaintext highlighter-rouge">AddManagedLibrary</code> - add specific details about an assembly dependency to be loaded</li>
  <li><code class="language-plaintext highlighter-rouge">AddNativeLibrary</code> - add specific details about an unmanaged binary to be loaded</li>
  <li><code class="language-plaintext highlighter-rouge">AddDependencyContext</code> - add managed and native libraries as described in a .deps.json file</li>
  <li>Finally, <code class="language-plaintext highlighter-rouge">.Build()</code> produce a new ALC. Multiple contexts can be created with the same info.</li>
</ul>

<h2 id="pluginloader-bring-it-all-together">PluginLoader: bring it all together</h2>

<p><code class="language-plaintext highlighter-rouge">McMaster.NETCore.Plugins.PluginLoader</code> simplifies assembly loading even more by hiding most of the details about <code class="language-plaintext highlighter-rouge">AssemblyLoadContextBuilder</code> behind a smaller API. This is the default entrypoint which should be sufficient for many plugin scenarios. It uses the ALC builder and a set of some well-known conventions to construct a rich load context.</p>

<p>As mentioned above, you need to first create a loader.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PluginLoader</span> <span class="n">loader</span> <span class="p">=</span> <span class="n">PluginLoader</span><span class="p">.</span><span class="nf">CreateFromAssemblyFile</span><span class="p">(</span>
    <span class="n">assemblyFile</span><span class="p">:</span> <span class="s">"./plugins/MyPlugin1.dll"</span><span class="p">,</span>
    <span class="n">sharedTypes</span><span class="p">:</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ILogger</span><span class="p">)</span> <span class="p">});</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">sharedTypes</code> parameter is important: this is used to define types which must exchange between the plugin and the host.
These types are used to ensure consistent type identity. <a href="https://github.com/natemcmaster/DotNetCorePlugins/blob/master/docs/what-are-shared-types.md">Read more details about this here.</a></p>

<p>Once you have the loader, you can then use <code class="language-plaintext highlighter-rouge">PluginLoader.LoadDefaultAssembly()</code> or <code class="language-plaintext highlighter-rouge">LoadAssembly(AssemblyName)</code> to get
<code class="language-plaintext highlighter-rouge">System.Reflection.Assembly</code> objects. You can get from this object to executing code using a little bit of reflection.</p>

<blockquote>
  <p><strong>UPDATE Aug. 28, 2019</strong>
This following paragraph appeared in the original post, but I’ve since abandoned this config file.
It turns out no one really needed this and it was overly complicated.</p>
</blockquote>

<p><del>Furthermore, I’ve begun work to define a way to express plugin behavior through <a href="https://github.com/natemcmaster/DotNetCorePlugins/blob/v0.1.0/docs/plugin-config.md">config files</a>. While this is in its early stages, the vision for config files is that you can define plugin behavior externally from the app (if you want) so you can make decisions about how plugins interact with the host app or other plugins.</del></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PluginLoader</span> <span class="n">loader</span> <span class="p">=</span> <span class="n">PluginLoader</span><span class="p">.</span><span class="nf">CreateFromConfigFile</span><span class="p">(</span>
    <span class="n">assemblyFile</span><span class="p">:</span> <span class="s">"./plugins/config.xml"</span><span class="p">,</span>
    <span class="n">sharedTypes</span><span class="p">:</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">typeof</span><span class="p">(</span><span class="n">ILogger</span><span class="p">)</span> <span class="p">});</span>
</code></pre></div></div>

<h2 id="demo">Demo</h2>

<p>A full example of the API in action can be seen here: <a href="https://github.com/natemcmaster/DotNetCorePlugins/tree/master/samples/">https://github.com/natemcmaster/DotNetCorePlugins/tree/master/samples/</a>.</p>

<p>This demo includes a fully-working ASP.NET Core app which has two plugins loaded side-by-side. The plugins use type unification to ensure the plugin can interact with the <code class="language-plaintext highlighter-rouge">IServiceCollection</code> and <code class="language-plaintext highlighter-rouge">IApplicationBuilder</code> of the host application.</p>

<h2 id="closing">Closing</h2>

<h4 id="more-reading">More reading</h4>

<p>For more information, I recommend the following articles:</p>

<ul>
  <li><a href="https://github.com/natemcmaster/DotNetCorePlugins/blob/master/docs/what-are-shared-types.md">An Explanation of Shared Types</a> - docs in the project about type identity, type exchange, and the <code class="language-plaintext highlighter-rouge">sharedTypes</code> parameter in the PluginLoader API</li>
  <li><a href="https://docs.microsoft.com/en-us/dotnet/framework/deployment/best-practices-for-assembly-loading">.NET Framework: Best Practices for Assembly Loading</a> on docs.microsoft.com. Although it doesn’t apply to .NET Core, it’s still a good read. Some the concepts were used when creating .NET Core.</li>
  <li><a href="/blog/2017/12/21/netcore-primitives/">Deep-dive into .NET Core primitives: deps.json, runtimeconfig.json, and dll’s</a>. A post I wrote last year about some of the foundations of .NET Core apps.</li>
  <li><a href="https://github.com/dotnet/coreclr/blob/v2.1.0/Documentation/design-docs/assemblyloadcontext.md">Design doc: AssemblyLoadContext</a> - design notes from
the creators of AssemblyLoadContext</li>
</ul>

<h4 id="why-its-still-experimental">Why it’s still experimental</h4>

<p>In <code class="language-plaintext highlighter-rouge">PluginLoader</code>, I’ve done my best to imitate most of the behaviors of corehost, however, there are some gaps which I can’t cover.</p>

<ul>
  <li>Unloading - once a plugin is loaded, the files it uses are locked by the process. The only way to unload a plugin is by killing the host app. Hopefully one day, .NET Core will <a href="https://github.com/dotnet/coreclr/issues/552">implement collectible ALC’s</a>, which will enable this feature. <strong>UPDATE: Aug. 28, 2019</strong> - this was in the v0.3.0.</li>
  <li>Localization and resource assemblies - if you have locale-specific resource assemblies, they’re not automagically loaded yet. <strong>UPDATE: Aug. 28, 2019</strong> - this was fixed in the v0.2.0.</li>
  <li>Conflict resolution - I haven’t yet defined behavior yet for what to do when there are multiple sources for the same assembly. For example, what if both the shared runtime and an local copy of the same binary exist which only differ by file version? TBD.</li>
  <li>Perf - I haven’t taken time to investigate performance, yet. Before I would recommend this for production, I want to take a closer look at memory impact, CPU throughput, etc.</li>
</ul>

<p>Plus, there is more work to be done on the “plugin config file” idea, API refinements, bugs to squash, etc.</p>

<p>I would not recommend this yet for production critical apps, but I hope to get it there. The project is open source, and I’m happy to take contributions. Give it a shot let me know what you think.</p>]]></content><author><name>Nate</name></author><category term="dotnet" /><summary type="html"><![CDATA[I recently published a new package for .NET Core developers that want to implement a plugin system. Dynamic assembly loading in .NET Core is difficult to get right. The API in this package wrangles the complexity through a feature called ‘load contexts’. In this post, I’ll walk through problems that motivated the creation of this project, and explain what the API can do. My hope is that this plugin API will let you focus more on writing your app, and put an end to the inevitable mess of creating your own assembly loading code.]]></summary></entry><entry><title type="html">Configuring ASP.NET Core, webpack, and hot module replacement (hmr) for fast TypeScript development</title><link href="https://natemcmaster.com/blog/2018/07/05/aspnetcore-hmr/" rel="alternate" type="text/html" title="Configuring ASP.NET Core, webpack, and hot module replacement (hmr) for fast TypeScript development" /><published>2018-07-05T00:00:00-07:00</published><updated>2018-07-05T00:00:00-07:00</updated><id>https://natemcmaster.com/blog/2018/07/05/aspnetcore-hmr</id><content type="html" xml:base="https://natemcmaster.com/blog/2018/07/05/aspnetcore-hmr/"><![CDATA[<p>Recently, I spent a weekend banging my head against the wall as I tried to figure out how to upgrade a personal project
to webpack 4, TypeScript 2.9, and React (used to be AngularJS 1.6). I finally got it all working together – and even got
hot module replacement (hmr) working. <strong>TL;DR?</strong> Checkout the code here: <a href="https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo">https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo</a></p>

<p>The important bits:</p>

<h3 id="use-the-webpackdevmiddleware">Use the WebpackDevMiddleware</h3>

<p>This middleware in ASP.NET Core is built-in to ASP.NET Core 2.1, but you have to specifically add an option to configure HMR.
Add this to your Startup.cs file.</p>

<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">UseWebpackDevMiddleware</span><span class="p">(</span><span class="k">new</span> <span class="n">WebpackDevMiddlewareOptions</span>
<span class="p">{</span>
    <span class="n">HotModuleReplacement</span> <span class="p">=</span> <span class="k">true</span>
<span class="p">});</span>
</code></pre></div></div>

<p><a href="https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo/blob/b969c8bca2a574fb84221379bbad575093c64426/WebApplication1/Startup.cs#L25-L28">See in source</a></p>

<h3 id="use-babel-core-and-es6">Use babel-core and ES6</h3>

<p>HMR was silently failing for a while until I discovered a few knobs in awesome-typescript-loader.
After a bunch of GitHub spelunking, I discovered that I needed these magical settings in webpack.config.js.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// webpack.config.js</span>
<span class="p">{</span>
    <span class="nl">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.</span><span class="sr">tsx</span><span class="se">?</span><span class="sr">$/</span><span class="p">,</span>
    <span class="nx">include</span><span class="p">:</span> <span class="o">/</span><span class="nx">ClientApp</span><span class="o">/</span><span class="p">,</span>
    <span class="nx">loader</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span>
            <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">awesome-typescript-loader</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">options</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">useCache</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                <span class="na">useBabel</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
                <span class="na">babelOptions</span><span class="p">:</span> <span class="p">{</span>
                    <span class="na">babelrc</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
                    <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">react-hot-loader/babel</span><span class="dl">'</span><span class="p">],</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Also, you may need to update your tsconfig.json file to target ES6.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es6"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"commonjs"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"jsx"</span><span class="p">:</span><span class="w"> </span><span class="s2">"react"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo/blob/b969c8bca2a574fb84221379bbad575093c64426/WebApplication1/webpack.config.js#L45-L61">See in source</a></p>

<h3 id="react-hot-loader-4">react-hot-loader 4</h3>

<p>If you’ve used previous versions, considering upgrading to version 4. It’s usage is super simple now. Here’s a minimal React app with hmr.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">ReactDOM</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-dom</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">hot</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-hot-loader</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">App</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">SFC</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>Hello, hot reloading<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;;</span>

<span class="kd">const</span> <span class="nx">HotApp</span> <span class="o">=</span> <span class="nx">hot</span><span class="p">(</span><span class="kr">module</span><span class="p">)(</span><span class="nx">App</span><span class="p">);</span>

<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(&lt;</span><span class="nc">HotApp</span> <span class="p">/&gt;,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">));</span>
</code></pre></div></div>

<h3 id="a-few-other-goodies">A few other goodies</h3>

<p>I prefer Yarn to npm because it is faster, deterministic, and it’s not too hard to integrate Yarn with the .NET Core command line.
Here are some MSBuild targets you can add to your project to lightup Yarn integration:</p>

<ul>
  <li><a href="https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo/blob/b969c8bca2a574fb84221379bbad575093c64426/WebApplication1/Webpack.targets">Webpack.targets</a></li>
  <li><a href="https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo/blob/b969c8bca2a574fb84221379bbad575093c64426/WebApplication1/WebApplication1.csproj#L13-L16">Configuration in your .csproj file</a></li>
</ul>]]></content><author><name>Nate</name></author><category term="aspnetcore" /><category term="typescript" /><category term="webpack" /><summary type="html"><![CDATA[Recently, I spent a weekend banging my head against the wall as I tried to figure out how to upgrade a personal project to webpack 4, TypeScript 2.9, and React (used to be AngularJS 1.6). I finally got it all working together – and even got hot module replacement (hmr) working. TL;DR? Checkout the code here: https://github.com/natemcmaster/aspnetcore-webpack-hmr-demo]]></summary></entry></feed>