Skip to content
Shiny.NET

Region Geofencing

NuGet package Shiny.AppDeviceBridge.RegionGeofencing

The region geofencing bridge monitors region sets with background GPS, with no OS limit on how many regions: which state or province the device is in, which delivery zone, which store it is near. It’s backed by Shiny.Geofencing.Regions. Each set tracks the one region the device is in, and entering or leaving one reaches the page as regiongeofence.change while it is open and background.js otherwise.

The sets are registered in C#. The page starts and stops monitoring and reads where the device is, but never defines what is monitored.

Android, iOS and Mac Catalyst. Everywhere else every route answers 501.

Terminal window
dotnet add package Shiny.AppDeviceBridge.RegionGeofencing

A set comes from reverse geocoding, from documents, or from your own IRegionSource:

builder.UseAppDeviceBridge(
bridge => bridge.AddRegionGeofenceBridge(cfg => cfg
// through the platform geocoder: no data to ship, but every reading needs the network
.AddGeocoderRegionSet("states", GeocoderRegionLevel.AdministrativeArea)
// documents in a Shiny.DocumentDb SQLite store (Shiny.Geofencing.Regions.DocumentDb)
.AddDocumentRegionSet<Zone>("zones", z => z.Id, z => z.Name, filter: z => z.Active) // polygons: containment
.AddDocumentRegionSet<Store>("stores", s => s.Id, s => s.Name, withinMeters: 500) // points: proximity
// anything else
.AddRegionSet("custom", sp => new MyRegionSource(sp.GetRequiredService<IMyApi>()))
),
webApp => { /* … */ }
);

Document sets need Shiny.Geofencing.Regions.DocumentDb and a SQLite document store you register, with each region type’s geometry mapped (cfg.MapSpatialProperty). It also carries US states, Canadian provinces and their cities:

builder.Services.AddDocumentStore(opts =>
{
opts.DatabaseProvider = new SqliteDatabaseProvider($"Data Source={path}");
opts.JsonSerializerOptions = GeoJsonContext.Default.Options;
opts.MapNorthAmericaData();
});
builder.Services.AddNorthAmericaSeeder(); // once, when the app first starts
bridge.AddRegionGeofenceBridge(
cfg => cfg
.AddNorthAmericanRegions() // "regions": ids like US-CO, CA-ON
.AddNorthAmericanCities(withinMeters: 25_000), // "cities": ids like US-CO-denver
o => o.RegionSerializerOptions = GeoJsonContext.Default.Options
);

Don’t also call AddRegionGeofencing — the bridge registers it, with WebAppRegionGeofenceDelegate as the delegate. To handle changes in C# as well, subclass that delegate, call the base OnRegionChanged, and pass o => o.UseDelegate<MyDelegate>().

Client C# TypeScript
IRegionGeofencesBridge AddRegionGeofencesBridgeClient() RegionGeofencesBridge
import { RegionGeofencesBridge } from "@shinyorg/appdevicebridge";
const regions = new RegionGeofencesBridge();
const { access } = await regions.requestAccess();
if (access !== "Available") return;
await regions.onChange(c => console.log(c.entered ? "entered" : "left", c.regionSet, c.regionName ?? c.regionId));
await regions.start();
const here = await regions.getCurrent(); // one entry per set, from the last GPS reading

Moving straight from one region to another in the same set raises the exit first, then the entry. start answers 409 (geofence_refused) without background location. stop forgets which regions the device was in, so the next start enters them again.

Each change and each current entry carries region: a geocoder set’s placemark, or the region object written through RegionSerializerOptions — usually the document store’s own JsonSerializerOptions. A type with no metadata there is left out (null) rather than reflected over, and so is an exit from a region the source can no longer describe.

Monitoring runs Shiny.Gps’ one listener. The GPS bridge drives that same listener, so a page that stops it through /_bridge/gps/listener stops region geofencing too.

GET/_bridge/regiongeofences/status#

Whether monitoring is running, and the region sets the app registered.

C#IRegionGeofencesBridge.GetStatusAsyncTSRegionGeofencesBridge.getStatus
Response
200 RegionGeofenceStatus
RegionGeofenceStatus · 2 fields
isStartedbooleanWhether monitoring is running. It survives an app restart.
regionSetsRegionSetInfo[]The region sets the app registered, in registration order.
POST/_bridge/regiongeofences/access#

Requests the background location permission monitoring needs.

C#IRegionGeofencesBridge.RequestAccessAsyncTSRegionGeofencesBridge.requestAccess
Response
200 RegionGeofenceAccessResult
RegionGeofenceAccessResult · 1 field
accessAccessState
POST/_bridge/regiongeofences/start#

Starts monitoring; a no-op while it runs. Fails with 409 without permission.

C#IRegionGeofencesBridge.StartAsyncTSRegionGeofencesBridge.start
Response
204 No content
Errors
409Conflict
POST/_bridge/regiongeofences/stop#

Stops monitoring and forgets which regions the device was in.

C#IRegionGeofencesBridge.StopAsyncTSRegionGeofencesBridge.stop
Response
204 No content
GET/_bridge/regiongeofences/current#

The region the device is in for each set, from the last GPS reading, without raising changes. Empty when there is no reading.

C#IRegionGeofencesBridge.GetCurrentAsyncTSRegionGeofencesBridge.getCurrent
Response
200 CurrentRegion[]
CurrentRegion · 5 fields
regionSetstringThe set that was queried.
regionIdstring | nullThe region the device is in, or null when it is outside every region of the set.
regionNamestring | nullIts display name, when the set's source has one.
distanceMetersnumberDistance to the region — 0 when the device is inside it.
regionunknown | nullThe region object, as on region.
EVENTregiongeofence.change#

The device entered or left a region, while the page is open.

C#IRegionGeofencesBridge.OnChangeAsyncTSRegionGeofencesBridge.onChange
Payload
RegionChange
regionSetstringThe set the region belongs to.
regionIdstringThe region's id.
regionNamestring | nullIts display name, when the set's source has one.
enteredbooleanTrue for an entry, false for an exit.
latitudenumberThe GPS position that produced the change.
longitudenumberThe GPS position that produced the change.
regionunknown | nullThe region object — a document, or the placemark of a geocoder set. Null when its type has no JSON metadata in the bridge's serializer options, or for an exit the source can no longer describe.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

RegionGeofenceStatusobject#
isStartedbooleanWhether monitoring is running. It survives an app restart.
regionSetsRegionSetInfo[]The region sets the app registered, in registration order.
RegionSetInfoobject#

A layer of regions the app registered in C# — states, cities, delivery zones.

namestringThe name every change in the set carries.
AccessStateenum#

Where a permission stands. Reported by every bridge that needs one; the names match Shiny's own.

"Unknown""NotSupported""NotSetup""Disabled""Restricted""Denied""Available"
CurrentRegionobject#

Where the device is now in one set.

regionSetstringThe set that was queried.
regionIdstring | nullThe region the device is in, or null when it is outside every region of the set.
regionNamestring | nullIts display name, when the set's source has one.
distanceMetersnumberDistance to the region — 0 when the device is inside it.
regionunknown | nullThe region object, as on region.
RegionChangeobject#

The device entered or left one region of one set.

regionSetstringThe set the region belongs to.
regionIdstringThe region's id.
regionNamestring | nullIts display name, when the set's source has one.
enteredbooleanTrue for an entry, false for an exit.
latitudenumberThe GPS position that produced the change.
longitudenumberThe GPS position that produced the change.
regionunknown | nullThe region object — a document, or the placemark of a geocoder set. Null when its type has no JSON metadata in the bridge's serializer options, or for an exit the source can no longer describe.

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. See Security.