The line builder.AddAzureStorage("storage").RunAsEmulator() in the AppHost and the line builder.AddAzureBlobServiceClient("blobs") in Kladde.Web belong together, even though they are in two projects. The first describes what exists: a storage account, locally as Azurite. The second describes how an app uses it: a BlobServiceClient with a health check and telemetry. Aspire calls both an integration, one for hosting and one for the client.

This lesson takes apart the integrations the Kladde has used since modules 3.5 and 3.6, shows what they do locally and what they do when publishing, and builds one of its own for the TED stub, so that local runs and the tests of the next lessons never ask the real TED.

Two halves of an integration

Hosting integration Client integration
Package Aspire.Hosting.… Aspire.… without Hosting
lives in Kladde.AppHost the app that uses the service
describes a resource in the model, locally as a container or emulator, when publishing as Bicep the app's client: registration, health check, telemetry, configuration
Example AddAzureStorage, AddAzureServiceBus, AddAzureSqlServer AddAzureBlobServiceClient, AddAzureServiceBusClient, AddSqlServerDbContext

The two halves are linked only by a name. WithReference(blobs) in the AppHost sets the environment variable ConnectionStrings__blobs in the app; AddAzureBlobServiceClient("blobs") in the app reads ConnectionStrings:blobs. If both are named the same, it fits. A typo shows up only at startup, with an exception saying that neither a connection string nor an address is given. Since lesson 3.5.2, the Kladde therefore keeps the names as constants in Kladde.Contracts (Messaging.Connection), which the AppHost and the apps both know.

The two halves don't depend on each other. An app can use a client integration without Aspire too, with a connection string from appsettings.json, and an AppHost can describe a resource whose app builds its client itself. The Kladde takes both halves, because together they can do more than either alone.