Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150318 stories
·
33 followers

Anatomy of an API: the small but mighty MapOpenApi()

1 Share

I’m really proud of the OpenAPI experience in Minimal APIs, especially in comparison to REST API frameworks in other ecosystems. Because we’re able to build on C#’s strong type system and .NET’s rich set of APIs for introspecting runtime behavior, we can create a really magical experience that lets us derive fairly accurate OpenAPI docs from APIs without a lot of user intervention. In the following code:

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

app.MapPost("/todos/{id}", (int id, Todo todo)
    => TypedResults.Created($"/todos/{id}", todo));

app.Run();

record Todo(int Id, string Title, bool IsCompleted, DateTime CreatedAt);

The AddOpenApi call registers services that are required for OpenAPI document generation, including a set of providers that understand how to examine the endpoints in the app and derive structured descriptions for them (see this earlier blog post I wrote). The MapOpenApi method registers an endpoint that emits the OpenAPI document in JSON format. By default, the document is served at http://localhost:{port}/openapi/v1.json, where v1 is the default document name. The document that you get is rich with metadata about the parameters and responses that this API produces.

Screenshot of OpenAPI document served from local endpoint

Today, I want to hone in on the MapOpenApi method and talk a little bit about some of the design choices wrapped up in it. It’s a small and tight API, but it’s a total workhorse. Here’s what its method signature in the framework looks like:

public static IEndpointConventionBuilder MapOpenApi(
    this IEndpointRouteBuilder endpoints,
    [StringSyntax("Route")] string pattern = "/openapi/{documentName}.json")

Let’s walk through the details of the these three lines of code.

First, why MapOpenApi instead of something like UseOpenApi? The Map verb typically refers to components that are modeled as endpoints in the ASP.NET Core ecosystem, whereas the Use verb typically refers to components that are modeled as middleware. The choice to model this as an endpoint instead of a middleware is actually pretty cool because it lets the OpenAPI document participate in all the endpoint-specific behavior that is available in ASP.NET Core’s other APIs. For example, if you want to lock down your API docs behind auth? Easy. Want to cache the document so you’re not regenerating it on every request? Also easy. Your code ends up being a chain of fluent calls to modify the behavior of the endpoint.

app.MapOpenApi()
  .RequireAuthorization()
  .WithOutputCaching()

You might’ve noticed the [StringSyntax("Route")] attribute on the pattern parameter. That’s a cute little hint to your editor that says “hey, this is a route template, maybe colorize it accordingly.” So if you’re staring at your code in VS Code or Visual Studio, you’ll get nice syntax highlighting on the route parameters. It’s one of those small touches that makes the DX a bit nicer for this API. In addition to colorization, it also opts in the parameter to a bunch of static analysis that ASP.NET Core does automatically. For example, if you provide a route pattern template that is invalid for whatever reason, you’ll get a warning during build about this and be able to rectify the situation. This is part of the “shift-left” philosophy of API design, where errors and warnings happen as code is written and built, not when it is running.

The default route pattern is sensible enough that most folks won’t need to change it, but if you want to customize it, there are plenty of options for you. The most important thing is making sure your route template includes a {documentName} parameter so the framework knows which document you’re asking for. The code below lets you serve the OpenAPI document from a different route in your service.

app.MapOpenApi("/docs/{documentName}/openapi.json");

Here’s a fun one: we added support for emitting OpenAPI documents in YAML after the initial API shipped. Rather than polluting the API surface with a new overload or a MapOpenApiYaml method (gross!), I just leaned into the file extension in the route. If you change .json to .yaml in the route template, boom, you get YAML. I’m particularly proud of this because it keeps the API surface tiny while still being expressive.

app.MapOpenApi("/docs/{documentName}/openapi.yaml");

And because you’re calling a method to register an endpoint and not some middleware, you can call MapOpenApi multiple times to register different routes. If you want to serve both YAML and JSON variants of your OpenAPI documents, you just register two different endpoints with two different extensions.

app.MapOpenApi("/docs/{documentName}/openapi.yaml");
app.MapOpenApi("/docs/{documentName}/openapi.json");

The beauty of this API is that it’s concise and expressive without being too clever. That said, it does lean pretty heavily on understanding how the route templating system works, which might trip up folks who are new to ASP.NET Core. But honestly, the terseness works out well in practice since most people are gonna be just fine with the defaults: serve a JSON document at the default route and plug it into the rest of their OpenAPI tooling.

And that’s MapOpenApi in a nutshell. It’s one of those APIs that looks deceptively simple on the surface but has a lot of thought packed into all the little details. The endpoint-based model gives you flexibility, the route templating keeps things consistent with the rest of the ecosystem, and the file extension trick for YAML support is just chef’s kiss (if I do say so myself!).

Read the whole story
alvinashcraft
54 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

How does Aspire launch the Azure Functions runtime when you call aspire run?

1 Share

Recently, someone was curious on an internal channel about how the Azure Functions integration for Aspire worked. Specifically, how the integration went about launching the Functions runtime locally when the user launches their Aspire app. I figured it’s worth immortalizing the behind-the-scenes details of this in a post since there are some fairly novel things going on here.

First things first, let’s get the basic details out of the way.

  • Azure Functions is a serverless compute platform that allows you to write code that is invoked by triggers. It supports workers written in multiple languages including C#, JavaScript, and Python. I’m calling out all these languages because at some point I’ll write a little bit more about the intersection of polyglot Functions and Aspire.
  • These workers are invoked and managed by the Functions host, a .NET-based runtime process. The host communicates with workers over a gRPC channel to facilitate interactions. This is how the isolated worker model works, which is the recommended model going forward now that the in-process model is on its way out.
  • When you run Functions locally, you launch the Functions host via the Azure Functions Core Tools by running func start. This command takes care of launching the Functions runtime for you locally.

OK, so that’s the basic machinery that is involved when you run Azure Functions locally. What happens when you wire them up into an Aspire AppHost?

var builder = DistributedApplication.CreateBuilder(args);

builder.AddAzureFunctionsProject<Projects.Functions>("my-func-app");

builder.Build().Run();

The code above registers a Functions project that’s been referenced by the AppHost. When we call aspire run, the Functions runtime will launch and set up the Functions worker defined in the project. How does this work?

The magic behind dotnet run

It takes advantage of a neat feature in the .NET SDK that allows you to override what command gets invoked when the user calls dotnet run. The SDK exposes a set of MSBuild properties, RunCommand, RunArguments, and RunWorkingDirectory, that let you customize exactly what happens during the run phase. In the Functions SDK, you’ll see some code that looks like this:

<Target Name="_FunctionsComputeRunArguments" BeforeTargets="ComputeRunArguments" DependsOnTargets="_FunctionsCheckForCoreTools">
    <!-- Windows Configuration -->
    <PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
        <RunCommand>cmd</RunCommand>
        <RunArguments>/C func start $(RunArguments)</RunArguments>
        <RunWorkingDirectory>$(OutDir)</RunWorkingDirectory>
    </PropertyGroup>

    <!-- Unix/Linux/macOS Configuration -->
    <PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
        <RunCommand>func</RunCommand>
        <RunArguments>start $(RunArguments)</RunArguments>
        <RunWorkingDirectory>$(OutDir)</RunWorkingDirectory>
    </PropertyGroup>
  </Target>

Let’s break this down. The _FunctionsComputeRunArguments target runs before ComputeRunArguments (the standard SDK target that figures out how to run your project). It overrides the default behavior to invoke the Azure Functions Core Tools (func) instead of the built .NET executable. On Windows, it uses cmd /C func start because the shell needs to resolve the func command. On Unix-like systems, it invokes func start directly. In both cases, the working directory is set to the output directory where the compiled Functions project lives.

Tying it all together with Aspire

When you call aspire run, Aspire will call dotnet run on the referenced projects. Because of the MSBuild magic above, that dotnet run invocation turns into func start against the output directory. This magic is what lets us automatically launch the Functions runtime without any explicit wiring on the user-facing side.

The beauty of this approach is that Aspire doesn’t need to know anything special about Azure Functions. It just relies on the standard dotnet run contract, and the Functions SDK handles the rest. This is a nice example of how layered extensibility in the .NET ecosystem can create seamless integrations without tightly coupling components together.

It’s important to note that this functionality only kicks in when you are launching Aspire via the CLI (aspire run) or via the VS Code extension for Aspire. Visual Studio has a separate process for launching the Functions runtime concurrently alongside the Aspire AppHost. This is because Visual Studio has its own debugging infrastructure that needs to attach to both the Functions runtime and the worker process, so it takes a more hands-on approach to orchestrating the launch sequence.

So there you have it: a small but clever use of MSBuild extensibility that makes the Aspire + Azure Functions integration feel like magic. A nice side-effect here is that it makes the experience for using Azure Functions without Aspire better too since you can just dotnet run your .NET-based Azure Functions the same way you would any other .NET project. A rising tide lifts all boats!

Read the whole story
alvinashcraft
54 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft Silently Fixes 8-Year Windows Security Flaw

1 Share

The flaw, tracked as CVE-2025-9491, allowed cybercriminals to hide malicious commands from users inspecting files through Windows' standard interface.

The post Microsoft Silently Fixes 8-Year Windows Security Flaw appeared first on TechRepublic.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Year in Search 2025: What and how we searched this year

1 Share
Learn more about Google’s Year in Search, which explores search trends from 2025.
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Global health backslide: Gates Foundation report links funding cuts to rising child deaths

1 Share
From left: Bill Gates, Dr. Bosede Afolabi and Dr. Opeyemi Akinajo at Lagos University Teaching Hospital in Lagos, Nigeria in June 2025. (Photo via Gates Foundation / Light Oriye, Nigeria)

Bill Gates and the Gates Foundation are raising the alarm over the deadly impacts of international funding cuts in global health. Slashed budgets are projected to reverse decades of progress, causing the number of children dying before their fifth birthday to rise for the first time this century. An estimated 4.8 million children are expected to die this year, an increase of 200,000 deaths compared to last year.

“That is something that we hope never to report on, but it is a sad fact. And there are many causes, but clearly one of the key causes has been significant cuts in international development assistance from a number of high-income countries,” said Mark Suzman, CEO of the Gates Foundation, in a briefing with media this week.

Suzman specifically called out the U.S., the United Kingdom, France and Germany for “making significant cuts” to their support. Internationally, funding plunged 26.9% below last year’s levels, according to the philanthropy.

The Gates Foundation today released its annual Goalkeepers Report, which tracks progress on measures including poverty, hunger, access to clean water and energy, environmental benchmarks and other metrics.

The Seattle-based foundation worked with the University of Washington’s Institute for Health Metrics and Evaluation to model the effects of reduced assistance. The researchers found that if the cuts to aid persist or worsen, an additional 12 million to 16 million children could die over the next 20 years.

The Gates Foundation marked its 25th anniversary in May 2025 with a panel, from left: Emma Tucker, Wall Street Journal’s editor-in-chief; Mark Suzman, CEO of the Gates Foundation; and Bill Gates. (Livestream screenshot)

While offering dire projections, the document aims to be a call to action for governments and philanthropists large and small.

“This report is a roadmap to progress,” Gates writes, “where smart spending meets innovation at scale.”

The billionaire Microsoft co-founder calls out some specific areas that could yield the most benefit, including primary healthcare, routine immunizations, the development of improved vaccines and new uses of data.

Modeling in the report predicts that by 2045, better vaccines for respiratory syncytial virus (RSV) and pneumonia could save 3.4 million children, while new malaria tools could save an additional 5.7 million kids. Shots of lenacapavir could successfully prevent and treat HIV.

The foundation calls attention to the life-saving benefits of vaccinations as the U.S. Secretary of Health Robert F. Kennedy Jr. continues to undercut public support of vaccines.

With the backdrop of reduced federal funding for global humanitarian causes and backpedaling on vaccinations, Gates earlier this year announced plans to give away $200 billion — including nearly all of his wealth — over the next two decades through the Gates Foundation.

The Seattle-based organization, which celebrates its 25th anniversary this year, will sunset its operations on Dec. 31, 2045. The philanthropy is the world’s largest and has already disbursed $100 billion since its founding.

“If we do more with less now — and get back to a world where there’s more resources to devote to children’s health — then in 20 years, we’ll be able to tell a different kind of story,” Gates writes in the report. “The story of how we helped more kids survive childbirth, and childhood.”

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Postman’s journey and unlocking the power of APIs

1 Share
Lessons learned building a global API platform, navigating hyper-growth, and API-powered AI agents.
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories