Skip to main content

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:

CallGives you
AddAlertCACosmosDbServicesCamera, user, device, shared-view, and Condor-event repositories
AddAlertCASQLDatabaseServicesThe 19 SQL repositories, plus geometry and location services
AddAlertCAStorageServicesBlob, Table, and Queue repositories in one call
AddCommunicationServices / AddGraphServicesEmail + SMS; Entra directory lookups
AddAlertService / AddNotificationServiceThe alert lifecycle and in-app notifications
AddAuditService / AddApiKeyValidator / AddJWTService / AddEncryptionServiceThe cross-cutting services
Gotcha: three storage backends hide behind one call

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.

Gotcha: your change isn't live until the version bumps

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.