The Shared Spine
Four different apps — ImageAcquisition, Admin.API, CameraOperation, Notification — all need to read the same camera out of Cosmos, write the same image metadata to Table Storage, and send the same alert email. Before this package existed, each one grew its own copy of that plumbing, and the copies drifted. A bug fixed in one app stayed alive in the other three.
Alert.CA.Infrastructure is the fix: one .NET 10 class library, published as the NuGet
package AlertCAInfrastructure, that every app references instead of re-implementing. Fix
a query here, publish a new version, and all four apps inherit it on their next bump.
The one idea to hold on to
Everything in the package is reached the same way: you call one Add… extension method per
Azure backend you need, and it hands you interfaces you inject and forget. You never new
a repository, never build a CosmosClient, never touch a connection string in app code. The
extension method wires all of that into the DI container; your code asks for
ICameraStaticRepository and uses it.
Picture it as a spine with three vertebrae stacked in one direction:
Registration on the left, the things you inject in the middle, Azure on the right. Every lesson in this section is one slice through that spine — a backend, the repositories it gives you, and the one trick that makes it non-obvious.
The smallest working example
You register only what your app actually uses. A function that just reads cameras and stores images needs two lines:
using Alert.CA.Infrastructure.Extensions;
builder.Services.AddAlertCACosmosDbServices(azureConfig); // camera + user + device state
builder.Services.AddAlertCAStorageServices(azureConfig); // blob + table + queue, together
Then inject and go — no wiring, no lifetimes to manage:
public class CameraService(ICameraStaticRepository cameras)
{
public Task<CameraStaticEntity?> Get(string id) => cameras.GetByIdAsync(id);
}
Each backend has its own registration call. The full set of entry points:
| Call | Gives you |
|---|---|
AddAlertCACosmosDbServices | Camera, user, device, shared-view, and Condor-event repositories |
AddAlertCASQLDatabaseServices | The 19 SQL repositories, plus geometry and location services |
AddAlertCAStorageServices | Blob, Table, and Queue repositories in one call |
AddCommunicationServices / AddGraphServices | Email + SMS; Entra directory lookups |
AddAlertService / AddNotificationService | The alert lifecycle and in-app notifications |
AddAuditService / AddApiKeyValidator / AddJWTService / AddEncryptionService | The cross-cutting services |
AddAlertCAStorageServices registers Blob, Table and Queue repositories at once — there
is no separate AddAlertCATableServices. If you injected a table repository and got a
"no service registered" error, this is almost always why: you registered a different backend
and assumed table storage came with it. It doesn't; it comes with storage.
Why it's a package and not a project reference
Because it ships as a versioned NuGet artifact, the boundary is real: a change here isn't
live in any app until a new AlertCAInfrastructure version is published to the
Alert.CA.Common feed and that app bumps its reference. That's the price of not having four
drifting copies — and it's the thing that surprises people most, so it's worth saying plainly.
Editing a repository and running an app locally against your source works. But the deployed apps consume the published package. A schema or repository fix silently does nothing in production until the package is republished and each app updates its version.
Wrapping up
Alert.CA.Infrastructure is one NuGet package that gives every Alert.CA app the same Azure
data and service layer. You register one Add… method per backend, inject the interfaces it
provides, and let the package own the clients, connections, and credentials. The rest of this
section walks the spine one backend at a time — each with the single trick that makes it more
than a CRUD wrapper.
Start where the real-time camera state lives: Real-Time State in Cosmos.