Google Places API w .NET 6 — integracja krok po kroku

Pokazuję, jak w .NET 6 spiąć się z Google Places API — HttpClient z BaseAddress, wywołanie nearbysearch, deserializacja JSON do silnie typowanych DTO.

JAJarosław Armatys 8 min czytania
Wersja wideo tego wpisu — obejrzyj na YouTube

W tym odcinku kursu "WordPress importer w .NET" bierzemy klucz do Google Places API, który konfigurowaliśmy w poprzedniej lekcji, i w końcu zaczynamy go używać. Zadanie brzmi prosto: mając listę firm w bazie danych, odpytać Google o szczegóły każdej z nich i zapisać wynik. Dotknę tu rzeczy, które w realnych integracjach wracają przy każdej obcej usłudze — jak poprawnie zbudować klienta HTTP, jak sensownie dobrać endpoint, jak nie dać się zaskoczyć kosztom i jak z tekstowego JSON-a zrobić porządny obiekt C#.

Zanim przejdziemy do kodu, jeszcze jedna sprawa porządkowa. Zaraz po włożeniu klucza do appsettings.json w poprzednim odcinku od razu wykluczyłem ten plik spod kontroli gita — żeby przypadkiem nie trafił do repo. Komenda git update-index --assume-unchanged appsettings.json robi dokładnie to: git przestaje go śledzić lokalnie, a Ty możesz spokojnie wkleić swój klucz i pracować. Mała rzecz, a ratuje przed publicznym wyciekiem.

Architektura — serwis do Places

Moim punktem wyjścia jest prosta zasada: cała logika gadania z Google siedzi w jednym serwisie, a reszta aplikacji o HTTP-ie nie wie nic. W projekcie zakładam folder Google i kładę tam interfejs plus implementację. Interfejs wystawia tyle publicznych metod, ile mam operacji na tym API — w tym odcinku zaczynamy od wyszukiwania, a w kolejnym dołożymy pobieranie szczegółów.

public interface IGooglePlacesService
{
    Task<List<PlaceDto>> FindNearby(double lat, double lng, int radius, string type);
}

public class GooglePlacesService : IGooglePlacesService
{
    private readonly HttpClient _http;
    private readonly GoogleApiOptions _options;

    public GooglePlacesService(HttpClient http, IOptions<GoogleApiOptions> options)
    {
        _http = http;
        _options = options.Value;
        _http.BaseAddress = new Uri(_options.BaseUrl);
    }

    public async Task<List<PlaceDto>> FindNearby(double lat, double lng, int radius, string type)
    {
        var url = $"place/nearbysearch/json?location={lat},{lng}&radius={radius}&type={type}&key={_options.Key}";
        var response = await _http.GetFromJsonAsync<PlacesResponse>(url);
        return response?.Results ?? new List<PlaceDto>();
    }
}

Zwróć uwagę na dwie rzeczy. Po pierwsze — HttpClient wstrzykiwany jest konstruktorem, nie tworzony ręcznie. Po drugie — klucz API nie jest hardkodowany, tylko siedzi w GoogleApiOptions, do których mamy dostęp przez IOptions<T> z poprzedniego odcinka. To jest ta sama paczka klocków, którą budowaliśmy wcześniej, teraz tylko wpinamy do nowego kontekstu.

Każde zapytanie do Google Places kosztuje. W dniu nagrania cennik szczegółowych informacji o miejscu to ok. 17 dolarów za 1000 requestów. Dlatego w kursie domyślnie pobieramy tylko pierwszą firmę z bazy, a dopiero świadoma decyzja uruchamia pętlę po całym zbiorze.

HttpClient z BaseAddress

Najczęstszy błąd początkujących przy integracji z zewnętrznym API to tworzenie new HttpClient() za każdym razem, gdy chcemy coś wysłać. To prowadzi do socket exhaustion na produkcji — system nie nadąża zwalniać portów. W .NET 6 właściwą drogą jest IHttpClientFactory. W Program.cs rejestrujesz typowany klient:

builder.Services
    .AddHttpClient<IGooglePlacesService, GooglePlacesService>();

builder.Services.Configure<GoogleApiOptions>(
    builder.Configuration.GetSection("GoogleApi"));

Dzięki temu kontener DI sam zajmuje się cyklem życia HttpClient-a, pooluje połączenia, a Ty dostajesz gotowy obiekt do wstrzyknięcia. W konstruktorze ustawiam BaseAddress na wartość z opcji (np. https://maps.googleapis.com/maps/api/), żeby w metodach operować już tylko na ścieżkach względnych. To znacząco upraszcza kod — gdy Google zmieni domenę albo gdy zechcesz podmienić środowisko, wystarczy jeden wpis w appsettings.json.

Jeszcze jedna drobna rzecz: jeżeli w projekcie chcesz używać GetFromJsonAsync, potrzebujesz paczki System.Net.Http.Json. W .NET 6 jest ona zwykle już wciągnięta przez SDK, ale przy pustej konsoli trzeba ją dodać ręcznie — razem z Microsoft.Extensions.Http pod IHttpClientFactory.

Zapytanie nearbysearch

Endpoint place/nearbysearch/json szuka miejsc w promieniu wokół konkretnego punktu geograficznego. Minimalny zestaw parametrów to:

  • location — współrzędne w formacie lat,lng (np. 50.0614,19.9366 dla Krakowa),
  • radius — promień w metrach (maks. 50 000),
  • type — kategoria miejsca (restaurant, cafe, store, pełna lista w dokumentacji Google),
  • key — Twój klucz API.

Budowanie URL-a przez interpolację stringa jest tu w porządku, bo wszystkie wartości są liczbowe albo kontrolowane przez Ciebie. W produkcyjnym kodzie, gdy wartości mogą pochodzić od użytkownika, wrzuciłbym je przez QueryHelpers.AddQueryString — żeby dostać darmowe enkodowanie znaków specjalnych.

Warto wiedzieć, że Google ma kilka podobnych endpointów:

  • nearbysearch — szukaj wokół punktu, podajesz promień i typ,
  • textsearch — szukaj po frazie tekstowej (np. "pizzeria Kraków"),
  • findplacefromtext — znajdź konkretny jeden obiekt po nazwie,
  • details — pełne informacje (numer telefonu, strona, opinie) pobrane po place_id.

W kursowym imporcie korzystam z dwóch etapów: najpierw szukam firmy po nazwie albo lokalizacji, zapisuję sobie place_id, a w kolejnym odcinku odpytuję details, żeby dostać rozszerzony obiekt z opiniami i godzinami otwarcia.

Przy endpoincie details pamiętaj o parametrze language=pl. Bez niego Google potrafi zwrócić Ci opinie przetłumaczone na angielski, mimo że w oryginale były po polsku — a to dokładnie odwrotność tego, czego zwykle chcesz.

Deserializacja JSON response

Gdy GetFromJsonAsync<T> pobierze odpowiedź, dostajesz już od razu obiekt C# — nie musisz ręcznie wołać ReadAsStringAsync i JsonSerializer.Deserialize. To skrócenie, które .NET 6 dał nam w prezencie.

public class PlacesResponse
{
    [JsonPropertyName("results")]
    public List<PlaceDto> Results { get; set; } = new();
}

public class PlaceDto
{
    public string Name { get; set; } = default!;
    public string Vicinity { get; set; } = default!;
    public double Rating { get; set; }
}

Kluczowy jest atrybut JsonPropertyName. Google zwraca pola pisane snake-case-em (place_id, formatted_address, user_ratings_total), a my w C# trzymamy PascalCase. Atrybut mapuje jedno na drugie. Alternatywnie możesz skonfigurować globalnie JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }, ale przy snake_case i tak wolę atrybuty — są lokalne i widoczne, a kod się łatwiej czyta po miesiącu.

Sprytna sztuczka z filmu: zamiast ręcznie klepać wszystkie klasy DTO, wrzuć przykładową odpowiedź JSON w narzędzie typu json2csharp albo quicktype i wygeneruj sobie zestaw modeli jednym kliknięciem. Ja zrobiłem tak samo — skopiowałem JSON-a z Postmana, wrzuciłem w konwerter, wkleiłem efekt do folderu Models w projekcie i tylko poprawiłem pojedyncze typy (np. Location ściągnąłem do wspólnej klasy Coordinates, żeby nie duplikować definicji).

Na koniec jedno: w realnej integracji obsłuż przypadek, gdy Google zwróci status: "ZERO_RESULTS" albo status: "OVER_QUERY_LIMIT". W materiale celowo zostawiam to jako zadanie domowe, bo każdy projekt ma inne wymagania co do fallbacków — w jednym wystarczy zalogować i pominąć rekord, w innym potrzebujesz retry z exponential backoff. Dobry punkt wyjścia to owinięcie GetFromJsonAsync w try/catch na HttpRequestException i dodanie sprawdzenia pola status w odpowiedzi, zanim uznasz, że wszystko zagrało.

Silnie typowany obiekt to nie luksus, tylko oszczędność godzin debugowania. Gdy Google kiedyś zmieni nazwę pola, kompilator Ci powie dokładnie, gdzie zaczyna się problem — zamiast ukrytego null w runtime.

Co dalej?

W tym odcinku postawiłem solidny fundament: serwis wystawiający operacje na Google Places API, klient HTTP zarządzany przez DI, świadomy wybór endpointa, czysta deserializacja na DTO. W kolejnej lekcji rozszerzymy to o place/details/json — tam wyjdą nam opinie, godziny otwarcia, numery telefonów. A potem spinamy wszystko w całość i wrzucamy do WordPressa.

Zadanie domowe, jeśli chcesz utrwalić materiał: pobierz odpowiedź z nearbysearch dla swojej miejscowości, skonwertuj JSON-a na klasy C# z pomocą generatora, a potem spróbuj dopisać prostą obsługę błędów — co zrobić, gdy results jest pustą listą albo gdy klucz API jest nieprawidłowy. Takie drobne ćwiczenia budują intuicję, której nie da się wyczytać z dokumentacji.

Tagi:#course-dotnet-wordpress#dotnet#google-api#httpclient#api

Masz pytanie do tego wpisu?

Napisz do mnie — chętnie porozmawiam o szczegółach technicznych, albo o tym jak zastosować to u Ciebie w projekcie.