Skip to content

Commit

Permalink
Service duplication fix (#224)
Browse files Browse the repository at this point in the history
Fixes when multiple nuget packages or project references contain Blazored.LocalStorage and call AddBlazoredLocalStorage(). No error occurs if it is added more than once, but the services are only needed once.
  • Loading branch information
czirok authored Oct 12, 2023
1 parent 3da6a70 commit e038a08
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 15 deletions.
40 changes: 25 additions & 15 deletions src/Blazored.LocalStorage/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Blazored.LocalStorage.JsonConverters;
using Blazored.LocalStorage.Serialization;
using Blazored.LocalStorage.StorageOptions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Blazored.LocalStorage
{
Expand All @@ -15,26 +18,30 @@ public static IServiceCollection AddBlazoredLocalStorage(this IServiceCollection

public static IServiceCollection AddBlazoredLocalStorage(this IServiceCollection services, Action<LocalStorageOptions> configure)
{
return services
.AddScoped<IJsonSerializer, SystemTextJsonSerializer>()
.AddScoped<IStorageProvider, BrowserStorageProvider>()
.AddScoped<ILocalStorageService, LocalStorageService>()
.AddScoped<ISyncLocalStorageService, LocalStorageService>()
.Configure<LocalStorageOptions>(configureOptions =>
services.TryAddScoped<IJsonSerializer, SystemTextJsonSerializer>();
services.TryAddScoped<IStorageProvider, BrowserStorageProvider>();
services.TryAddScoped<ILocalStorageService, LocalStorageService>();
services.TryAddScoped<ISyncLocalStorageService, LocalStorageService>();
if (services.All(serviceDescriptor => serviceDescriptor.ServiceType != typeof(IConfigureOptions<LocalStorageOptions>)))
{
services.Configure<LocalStorageOptions>(configureOptions =>
{
configure?.Invoke(configureOptions);
configureOptions.JsonSerializerOptions.Converters.Add(new TimespanJsonConverter());
});
}

return services;
}

/// <summary>
/// Registers the Blazored LocalStorage services as singletons. This should only be used in Blazor WebAssembly applications.
/// Using this in Blazor Server applications will cause unexpected and potentially dangerous behaviour.
/// </summary>
/// <returns></returns>
public static IServiceCollection AddBlazoredLocalStorageAsSingleton(this IServiceCollection services)
=> AddBlazoredLocalStorageAsSingleton(services, null);

/// <summary>
/// Registers the Blazored LocalStorage services as singletons. This should only be used in Blazor WebAssembly applications.
/// Using this in Blazor Server applications will cause unexpected and potentially dangerous behaviour.
Expand All @@ -44,16 +51,19 @@ public static IServiceCollection AddBlazoredLocalStorageAsSingleton(this IServic
/// <returns></returns>
public static IServiceCollection AddBlazoredLocalStorageAsSingleton(this IServiceCollection services, Action<LocalStorageOptions> configure)
{
return services
.AddSingleton<IJsonSerializer, SystemTextJsonSerializer>()
.AddSingleton<IStorageProvider, BrowserStorageProvider>()
.AddSingleton<ILocalStorageService, LocalStorageService>()
.AddSingleton<ISyncLocalStorageService, LocalStorageService>()
.Configure<LocalStorageOptions>(configureOptions =>
services.TryAddSingleton<IJsonSerializer, SystemTextJsonSerializer>();
services.TryAddSingleton<IStorageProvider, BrowserStorageProvider>();
services.TryAddSingleton<ILocalStorageService, LocalStorageService>();
services.TryAddSingleton<ISyncLocalStorageService, LocalStorageService>();
if (services.All(serviceDescriptor => serviceDescriptor.ServiceType != typeof(IConfigureOptions<LocalStorageOptions>)))
{
services.Configure<LocalStorageOptions>(configureOptions =>
{
configure?.Invoke(configureOptions);
configureOptions.JsonSerializerOptions.Converters.Add(new TimespanJsonConverter());
});
}
return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Linq;
using Blazored.LocalStorage.Serialization;
using Blazored.LocalStorage.StorageOptions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;

namespace Blazored.LocalStorage.Tests.LocalStorageServiceTests;

public class ServiceCollectionExtensionsTest
{
[Fact]
public void Scoped()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddBlazoredLocalStorage();
services.AddBlazoredLocalStorage();
services.AddBlazoredLocalStorage();
services.AddBlazoredLocalStorage();

// Assert
AssertEqual(services, typeof(IJsonSerializer), typeof(SystemTextJsonSerializer), ServiceLifetime.Scoped);
AssertEqual(services, typeof(IStorageProvider), typeof(BrowserStorageProvider), ServiceLifetime.Scoped);
AssertEqual(services, typeof(ILocalStorageService), typeof(LocalStorageService), ServiceLifetime.Scoped);
AssertEqual(services, typeof(ISyncLocalStorageService), typeof(LocalStorageService), ServiceLifetime.Scoped);
AssertEqualConfigureOptions(services, typeof(IConfigureOptions<LocalStorageOptions>), ServiceLifetime.Singleton);
}

[Fact]
public void Singleton()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddBlazoredLocalStorageAsSingleton();
services.AddBlazoredLocalStorageAsSingleton();
services.AddBlazoredLocalStorageAsSingleton();
services.AddBlazoredLocalStorageAsSingleton();

// Assert
AssertEqual(services, typeof(IJsonSerializer), typeof(SystemTextJsonSerializer), ServiceLifetime.Singleton);
AssertEqual(services, typeof(IStorageProvider), typeof(BrowserStorageProvider), ServiceLifetime.Singleton);
AssertEqual(services, typeof(ILocalStorageService), typeof(LocalStorageService), ServiceLifetime.Singleton);
AssertEqual(services, typeof(ISyncLocalStorageService), typeof(LocalStorageService), ServiceLifetime.Singleton);
AssertEqualConfigureOptions(services, typeof(IConfigureOptions<LocalStorageOptions>), ServiceLifetime.Singleton);
}


static void AssertEqual(
IServiceCollection services,
Type serviceType,
Type implementationType,
ServiceLifetime serviceLifetime
)
{
var descriptors = services.Where(serviceDescriptor => serviceDescriptor.ServiceType == serviceType).ToArray();
Assert.Single(descriptors);
var descriptor = descriptors.Single();
Assert.Equal(serviceType, descriptor.ServiceType);
Assert.Equal(implementationType, descriptor.ImplementationType);
Assert.Equal(serviceLifetime, descriptor.Lifetime);
}

static void AssertEqualConfigureOptions(
IServiceCollection services,
Type serviceType,
ServiceLifetime serviceLifetime
)
{
var descriptors = services.Where(serviceDescriptor => serviceDescriptor.ServiceType == serviceType).ToArray();
Assert.Single(descriptors);
var descriptor = descriptors.Single();
Assert.Equal(serviceType, descriptor.ServiceType);
Assert.Equal(serviceLifetime, descriptor.Lifetime);
}
}

0 comments on commit e038a08

Please sign in to comment.