Skip to content

Commit

Permalink
PlaceService GetDetails crash that JSON value could not be converted …
Browse files Browse the repository at this point in the history
…to GoogleMapsComponents.Maps.Places.PlaceResult[] #351
  • Loading branch information
valentasm committed Aug 12, 2024
1 parent c102126 commit 30883e1
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 72 deletions.
6 changes: 6 additions & 0 deletions GoogleMapsComponents/DependencyInjectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ public static IServiceCollection AddBlazorGoogleMaps(this IServiceCollection ser
services.AddScoped<IBlazorGoogleMapsKeyService>(_ => new BlazorGoogleMapsKeyService(opts));
return services;
}

public static IServiceCollection AddBlazorGoogleMaps(this IServiceCollection services, IBlazorGoogleMapsKeyService blazorGoogleMapsKeyService)
{
services.AddScoped(_ => blazorGoogleMapsKeyService);
return services;
}
}
2 changes: 1 addition & 1 deletion GoogleMapsComponents/GoogleMapsComponents.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<RazorLangVersion>3.0</RazorLangVersion>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<PackageId>BlazorGoogleMaps</PackageId>
<Version>4.7.5</Version>
<Version>4.7.6</Version>
<Authors>Rungwiroon</Authors>
<Company>QueueStack Solution</Company>
<Product>BlazorGoogleMaps</Product>
Expand Down
2 changes: 1 addition & 1 deletion GoogleMapsComponents/Maps/Places/PlaceResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace GoogleMapsComponents.Maps.Places;

public class PlaceResponse
{
public PlaceResult[] Results { get; set; } = new PlaceResult[] { };
public PlaceResult? Results { get; set; }

[JsonConverter(typeof(EnumMemberConverter<PlaceServiceStatus>))]
public PlaceServiceStatus Status { get; set; }
Expand Down
31 changes: 31 additions & 0 deletions ServerSideDemo/EnvVariableBlazorGoogleMapsKeyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using GoogleMapsComponents;
using GoogleMapsComponents.Maps;
using System;
using System.Threading.Tasks;

namespace ServerSideDemo;

public class EnvVariableBlazorGoogleMapsKeyService : IBlazorGoogleMapsKeyService
{
public Task<MapApiLoadOptions> GetApiOptions()
{

var key = Environment.GetEnvironmentVariable("GOOGLE_MAPS_API_KEY");
if (string.IsNullOrEmpty(key))
{
key = "AIzaSyBdkgvniMdyFPAcTlcZivr8f30iU-kn1T0";
}

key = "AIzaSyBabXxMdOrVBdIPXZuZObAgrP4inwDAk98";

return Task.FromResult(new MapApiLoadOptions(key)
{
Version = "beta"
});

IsApiInitialized = true;

}

public bool IsApiInitialized { get; set; }
}
133 changes: 68 additions & 65 deletions ServerSideDemo/Pages/MapServices.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
@using GoogleMapsComponents
@using GoogleMapsComponents.Maps
@using GoogleMapsComponents.Maps.Places
@using GoogleMapsComponents

@*
The code for this sample was taken from below on 09/02/2020:
Expand All @@ -12,21 +11,21 @@
<h1>Google Map AutocompleteService, PlacesService and Geocoder</h1>

<div style="margin-bottom: 10px;">
<input type="text" @ref="this.searchBox" id="searchBox" @bind-value="this.search" />
<input type="text" id="searchBox" @bind-value="_search" />
<button @onclick="SearchAsync">Search</button>
<button @onclick="() => this.suggestions = null">Clear Suggestions</button>
<button @onclick="() => _suggestions = null">Clear Suggestions</button>
<button @onclick="GeocodeAsync">Geocode</button>
<button @onclick="ClearMarkersAsync">Clear Markers</button>
<div>
<span>Suggestions:</span><br/>
@if ((this.suggestions?.Length ?? 0) == 0)
@if ((_suggestions.Length) == 0)
{
<span>No suggestions</span>
}
else
{
<ol>
@foreach (var item in this.suggestions)
@foreach (var item in _suggestions)
{
<li @key="item.PlaceId">
<span role="button" class="oi oi-map-marker text-danger" @onclick="async () => await GetPlaceDetailAsync(item.PlaceId)"></span>
Expand All @@ -39,31 +38,30 @@
</div>

<div>
<p style="font-weight: bold; font-size: 1.2em">@this.message</p>
<p style="font-weight: bold; font-size: 1.2em">@_message</p>
</div>

<GoogleMap @ref="@(this.map1)" Id="map1" Options="@(this.mapOptions)" OnAfterInit="async () => await OnAfterMapInit()"></GoogleMap>
<GoogleMap @ref="@(_map1)" Id="map1" Options="@(_mapOptions)" OnAfterInit="async () => await OnAfterMapInit()"></GoogleMap>

@functions {
private readonly Stack<Marker> markers = new Stack<Marker>();
private readonly Stack<Marker> _markers = new Stack<Marker>();

private string? search;
private GoogleMap map1;
private MapOptions mapOptions;
private AutocompleteService? autocompleteService;
private PlacesService? placesService;
private AutocompleteSessionToken? token;
private Geocoder? geocoder;
private DateTime tokenStamp = DateTime.MinValue;
private string? _search;
private GoogleMap _map1;
private MapOptions _mapOptions;
private AutocompleteService? _autocompleteService;
private PlacesService? _placesService;
private AutocompleteSessionToken? _token;
private Geocoder? _geocoder;
private DateTime _tokenStamp = DateTime.MinValue;

private string message;
private string? _message;

private ElementReference searchBox;
private AutocompletePrediction[]? suggestions;
private AutocompletePrediction[] _suggestions = [];

protected override void OnInitialized()
{
this.mapOptions = new MapOptions
_mapOptions = new MapOptions
{
Zoom = 13,
Center = new LatLngLiteral
Expand All @@ -77,31 +75,31 @@

private async Task OnAfterMapInit()
{
this.autocompleteService = await AutocompleteService.CreateAsync(this.map1.JsRuntime);
this.placesService = await PlacesService.CreateAsync(this.map1.JsRuntime, this.map1.InteropObject);
this.geocoder = await Geocoder.CreateAsync(this.map1.JsRuntime);
_autocompleteService = await AutocompleteService.CreateAsync(_map1.JsRuntime);
_placesService = await PlacesService.CreateAsync(_map1.JsRuntime, _map1.InteropObject);
_geocoder = await Geocoder.CreateAsync(_map1.JsRuntime);
}

private async Task<AutocompleteSessionToken> GetOrCreateTokenAsync()
{
// It is not officially documented how long a session token is valid for.
// Google only says "a few minutes", but there is one mention of 3 minutes in StackOverflow
// https://stackoverflow.com/questions/50398801/how-long-do-the-new-places-api-session-tokens-last
if (token is null || tokenStamp == DateTime.MinValue || tokenStamp.AddMinutes(3).CompareTo(DateTime.Now) < 1)
if (_token is null || _tokenStamp == DateTime.MinValue || _tokenStamp.AddMinutes(3).CompareTo(DateTime.Now) < 1)
{
this.token?.Dispose();
this.token = await AutocompleteSessionToken.CreateAsync(this.map1.JsRuntime);
this.tokenStamp = DateTime.Now;
_token?.Dispose();
_token = await AutocompleteSessionToken.CreateAsync(_map1.JsRuntime);
_tokenStamp = DateTime.Now;
}

return token;
return _token;
}

private bool IsValidInput()
{
if (string.IsNullOrEmpty(this.search))
if (string.IsNullOrEmpty(_search))
{
this.message = "Invalid input. Please enter some text and try again.";
_message = "Invalid input. Please enter some text and try again.";
return false;
}

Expand All @@ -110,9 +108,9 @@

private async Task ClearMarkersAsync()
{
while (this.markers.Count > 0)
while (_markers.Count > 0)
{
var marker = this.markers.Pop();
var marker = _markers.Pop();
await marker.SetMap(null);
marker.Dispose();
}
Expand All @@ -123,65 +121,70 @@
//This can be executed on every key stroke with 300ms debounce, but I decided to use the search button
//These requests will be bundled together via the session token until it either expires or placesService.GetDetails is executed.
//Every session is charged (billed). If session token is omitted/invalid/expired, every request is charged (billed).
if (autocompleteService is null || !IsValidInput())
if (_autocompleteService is null || !IsValidInput())
{
return;
}

var request = new AutocompletionRequest
{
Input = this.search,
Input = _search,
SessionToken = await GetOrCreateTokenAsync()
};

var response = await autocompleteService.GetPlacePredictions(request);
var response = await _autocompleteService.GetPlacePredictions(request);
if (response.Status == PlaceServiceStatus.Ok)
{
this.suggestions = response.Predictions;
_suggestions = response.Predictions;
}
else
{
this.message = $"Your request failed with status code: {response.Status}";
_message = $"Your request failed with status code: {response.Status}";
}
}

private async Task GeocodeAsync()
{
if (geocoder is null || !IsValidInput())
if (_geocoder is null || !IsValidInput())
{
return;
}

var response = await geocoder.Geocode(new GeocoderRequest
var response = await _geocoder.Geocode(new GeocoderRequest
{
Address = this.search
Address = _search
});

if (response.Status == GeocoderStatus.Ok)
{
await ClearMarkersAsync();

var bounds = await LatLngBounds.CreateAsync(this.map1.JsRuntime);
var bounds = await LatLngBounds.CreateAsync(_map1.JsRuntime);

foreach (var result in response.Results)
{
await RenderLocationAsync(result.FormattedAddress, result.Geometry.Location);
bounds.Extend(result.Geometry.Location);
await bounds.Extend(result.Geometry.Location);
}

await this.map1.InteropObject.FitBounds(await bounds.ToJson(), 5);
await _map1.InteropObject.FitBounds(await bounds.ToJson(), 5);
}
else
{
this.message = $"Your request failed with status code: {response.Status}";
_message = $"Your request failed with status code: {response.Status}";
}
}

private async Task GetPlaceDetailAsync(string placeId)
{
if (_placesService is null)
{
return;
}

try
{
var place = await placesService.GetDetails(new PlaceDetailsRequest
var place = await _placesService.GetDetails(new PlaceDetailsRequest
{
PlaceId = placeId,
Fields = new string[] { "address_components", "formatted_address", "geometry", "name", "place_id" },
Expand All @@ -190,70 +193,70 @@

if (place.Status == PlaceServiceStatus.Ok)
{
await RenderPlaceAsync(place.Results.FirstOrDefault());
await RenderPlaceAsync(place.Results);
}
else
{
this.message = $"Your request failed with status code: {place.Status}";
_message = $"Your request failed with status code: {place.Status}";
}
}
finally
{
//After a request to PlacesService, the token is no longer valid
this.token.Dispose();
this.token = null;
_token?.Dispose();
_token = null;
}
}

private async Task RenderLocationAsync(string title, LatLngLiteral location)
{
var marker = await Marker.CreateAsync(this.map1.JsRuntime, new MarkerOptions
var marker = await Marker.CreateAsync(_map1.JsRuntime, new MarkerOptions
{
Position = location,
Map = this.map1.InteropObject,
Map = _map1.InteropObject,
Title = title
});

this.markers.Push(marker);
_markers.Push(marker);
}

private async Task RenderPlaceAsync(PlaceResult? place)
{
//Below code borrowed from MapAutocomplete.razor "place_changed" event handler
if (place?.Geometry == null)
{
this.message = "No results available for " + place?.Name;
_message = "No results available for " + place?.Name;
}
else if (place.Geometry.Location != null)
{
await this.map1.InteropObject.SetCenter(place.Geometry.Location);
await this.map1.InteropObject.SetZoom(13);
await _map1.InteropObject.SetCenter(place.Geometry.Location);
await _map1.InteropObject.SetZoom(13);

var marker = await Marker.CreateAsync(this.map1.JsRuntime, new MarkerOptions
var marker = await Marker.CreateAsync(_map1.JsRuntime, new MarkerOptions
{
Position = place.Geometry.Location,
Map = this.map1.InteropObject,
Map = _map1.InteropObject,
Title = place.FormattedAddress
});

this.markers.Push(marker);
_markers.Push(marker);

this.message = "Displaying result for " + place.Name;
_message = "Displaying result for " + place.Name;
}
else if (place.Geometry.Viewport != null)
{
await this.map1.InteropObject.FitBounds(place.Geometry.Viewport, 5);
this.message = "Displaying result for " + place.Name;
await _map1.InteropObject.FitBounds(place.Geometry.Viewport, 5);
_message = "Displaying result for " + place.Name;
}
}

public async ValueTask DisposeAsync()
{
await ClearMarkersAsync();

this.token?.Dispose();
this.autocompleteService?.Dispose();
this.placesService?.Dispose();
this.geocoder?.Dispose();
_token?.Dispose();
_autocompleteService?.Dispose();
_placesService?.Dispose();
_geocoder?.Dispose();
}
}
10 changes: 5 additions & 5 deletions ServerSideDemo/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using GoogleMapsComponents;
using GoogleMapsComponents.Maps;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
Expand All @@ -25,10 +24,11 @@ public void ConfigureServices(IServiceCollection services)
services.AddServerSideBlazor().AddHubOptions(config => config.MaximumReceiveMessageSize = 1048576);

// Adds the service to use bootstrap loader for Google API JS.
services.AddBlazorGoogleMaps(new MapApiLoadOptions("AIzaSyBdkgvniMdyFPAcTlcZivr8f30iU-kn1T0")
{
Version = "beta"
});
services.AddBlazorGoogleMaps(new EnvVariableBlazorGoogleMapsKeyService());
//services.AddBlazorGoogleMaps(new MapApiLoadOptions("AIzaSyBdkgvniMdyFPAcTlcZivr8f30iU-kn1T0")
//{
// Version = "beta"
//});
// Or manually set version and libraries for entire app:
//services.AddBlazorGoogleMaps(new GoogleMapsComponents.Maps.MapApiLoadOptions("AIzaSyBdkgvniMdyFPAcTlcZivr8f30iU-kn1T0")
//{
Expand Down

0 comments on commit 30883e1

Please sign in to comment.