<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor Frye</title>
    <description>The latest articles on DEV Community by Victor Frye (@victorfrye).</description>
    <link>https://dev.to/victorfrye</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1031280%2Fdff0a17c-1030-4360-9036-bb59a01650a8.jpeg</url>
      <title>DEV Community: Victor Frye</title>
      <link>https://dev.to/victorfrye</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victorfrye"/>
    <language>en</language>
    <item>
      <title>From Dev to DevOps</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 22 Oct 2025 16:10:28 +0000</pubDate>
      <link>https://dev.to/leading-edje/from-dev-to-devops-ib2</link>
      <guid>https://dev.to/leading-edje/from-dev-to-devops-ib2</guid>
      <description>&lt;p&gt;DevOps is more than a role; it's a culture and mindset that bridges the gap between development and operations. Any member of an IT organization or software company can embrace DevOps principles to improve collaboration, streamline processes, and enhance software delivery. Any person can carry more than one role. However, the literature for DevOps often starts with operations: system administrators, infrastructure engineers, and site reliability engineers (SREs). One of the best books on the topic, &lt;a href="https://itrevolution.com/product/the-phoenix-project/" rel="noopener noreferrer"&gt;The Phoenix Project&lt;/a&gt;, is written from the perspective of an operations manager (and I highly recommend reading it). DevOps is about operations, but it is also about development. In truth, DevOps is about the entire software lifecycle and thus any person involved in it can learn and grow into a DevOps role. One such path is from developer to a DevOps engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common guidance
&lt;/h2&gt;

&lt;p&gt;The most common guidance for learning DevOps is to start with tooling from the operations perspective with recommendations to start with Linux or containers or Kubernetes. Some may find success this way, but I find it misleading. DevOps is difficult to learn first and these technologies are complex. It also does not matter if your code is executed in a container, virtual machine, or bare-metal on a Windows server to practice DevOps. However, a well-informed DevOps engineer knows why containerization is used and why choice of operating system matters. Instead, I recommend starting with what you know and building on that. If you want to learn DevOps, start with the various roles that practice it: developers, testers, operations engineers, or project managers. Here, I am focused on the developer role because that is my background and what I know best.&lt;/p&gt;

&lt;h2&gt;
  
  
  The developer role
&lt;/h2&gt;

&lt;p&gt;A developer is responsible for writing, testing, and maintaining code that forms the basis of software applications. They work with team members in various roles to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand requirements of what to build and translate them into functional software.&lt;/li&gt;
&lt;li&gt;Write clean, efficient, and quality code that is testable and maintainable.&lt;/li&gt;
&lt;li&gt;Ensure the software is buildable, deployable, and operational in installed environments.&lt;/li&gt;
&lt;li&gt;Deliver software that meets user needs and business goals in a timely manner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One distinction is that a person may perform more than one role. For example, a developer may also be acting in the role of a manager, a designer, or a network engineer. The role of a developer is focused on developing software, but a person is often responsible for more than just writing code. Commonly, people in the developer role are also responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Troubleshooting business applications and triaging why behavior is not as expected.&lt;/li&gt;
&lt;li&gt;Understanding legacy software and how it operates critical business logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an enterprise and during a production incident, someone in the role of developer may be called to explain why insurance claims are still pending or an appointment booking failed. At the intersection of operations and development, a developer may be the first to know when a database failure or network outage is causing business disruption. In this way, developers are already acting outside of the limited scope of writing code. This is where DevOps comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DevOps shift
&lt;/h2&gt;

&lt;p&gt;DevOps is about the entire software lifecycle and the interrelationships between traditional developer and operations roles. A person in the role of both developer and DevOps engineer is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding the entire software lifecycle, from planning feature requirements and writing code to deploying and maintaining applications in production.&lt;/li&gt;
&lt;li&gt;Developing the solutions that support the software lifecycle, such as CI/CD pipelines, infrastructure in the form of code, and automating tests.&lt;/li&gt;
&lt;li&gt;Knowing the difference between code written and value delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Being a DevOps engineer may never include a direct title change. However, it may represent a growth in responsibilities commonly required for promotion. A developer who understands how to implement DevOps practices in tooling is one who can understand architecture, processes, and the business value of their application and how to drive change with teams. These are requirements that lead to senior and principal engineer roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning DevOps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakhg9myj9n9dr8ugtfi7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakhg9myj9n9dr8ugtfi7.jpg" alt="The DevOps learning path for developers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build systems
&lt;/h3&gt;

&lt;p&gt;As a developer, you are already working with various technologies used for DevOps. The first is your build system. Today, software is built often. You need to build your application locally multiple times to test changes. You may use pipelines to build your application in another environment for verifying changes in a pull request. If you want to move from developer to DevOps engineer, the first place to start is understanding how your code is built and how it is run in all the different environments.&lt;/p&gt;

&lt;p&gt;With .NET, this means understanding the differences between the .NET SDK and runtime and the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/" rel="noopener noreferrer"&gt;dotnet CLI&lt;/a&gt; used to build, run, and publish code. For JavaScript, this means understanding the differences between development servers, bundling, and how static files are served in browsers. Every language has its own build tools and is different in execution environments. For .NET, the common language runtime (CLR) is used to run code on Windows, Linux, and macOS. For JavaScript, the runtime is the browser or Node.js. Understanding how your code is built and executed is critical to automation and maintenance. When you know this, you can begin to optimize and automate the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source control concepts
&lt;/h3&gt;

&lt;p&gt;Most developers are already using source control, such as Git, to store and collaborate on code. However, it is an underappreciated tool that is critical to developers and DevOps engineers alike. Source control systems are the foundation of collaboration and change management. GitOps is a practice that uses Git repositories as the source of truth for all kinds of code, including application code, infrastructure as code, configuration files, and CI/CD pipelines. Your branching strategies and pull request processes are key aspects of how you audit and manage change. Git is the tool, but GitOps is the adoption of DevOps practices for automation of operational concerns. Turns out this developer tool is also a DevOps tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line and scripting
&lt;/h3&gt;

&lt;p&gt;The command-line can be avoided by most developers these days. IDEs and graphical interfaces often abstract away the need to use a command-line interface (CLI). However, CLIs are necessary for DevOps automation. You can know F5 runs your code in the IDE, but when authoring a pipeline you need to know the commands that do this. Sometimes it becomes a series of commands, at which point you transition from simple commands to scripting. Commonly, the recommended scripting language is Bash as it is the native shell on Linux. However, any scripting language will help you as you learn DevOps. You can learn PowerShell or Python and still accomplish much of what you need to do. The key is to learn how to automate tasks that you would otherwise do manually without your mouse. Bash, PowerShell, and Python are all cross-platform choices. Practice navigating your file system, managing installed apps, and running your build commands from the command line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous integration and delivery
&lt;/h3&gt;

&lt;p&gt;The best-known acronym in DevOps is CI/CD, which stands for continuous integration and continuous delivery (or deployment). As a developer, you may already be using CI/CD pipelines to build and test your code. It may be tied to your source control platform, such as GitHub Actions or Azure DevOps Pipelines, or GitLab CI/CD, or it may be a standalone system like Jenkins. This is likely the first tooling primarily associated with DevOps that you will start authoring as you learn the role of DevOps engineer. However, a pipeline in and of itself is not CI/CD. You can write a pipeline that copies source code to a server, but that does not give you continuous integration, delivery, or deployment. Continuous integration is improved through pipelines that compile code consistently, run tests to verify changes, or enforce quality through additional checks like linters and static code analyzers. Continuous delivery is about when your pipelines produce deployment-ready artifacts that are reusable and ready to deploy to any environment. Continuous deployment is achieved when your pipelines automatically deploy code to your environments without human intervention. A pipeline is a tool, but CI/CD is a practice and outcome. Learn pipeline tooling, but learn them with the goal of automating the steps needed for CI/CD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting and runtime environments
&lt;/h3&gt;

&lt;p&gt;As you learn pipelines and the concepts of CI/CD, you will also need to understand where your code is run. This can vary widely depending on your organization or application. You may be running on bare-metal servers, virtual machines, containers, or serverless environments. You may be running on-premises or in the cloud. You may be using a platform-as-a-service (PaaS) or infrastructure-as-a-service (IaaS). The key is to understand where your code is run, the benefits and trade-offs of each environment, and how to get your code there. Learning Kubernetes in-depth may help if your organization is using it, but it is overkill for a static website or hobby project. It also doesn't help if your organization isn't using containers. Instead, focus on learning the environment your code is run in already. What operating system is used? What cloud provider? Is there differences between the platform used in development versus production?&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as code
&lt;/h3&gt;

&lt;p&gt;As you learn where your code is run, you will also need to learn how that environment is created and configured. This is where infrastructure as code (IaC) comes in and the developer skills you already possess can shine. IaC is the practice of defining your hosting and runtime environments through code. Various languages and tools exist for this, such as Terraform, Azure Bicep, Ansible, Pulumi, and PowerShell DSC. The value in IaC is the same as traditional source code: it is versioned, readable, and traceable. If you write something to create a virtual machine and never commit it to a central repository, it is lost. However, if you write a Terraform file to create a VM and commit it to source control, you can track changes, review history, and implement CI/CD practices to validate changes and achieve infrastructure automation. As a developer, you already know how to write code. You can learn IaC and apply your existing skills to an operations domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous learning
&lt;/h2&gt;

&lt;p&gt;The journey from developer to DevOps engineer is a surprisingly natural evolution. Developers already know their application and the value it delivers. They already know how to write code and collaborate with others. They already know the software lifecycle and the pains of delivering software. Learning DevOps is about expanding their existing knowledge and skills to automate and optimize the concerns outside of developing new features. The best way to learn DevOps is not necessarily learning Linux or Kubernetes, but instead mastering the tools they are already using and expanding knowledge of the whole system. Learn your how your code is built, where it is run, and how it gets there. Automate the friction in the process. When you start there, the mindset of DevOps fits into place:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb00nvgodwsqg3nkwhmse.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb00nvgodwsqg3nkwhmse.jpg" alt="Continuous learning and applications of DevOps knowledge for developers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you understand your build system, you can optimize your code runtime and &lt;a href="https://victorfrye.com/blog/posts/multi-stage-docker-dotnet-guide" rel="noopener noreferrer"&gt;apply containerization efficiently&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;When you know source control concepts, you can apply them to infrastructure and pipelines for version control, collaboration, and traceability.&lt;/li&gt;
&lt;li&gt;When you possess command-line knowledge, you can automate tasks for test quality and CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;When you control your pipelines, you can automate for faster feedback and software delivery.&lt;/li&gt;
&lt;li&gt;When you understand your hosting environment, you can optimize for scalability and apply effective deployment strategies.&lt;/li&gt;
&lt;li&gt;When you write high quality code, you can apply the same principles to infrastructure, pipeline, and test code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevOps is not a set of tools or a team, but a fuzzier concept: a mindset and shared responsibility. The path to learning DevOps is likewise non-exact. The concepts and tooling mentioned are how I started to learn DevOps as a developer. Your path may be different, but the key is to start with what you know and use today. From there, you learn the adjacent concepts, the tooling, and the why behind it all. And then, you keep learning.&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Aspire Roadmap 2025: Code-first DevOps, polyglot, and AI</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Fri, 01 Aug 2025 12:43:33 +0000</pubDate>
      <link>https://dev.to/leading-edje/aspire-roadmap-2025-code-first-devops-polyglot-and-ai-4h8g</link>
      <guid>https://dev.to/leading-edje/aspire-roadmap-2025-code-first-devops-polyglot-and-ai-4h8g</guid>
      <description>&lt;p&gt;The Aspire team has recently published their &lt;a href="https://github.com/dotnet/aspire/discussions/10644" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt;, revealing an exciting evolution from local development orchestration to a comprehensive framework for DevOps concerns. &lt;a href="https://victorfrye.com/blog/posts/hello-aspire-breaking-down-key-features" rel="noopener noreferrer"&gt;Aspire&lt;/a&gt; launched with a code-first application model and instantaneous run experience, then expanded into deploy scenarios with publishers. This roadmap shows how it's becoming a complete code-first alternative to YAML-heavy DevOps toolchains while embracing polyglot development and AI workload orchestration.&lt;/p&gt;

&lt;p&gt;While these are aspirational goals rather than firm commitments, they provide valuable insight into Aspire's direction. Let's explore the most compelling features and why they position Aspire as a game-changing DevOps framework for .NET, polyglot, and AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code-first DevOps
&lt;/h2&gt;

&lt;p&gt;DevOps combines development (Dev) and operations (Ops) to deliver software faster and with higher quality. While DevOps is fundamentally about people and processes, the technology and tooling often involve tedious YAML configuration files for CI/CD pipelines and infrastructure management. Aspire is changing this by providing a code-first approach to local development, testing, and deployment, replacing configuration complexity with familiar programming languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local development
&lt;/h3&gt;

&lt;p&gt;Aspire already excels at code-first application modeling in C#, expressing your entire architecture—databases, services, .NET projects, and polyglot components—then spinning it up locally with &lt;code&gt;aspire run&lt;/code&gt;. No YAML configuration files, just standard .NET code that ideally mirrors your production architecture. The roadmap expands this with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved container support&lt;/strong&gt;: Shell commands, scripts, and interactive debugging inside containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-repo support&lt;/strong&gt;: Native orchestration across multiple repositories
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in runtime acquisition&lt;/strong&gt;: Automatic installation of Node.js, .NET, and other required runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aspire local development is a mature feature set already. These improvements focus on further simplifying the developer experience and tackling complex orchestration scenarios. Multi-repo support has been a long-standing pain point as many developers opt to separate components, like a frontend and backend, in separate repositories. Removing the monorepo requirement or custom cross-repo orchestration makes Aspire more accessible to many teams. You can already run polyglot applications in containers with Aspire, but continued improvements will allow more robust debugging and feedback loops with local containerized applications. The built-in runtime acquisition is both the most exciting and most daunting feature here. It may simplify the first run experience, which helps with onboarding and CI/CD pipelines and one area that I adore of Aspire. However, depending on its implementation, it could also lead to extra local machine complexity with Aspire managed runtimes versus system-wide runtimes. The local development experience is already fantastic and delivers the code-first developer experience Aspire promises. Therefore, I am optimistic that these improvements will build on that foundation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Aspire's code-first model and instant run experience create ideal conditions for integration and end-to-end testing. You can spin up your entire application stack locally, creating an instant integration test environment with minimal friction. The &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package provides this test host for xUnit and other testing frameworks and allows you to benefit from Aspire features like intelligent resource state notifications that eliminate arbitrary sleep times in tests. The roadmap adds advanced testing capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partial app host execution&lt;/strong&gt;: Run only specific components in tests to reduce overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request redirection and mocking&lt;/strong&gt;: Control traffic between components for chaos engineering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code coverage support&lt;/strong&gt;: Coverage collection and reporting for integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where local development is the mature foundation of Aspire, testing is currently a secondary benefit that often surprises users by revealing the true value of the framework. These improvements take the Aspire testing story to the next level. Aspire goes from being the startup tooling that manages your integration testing components to a chaos engineering and middleware validation powerhouse. The partial app host execution isn't limited to testing and reduces overhead in local development scenarios where certain components are not needed. In testing, this partial execution may allow each test to receive the benefit so API integrations can be isolated without starting up the frontend or further broken down to individual microservices that matter. Coupled together with request redirection and mocking of components, you could create test scenarios that simulate real-world failures between integrations and validate chaos behavior. Imagine chaos testing your application before you even deploy it from your machine with the same ease of unit testing. The code coverage support is the extra bonus reward: get code coverage metrics for your integration and chaos tests in a way that is often limited to unit tests? Yes, please! The roadmap suggests the current Aspire testing story is only in its infancy, and these improvements will make it a reason to adopt Aspire for testing alone if they materialize as envisioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Deployment bridges development and customer value delivery. Aspire's local orchestration model naturally extends to cloud deployment scenarios. Aspire has been expanding to include deployment targets and publishers, simplifying the process of getting your application into production.&lt;br&gt;
Currently, Aspire publishes artifacts like Bicep, Docker Compose, and Kubernetes manifests. You can deploy any Aspire resource the same way you would without Aspire, but with it you get seamless delivery to deployment targets like Azure Container Apps. While deployment targets are limited and opinionated, the roadmap addresses key enterprise needs that are still missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Additional deployment targets&lt;/strong&gt;: Support for Azure App Service, Azure Functions, and improved Docker/Kubernetes workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment support&lt;/strong&gt;: Define dev/stage/prod environments with specific configurations and secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipeline generation&lt;/strong&gt;: Auto-generate GitHub Actions, Azure DevOps, and GitLab pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment is an emerging focus in the Aspire story. Azure Container Apps is the first focus for deployment target and flexible as a hosting platform, but it's not flexible enough for all enterprise scenarios even within corporate environments invested in Azure. The roadmap as expected promises more common Azure deployment targets for traditional .NET workloads like Azure App Service and Azure Functions, but it is still lacking in amazing &lt;a href="https://victorfrye.com/blog/posts/reviewing-aspirejs#deployment-targets" rel="noopener noreferrer"&gt;polyglot deployment targets like Azure Static Web Apps&lt;/a&gt;. Environment support is critical for enterprise adoption as the majority of enterprises host multiple environments. DevOps practices may push us for consistency between environments, but there are always differences in configuration and secrets to isolate environments. The CI/CD pipeline generation in addition to environment support delivers on the idea of code-first DevOps: define your environments and application model in code, then generate the necessary pipelines to deploy it based on your code-first model. The overall deployment story is still evolving, but the question that will persist is whether Aspire can provide enough flexibility to meet the diverse needs of enterprises' existing applications. These features are a step in that direction. I hope the Aspire team delivers, and we see Aspire become a code-first framework for continuous delivery and deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polyglot aspirations
&lt;/h2&gt;

&lt;p&gt;Aspire is not just a .NET framework; it is a polyglot orchestration framework that allows you to model and run conjoined applications in various languages. .NET, JavaScript, Python, and more are all supported, but the only first-class experience is in .NET projects. With the app host authored in C#, the service defaults project providing .NET best practices, and NuGet client integrations for simplifying configuration in your application code, Aspire is an amazing .NET developer experience. You can &lt;a href="https://victorfrye.com/blog/posts/reviewing-aspirejs" rel="noopener noreferrer"&gt;host JavaScript&lt;/a&gt; and Python applications, but you don't get the same level of integration and tooling. The roadmap reveals the Aspire team's ambition to provide a first-class polyglot experience that extends beyond .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uniform client integrations&lt;/strong&gt;: Connection strings, configuration, and telemetry work consistently with new language support via npm (JavaScript) and pip (Python) packages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates and samples&lt;/strong&gt;: Quickstarts and documentation for C#, JavaScript, and Python&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language app host&lt;/strong&gt;: Experimental WebAssembly support for multiple runtimes in a single process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The polyglot aspirations of Aspire are focusing on JavaScript and Python support first. The uniform client integrations with npm packages for JavaScript and other languages will get us closer to parity with the .NET experience. Improved documentation and more polyglot samples will also help as figuring out how to use Aspire currently relies on developers doing the translation between C# and other languages themselves. Technically a hosting integration, but if Aspire supports the &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package in JavaScript I would be ecstatic. Documentation and packages together could elevate the polyglot experience to make Aspire stand out beyond traditional .NET developers. It may invite more developers to experiment with the .NET platform beyond Aspire as well.&lt;/p&gt;

&lt;p&gt;The cross-language app host is a fascinating item and the one I find hardest to envision myself. Will this be a way to write the app host without .NET? Will it wrap all the runtimes in a single process on your computer? What will it actually look like? The roadmap tells us it is experimental, so it may never materialize, or it may be something we start to see soon. I will be watching this closely as it starts to take shape and the value becomes clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artificial intelligence
&lt;/h2&gt;

&lt;p&gt;While AI dominates software conversations, Aspire has focused on fundamental developer experience improvements rather than AI-first features. As AI applications continue to be mainstream, Aspire is positioned to apply its orchestration strengths to AI workloads. The roadmap outlines several AI-specific features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token usage visualization&lt;/strong&gt;: Real-time token counts, latency, and evaluation metadata in the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-specific metrics&lt;/strong&gt;: Native support for generative AI telemetry, including model name, temperature, and function call traces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure AI Foundry&lt;/strong&gt;: Integration for building agent-based applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspire MCP server&lt;/strong&gt;: Optional runtime endpoint exposing the Aspire model as an MCP server for AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building AI applications is itself a nascent discipline. The Aspire team appears to be taking a measured approach to AI integration instead of branding itself another set of AI-native tools. These Aspire AI features are focused on two key areas: observability and agents. Observability is another area Aspire already excels at with the Aspire dashboard. Token usage and LLM-specific metrics visualizations in the Aspire dashboard will be a wonderful addition to the existing telemetry and observability features. It stays true to the natural value of Aspire while also extending to needs of AI local development needs.&lt;/p&gt;

&lt;p&gt;In agentic regards, Aspire works but has a lot of limitations. Existing AI integrations, like Azure OpenAI and Ollama, provide some options for local and cloud-hosted LLMs. The integration with Azure AI Foundry may extend the catalog and options for LLMs. It will be exceptionally interesting if the integration supports Azure AI Foundry Local capabilities to provide a unified catalog of models both locally and in the cloud. The Aspire MCP server likewise adds agentic capabilities to Aspire. Model Context Protocol (MCP) is becoming an industry standard for AI agents communicating, understanding, and interacting with outside systems. An Aspire MCP server could provide development tools like GitHub Copilot with deep context on your application model and all the resources Aspire manages. I am all for more intelligent development workflows. Like so many other technologies, Aspire is targeting AI trends and trying to provide its own value in the space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aspire tooling
&lt;/h2&gt;

&lt;p&gt;As Aspire evolves into a mature framework, its tooling ecosystem continues expanding beyond the core .NET SDK. The roadmap includes several improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aspire CLI&lt;/strong&gt;: Continued improvements and unified commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WinGet and Homebrew installers&lt;/strong&gt;: Standard install support for Windows and macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extension&lt;/strong&gt;: Run, debug, and orchestrate polyglot Aspire applications in VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tooling of Aspire is a meta story and so are its roadmap items. The code-first DevOps value and the polyglot aspirations, they all deliver on a core premise of Aspire: a simplified developer experience. When the tooling to setup Aspire or interact with it isn't easy, the core premise is lost. The Aspire CLI has already started this meta story with my favorite command, &lt;code&gt;aspire run&lt;/code&gt;, which provides a consistent way to run your Aspire hosted applications locally. Continued improvements to the CLI and other commands will help make it easier to adopt and utilize Aspire. The WinGet and Homebrew installers are similar in value and may simplify installing the Aspire CLI which is already more complex than it should be. Finally, the VS Code extension may help deliver on the polyglot aspirations of Aspire by making development with Aspire more accessible to the tools JavaScript and Python developers already use without relying on CLI knowledge. Sure, CLI commands mean you can do it today but installing the Aspire CLI and generating projects requires a &lt;a href="https://victorfrye.com/blog/posts/adding-aspire-cli-guide" rel="noopener noreferrer"&gt;guide of the right CLI commands&lt;/a&gt;. Overall, the meta story of these tools is to simplify using Aspire so that Aspire can simplify your developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/dotnet/aspire/discussions/10644" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt; that the Aspire team published is an exciting glimpse into a rapidly evolving framework. Nothing is a commitment, but the vision tells a story of what Aspire is developing into: a code-first DevOps framework that simplifies local development, testing, and deployment while embracing polyglot development and AI orchestration. I am incredibly excited by this roadmap as it aligns with my own dreams for Aspire. I love what it is today and recommend it to every .NET developer and some polyglot developers. If the Aspire team can deliver on half of these features, it will only continue to be a game-changer for developing distributed applications.&lt;/p&gt;

&lt;p&gt;Let me know what you think of Aspire and where it is going. Are you excited about the roadmap? Do you think Aspire can deliver on these promises? I would love to hear your thoughts and experiences with Aspire so far.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>javascript</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Reviewing Aspire.JS: Current state of Aspire for JavaScript</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Fri, 01 Aug 2025 12:42:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/reviewing-aspirejs-current-state-of-aspire-for-javascript-cpm</link>
      <guid>https://dev.to/leading-edje/reviewing-aspirejs-current-state-of-aspire-for-javascript-cpm</guid>
      <description>&lt;p&gt;Aspire is the coolest thing in software development right now. That's a statement I frequently make, but it comes from a place of genuine excitement for this nascent framework that is transforming how we can model, run, and deploy applications. Local development with Aspire is effortless regardless of the complexity of your architecture. Aspire is a part of the .NET platform, but it extends past .NET to provide polyglot orchestration for JavaScript, Python, and other languages.&lt;/p&gt;

&lt;p&gt;One reason I refer to Aspire as the coolest thing in software development is my frequent use of it in JavaScript projects. Whether it's a simple static site or a full-stack application, Aspire has become my go-to tool for local development.&lt;/p&gt;

&lt;p&gt;This post is a review of Aspire for JavaScript including current state, my personal experiences, and future aspirations. If you are a JavaScript, .NET, or polyglot developer interested in Aspire, this analysis is for you. If you are not familiar with Aspire, you may want to read a &lt;a href="https://victorfrye.com/blog/posts/hello-aspire-breaking-down-key-features" rel="noopener noreferrer"&gt;breakdown of its key features&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;Aspire in its current state is a code-first orchestration framework written in C# but enabling local development and hosting of polyglot applications. JavaScript and .NET exist in harmony with Aspire. Given the common stack of a JavaScript web frontend, a .NET web API backend, and a containerized database or other backing services, Aspire hosts the entire stack and abstracts away the different mechanisms for running and connecting each component.&lt;/p&gt;

&lt;p&gt;The above stack is the apparent assumption of Aspire for JavaScript. Aspire allows for modeling and running JavaScript backends, full-stack JavaScript applications, and scripts, but given the .NET-first nature of Aspire, you will be writing some C# code if you use Aspire. Accepting this for the app host, the orchestrator and C# model, Aspire provides two extension points of note for JavaScript development: Integration packages and deployment targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrations packages
&lt;/h3&gt;

&lt;p&gt;Integration packages are the libraries that extend Aspire to support the various projects, executables, containers, and services that make up your application. These packages are hosted as NuGet packages and can either be hosting or client integrations. Hosting integrations extend the app host of Aspire to model and run components like a JavaScript web app. Client integrations are libraries that allow you to consume the Aspire configurations and defaults to connect to the hosted components. However, client integrations are exclusive to .NET projects given they are packed as NuGet packages.&lt;/p&gt;

&lt;p&gt;This still leaves a variety of hosting integrations for JavaScript development to benefit from. The following are some of the most relevant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.NodeJs" rel="noopener noreferrer"&gt;Aspire.Hosting.NodeJs&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Node.js applications via node or npm scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bun" rel="noopener noreferrer"&gt;CommunityToolkit.Aspire.Hosting.Bun&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Bun applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.Deno" rel="noopener noreferrer"&gt;CommunityToolkit.Aspire.Hosting.Deno&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Deno applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.PostgreSQL" rel="noopener noreferrer"&gt;Aspire.Hosting.PostgreSQL&lt;/a&gt;&lt;/strong&gt;: Provides hosting for PostgreSQL database via &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;Docker Hub registry postgres images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.MongoDB" rel="noopener noreferrer"&gt;Aspire.Hosting.MongoDB&lt;/a&gt;&lt;/strong&gt;: Provides hosting for MongoDB database via &lt;a href="https://hub.docker.com/_/mongo" rel="noopener noreferrer"&gt;Docker Hub registry mongo images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Redis" rel="noopener noreferrer"&gt;Aspire.Hosting.Redis&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Redis via &lt;a href="https://hub.docker.com/_/redis" rel="noopener noreferrer"&gt;Docker Hub registry redis images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Azure.Storage" rel="noopener noreferrer"&gt;Aspire.Hosting.Azure.Storage&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Microsoft Azure cloud storage services, including Blob, Queue, Table, and Azurite emulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Testing" rel="noopener noreferrer"&gt;Aspire.Hosting.Testing&lt;/a&gt;&lt;/strong&gt;: Provides a test host for .NET unit testing frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the packages provide flexibility of hosting JavaScript apps with the Node.js, Bun, or Deno runtimes. Databases and cloud service hosting include popular JavaScript data solutions like PostgreSQL, MongoDB, Redis, and Azure Blob Storage. Together, Aspire still provides the same local development experience for JavaScript application as it does for .NET with instantaneous runs and abstractions over other configuration files. The major deficits are you must minimally write C# code for the Aspire app host and to consume the Aspire host for testing, you will need to write integration tests using .NET testing frameworks like xUnit. For true polyglot developers familiar with both JavaScript and .NET, this is a non-issue that provides all the benefits of Aspire with the flexibility of using JavaScript for what JavaScript is best at. However, for a JavaScript only developer, there are extra barriers to entry here that Aspire has yet to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment targets
&lt;/h3&gt;

&lt;p&gt;Deployment is an emerging focus in the Aspire story. Applications orchestrated with Aspire can be deployed anywhere the same way you would deploy without Aspire, however the focus here is deployment through Aspire. Aspire is expanding to include publishers and deployment targets, taking your modeled application and using it to generate artifacts like Bicep and container images. Given Aspire's origins in .NET and Microsoft solutions, the initial deployment targets are opinionated and limited. By default, the easiest deployment target is &lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/overview" rel="noopener noreferrer"&gt;Azure Container Apps&lt;/a&gt;, a serverless platform for running containerized applications. However, there is a fundamental flaw here for JavaScript developers: hosting with Azure Containers Apps assumes you need a server.&lt;/p&gt;

&lt;p&gt;JavaScript developers are accustomed to a true serverless experience, one in which the web browser is the host environment. Frameworks like Next.js allow for server-side computation, but many JavaScript frameworks and applications are designed to run entirely in the browser using a bundle of JavaScript, HTML, and CSS. This has a lot of benefits for developers, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No server management&lt;/strong&gt;: No need to manage servers or containers, just a static file host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant scaling&lt;/strong&gt;: Static files can be served from a CDN, scaling automatically with demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower costs&lt;/strong&gt;: Static file hosting is often cheaper than running containers or VMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so much more. This is antithetical to traditional .NET development and represents a fundamental difference in JavaScript versus .NET. Some JavaScript developers may opt for containerized hosting due to enterprise infrastructure or for self-managed static web servers like Nginx, but Azure already provides a first-class static web hosting solution with &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;. Azure Static Web Apps are nowhere to be found in the Aspire deployment story, which is a major gap for Aspire for JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Aspire.JS story
&lt;/h2&gt;

&lt;p&gt;To understand how Aspire fits JavaScript development currently and potentially in the future, it is helpful to understand how a developer who has adopted Aspire already uses it. I am a full-stack developer who currently favors .NET for backend development, React for frontend development, and Azure for cloud hosting. I started using Aspire for a sample .NET web API that I wanted to run on macOS and Windows, so that anyone could pull down the code and run it with minimal configuration. Aspire was perfect for this, so I started using it for all my .NET projects. This in turn led me to use Aspire to host a React frontend alongside my web API and database, which also proved to be effortless. Finally, I asked the question: &lt;em&gt;Why not use Aspire for my JavaScript only projects?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have 3 static sites that I maintain, including my personal blog, and I wanted to use Aspire for local development to provide a consistent local development experience across all my projects. It works. Every personal project, including live websites or demo applications, is locally orchestrated with Aspire by default. I also recommend it for any enterprise .NET project I work on. However, the barrier of recommendation stops at projects that do not include .NET components currently. Aspire is currently an excellent choice for .NET and polyglot projects that include .NET, but the benefits of Aspire for JavaScript only or polyglot projects without .NET are not an easy sell. The C# app host, NuGet client integrations, and lack of polyglot deployment targets that do not align are all barriers to entry for JavaScript developers.&lt;/p&gt;

&lt;p&gt;The C# app host is a non-issue for me as a .NET developer, but for any project not already using .NET, it means extra SDKs to install and a new language to learn. Admittedly, the app host is not complex C# code until you start creating your own custom components. It is the download of the .NET SDK that is the high barrier. The NuGet client integrations are less a barrier and more a missing feature to sell the value story. Finally, deployment targets are a nascent feature in a nascent framework. I started using Aspire without its deployment features due to feature immaturity. To this day, I favor Azure Container Apps or Azure Functions for .NET workloads and Azure Static Web Apps for JavaScript workloads and handle deployment separately from Aspire. Together, this means the Aspire story for non-.NET applications adds .NET as a development dependency and is missing client integrations and deployment flexibility I would expect for recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future aspirations
&lt;/h2&gt;

&lt;p&gt;Aspire recently published their &lt;a href="https://victorfrye.com/blog/posts/aspire-roadmap-2025" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt;, which includes several features that may solve the current limitations of Aspire for JavaScript. The most exciting are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot client integrations&lt;/strong&gt;: Connection strings, configuration, and telemetry work consistently via npm packages as they do with existing NuGet packages for .NET projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates and samples&lt;/strong&gt;: More documentation and quickstart examples for JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language app host&lt;/strong&gt;: An experimental WebAssembly app host that may reduce .NET friction for JavaScript developers authoring the Aspire app host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features may further make Aspire an accessible choice for JavaScript developers and provide some of the .NET exclusive benefits for JavaScript components in your applications. The npm client integration packages excite me the most as a polyglot developer because it would allow me to integrate databases and cloud services like Azure Storage into my JavaScript components with the same reduced configuration as Aspire is doing for .NET projects. This adds parity in developer experience and closes the gap for recommendation and adoption of Aspire for JavaScript development. Documentation improvements are also always welcome and ease adoption. The cross-language app host is interesting, but I am still unsure of what it may amount to or if it'll even materialize. If it does, maybe it removes the .NET SDK download as a barrier. These features are directional and not commitments but provide a hope for increased parity with Aspire .NET developer experience.&lt;/p&gt;

&lt;p&gt;The remaining gap is deployment. This is an emerging area and the .NET story itself is still evolving for deploying with Aspire. However, I am actively watching for how this matures and the targets that get first-class support. If static hosting targets like Azure Static Web Apps are added, Aspire for JavaScript becomes a much more compelling recommendation. If Aspire only provides first-class support for traditionally .NET hosting targets like Azure App Service, Azure Functions, and Azure Container Apps, then the polyglot story remains incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final assessment
&lt;/h2&gt;

&lt;p&gt;Aspire is the coolest thing in software development right now and is actively evolving. For polyglot developers familiar with .NET, Aspire is a game-changer and you should experiment with adding it yourself. However, for JavaScript development and polyglot applications without .NET, there are still barriers to entry that prevent Aspire from being a compelling recommendation. Can you do it? Absolutely. Do I use Aspire for JavaScript development? Yes. Do I recommend it for JavaScript only projects? Not yet. But maybe in the future. Maybe soon.&lt;/p&gt;

</description>
      <category>aspire</category>
      <category>dotnet</category>
      <category>javascript</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>dotnet run file.cs: The new file-based application model</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 02 Jul 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/dotnet-run-filecs-the-new-file-based-application-model-1d24</link>
      <guid>https://dev.to/leading-edje/dotnet-run-filecs-the-new-file-based-application-model-1d24</guid>
      <description>&lt;p&gt;I missed something at &lt;a href="https://victorfrye.com/blog/posts/microsoft-build-2025-wrapped" rel="noopener noreferrer"&gt;Microsoft Build 2025&lt;/a&gt;: the announcement of the new &lt;code&gt;dotnet run file.cs&lt;/code&gt; model in &lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-10-preview-4/" rel="noopener noreferrer"&gt;.NET 10 Preview 4&lt;/a&gt;. This is a new paradigm for running and writing .NET applications and if you are reading this, you might not be the target of this feature. However, you will probably meet or read C# code that is written this way.&lt;/p&gt;

&lt;p&gt;This article will explore the new feature of &lt;code&gt;dotnet run file.cs&lt;/code&gt; and the value it brings to the .NET ecosystem. Run it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Project-Based Model
&lt;/h2&gt;

&lt;p&gt;Today, if I wanted to write a simple C# console application that output "Hello, World!", I need to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the .NET SDK.&lt;/li&gt;
&lt;li&gt;Install an IDE or text editor like Visual Studio or Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Create a new .NET project using the IDE or the &lt;code&gt;dotnet new&lt;/code&gt; CLI command.&lt;/li&gt;
&lt;li&gt;Write my code in the &lt;code&gt;Program.cs&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is changing, or at least the steps. However, the output of this today is as given the command: &lt;code&gt;dotnet new console --name HelloWorld&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File actions would have been taken:
  Create: ./HelloWorld.csproj
  Create: ./Program.cs

Processing post-creation actions...
Action would have been taken automatically:
   Restore NuGet packages required by this project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is the dry run output of the &lt;code&gt;dotnet new&lt;/code&gt; command. Notice two files are created: &lt;code&gt;HelloWorld.csproj&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;. The &lt;code&gt;csproj&lt;/code&gt; file is an XML file that contains information any .NET developer is all too familiar with. The &lt;code&gt;Program.cs&lt;/code&gt; file is where I write my code. Additionally, you will quickly see &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories created and start populating as you write and publish your application. Do you know what both directories are for, even today? Do you find XML friendly to read? Microsoft asked a new question: Is this all overwhelming for someone new?&lt;/p&gt;

&lt;p&gt;The keyword above was &lt;strong&gt;new&lt;/strong&gt;. I invite you to recall your days learning to code and suppress your experienced instincts. When I do, I remember sitting in a classroom feeling like I might never understand programming and would fail. C# was my first language. We have bootcamps, universities, and online courses in excess to help new developers. That is working, but they are learning JavaScript or Python. Why? Because the onboarding experience is easier. The barrier to entry lower.&lt;/p&gt;

&lt;p&gt;What if this changed? Introducing the new &lt;code&gt;dotnet run file.cs&lt;/code&gt; paradigm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New File-Based Model
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dotnet run&lt;/code&gt; we keep discussing is the Dotnet CLI command any .NET command-line user is familiar with. However, the &lt;code&gt;file.cs&lt;/code&gt; is in reference to a new single file-based application model. That means in our steps from earlier, we change them to the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the .NET SDK.&lt;/li&gt;
&lt;li&gt;Install an IDE or text editor like Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Create a new C# file, e.g. &lt;code&gt;hello.cs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write my code in the &lt;code&gt;hello.cs&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The steps are incredibly similar but also simplified. You need the .NET SDK and a tool for writing code still, but you no longer need to understand a complex project generation process and only have one file to manage. Let's review it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;!/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="n"&gt;AssemblyName&lt;/span&gt; &lt;span class="n"&gt;VictorFrye&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HelloWorld&lt;/span&gt;

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

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

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That is it. I could link to a repository, but if you copy and paste this you get a complete .NET application you can run. There is no &lt;code&gt;csproj&lt;/code&gt; file, and &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories are not created in your working directory. And if you run the command &lt;code&gt;dotnet run hello.cs&lt;/code&gt;, you get an active Kestrel web server that responds with "Hello World!". The latter half of the code is top-level statements, a feature not so new. However, the first three lines are special.&lt;/p&gt;

&lt;p&gt;The first line is a shebang: a Unix convention that tells the system how to execute the file. In this case, it tells the system to use the &lt;code&gt;dotnet run&lt;/code&gt; command to execute the file. With this new paradigm, you must have the .NET SDK installed and Dotnet CLI available still. A shebang is not required, but it does enable running the file without explicitly calling &lt;code&gt;dotnet run&lt;/code&gt; on Unix-like systems. This is cool, but mostly just a convenience.&lt;/p&gt;

&lt;p&gt;The second and third lines are new directives. You may be using directives in your code today, such as &lt;code&gt;#if DEBUG&lt;/code&gt; or &lt;code&gt;#region Feature X&lt;/code&gt;. However, the new &lt;code&gt;#:&lt;/code&gt; directives are unique to the run file paradigm. The &lt;code&gt;.csproj&lt;/code&gt; file normally tells our .NET application critical information like SDKs, MSBuild properties, or NuGet packages to use. The run file paradigm still supports these, but instead you use a &lt;code&gt;#:sdk&lt;/code&gt; directive or &lt;code&gt;#:property&lt;/code&gt; directive. In this case, I'm using the &lt;code&gt;Microsoft.NET.Sdk.Web&lt;/code&gt; SDK to pull in ASP.NET Core features for web APIs and setting the assembly name to &lt;code&gt;VictorFrye.HelloWorld&lt;/code&gt; because I like my name. These new directives are only for the run file paradigm, and you will get warnings if you try to use them in a traditional project model.&lt;/p&gt;

&lt;p&gt;Behind the scenes, everything is still there. The project file still exists but is virtual and interpreted by the Dotnet CLI. The &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories are created, but in a temporary location that is abstracted away. The application is still built and run like any other .NET application. The difference is in the simplicity of authoring C#. However, when the project reaches maturity or someone is ready to take it to the next level, they can convert the file-based application to a traditional project-based application. All you must do is run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet project convert hello.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Value Added
&lt;/h2&gt;

&lt;p&gt;I am really excited about the &lt;code&gt;dotnet run file.cs&lt;/code&gt;. The primary users targeted are new developers. This is a win if Microsoft succeeds and more developers embrace modern .NET applications. Some might be concerned about not learning all the details of the full project-based application model, but new developers learning .NET mean a larger .NET community, new libraries, and more innovation in the ecosystem. This is a huge win for the .NET developer community.&lt;/p&gt;

&lt;p&gt;However, the value added doesn't stop there. File-based applications are also great for scripts and small utility apps. You don't need a folder structure or a csproj file. You can now write a couple C# scripts to help you maintain your existing codebase or automate tasks. This is a huge win for scripting capabilities and reducing project overhead.&lt;/p&gt;

&lt;p&gt;Another use-case is one you might have to read yourself: .NET samples. Sample applications are used by libraries to showcase how to use specific features or APIs. They are also used by conference speakers and at meetups to illustrate concepts or provide live demos of features. In this article itself, I would normally have to create a full project to demonstrate the feature, and I would link the repository so a reader could copy it exactly and reference it or run it themselves. Now, I can provide the entire sample in a code block that is easy to copy and paste. This is a huge win for documentation and sample authors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limitations So Far
&lt;/h2&gt;

&lt;p&gt;Right now, file-based applications are limited to a single file. They are also unsupported in Visual Studio, favoring Visual Studio Code as a more likely editor for targeted users. Finally, it is only in .NET 10 Preview versions at the moment. It will not be until November 2025 that we see the first general availability release of file-based applications and likely time after before we see new developers learning in this form or a C# scripting revolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluding Remarks
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dotnet run file.cs&lt;/code&gt; paradigm is a new way to write and run .NET applications. It may or may not be for you, but the goal is a more inclusive and accessible .NET ecosystem. The best outcome is more developers learning and using .NET. Maybe C# scripts take off and we see C# become the new Python. Maybe documentation and sample applications get less verbose. The future is hard to predict, but I am hopeful for a future where I see file-based C# applications in the wild.&lt;/p&gt;

</description>
      <category>dotnet</category>
    </item>
    <item>
      <title>Local Friendly .NET Aspire: Modeling your local environment</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Mon, 30 Jun 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/local-friendly-net-aspire-modeling-your-local-environment-5f0g</link>
      <guid>https://dev.to/leading-edje/local-friendly-net-aspire-modeling-your-local-environment-5f0g</guid>
      <description>&lt;p&gt;.NET Aspire is a new framework for building cloud-native and distributed applications. It brings a &lt;a href="https://victorfrye.com/blog/posts/hello-aspire-breaking-down-key-features" rel="noopener noreferrer"&gt;host of key features as I've previously discussed&lt;/a&gt;, but my two favorites are the ability to model your application stack in C# code and the instant local development environment you get after doing so.&lt;/p&gt;

&lt;p&gt;In this post, I'll explore these features through two sample applications: a classic CRUD application with containerized backing services and an AI prototype that relies on cloud services. Both showcase how Aspire solves the complexity of running distributed applications locally. You can find the code for these examples on GitHub at &lt;a href="https://github.com/victorfrye/crudcounter" rel="noopener noreferrer"&gt;victorfrye/crudcounter&lt;/a&gt; and &lt;a href="https://github.com/victorfrye/mockingmirror" rel="noopener noreferrer"&gt;victorfrye/mockingmirror&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling your application
&lt;/h2&gt;

&lt;p&gt;To start modeling our application, we need an Aspire app host project. This is a .NET project that uses the Aspire SDK to define the resources and integrations needed for our application. I cover how to add an Aspire app host project in my &lt;a href="https://victorfrye.com/blog/posts/adding-aspire-cli-guide" rel="noopener noreferrer"&gt;command line guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here I'll show you two app host projects. Two files make up the core of the Aspire app host: a &lt;code&gt;Program.cs&lt;/code&gt; file and a &lt;code&gt;.csproj&lt;/code&gt; file. These can be extended to your pleasure, but these two files are the current minimum. The &lt;code&gt;csproj&lt;/code&gt; file will provide our SDK and hosting integration package references, while the &lt;code&gt;Program.cs&lt;/code&gt; will be the C# code for modeling and serve as the entrypoint for our local application going forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRUD application
&lt;/h3&gt;

&lt;p&gt;First, the &lt;code&gt;csproj&lt;/code&gt; file for our CRUD application host looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Sdk&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.AppHost.Sdk"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;AssemblyName&amp;gt;&lt;/span&gt;VictorFrye.CrudCounter.AppHost&lt;span class="nt"&gt;&amp;lt;/AssemblyName&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RootNamespace&amp;gt;&lt;/span&gt;VictorFrye.CrudCounter.AppHost&lt;span class="nt"&gt;&amp;lt;/RootNamespace&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;UserSecretsId&amp;gt;&lt;/span&gt;2bad5002-9943-41cd-9a77-ec579ba4a680&lt;span class="nt"&gt;&amp;lt;/UserSecretsId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.AppHost"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.NodeJs"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.Redis"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.SqlServer"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ProjectReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\WebApi\WebApi.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"RestoreNpm"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"Build"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(DesignTimeBuild)' != 'true' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;PackageJsons&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\WebClient\package.json"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Install npm packages if node_modules is missing --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"Normal"&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Installing npm packages for %(PackageJsons.RelativeDir)"&lt;/span&gt;
      &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!Exists('%(PackageJsons.RootDir)%(PackageJsons.Directory)/node_modules')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"npm install"&lt;/span&gt; &lt;span class="na"&gt;WorkingDirectory=&lt;/span&gt;&lt;span class="s"&gt;"%(PackageJsons.RootDir)%(PackageJsons.Directory)"&lt;/span&gt;
      &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!Exists('%(PackageJsons.RootDir)%(PackageJsons.Directory)/node_modules')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are four callouts here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Sdk&lt;/code&gt; element specifies we are using the Aspire SDK for this project. This extends our .NET SDK for Aspire app hosting capabilities.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ProjectReference&lt;/code&gt; element includes a reference to my existing ASP.NET Core Web API project. Aspire integrates seamlessly with other .NET projects.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;PackageReference&lt;/code&gt; item group includes a mix of dependencies for hosting our various services, e.g. Node.js for the web client, Redis for caching, and SQL Server for our database.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;RestoreNpm&lt;/code&gt; target, a nasty bit of build magic that I'm using to ensure all npm packages are installed for the web client for first-time repository runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You could get away with only the &lt;code&gt;Sdk&lt;/code&gt; element, but why reinvent the wheel? Referencing existing .NET projects is commonplace in the ecosystem already. You could build your own resource models, but prebuilt packages from Microsoft are less work and at this stage we probably don't even know how. Finally, that &lt;code&gt;RestoreNpm&lt;/code&gt; target is a huge convenience for our end-goal: a single step to run our entire application.&lt;/p&gt;

&lt;p&gt;Okay, but what about the actual C# code? Let's look at the &lt;code&gt;Program.cs&lt;/code&gt; file now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sql"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/alive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddNpmApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"../WebClient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NEXT_PUBLIC_API_BASEURL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code models our entire application stack in a few lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates backing infrastructure, i.e. SQL Server and Redis cache as containers&lt;/li&gt;
&lt;li&gt;References our .NET web API project and configures it to wait for dependencies&lt;/li&gt;
&lt;li&gt;Adds our JavaScript frontend and connects it to the API&lt;/li&gt;
&lt;li&gt;Orchestrates everything to run together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Aspire, this would require multiple manual steps: starting containers, launching the API, configuring the frontend with the right API URL, and hoping everything connects correctly.&lt;/p&gt;

&lt;p&gt;With Aspire, we do it in a single step in three forms: press F5 in your IDE, run &lt;code&gt;dotnet run --project "Path/To/MyAspireApp.AppHost"&lt;/code&gt; in your terminal, or &lt;code&gt;aspire run&lt;/code&gt; with the Aspire CLI.&lt;/p&gt;

&lt;p&gt;If you chose to install the Aspire CLI and run that way, you are greeted with a beautiful output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard:
📈  https://localhost:17280/login?t=af9ee87cf516605f0639052a6731c2d0

╭──────────┬───────────────────────────┬─────────┬────────────────────────╮ 
│ Resource │ Type                      │ State   │ Endpoint(s)            │ 
├──────────┼───────────────────────────┼─────────┼────────────────────────┤ 
│ api      │ Project                   │ Running │ https://localhost:7558 │ 
│          │                           │         │ http://localhost:5556  │ 
│ cache    │ Container                 │ Running │ tcp://localhost:60771  │ 
│ client   │ Executable                │ Running │ http://localhost:60772 │ 
│ db       │ SqlServerDatabaseResource │ Running │ None                   │ 
│ sql      │ Container                 │ Running │ tcp://localhost:60773  │ 
╰──────────┴───────────────────────────┴─────────┴────────────────────────╯ 
Press CTRL-C to stop the app host and exit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regardless of using the Aspire CLI or not, you will see a dashboard in your terminal or pop out in your browser. This is the Aspire dashboard, which provides a web portal to your local environment with a resource table or graph, console logs for each, traces for requests between resources, and stateful information about each resource.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69qwww84sxfkt1uip5cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69qwww84sxfkt1uip5cb.png" alt="The dashboard of our CRUD application sample with resources for SQL database, Redis, .NET web API, and npm web client." width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, but what if not everything is local? Well let's look at the AI sample now.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI application
&lt;/h3&gt;

&lt;p&gt;First, the &lt;code&gt;csproj&lt;/code&gt; file for our AI application host looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Sdk&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.AppHost.Sdk"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;AssemblyName&amp;gt;&lt;/span&gt;VictorFrye.MockingMirror.AppHost&lt;span class="nt"&gt;&amp;lt;/AssemblyName&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RootNamespace&amp;gt;&lt;/span&gt;VictorFrye.MockingMirror.AppHost&lt;span class="nt"&gt;&amp;lt;/RootNamespace&amp;gt;&lt;/span&gt;

   &lt;span class="nt"&gt;&amp;lt;UserSecretsId&amp;gt;&lt;/span&gt;353d8321-8cea-41fd-b09b-0503c184b4c8&lt;span class="nt"&gt;&amp;lt;/UserSecretsId&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.AppHost"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.Azure.CognitiveServices"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.NodeJs"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.3.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ProjectReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\WebApi\WebApi.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"RestoreNpm"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"Build"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;" '$(DesignTimeBuild)' != 'true' "&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;PackageJsons&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\WebClient\package.json"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Install npm packages if node_modules is missing --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"Normal"&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Installing npm packages for %(PackageJsons.RelativeDir)"&lt;/span&gt;
      &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!Exists('%(PackageJsons.RootDir)%(PackageJsons.Directory)/node_modules')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"npm install"&lt;/span&gt; &lt;span class="na"&gt;WorkingDirectory=&lt;/span&gt;&lt;span class="s"&gt;"%(PackageJsons.RootDir)%(PackageJsons.Directory)"&lt;/span&gt;
      &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!Exists('%(PackageJsons.RootDir)%(PackageJsons.Directory)/node_modules')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Very similar to the CRUD app with the SDK, package references, project references, and a custom target. The main difference is the &lt;code&gt;Aspire.Hosting.Azure.CognitiveServices&lt;/code&gt; package reference, which provides the Azure AI services we will be using. And the Program.cs? This gets a bit different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oaiName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OpenAIName"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oaiResourceGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OpenAIResourceGroup"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oaiModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OpenAIModel"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;speechKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SpeechKey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;speechRegion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SpeechRegion"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsExisting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oaiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oaiResourceGroup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ChatClientSettings__DeploymentName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oaiModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SpeechClientSettings__ApiKey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speechKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SpeechClientSettings__Region"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speechRegion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/alive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddNpmApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"../WebClient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NEXT_PUBLIC_API_BASEURL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we model external cloud services that can't run locally. We use parameters to extract variables, such as API keys and Azure resource information, from local configuration stores. We use the &lt;code&gt;AsExisting&lt;/code&gt; to reference pre-provisioned Azure OpenAI resources with an existing Aspire hosting integration. For Azure AI Speech which does not have an Aspire integration, we simply pass the parameter values as environment variables. This approach connects our web API to backing Azure AI services while still allowing us the convenience of Aspire local orchestration.&lt;/p&gt;

&lt;p&gt;There's a lot to consume here, but the key takeaway is you have options. Regardless of whether your local environment is fully isolated or has external dependencies, regardless of if your external dependencies have packages for them already or not, you can start to model out your application with Aspire and we get the same benefits as that CRUD application: we can start up what is needed locally with a single step. Again, I pick &lt;code&gt;aspire run&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard:
📈  https://localhost:17244/login?t=e5f80d45a566a13a40755d0154e1d410

╭─────────────────────┬───────────────────────────┬─────────┬────────────────────────╮                
│ Resource            │ Type                      │ State   │ Endpoint(s)            │                
├─────────────────────┼───────────────────────────┼─────────┼────────────────────────┤                
│ api                 │ Project                   │ Running │ https://localhost:7034 │                
│                     │                           │         │ http://localhost:5170  │                
│ client              │ Executable                │ Running │ http://localhost:63526 │                
│ openai              │ AzureOpenAIResource       │ Running │ None                   │                
│ openai-roles        │ AzureProvisioningResource │ Running │ None                   │                
│ OpenAIModel         │ Parameter                 │ Unknown │ None                   │                
│ OpenAIName          │ Parameter                 │ Unknown │ None                   │                
│ OpenAIResourceGroup │ Parameter                 │ Unknown │ None                   │                
│ SpeechKey           │ Parameter                 │ Unknown │ None                   │                
│ SpeechRegion        │ Parameter                 │ Unknown │ None                   │                
╰─────────────────────┴───────────────────────────┴─────────┴────────────────────────╯                
Press CTRL-C to stop the app host and exit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzjhzsbmzcu2v15qtv14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzjhzsbmzcu2v15qtv14.png" alt="The dashboard of our AI application sample with resources for OpenAI, .NET web API, and npm web client." width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our dashboard still gives us a portal to interact with our local environment and states of resources. I get extra information for the Azure OpenAI service as I was able to use an existing hosting integration, despite re-using an existing service. For the Speech service, I don't have to configure anything extra for other projects and can just target the configuration for the AppHost project. My backend and frontend still benefit entirely from the Aspire dashboard and my application is up and running with the press of a button.&lt;/p&gt;

&lt;p&gt;The Aspire parameters and Azure configuration use the &lt;a href="https://victorfrye.com/blog/posts/dotnet-options-pattern" rel="noopener noreferrer"&gt;options pattern&lt;/a&gt; to bind values from configuration sources. The parameters use the &lt;code&gt;Parameters&lt;/code&gt; section of your configuration sources, such as app settings, user secrets, or environment variables, while the Azure configuration uses the &lt;code&gt;Azure&lt;/code&gt; section. For more on these specifically, you can refer to the Aspire documentation on &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/external-parameters" rel="noopener noreferrer"&gt;external parameters&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/azure/local-provisioning" rel="noopener noreferrer"&gt;Azure local provisioning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So now we have two different kinds of applications modeled in Aspire, one fully local and one with external dependencies. Both of these applications can be run with a single command, &lt;code&gt;aspire run&lt;/code&gt;, and provide a dashboard to interact with them. Cool, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters?
&lt;/h2&gt;

&lt;p&gt;This modeling approach aligns perfectly with how we think about our applications, both locally and in deployment. The Aspire app host serves as a bill of materials for our application, defining all integrations and parameters needed to run.&lt;/p&gt;

&lt;p&gt;The immediate benefit is a dramatically improved developer experience. New team members can clone the repository, run a single command (&lt;code&gt;aspire run&lt;/code&gt;), and have a functional environment without learning setup procedures for each component. When you add new services to your stack, just update the model.&lt;/p&gt;

&lt;p&gt;This pre-modeled environment also serves as an integration testing foundation. The &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package lets you run your application host in test frameworks like xUnit or MSTest, enabling tests that validate your entire stack or specific components.&lt;/p&gt;

&lt;p&gt;Beyond the inner loop, Aspire is addressing deployment scenarios by generating infrastructure as code for Bicep, Terraform, Kubernetes, and more based on your application model with publishers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The service defaults were what drew me to Aspire initially, but my interest quickly evolved into excitement about the transformative developer experience it offers. The simplicity of modeling your application and running it locally with a single command changes expectations for development inner loop.&lt;/p&gt;

&lt;p&gt;All of my projects, including pure JavaScript apps like this blog, now run with Aspire because it's become my new standard. I encourage you to try it in your projects and experience how it reshapes your workflow. This feels like the future of .NET development, and I'll continue exploring Aspire's capabilities in future posts.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Adding .NET Aspire: A command line user's guide</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Fri, 27 Jun 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/adding-net-aspire-a-command-line-users-guide-1o8</link>
      <guid>https://dev.to/leading-edje/adding-net-aspire-a-command-line-users-guide-1o8</guid>
      <description>&lt;p&gt;.NET Aspire is a new framework for building cloud-native distributed applications that simplifies local development and deployment. Aspire offers significant benefits through its application modeling and orchestration capabilities. But how do you actually add Aspire to your projects?&lt;/p&gt;

&lt;p&gt;In this guide, I'll walk through command line options to adding Aspire into both new and existing applications. Whether you're starting from scratch or enhancing an established project, the dotnet CLI provides templates to generate projects, new solutions, or common standalone files. This post focuses on the Aspire project templates and another useful tool, the aspire CLI, using command lines for those interested in checking out .NET Aspire.&lt;/p&gt;

&lt;p&gt;Let's dive into adding Aspire to your .NET projects through the command line!&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Aspire to your project
&lt;/h2&gt;

&lt;p&gt;You can do all of this in Visual Studio or the dotnet CLI, but I will be guiding with CLI commands as they are universal across all OSes and IDEs. Also, CLI is just more fun, right?&lt;/p&gt;

&lt;p&gt;With Aspire, you will have two new projects in your solution: &lt;code&gt;AppHost&lt;/code&gt; and &lt;code&gt;ServiceDefaults&lt;/code&gt;. These can have any name, but these names are the standard. The &lt;code&gt;AppHost&lt;/code&gt; project is your local environment entrypoint and where you will model your application. The &lt;code&gt;ServiceDefaults&lt;/code&gt; sets up default configurations deemed best practice for distributed applications. To add these, you can use the Aspire project templates. Using the dotnet CLI, you can run the following command options:&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Aspire templates
&lt;/h3&gt;

&lt;p&gt;First thing you'll have to do is install the Aspire project templates. This will provide the necessary scaffolding for any of the other commands below to work. You can do this by running the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Aspire.ProjectTemplates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have to append a &lt;code&gt;@X.X.X&lt;/code&gt; version and/or the &lt;code&gt;--force&lt;/code&gt; option to install a newer version of the templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Add AppHost and ServiceDefaults
&lt;/h3&gt;

&lt;p&gt;To add both the &lt;code&gt;AppHost&lt;/code&gt; and &lt;code&gt;ServiceDefaults&lt;/code&gt; projects to your existing solution, you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new aspire &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"MyAspireApp"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides a both new projects. The &lt;code&gt;--name&lt;/code&gt; option specifies the root solution name and both projects will be created as &lt;code&gt;MyAspireApp.AppHost&lt;/code&gt; and &lt;code&gt;MyAspireApp.ServiceDefaults&lt;/code&gt; in this example. The &lt;code&gt;--output&lt;/code&gt; option specifies the current directory as the output location, but you can change this to any directory you prefer such as &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Add AppHost only
&lt;/h3&gt;

&lt;p&gt;If you only want to start with the &lt;code&gt;AppHost&lt;/code&gt; project, you can run the following template option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new aspire-apphost &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"MyAspireApp.AppHost"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"AppHost"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create only the &lt;code&gt;AppHost&lt;/code&gt; project. This provides the local orchestration and entrypoint for Aspire applications. For the above, the &lt;code&gt;--name&lt;/code&gt; option now specifies the fully qualified project name, &lt;code&gt;MyAspireApp.AppHost&lt;/code&gt;, and the &lt;code&gt;--output&lt;/code&gt; option specifies a new directory called &lt;code&gt;AppHost&lt;/code&gt; to contain these specific project files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Add ServiceDefaults only
&lt;/h3&gt;

&lt;p&gt;If you want to start with just the &lt;code&gt;ServiceDefaults&lt;/code&gt; project, you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new aspire-servicedefaults &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"MyAspireApp.ServiceDefaults"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="s2"&gt;"ServiceDefaults"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create only the &lt;code&gt;ServiceDefaults&lt;/code&gt; project. This project is a shared .NET library that contains useful defaults for distributed applications, such as HTTP resiliency and health check configuration. The &lt;code&gt;--name&lt;/code&gt; option specifies the fully qualified project name, &lt;code&gt;MyAspireApp.ServiceDefaults&lt;/code&gt;, and the &lt;code&gt;--output&lt;/code&gt; option specifies a new directory called &lt;code&gt;ServiceDefaults&lt;/code&gt; to contain these specific project files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: Start a new solution with Aspire
&lt;/h3&gt;

&lt;p&gt;One final option is the Aspire starter template. This will create a new solution with both the &lt;code&gt;AppHost&lt;/code&gt; and &lt;code&gt;ServiceDefaults&lt;/code&gt; projects, plus a Blazor web frontend and web API backend service. You can run the following command to create a new solution with all of these projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new aspire-starter &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"MyAspireApp"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting solution will contain at least four projects in a new solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MyAspireApp.AppHost&lt;/code&gt;: The main entry point for the application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MyAspireApp.ServiceDefaults&lt;/code&gt;: A library of default services and configurations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MyAspireApp.Web&lt;/code&gt;: A Blazor web frontend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MyAspireApp.ApiService&lt;/code&gt;: An ASP.NET web API backend service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing the Aspire CLI
&lt;/h2&gt;

&lt;p&gt;Okay, now that we have the Aspire projects set up, there's one more thing we might want: the aspire CLI. The aspire CLI is a preview tool that simplifies application startup. To install it, you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; aspire.cli &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing the CLI, you can run the &lt;code&gt;aspire --help&lt;/code&gt; command to see usage and available options. Effectively, this CLI is a wrapper around the &lt;code&gt;dotnet run&lt;/code&gt; command that removes the need to specify the &lt;code&gt;--project&lt;/code&gt; option. It will automatically find the &lt;code&gt;AppHost&lt;/code&gt; project in your solution and run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;At this point, you should have the Aspire projects you want in your solution and the aspire CLI installed. You can now start modeling your application in the &lt;code&gt;AppHost&lt;/code&gt; project and customize the &lt;code&gt;ServiceDefaults&lt;/code&gt; project to fit your needs.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>.NET Options Pattern: Binding settings and user secrets</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 25 Jun 2025 12:41:56 +0000</pubDate>
      <link>https://dev.to/leading-edje/net-options-pattern-binding-settings-and-user-secrets-3ck5</link>
      <guid>https://dev.to/leading-edje/net-options-pattern-binding-settings-and-user-secrets-3ck5</guid>
      <description>&lt;p&gt;Configuration management is a crucial aspect of building applications. Whether locally or in the cloud, you need a way to manage settings and secrets without hardcoding values everywhere. In .NET, the options pattern is a powerful out-of-the-box approach to binding configuration values from just about any source, including JSON files, environment variables, user secrets, and even cloud configuration providers.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore the .NET options pattern, how to set it up in your application, and some key features for flexibility in your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Options Pattern?
&lt;/h2&gt;

&lt;p&gt;The options pattern is a .NET design pattern that provides a way to bind configuration values to strongly typed classes. This allows us to take advantage of object-oriented programming principles, such as type safety and separation of concerns, while also extracting values from various configuration stores. In .NET, the options pattern is implemented through the &lt;code&gt;Microsoft.Extensions.Options&lt;/code&gt; and &lt;code&gt;Microsoft.Extensions.Configuration&lt;/code&gt; libraries, already included in frameworks like ASP.NET Core, and the base &lt;code&gt;IOptions&amp;lt;TOptions&amp;gt;&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;I consider the options pattern table stakes for any .NET application as it cleanly separates application code from configuration. Local development can use JSON files and user secrets, while production can use remote configuration stores like Azure App Configuration and Key Vault, all without changing your code. You can even mix and match local and remote sources for dev/test integrated local development. This flexibility significantly improves developer productivity and application maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Options Pattern
&lt;/h2&gt;

&lt;p&gt;To get started with the options pattern, you need 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A settings class that represents the configuration values you want to bind.&lt;/li&gt;
&lt;li&gt;A configuration source, such as your &lt;code&gt;appsettings.json&lt;/code&gt; file or user secrets.&lt;/li&gt;
&lt;li&gt;A binding and registration of the settings class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this example, we'll be binding a simple settings class for configuration values related to calling a downstream API. You can find the complete code for this example in the &lt;a href="https://github.com/victorfrye/hellooptions" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Settings Class
&lt;/h3&gt;

&lt;p&gt;When defining your settings classes, you will want to split apart your settings based on their consumption. This allows us to separate the concerns of different parts of our application configuration and later will make it easier to consume using dependency injection. In this case, we have only one settings class named &lt;code&gt;PlaceholderApiSettings&lt;/code&gt; that contains a base URL, an API key, and an optional version number:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaceholderApiSettings&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SectionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PlaceholderApiSettings&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;BaseUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ApiKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice that we also have a constant for the section name. Since we might have multiple settings classes, it is a good idea to define sections in our configuration to avoid overlapping settings. Additionally, we are using the &lt;code&gt;Required&lt;/code&gt; attribute and &lt;code&gt;required&lt;/code&gt; keyword to denote we expect these properties to always be set, whether via constructor or binding. These are all optional elements.&lt;/p&gt;

&lt;p&gt;The requirements for a settings class are simple: we need field names that express our expected configuration sources to also use.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Configuration Sources
&lt;/h3&gt;

&lt;p&gt;The options pattern binds configuration values from any source supported by &lt;code&gt;Microsoft.Extensions.Configuration&lt;/code&gt;. That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your &lt;code&gt;appsettings.json&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;.NET Secrets Manager (aka user secrets)&lt;/li&gt;
&lt;li&gt;Azure App Configuration&lt;/li&gt;
&lt;li&gt;Azure Key Vault&lt;/li&gt;
&lt;li&gt;And more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our example, we will use the &lt;code&gt;appsettings.json&lt;/code&gt; file and user secrets. Recalling our section name in the settings class, we can define a section and two field values in our &lt;code&gt;appsettings.json&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;settings...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PlaceholderApiSettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BaseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jsonplaceholder.typicode.com/todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can nest our settings under our section name or nest that under further prefixes for any settings we may want to group together. We are creating a fully qualified path to each setting field for any configuration source we bind equivalent to what we are expecting, &lt;code&gt;PlaceholderApiSettings:BaseUrl&lt;/code&gt; for example. This value might differ by environment, but as long as we have one value set per environment by any configuration source available in that environment, we can bind it to our settings class.&lt;/p&gt;

&lt;p&gt;Sometimes we have sensitive information that we don't want to commit in source code. Here, we can use the .NET Secrets Manager to store and still bind sensitive values like API keys. To do this, we can use the &lt;code&gt;dotnet user-secrets&lt;/code&gt; command group in the dotnet CLI. If you haven't already initialized user secrets for your project, you can do so with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet user-secrets init &lt;span class="nt"&gt;--project&lt;/span&gt; &lt;span class="s2"&gt;"Path/To/Your/Project.csproj"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will add a &lt;code&gt;UserSecretsId&lt;/code&gt; property to your project file, which uniquely identifies your project. This ID allows you to store secrets safely outside of your source code.&lt;/p&gt;

&lt;p&gt;From here, we can now add our API key to the secrets file with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet user-secrets &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"PlaceholderApiSettings:ApiKey"&lt;/span&gt; &lt;span class="s2"&gt;"SuperSecretApiKey123"&lt;/span&gt; &lt;span class="nt"&gt;--project&lt;/span&gt; &lt;span class="s2"&gt;"Path/To/Your/Project.csproj"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create or update the &lt;code&gt;secrets.json&lt;/code&gt; file with the key-value pair for our API key. The resulting file will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PlaceholderApiSettings:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SuperSecretApiKey123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have two configuration sources with the various values set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binding the Options
&lt;/h3&gt;

&lt;p&gt;The final step towards setting up the options pattern is binding and registration of our settings class. This is where the &lt;code&gt;Microsoft.Extensions.Options&lt;/code&gt; library comes into play. In our &lt;code&gt;Program.cs&lt;/code&gt; entry point, we need to add the options, configure the binding source, and, optionally, validate the settings to ensure they meet our expectations. To do this, we can use the &lt;code&gt;AddOptions&lt;/code&gt;, &lt;code&gt;BindConfiguration&lt;/code&gt;, and &lt;code&gt;ValidateDataAnnotations&lt;/code&gt; method chain on the &lt;code&gt;IServiceCollection&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlaceholderApiSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BindConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PlaceholderApiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SectionName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateDataAnnotations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Registration of other services and configuration...&lt;/span&gt;

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

&lt;span class="c1"&gt;// The rest of your application setup...&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AddOptions&amp;lt;T&amp;gt;&lt;/code&gt; method gets the options builder for our settings class, allowing us to configure it. The &lt;code&gt;BindConfiguration&lt;/code&gt; method takes the section name from our settings class, telling the options builder the configuration section path to bind to (in this case, &lt;code&gt;PlaceholderApiSettings:*&lt;/code&gt;). Finally, the optional &lt;code&gt;ValidateDataAnnotations&lt;/code&gt; method tells the options builder to use the data annotations we defined to validate the values during binding. You can use other validation methods, such as &lt;code&gt;ValidateOnStart&lt;/code&gt; or &lt;code&gt;Validate&lt;/code&gt; to perform validation at different times or with custom logic. The key is we have a strongly typed settings class and a section path that aligns with the keys in our various configuration sources.&lt;/p&gt;

&lt;p&gt;After this setup, we now have multiple new classes registered in our dependency injection container that we can use to access our settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming the Options
&lt;/h2&gt;

&lt;p&gt;We have defined our settings class, configured our sources, and bound our options. Now we need to consume them in our application. The options pattern has already three interfaces implemented that we can use to access our settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt;: A singleton instance bound with the settings values at start up.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IOptionsSnapshot&amp;lt;T&amp;gt;&lt;/code&gt;: A scoped instance that can be used to access settings in transient or per-request services and can be configured to reload on changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IOptionsMonitor&amp;lt;T&amp;gt;&lt;/code&gt;: A singleton instance that can be used to access settings singleton scenarios and also supports change notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instances of all three interfaces are pre-configured in our dependency injection container by the &lt;code&gt;AddOptions&lt;/code&gt; method chain and can be used in any service through constructor injection. The base use case is the &lt;code&gt;IOptions&lt;/code&gt; interface, though the later two support advanced scenarios like refreshing settings during runtime. In our example, we will use the &lt;code&gt;IOptions&amp;lt;PlaceholderApiSettings&amp;gt;&lt;/code&gt; to access our settings in client class for our placeholder API.&lt;/p&gt;

&lt;p&gt;Using dependency injection, we can retrieve our settings now like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaceholderClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlaceholderApiSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;PlaceholderApiSettings&lt;/span&gt; &lt;span class="n"&gt;_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Omitted methods for brevity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Through constructor injection of the options interface of our choice, we can access the bound settings class and all its members through the &lt;code&gt;Value&lt;/code&gt; property. Now, we can use the &lt;code&gt;_settings&lt;/code&gt; field to access any of the properties we defined include the base URL from our JSON source and API key from our user secrets source in this &lt;code&gt;PlaceholderClient&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The .NET options pattern is a simple, unified way to manage and consume configuration values in our applications. By defining custom settings classes, we get the benefits of type safety and the separation of concerns. We are able to consume values from multiple configuration sources like our application settings or user secrets locally and remote configuration stores like Azure App Configuration or Key Vault in the cloud. Finally, by binding our settings to configuration paths, we can easily access them through dependency injection and reap the benefits of inversion of control in our application. All together, this design pattern offers a flexible foundation for managing configuration values in .NET applications.&lt;/p&gt;

&lt;p&gt;If you want to explore more about the options pattern, you can find additional resources and extensions in the official &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/options" rel="noopener noreferrer"&gt;Microsoft Learn documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
    </item>
    <item>
      <title>Microsoft Build 2025 Wrapped</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Thu, 22 May 2025 16:10:55 +0000</pubDate>
      <link>https://dev.to/leading-edje/microsoft-build-2025-wrapped-385c</link>
      <guid>https://dev.to/leading-edje/microsoft-build-2025-wrapped-385c</guid>
      <description>&lt;p&gt;Microsoft Build 2025 has come and gone, and it has been a whirlwind of announcements, a buzzword storm of AI, and some hidden gems that you might have missed. Let's attempt to wrap up the highlights according to myself, your friendly neighborhood developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Everywhere
&lt;/h2&gt;

&lt;p&gt;Microsoft Build 2025 was all about artificial intelligence, with nearly every session including the words "AI", "MCP", or "Copilot". Whether we like it or not, AI is the current trend and Microsoft is all in. The company is integrating AI into nearly every product, from Azure to VS Code, and from Windows to .NET Aspire! That's right, even my favorite new .NET framework is getting the AI treatment. Not only were the sessions all about AI, but the keynote and announcements also had a heavy focus on intelligence and Copilot additions up and down the technology stack. Some of these need their own blog posts, but here are some of the highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot Coding Agent&lt;/strong&gt;: A new feature that allows developers to assign tasks to Copilot in the GitHub platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot for .NET Aspire&lt;/strong&gt;: The Aspire dashboard now includes Copilot integration, allowing developers to chat with Copilot with Aspire context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NLWeb&lt;/strong&gt;: A new protocol built on top of MCP that allows for natural language to be a first-class citizen in web development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure AI Foundry&lt;/strong&gt;: New enhancements to Azure AI Foundry, including Foundry Local in preview—which brings models to your local machine—and Foundry Agent Service going to general availability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to focus on the first two items here as they are the two I am closest to and the most excited about.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Copilot Coding Agent
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bogxi6w8r0wh3e0bazi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bogxi6w8r0wh3e0bazi.jpg" alt="GitHub Copilot Coding Agent hero." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Copilot continues to evolve with the introduction of the new Coding Agent. This allows developers to assign development tasks to Copilot in GitHub. From there, Copilot takes over the developer inner loop and handles writing code, running tests, and drafting a pull request to integrate the changes. You can watch the PR draft in real-time to see the changes Copilot pushes and collaborate through comments to suggest changes. It even allows control over the MCP servers used in the development process. This sounds like the automated software engineer pitch, but instead it is being pitched for the tedious work developers often need to do, e.g. upgrading your .NET 8 project to .NET 10 this fall. It is in preview now, but requires a GitHub Copilot Pro+ or Enterprise license currently. Neither of which I have as they are expensive at $390 a year. I might need to review my training budget...&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Copilot for .NET Aspire
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1ysyd52xjq1elp4rtv3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1ysyd52xjq1elp4rtv3.jpg" alt="GitHub Copilot for .NET Aspire hero. Additional text reads: " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeah that's right, &lt;a href="https://dev.to/leading-edje/hello-net-aspire-breaking-down-the-key-features-io7"&gt;.NET Aspire&lt;/a&gt; is getting the Copilot treatment too! When launching the app from Visual Studio or Visual Studio Code, you will now see the familiar GitHub Copilot icon in the top right corner of the Aspire dashboard. Using it to test my own website, I asked it get traces and structured logs and it was able to identify that no telemetry exists but my web client application was in the running state. That's correct as I haven't configured any telemetry with the Next.js frontend just yet... I have been meaning to get to that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1f939l9b3iv0f6k1owo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1f939l9b3iv0f6k1owo.png" alt="A screenshot of the .NET Aspire dashboard with GitHub Copilot. Copilot shows a question asked for " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Immediately, I am both impressed and annoyed. Will I use Copilot here? Probably, given work where I can use GitHub Copilot professionally and we are utilizing .NET Aspire. However it does not work when running from the dotnet CLI nor the aspire CLI. For me, this is a big miss even if it is a technical limitation as I am a command-line enthusiast and my workflows start in the terminal. Secondly, the AI-ification of .NET Aspire means more people may be turned off by the product due to AI fatigue in the industry. But for those fatigued and who want nothing to do with GitHub Copilot, you can disable it, thankfully. You can set the &lt;code&gt;ASPIRE_DASHBOARD_AI_DISABLED&lt;/code&gt; environment variable to &lt;code&gt;true&lt;/code&gt; in the app host &lt;code&gt;launchSettings.json&lt;/code&gt; file to hide all Copilot UI elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://json.schemastore.org/launchsettings.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"commandName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dotnetRunMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"launchBrowser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"applicationUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:17168;http://localhost:15027"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environmentVariables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;variables&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ASPIRE_DASHBOARD_AI_DISABLED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Disable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GitHub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Copilot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Aspire&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Open Source Commitments
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6mf91q4aoaqkonnean6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6mf91q4aoaqkonnean6.png" alt="A screenshot of wsl CLI in Windows Terminal after the help argument was provided." width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microsoft continues to be a major contributor to open source and announced a couple major projects moving from closed-source to the open on GitHub. The first is a long-time coming project, the Windows Subsystem for Linux (WSL). I first used WSL to port a Java stack to Windows. That stack was a nightmare to run on Windows due to a team optimizing for macOS workflows but we wanted to enable new developers to use standard Windows dev machines and stop requiring expensive macOS hardware for a cross-platform native toolchain like Java. Today, WSL is a major part of the Windows developer experience. And now, Microsoft is open-sourcing WSL to allow the community to contribute and innovate on the &lt;a href="https://github.com/microsoft/wsl" rel="noopener noreferrer"&gt;project on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another major project moving to open source is the GitHub Copilot Chat extension for Visual Studio Code. As more and more IDEs and text editors are adding AI features, the Copilot Chat extension is being moved to open source &lt;strong&gt;AND&lt;/strong&gt; the code being integrated into the Visual Studio Code core codebase. This means the main AI UI experience for Visual Studio Code will become a first-class component of VS Code. Personally, I am excited about this as pushes the AI developer experience towards transparency and competing with other juggernauts like Cursor and Windsurf. This is also another blurred line between the GitHub org and the developer division at Microsoft. It is a small step, but a step in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Command Line Editor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ajkcyal9vnj0g7r4lhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ajkcyal9vnj0g7r4lhj.png" alt="A screenshot of Edit running in Windows Terminal. The README for the project is opened along with the About dialog in the foreground showing the name " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A small footnote in the book of news and not mentioned in the keynote is my favorite announcement from Build: &lt;strong&gt;Edit&lt;/strong&gt;. It is a command line text editor, similar to Neovim or Emacs, that pays homage to the classic MS-DOS editor with modern inspiration from VS Code. Edit allows for a modeless command-line interface, meaning you do not have to switch between command and edit modes like in Neovim. This makes it far easier to use for new developers or those unfamiliar with command-line workflows. I have already dropped it into my inner loop in favor of Neovim. There are a couple kinks to work out, but I see a ton of potential and community input to the app. It is already available to install via WinGet and launched with binaries for Windows and Linux. The source code is &lt;a href="https://github.com/microsoft/edit" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;, and issues and pull requests are already open for various features including macOS support and additional localization support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Microsoft Build 2025 was a whirlwind and a lot to digest. There was a ton of AI innovation, some open source announcements, and cool new tools like Edit released. Like every year, where any of these new technologies go is up to community adoption and may change over time. It is also hard to catch everything, so I recommend you check out the &lt;a href="https://news.microsoft.com/build-2025-book-of-news/" rel="noopener noreferrer"&gt;Book of News&lt;/a&gt; for a full list of announcements from Build. I look forward to seeing which of these technologies resonate with others. Until then, happy coding!&lt;/p&gt;

</description>
      <category>msbuild</category>
      <category>azure</category>
      <category>dotnet</category>
      <category>ai</category>
    </item>
    <item>
      <title>Multi-Stage Docker Builds: A .NET developer's guide</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Mon, 12 May 2025 17:34:02 +0000</pubDate>
      <link>https://dev.to/leading-edje/multi-stage-docker-builds-a-net-developers-guide-521k</link>
      <guid>https://dev.to/leading-edje/multi-stage-docker-builds-a-net-developers-guide-521k</guid>
      <description>&lt;p&gt;My Docker skills were getting rusty. My day-to-day work has shifted away from containerized workloads and more towards modernizing legacy systems or architecting serverless solutions. Somehow, I've also never drafted my own Dockerfile from scratch. Docker and containers are culturally synonymous, and both are core cloud native technologies that any modern developer should be familiar with. So, I decided to write my own multi-stage build for a .NET web API for fun. This post will explore the results and guide you based on my learnings.&lt;/p&gt;

&lt;p&gt;For this post, I will be using .NET 10 in preview. The application is a simple web API with a single endpoint that returns "Hello, .NET!". You can find the code for this &lt;a href="https://github.com/victorfrye/hellodotnet" rel="noopener noreferrer"&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a multi-stage build?
&lt;/h2&gt;

&lt;p&gt;A traditional Dockerfile might follow the same pattern as a pipeline: You install your tooling, check out your source code, and build your artifacts. It might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /source&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/WebApi/WebApi.csproj src/WebApi/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; test/Tests/Tests.csproj test/Tests/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; VictorFrye.HelloDotnet.slnx ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-restore&lt;/span&gt; 

&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish ./src/WebApi/WebApi.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /app

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $APP_UID&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "VictorFrye.HelloDotnet.WebApi.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problems with this approach are multifold. Firstly, the image is large. It is over &lt;strong&gt;2 GB&lt;/strong&gt; as it contains all the build artifacts, including the .NET SDK and all the source code. The massive means you take a performance hit as the size corresponds to longer build and deploy times and is more expensive for storage and network transfers. Additionally, this is a security risk, as it exposes your source code which may contain intellectual property or sensitive information. We can optimize this all by using a multi-stage build.&lt;/p&gt;

&lt;p&gt;Notice our first Dockerfile includes exactly one &lt;code&gt;FROM&lt;/code&gt; statement. This means we reused the same base image for our build and runtime. In a multi-stage build, we use multiple &lt;code&gt;FROM&lt;/code&gt; statements to separate our stages. This results in distinct images for our build and for production runtime. The build image will utilize the full .NET SDK and all of the source code. The runtime image will only include the ASP.NET runtimes and the artifacts we need to run our application. This results in a smaller production image, faster to build and deploy and with a reduced attack surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the multi-stage Dockerfile
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is decide on our base images. For production, I know this is an ASP.NET web API and want to keep it slim. We do not want the SDK included and only need that ASP.NET runtime and its dependencies for. As for our Linux flavor, Alpine is my go-to choice as it's stripped down to the essentials and security minded. Keep in mind that Alpine is not always the best choice for every application. Thus, I will be using the &lt;code&gt;mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine&lt;/code&gt; image for our production &lt;strong&gt;base&lt;/strong&gt; image. For our &lt;strong&gt;build&lt;/strong&gt; image, we want to align architecture to production but need the full .NET SDK. This means we will use the &lt;code&gt;mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine&lt;/code&gt; image. For deciding on yours, I recommend browsing the &lt;a href="https://mcr.microsoft.com/" rel="noopener noreferrer"&gt;Microsoft Artifact Registry&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; com.github.owner="victorfrye"&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; com.github.repo="hellodotnet"&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $APP_UID&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;AS&lt;/code&gt; keyword and the names we have assigned. This is how we can reference our stages later. In the production &lt;strong&gt;base&lt;/strong&gt;, we can set additional production configurations including the USER or add labels. Now that we have our base images, we can start to compile our application.&lt;/p&gt;

&lt;p&gt;For our &lt;strong&gt;build&lt;/strong&gt;, we first need to install our dependencies and run a &lt;code&gt;dotnet restore&lt;/code&gt;. To do this, we need to copy our solution and project files into the build image. Remember Docker uses layers and caching to optimize images so small steps in our build create efficiencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/WebApi/WebApi.csproj src/WebApi/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; test/Tests/Tests.csproj test/Tests/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; VictorFrye.HelloDotnet.slnx ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to copy the rest of our source code into the &lt;strong&gt;build&lt;/strong&gt; image and build our binaries for release. We also want to explicitly ensure we are not repeating the previous steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-restore&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we are wrapping up our initial build stage. Our builder still needs to run our tests and publish, but like a pipeline we can separate these into their own stages. This is good practice as it allows Docker to fail fast and create a logical separation of concerns. Our next stage will be our &lt;strong&gt;test&lt;/strong&gt; stage and use the build stage as its base. It will execute our tests without rebuilding and fail the image builder if they do not pass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can move on to our final builder stage: &lt;strong&gt;publish&lt;/strong&gt;. This stage will use the &lt;strong&gt;test&lt;/strong&gt; stage including the previous build and test steps.  We need to reference the test stage to ensure the full chain of events is executed. The goal of publishing is to output the compiled binaries and dependencies to a directory. They are the artifacts our application needs to run in production. We want to be explicit about our output directory as we will use it in our final image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish ./src/WebApi/WebApi.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are almost done! We have our entire builder and within it the artifacts we want. Our builder now ensures a repeatable process and environment for portable consistency. The last step is to copy our artifacts to our production base image as our &lt;strong&gt;final&lt;/strong&gt; stage. We will be using that &lt;strong&gt;base&lt;/strong&gt; stage we defined at the beginning and copying the artifacts from the &lt;strong&gt;publish&lt;/strong&gt; stage. We will then be setting our application entry point to the compiled DLL artifact for our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /out .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "VictorFrye.HelloDotnet.WebApi.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The final result
&lt;/h2&gt;

&lt;p&gt;Putting all our stages together, we still have a single Dockerfile. My result looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; com.github.owner="victorfrye"&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; com.github.repo="hellodotnet"&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $APP_UID&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /source&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/WebApi/WebApi.csproj src/WebApi/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; test/Tests/Tests.csproj test/Tests/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; VictorFrye.HelloDotnet.slnx ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-restore&lt;/span&gt; 

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish ./src/WebApi/WebApi.csproj &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-build&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /out

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /out .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "VictorFrye.HelloDotnet.WebApi.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This includes multiple stages but benefits from a smaller production image size of &lt;strong&gt;167.5 MB&lt;/strong&gt;, down from our initial &lt;strong&gt;2 GB&lt;/strong&gt;! That is over a 80% reduction in size and includes none of the source code or build artifacts. It also benefits from faster build times as changes to various stages only require rebuilding the subsequent stages. My favorite part is how similar the structure is to a non-containerized pipeline or GitHub Actions workflow. You move from setup to build to test to publish to production-ready artifacts.&lt;/p&gt;

&lt;p&gt;Writing this Dockerfile was a fun exercise and preparation for me in working heavily with Docker again. Hopefully, it also helps you understand the mental process, structure, and benefits of a multi-stage build.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dotnet</category>
      <category>cloudnative</category>
      <category>devops</category>
    </item>
    <item>
      <title>Hello .NET Aspire: Breaking down the key features</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 07 May 2025 19:59:22 +0000</pubDate>
      <link>https://dev.to/leading-edje/hello-net-aspire-breaking-down-the-key-features-io7</link>
      <guid>https://dev.to/leading-edje/hello-net-aspire-breaking-down-the-key-features-io7</guid>
      <description>&lt;p&gt;.NET Aspire is the latest framework from Microsoft in the .NET ecosystem, adding to ASP.NET, Blazor, Entity Framework, MAUI, etc. Released in 2023, it was designed specifically for cloud-native and distributed applications and acts as an orchestrator for the entire application stack. It is opinionated, meaning it provides a set of conventions and best practices for how to build applications. If you adopt these opinions, Aspire makes the developer experience much smoother and more productive. Some of these key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application modeling&lt;/strong&gt;: Aspire allows you to model your application in C# code instead of using YAML or other configuration languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local development&lt;/strong&gt;: It provides a seamless local development experience, allowing you to start and stop your entire application with a single command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opinionated service defaults&lt;/strong&gt;: It provides a set of default configurations and settings for common cloud-native concerns, which can be extended or overridden as needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client integrations&lt;/strong&gt;: Aspire includes client libraries and SDKs for common cloud-native services, making it easier to integrate your application with dependencies locally and in the cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated testing&lt;/strong&gt;: Aspire can be used with existing test frameworks to spin up and tear down your entire application stack for integration or functional tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I will provide some background on Aspire and overview of its key features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brief history so far
&lt;/h2&gt;

&lt;p&gt;At the initial launch of Aspire, the communication was obtuse about the purpose of this new framework. It was described as "an opinionated, cloud ready stack for building observable, production ready, distributed applications." This was a bit vague and left many developers including myself wondering what exactly it offered. However, as others including myself started to play with it, we quickly realized it was an exciting new way to manage the complexity of building modern applications.&lt;/p&gt;

&lt;p&gt;Since then, the Aspire team has been quickly evolving the framework and adding new features based on community reception. This has led to features like the &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package for automated testing and the upcoming Aspire CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support and updates
&lt;/h2&gt;

&lt;p&gt;Aspire is unique in that most of the features are not used in production but instead are used during the local development process and help prepare for production. Additionally, the Aspire team is releasing new features and updates at a rapid pace as a new framework. Because of this, Aspire and the team at Microsoft are shipping updates to Aspire multiple times a year and &lt;em&gt;only the latest version of Aspire is currently supported&lt;/em&gt;. This is a culture shock for many .NET developers familiar with the LTS/STS yearly releases of .NET but as we explore what Aspire is and how it works, you will see this is a good thing and introduces minimal risk to your production applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features of Aspire
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Application modeling
&lt;/h3&gt;

&lt;p&gt;Aspire provides a set of abstractions and patterns for modeling your application in C# code. This significantly differs from alternatives like Docker Compose where YAML is used to define your model. The C# code-first approach feels more natural for .NET developers and allows for a similar experience to how you write other application configuration features for startup or dependency injection, but now modeling your external and distributed dependencies.&lt;/p&gt;

&lt;p&gt;Using a new &lt;code&gt;AppHost&lt;/code&gt; project in your solution, you can model the makeup of your distributed application. Below is a sample &lt;code&gt;Program.cs&lt;/code&gt; that includes a ASP.NET Web API, SQL Server database, and Redis cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sql"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app host project also includes a &lt;code&gt;.csproj&lt;/code&gt; file includes a reference to the Aspire SDK and hosting integrations unique to Aspire. A sample &lt;code&gt;.csproj&lt;/code&gt; file for the above app host project might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Sdk&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.AppHost.Sdk"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.2.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;UserSecretsId&amp;gt;&lt;/span&gt;{{SomeGuid}}&lt;span class="nt"&gt;&amp;lt;/UserSecretsId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ProjectReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\WebApi.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.AppHost"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.2.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.Redis"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.2.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.SqlServer"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.2.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two key files make up the heart of Aspire's core feature: the app host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local development
&lt;/h3&gt;

&lt;p&gt;Aspire provides a local development experience that is similar to how you would run your application in the cloud. This includes support for containerized services and executable workloads traditionally outside the .NET ecosystem. For example, you can run a PostgreSQL database, Java backend, and JavaScript frontend by starting the Aspire app host project with or without any other C# code in your solution. You get a dashboard for visualizing your application stack and a unified way to start and stop the entire distributed system.&lt;/p&gt;

&lt;p&gt;With our app host project, we have a new entry point for our local application that can start up entire application stack. With the press of F5 in Visual Studio or running &lt;code&gt;dotnet run&lt;/code&gt; against our app host project, all resources defined in the application model will start up and be available for use. For our sample app, this includes the ASP.NET Web API, a SQL Server database, and Redis for caching. The app host project also provides a dashboard for visualizing your application stack and managing the lifecycle of your services.&lt;/p&gt;

&lt;p&gt;Our dashboard will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk432hqsdto3y8wfhuwc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk432hqsdto3y8wfhuwc6.png" alt="A screenshot of the .NET Aspire sample app dashboard. The resource table is displayed along with the cache, database, and api resource entries." width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This dashboard provides a visual representation of our application stack resources, console logs, and telemetry data for each of the services. It becomes the central UI hub for exploring our local app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opinionated service defaults
&lt;/h3&gt;

&lt;p&gt;Aspire provides a set of default configurations and settings for common cloud-native concerns including instrumentation, monitoring, and service discovery. These defaults are defined once in a new project and then applied to other projects in the solution. You can extend, override, or opt-out of these defaults as needed, but the goal is to provide a set of best practices that you can follow to get started quickly.&lt;/p&gt;

&lt;p&gt;The service defaults are a second new project type in your solution. This shared project, commonly named &lt;code&gt;ServiceDefaults&lt;/code&gt;, includes a &lt;code&gt;.csproj&lt;/code&gt; file and a single &lt;code&gt;Extensions.cs&lt;/code&gt; file. The project is then referenced by your application projects. The &lt;code&gt;Extensions.cs&lt;/code&gt; file includes default configurations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenTelemetry&lt;/strong&gt;: Adds OpenTelemetry SDK services including logger, metrics, and tracing. Additionally, it configures the OpenTelemetry Protocol (OTLP) exporter for sending data to a collector.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Health Checks&lt;/strong&gt;: Adds two default health check endpoints: &lt;code&gt;/health&lt;/code&gt; and &lt;code&gt;/alive&lt;/code&gt;. The former includes predefined checks from hosting integrations, while the latter simply responds if the application is running.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;: Turns on service discovery by default with services for dependency injection and in the http client builder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Http Client&lt;/strong&gt;: Adds resiliency defaults using Polly for all &lt;code&gt;HttpClient&lt;/code&gt; instances.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These defaults are exported as two extension methods &lt;code&gt;AddServiceDefaults&lt;/code&gt; and &lt;code&gt;MapDefaultEndpoints&lt;/code&gt; that can be called while building your .NET application as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServiceDefaults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ... existing service configuration&lt;/span&gt;

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

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client integrations
&lt;/h3&gt;

&lt;p&gt;Aspire provides a set of client libraries and SDKs for common cloud-native services, such as SQL Server, Redis, Azure Service Bus, and OpenAI. These libraries are designed to seamlessly integrate your application with dependent services locally and post-deployment. This allows you to focus on writing your application code without worrying about the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;Some of the client integrations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Azure Service Bus&lt;/li&gt;
&lt;li&gt;Azure Blob Storage&lt;/li&gt;
&lt;li&gt;Azure OpenAI&lt;/li&gt;
&lt;li&gt;Ollama&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These client integrations are added to your application project via NuGet packages. For example, to add the Entity Framework SQL Server integration you might run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated testing
&lt;/h3&gt;

&lt;p&gt;As an orchestrator for your local environment, Aspire can spin up and tear down your entire application stack for automated testing. Using the &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package and your existing test framework, you can write integration or functional tests in C# that run against your actualized application in the same environment as you develop in. This allows you to test your application in more realistic scenarios and catch issues earlier in the local developer process instead of waiting for a deployment to a staging or production environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Aspire is an exciting new addition to the .NET ecosystem. It is actively evolving and has a lot of potential to simplify the developer experience locally and in the path to production. If you have not already, I highly encourage you to try adding it to your existing projects or start your next with it. If you are interested in learning more, follow along as I explore the framework in more detail in future posts. I will be diving into specific features such as the application host, extending service defaults, and integration testing. Or, if you cannot wait, check out the &lt;a href="https://learn.microsoft.com/en-us/dotnet/aspire/" rel="noopener noreferrer"&gt;Aspire on Microsoft Learn&lt;/a&gt; for official documentation.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>developertools</category>
      <category>cloudnative</category>
    </item>
  </channel>
</rss>
