Creating a C#/ .net service to do Google Places API look up
The application I am working on requires address autocomplete feature, there are few paid services that do this already, but I choose Google Places API – its free (well I don’t expect to eat up quota – my app no way near the limitation).
Google Places API does not return the postcode on the “autocomplete” endpoint (at least to my knowledge – according to documentation), therefore you will need to to another GET request by the Place ID to get the postcode.
I am going to use my favorite packages to help with this development, Newtonsoft.Json and AutoMapper.
Below are the JSON objects
public class PlacePrediction { [JsonProperty("place_id")] public string PlaceId { get; set; } [JsonProperty("description")] public string Description { get; set; } [JsonProperty("terms")] public List<PlacePredictionTerm> Terms { get; set; } } public class PlacePredictionTerm { [JsonProperty("offset")] public int Offset { get; set; } [JsonProperty("value")] public string Value { get; set; } } public class GooglePlacesAutocompleteResult<T> { [JsonProperty("predictions")] public List<T> Places { get; set; } [JsonProperty("status")] public string Status { get; set; } } public class GooglePlacesDetailsResult { [JsonProperty("result")] public GooglePlacesSingleResult Result { get; set; } [JsonProperty("status")] public string Status { get; set; } } public class GooglePlacesSingleResult { [JsonProperty("place_id")] public string PlaceId { get; set; } [JsonProperty("address_components")] public List<GooglePlacesAddressComponent> AddressComponents { get; set; } [JsonProperty("geometry")] public GooglePlacesGeometry Geometry { get; set; } } public class GooglePlacesAddressComponent { [JsonProperty("long_name")] public string LongName { get; set; } [JsonProperty("short_name")] public string ShortName { get; set; } [JsonProperty("types")] public List<string> Types { get; set; } } public class GooglePlacesGeometry { [JsonProperty("location")] public GooglePlacesLocation Location { get; set; } } public class GooglePlacesLocation { [JsonProperty("lat")] public double Latitude { get; set; } [JsonProperty("lng")] public double Longitude { get; set; } }
Automapper profile
public class MappingProfile : Profile { private const string TypesLocality = "locality"; private const string TypesPolitical = "political"; private const string TypesCountry = "country"; private const string TypesPostalCode = "postal_code"; private const string TypesAdministrativeAreaLevel1 = "administrative_area_level_1"; public MappingProfile() { //TODO: I will need to improve this mapping like the 2nd, I will post it here anyway CreateMap<PlacePrediction, PlaceEnvelope>(MemberList.None) .ForMember(dest => dest.Suburb, expression => expression.ResolveUsing(s => s.Terms?.FirstOrDefault()?.Value)) .ForMember(dest => dest.State, expression => expression.ResolveUsing(s => s.Terms?.Skip(1).FirstOrDefault()?.Value)) .ForMember(dest => dest.Country, expression => expression.ResolveUsing(s => s.Terms?.LastOrDefault()?.Value)); CreateMap<GooglePlacesSingleResult, PlaceDetailsEnvelope>(MemberList.None) .ForMember(dest => dest.Suburb, opt => opt.ResolveUsing<SuburbResolver>()) .ForMember(dest => dest.State, opt => opt.ResolveUsing<StateResolver>()) .ForMember(dest => dest.Country, opt => opt.ResolveUsing<CountryResolver>()) .ForMember(dest => dest.Postcode, opt => opt.ResolveUsing<PostcodeResolver>()) .ForMember(dest => dest.Latitude, expression => expression.ResolveUsing(s => s.Geometry.Location.Latitude)) .ForMember(dest => dest.Longitude, expression => expression.ResolveUsing(s => s.Geometry.Location.Longitude)); } #region CustomResolvers public class SuburbResolver : IValueResolver<GooglePlacesSingleResult, PlaceDetailsEnvelope, string> { public string Resolve(GooglePlacesSingleResult source, PlaceDetailsEnvelope destination, string member, ResolutionContext context) { return source.AddressComponents.SingleOrDefault(o => o.Types.Contains(TypesLocality) && o.Types.Contains(TypesPolitical))?.LongName; } } public class CountryResolver : IValueResolver<GooglePlacesSingleResult, PlaceDetailsEnvelope, string> { public string Resolve(GooglePlacesSingleResult source, PlaceDetailsEnvelope destination, string member, ResolutionContext context) { return source.AddressComponents.SingleOrDefault(o => o.Types.Contains(TypesCountry) && o.Types.Contains(TypesPolitical))?.LongName; } } public class PostcodeResolver : IValueResolver<GooglePlacesSingleResult, PlaceDetailsEnvelope, string> { public string Resolve(GooglePlacesSingleResult source, PlaceDetailsEnvelope destination, string member, ResolutionContext context) { return source.AddressComponents.SingleOrDefault(o => o.Types.Contains(TypesPostalCode))?.LongName; } } public class StateResolver : IValueResolver<GooglePlacesSingleResult, PlaceDetailsEnvelope, string> { public string Resolve(GooglePlacesSingleResult source, PlaceDetailsEnvelope destination, string member, ResolutionContext context) { return source.AddressComponents.SingleOrDefault(o => o.Types.Contains(TypesAdministrativeAreaLevel1) && o.Types.Contains(TypesPolitical))?.LongName; } } #endregion }
Here is the IGooglePlacesService Interface.
public interface IGooglePlacesService { Task<List<PlacePrediction>> GetAutocompleteAsync(string searchTerm); Task<GooglePlacesSingleResult> GetSingleAsync(string placeId); }
Here is the implementation
public class GooglePlacesService : IGooglePlacesService { static readonly HttpClient Client = new HttpClient(); private readonly GoogleWebServiceSettings _googleWebServiceSettings; public GooglePlacesService(IOptions<GoogleWebServiceSettings> googleWebServiceSettings) { _googleWebServiceSettings = googleWebServiceSettings.Value; } public async Task<List<PlacePrediction>> GetAutocompleteAsync(string searchTerm) { var response = await Client.GetAsync($"{_googleWebServiceSettings.PlacesAPIUrl}autocomplete/json?key={_googleWebServiceSettings.PlacesAPIKey}&input={searchTerm}"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsAsync<GooglePlacesAutocompleteResult<PlacePrediction>>(); return content.Places; } return new List<PlacePrediction>(); } public async Task<GooglePlacesSingleResult> GetSingleAsync(string placeId) { var response = await Client.GetAsync($"{_googleWebServiceSettings.PlacesAPIUrl}details/json?key={_googleWebServiceSettings.PlacesAPIKey}&placeid={placeId}"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsAsync<GooglePlacesDetailsResult>(); return content.Result; } return new GooglePlacesSingleResult(); } }
Hope this helps