Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bootstrapping via service #328

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions GoogleMapsComponents/BlazorGoogleMapsKeyService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Threading.Tasks;

namespace GoogleMapsComponents;

/// <summary>
/// Interface for the key service that is injected into the Map Component in order to retrieve
/// a google api key when injecting the google api JS package
/// </summary>
public interface IBlazorGoogleMapsKeyService
{
public Task<Maps.MapApiLoadOptions> GetApiOptions();

public bool IsApiInitialized { get; set; }
TheAtomicOption marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Basic implmentation of <see cref="IBlazorGoogleMapsKeyService"/> to load Google API JS dynamically instead of through _Hosts.cshtml.<br/>
/// Replace with your own service to provide keys via dynamic lookup.
/// </summary>
public class BlazorGoogleMapsKeyService : IBlazorGoogleMapsKeyService
{
private readonly Maps.MapApiLoadOptions initOptions;
public bool IsApiInitialized { get; set; } = false;

public BlazorGoogleMapsKeyService(string key)
{
initOptions = new Maps.MapApiLoadOptions(key);
}
public BlazorGoogleMapsKeyService(Maps.MapApiLoadOptions opts)
{
initOptions = opts;
}
public Task<Maps.MapApiLoadOptions> GetApiOptions()
{
// Can do async things to get the API key if needed here.
return Task.FromResult(initOptions);
}
}
75 changes: 26 additions & 49 deletions GoogleMapsComponents/DependencyInjectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using System;

namespace GoogleMapsComponents;

public static class DependencyInjectionExtensions
{
internal static BlazorGoogleMapsOptions MapOptions { get; set; } = new BlazorGoogleMapsOptions();
public static IServiceCollection AddBlazorGoogleMaps(this IServiceCollection service, Action<BlazorGoogleMapsOptions>? opts = null)
{
MapOptions = new BlazorGoogleMapsOptions();
opts?.Invoke(MapOptions);

if (MapOptions.UseBootstrapLoader)
{
if (MapOptions.KeyProvider is null)
{
throw new ArgumentNullException(nameof(MapOptions.KeyProvider), "Key is required when using bootstrap loader.");
}

}

return service;
}
}

//All options
//https://developers.google.com/maps/documentation/javascript/load-maps-js-api#required_parameters
public class BlazorGoogleMapsOptions
{
/// <summary>
/// Is selected option then it uses the bootstrap loader to load the google maps script.
/// https://developers.google.com/maps/documentation/javascript/overview#Loading_the_Maps_API
/// </summary>
public bool UseBootstrapLoader { get; set; }

/// <summary>
/// Default is weekly.
/// https://developers.google.com/maps/documentation/javascript/versions
/// </summary>
public string Version { get; set; } = "weekly";

/// <summary>
/// Comma separated list of libraries to load.
/// https://developers.google.com/maps/documentation/javascript/libraries
/// </summary>
public string? Libraries { get; set; }

public Func<string>? KeyProvider { get; set; }
using Microsoft.Extensions.DependencyInjection;
using System;

namespace GoogleMapsComponents;

public static class DependencyInjectionExtensions
{
/// <summary>
/// Adds a basic service to provide a google api key for the application when using bootstrap loader method for Google API <para/>
///
/// If implementing your own key service, be sure to register the service as the implementation of <see cref="IBlazorGoogleMapsKeyService"/>,
/// so that the <see cref="GoogleMap"/> component can find it. For example instead of this function you might use:
/// <code>services.AddScoped&lt;<see cref="IBlazorGoogleMapsKeyService"/>, YourServiceImplmentation&gt;();</code>
/// </summary>
public static IServiceCollection AddBlazorGoogleMaps(this IServiceCollection services, string key)
TheAtomicOption marked this conversation as resolved.
Show resolved Hide resolved
{
services.AddScoped<IBlazorGoogleMapsKeyService>(serviceProvider => new BlazorGoogleMapsKeyService(key));
return services;
}

/// <inheritdoc cref="AddBlazorGoogleMaps(IServiceCollection, string)"/>
public static IServiceCollection AddBlazorGoogleMaps(this IServiceCollection services, Maps.MapApiLoadOptions opts)
{
services.AddScoped<IBlazorGoogleMapsKeyService>(serviceProvider => new BlazorGoogleMapsKeyService(opts));
TheAtomicOption marked this conversation as resolved.
Show resolved Hide resolved
return services;
}
}
2 changes: 1 addition & 1 deletion GoogleMapsComponents/GoogleMap.razor
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@

//Debug.WriteLine("Init finished");

await OnAfterInit.InvokeAsync(null);
await OnAfterInit.InvokeAsync();
}

protected override bool ShouldRender()
Expand Down
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.2.0</Version>
<Version>4.3.0</Version>
<Authors>Rungwiroon</Authors>
<Company>QueueStack Solution</Company>
<Product>BlazorGoogleMaps</Product>
Expand Down
189 changes: 104 additions & 85 deletions GoogleMapsComponents/MapComponent.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,105 @@
using GoogleMapsComponents.Maps;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Threading.Tasks;

namespace GoogleMapsComponents;

public class MapComponent : ComponentBase, IDisposable, IAsyncDisposable
{
private bool _isDisposed;

[Inject]
public IJSRuntime JsRuntime { get; protected set; } = default!;

public Map InteropObject { get; private set; } = default!;

public async Task InitAsync(ElementReference element, MapOptions? options = null)
{
InteropObject = await Map.CreateAsync(JsRuntime, element, options);
}



public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore();

// Dispose of unmanaged resources.
Dispose(false);

// Suppress finalization.
GC.SuppressFinalize(this);
}

protected virtual async ValueTask DisposeAsyncCore()
{
if (InteropObject is not null)
{
try
{
await InteropObject.DisposeAsync();
InteropObject = null;
}
catch (Exception ex)
{
var isPossibleRefreshError = ex.HasInnerExceptionsOfType<TaskCanceledException>();
isPossibleRefreshError |= ex.HasInnerExceptionsOfType<ObjectDisposedException>();
//Unfortenatly, JSDisconnectedException is available in dotnet >= 6.0, and not in dotnet standard.
isPossibleRefreshError |= true;
//If we get an exception here, we can assume that the page was refreshed. So assentialy, we swallow all exception here...
//isPossibleRefreshError = isPossibleRefreshError || ex.HasInnerExceptionsOfType<JSDisconnectedException>();


if (!isPossibleRefreshError)
{
throw;
}
}
}
}


protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
InteropObject?.Dispose();
}

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_isDisposed = true;
}
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
using GoogleMapsComponents.Maps;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using System;
using System.Threading.Tasks;

namespace GoogleMapsComponents;

public class MapComponent : ComponentBase, IDisposable, IAsyncDisposable
{
private bool _isDisposed;

[Inject]
public IJSRuntime JsRuntime { get; protected set; } = default!;
[Inject]
public IServiceProvider ServiceProvider { get; protected set; } = default!;

private IBlazorGoogleMapsKeyService? KeyService;

protected override void OnInitialized()
{
// get the service from the provider instead of with [Inject] in case no
// service was registered. e.g. when the user loads the api with a script tag.
KeyService = ServiceProvider.GetService<IBlazorGoogleMapsKeyService>();
base.OnInitialized();
}

public Map InteropObject { get; private set; } = default!;

public async Task InitAsync(ElementReference element, MapOptions? options = null)
{
if (options?.ApiLoadOptions == null && KeyService != null && !KeyService.IsApiInitialized)
{
KeyService.IsApiInitialized = true;
options ??= new MapOptions();
options.ApiLoadOptions = await KeyService.GetApiOptions();
}
InteropObject = await Map.CreateAsync(JsRuntime, element, options);
}



public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore();

// Dispose of unmanaged resources.
Dispose(false);

// Suppress finalization.
GC.SuppressFinalize(this);
}

protected virtual async ValueTask DisposeAsyncCore()
{
if (InteropObject is not null)
{
try
{
await InteropObject.DisposeAsync();
InteropObject = null;
}
catch (Exception ex)
{
var isPossibleRefreshError = ex.HasInnerExceptionsOfType<TaskCanceledException>();
isPossibleRefreshError |= ex.HasInnerExceptionsOfType<ObjectDisposedException>();
//Unfortenatly, JSDisconnectedException is available in dotnet >= 6.0, and not in dotnet standard.
isPossibleRefreshError |= true;
//If we get an exception here, we can assume that the page was refreshed. So assentialy, we swallow all exception here...
//isPossibleRefreshError = isPossibleRefreshError || ex.HasInnerExceptionsOfType<JSDisconnectedException>();


if (!isPossibleRefreshError)
{
throw;
}
}
}
}


protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
InteropObject?.Dispose();
}

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_isDisposed = true;
}
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
10 changes: 5 additions & 5 deletions GoogleMapsComponents/Maps/Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public static async Task<Map> CreateAsync(
ElementReference mapDiv,
MapOptions? opts = null)
{
if (DependencyInjectionExtensions.MapOptions.UseBootstrapLoader)
if (opts?.ApiLoadOptions != null)
{
var mapOpts = DependencyInjectionExtensions.MapOptions;
MapApiLoadOptions apiOpts = opts.ApiLoadOptions;
await jsRuntime.InvokeVoidAsync("blazorGoogleMaps.objectManager.initMap",
mapOpts.KeyProvider?.Invoke(),
mapOpts.Version,
mapOpts.Libraries);
apiOpts.Key,
apiOpts.Version,
apiOpts.Libraries);
}
var jsObjectRef = await JsObjectRef.CreateAsync(jsRuntime, "google.maps.Map", mapDiv, opts);
var dataObjectRef = await jsObjectRef.GetObjectReference("data");
Expand Down
25 changes: 25 additions & 0 deletions GoogleMapsComponents/Maps/MapApiLoadOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace GoogleMapsComponents.Maps;

/// <summary>
/// All options
/// https://developers.google.com/maps/documentation/javascript/load-maps-js-api#required_parameters <br/>
/// </summary>
public class MapApiLoadOptions(string key)
{
/// <summary>
/// Default is weekly.
/// https://developers.google.com/maps/documentation/javascript/versions
/// </summary>
public string Version { get; set; } = "weekly";

/// <summary>
/// Comma separated list of libraries to load.
/// https://developers.google.com/maps/documentation/javascript/libraries
/// </summary>
public string? Libraries { get; set; } = "places,visualization,drawing,marker";

/// <summary>
/// Synchronous key provider function used by the default implementation
/// </summary>
public string? Key { get; set; } = key;
}
Loading