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 formacielat,lng(np.50.0614,19.9366dla 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 poplace_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
detailspamiętaj o parametrzelanguage=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
nullw 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.



