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
