Skip to content
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
26 changes: 21 additions & 5 deletions src/componentsBase/IIgniteUIBlazorRuntime.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Runtime.CompilerServices;
using Microsoft.JSInterop;

namespace IgniteUI.Blazor.Controls
{
/// <summary>
/// The runtime behind the <see cref="IIgniteUIBlazor"/> marker; the components and
/// The runtime behind <see cref="IIgniteUIBlazor"/>; the components and
/// <see cref="ModuleLoader"/> reach it through <see cref="IgniteUIBlazorRuntimeExtensions.AsRuntime"/>.
/// </summary>
internal interface IIgniteUIBlazorRuntime
Expand All @@ -19,14 +20,29 @@ internal interface IIgniteUIBlazorRuntime

internal static class IgniteUIBlazorRuntimeExtensions
{
private static readonly ConditionalWeakTable<IIgniteUIBlazor, IgniteUIBlazor> _adopted = new();

/// <summary>
/// The single conversion from the public marker to the runtime it is implemented by.
/// The single conversion from the public service to the runtime it is implemented by.
/// </summary>
internal static IIgniteUIBlazorRuntime AsRuntime(this IIgniteUIBlazor service)
{
return service as IIgniteUIBlazorRuntime
?? throw new InvalidOperationException(
$"The registered {nameof(IIgniteUIBlazor)} instance is of type {service?.GetType().Name ?? "null"}, which is not the runtime registered by AddIgniteUIBlazor.");
if (service is IIgniteUIBlazorRuntime runtime)
{
return runtime;
}
if (service != null && IsFullPackageRuntime(service.GetType()))
{
return _adopted.GetValue(service, s => new IgniteUIBlazor(s.JsRuntime));
}
throw new InvalidOperationException(
$"The registered {nameof(IIgniteUIBlazor)} instance is of type {service?.GetType().Name ?? "null"}, which is not the runtime registered by AddIgniteUIBlazor.");
}

// Temporary, while the full IgniteUI.Blazor package deprecates its IgniteUIBlazor class: an app that registers
// that class instead of calling AddIgniteUIBlazor gets a runtime of its own, one per instance and so one per scope.
// Matched by name because that package depends on this one.
private static bool IsFullPackageRuntime(Type type) =>
type.FullName == "IgniteUI.Blazor.Controls.IgniteUIBlazor" && type.Assembly.GetName().Name == "IgniteUI.Blazor";
}
}
6 changes: 6 additions & 0 deletions src/componentsBase/IgbComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3857,6 +3857,12 @@ internal IgniteUIBlazorSettings(IIgniteUIBlazorSettings settings)
/// </summary>
public interface IIgniteUIBlazor
{
/// <summary>
/// The JS runtime the client resources load through. Supports the product infrastructure and is not
/// intended to be used directly from your code.
/// </summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
IJSRuntime JsRuntime { get; }
}

internal class IgniteUIBlazor : IIgniteUIBlazor, IIgniteUIBlazorRuntime
Expand Down
51 changes: 51 additions & 0 deletions tests/IgniteUI.Blazor.Tests/ServiceRegistrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Reflection;
using System.Reflection.Emit;
using Bunit;
using IgniteUI.Blazor.Controls;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
Expand Down Expand Up @@ -180,6 +183,54 @@ public void AddIgniteUIBlazor_MutatingOriginalArray_DoesNotAffectRegisteredModul
Assert.Equal([typeof(FirstModule)], SettingsOf(host).ModulesToLoad);
}

[Fact]
public void Runtime_ExposesTheRegisteredJsRuntime()
{
using var host = new Host(s => s.AddIgniteUIBlazor());

Assert.Same(host.Services.GetRequiredService<IJSRuntime>(), host.Services.GetRequiredService<IIgniteUIBlazor>().JsRuntime);
}

/// <summary>The full IgniteUI.Blazor package's deprecated <c>IgniteUIBlazor</c>, registered in place of <c>AddIgniteUIBlazor</c>.</summary>
[Fact]
public void Component_WithFullPackageRuntimeRegistered_RequestsItsModuleThroughItsJsRuntime()
{
using var context = new BunitContext();
context.JSInterop.Mode = JSRuntimeMode.Loose;
context.Services.AddScoped<IIgniteUIBlazor>(sp =>
{
var runtime = (HandRegisteredRuntime)Activator.CreateInstance(_fullPackageRuntimeType)!;
runtime.JsRuntime = sp.GetRequiredService<IJSRuntime>();
return runtime;
});

context.Render<IgbButton>();

var load = Assert.Single(context.JSInterop.Invocations, v => v.Identifier == "igRequestLoad");
Assert.Equal("WebButtonModule", load.Arguments[0]);
}

[Fact]
public void Component_WithOtherRuntimeRegistered_Throws()
{
using var context = new BunitContext();
context.Services.AddScoped<IIgniteUIBlazor>(_ => new HandRegisteredRuntime());

var error = Assert.Throws<InvalidOperationException>(() => context.Render<IgbButton>());
Assert.Contains("not the runtime registered by AddIgniteUIBlazor", error.Message);
}

public class HandRegisteredRuntime : IIgniteUIBlazor
{
public IJSRuntime JsRuntime { get; set; } = null!;
}

// HandRegisteredRuntime under the full package's type and assembly name; this repository cannot reference that package.
private static readonly Type _fullPackageRuntimeType = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("IgniteUI.Blazor"), AssemblyBuilderAccess.Run)
.DefineDynamicModule("IgniteUI.Blazor")
.DefineType("IgniteUI.Blazor.Controls.IgniteUIBlazor", TypeAttributes.Public, typeof(HandRegisteredRuntime))
.CreateType();

[Fact]
public void Runtime_IsScoped_SoEachScopeRegistersItsOwn()
{
Expand Down
Loading