Google's Places API (New) and Routes API are good APIs. Calling them from .NET by hand is plumbing: JSON bodies, a mandatory field mask header on every request, SCREAMING_SNAKE_CASE enums, durations like "1234s", and status codes to decode.

AarxMaps does that plumbing for you. Version 1.0 is out on NuGet, MIT licensed. Here's one real flow: find a place, then drive there.

Install

dotnet add package AarxMaps.Places dotnet add package AarxMaps.Routes 

You need .NET 10 and a Google Cloud API key with both APIs enabled (and billing on).

Register

dotnet user-secrets init dotnet user-secrets set "AarxMaps:ApiKey" "YOUR_API_KEY" 
builder.Services.AddAarxMaps(o => builder.Configuration.GetSection("AarxMaps").Bind(o)); builder.Services.AddAarxMapsPlaces(); builder.Services.AddAarxMapsRoutes(); 

Config is validated at startup. Forget AddAarxMaps and the error names the missing call. Then inject IPlacesService and IRoutesService.

Find a place

var searchRequest = new TextRequestBuilder() .WithTextQuery("Space Needle, Seattle") .WithPageSize(1) .Build(); var search = await places.Search.SearchByTextAsync(searchRequest, ct); if (search.Places.Count == 0) return; var place = search.Places[0]; 

Builders validate as you go: a blank query or an out-of-range page size throws MapsInvalidRequestException before anything is sent.

Get there

Waypoints take an address, a coordinate or a place ID, so the search result plugs straight in:

var origin = new WaypointBuilder() .FromLocation(new LatLng(47.6097, -122.3422)) .Build(); var destination = new WaypointBuilder() .FromPlaceId(place.PlaceId) .Build(); var request = new RouteRequestBuilder() .From(origin) .To(destination) .WithTravelMode(TravelMode.Drive) .Build(); var result = await routes.Directions.GetRoutesAsync(request, ct: ct); var route = result.Routes .FirstOrDefault(r => r.Labels.Contains(RouteLabel.DefaultRoute)); Console.WriteLine($"{place.DisplayName}: {route?.DistanceMeters} m, {route?.Duration}"); 

Duration is a TimeSpan. The builder knows Google's per-mode rules too, so an arrival time on a driving route fails at Build(), not as a 400.

When things go wrong

Every exception derives from MapsApiException:

Exception When
MapsInvalidRequestException Builder rejected a value, or Google returned 400
MapsApiAuthException 401/403: bad key or API not enabled
MapsNotFoundException 404, e.g. an unknown place ID
MapsRateLimitException 429, with RetryAfter

Each one carries the status code and Google's error body. Cancellation stays a plain OperationCanceledException.

Caching in one line

builder.Services.AddAarxMapsMemoryCache(o => o.AbsoluteExpiration = TimeSpan.FromMinutes(10)); 

It covers every module. Need Redis? Use AddAarxMapsDistributedCache. Cache failures are logged and fall through to Google, never thrown. Mind Google's terms on how long you may cache.

Design choices

  • Validate up front: mistakes surface at Build(), not as cryptic 400s.
  • Field masks handled: each method sends what it maps. Need more? GetDetailsRawAsync and GetRoutesRawAsync take your own field list.
  • Cancellation everywhere: every async method passes your token to the HTTP call.
  • Escape hatch: IMapsApiClient calls endpoints the modules don't cover, with the same auth, errors and caching.

What's not there yet

No built-in retries, minimal search results (ID, name, types), and some request options aren't exposed yet. Every gap and its workaround is on Known limitations.

Try it

Feedback and issues welcome, especially on the API shape.

Drafted with AI assistance, reviewed by the author.