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

feat(JSModule): redesign JSModule support null js object #4234

Merged
merged 8 commits into from
Sep 9, 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
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)

private async Task OpenUrl_Self() => await Module.OpenUrl(Url, "_self");

private bool IsMobile { get; set; }
private bool? IsMobile { get; set; }

private async Task GetIsMobile() => IsMobile = await Module.IsMobile();

Expand All @@ -49,19 +49,19 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
`当前URL: ${currentUrl}`;
""";

private string evalResult { get; set; } = string.Empty;
private string? evalResult { get; set; }

private async Task RunEval() => evalResult = await Module.Eval<string>(evalContent);
private async Task RunEval() => evalResult = await Module.Eval<string?>(evalContent);

private string functionContent = """
const currentUrl = window.location.href;

return `当前URL: ${currentUrl}`;
""";

private string functionResult { get; set; } = string.Empty;
private string? functionResult { get; set; }

private async Task RunFunction() => functionResult = await Module.Function<string>(functionContent);
private async Task RunFunction() => functionResult = await Module.Function<string?>(functionContent);

private MethodItem[] GetMethods() =>
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class WebSpeechSynthesizer(JSModule module, IComponentIdGenerator compone
/// 获得 语音包方法
/// </summary>
/// <returns></returns>
public async Task<WebSpeechSynthesisVoice[]> GetVoices() => await module.InvokeAsync<WebSpeechSynthesisVoice[]>("getVoices");
public async Task<WebSpeechSynthesisVoice[]?> GetVoices() => await module.InvokeAsync<WebSpeechSynthesisVoice[]?>("getVoices");

/// <summary>
/// 开始朗读方法
Expand Down
91 changes: 15 additions & 76 deletions src/BootstrapBlazor/Extensions/JSModuleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,18 @@ public static class JSModuleExtensions
/// <returns>A <see cref="Task"/><![CDATA[<]]><see cref="JSModule"/><![CDATA[>]]> 模块加载器</returns>
public static async Task<JSModule> LoadModule(this IJSRuntime jsRuntime, string fileName, string? version = null)
{
JSModule? module = null;
if (!string.IsNullOrEmpty(version))
{
fileName = $"{fileName}?v={version}";
}
try
{
var jSObjectReference = await jsRuntime.InvokeAsync<IJSObjectReference>(identifier: "import", fileName);
return new JSModule(jSObjectReference);
}
catch (Exception ex)
{
throw new Exception($"load {fileName} module fail", ex);
module = new JSModule(jSObjectReference);
}
catch (OperationCanceledException) { }
return module ?? new JSModule(null);
}

/// <summary>
Expand Down Expand Up @@ -80,7 +79,7 @@ public static string GetTypeModuleName(this Type type)
/// <param name="module"><see cref="JSModule"/> 实例</param>
/// <param name="script"></param>
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
public static ValueTask<T> Eval<T>(this JSModule module, string script) => module.InvokeAsync<T>("runEval", script);
public static ValueTask<TValue?> Eval<TValue>(this JSModule module, string script) => module.InvokeAsync<TValue?>("runEval", script);

/// <summary>
/// 通过 Function 动态运行 JavaScript 代码
Expand All @@ -94,80 +93,20 @@ public static string GetTypeModuleName(this Type type)
/// <summary>
/// 动态运行js代码
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="module"><see cref="JSModule"/> 实例</param>
/// <param name="script"></param>
/// <param name="args"></param>
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
public static ValueTask<T> Function<T>(this JSModule module, string script, params object?[]? args) => module.InvokeAsync<T>("runFunction", script, args);

///// <summary>
///// 动态增加 head 标签
///// <para>
///// 示例:
///// <code>
///// [Inject]
///// [NotNull]
///// private <see cref="IJSRuntime"/> JSRuntime
/////
///// [NotNull]
///// private <see cref="JSModule"/>? Module { get; set; }
/////
///// protected override <see langword="async"/> <see cref="Task"/> OnAfterRenderAsync(bool firstRender)
///// {
///// <see langword="await"/> <see langword="base"/>.OnAfterRenderAsync(firstRender);
/////
///// <see langword="if"/>(firstRender)
///// {
///// Module = <see langword="await"/> JSRuntime.LoadUtility();
///// }
///// }
/////
///// private <see langword="async"/> <see cref="Task"/> OnClick()
///// {
///// var result = <see langword="await"/> Module.AddMetaAsync("styles.css")
///// }
///// </code>
///// </para>
///// </summary>
///// <param name="module"><see cref="JSModule"/> 实例</param>
///// <param name="content">添加的 Meta 内容</param>
///// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
//public static ValueTask<bool> AddMetaAsync(this JSModule module, string content) => module.InvokeAsync<bool>("addMeta", content);

///// <summary>
///// 动态移除 head 标签
///// <para>
///// 示例:
///// <code>
///// [Inject]
///// [NotNull]
///// private <see cref="IJSRuntime"/> JSRuntime
/////
///// [NotNull]
///// private <see cref="JSModule"/>? Module { get; set; }
/////
///// protected override <see langword="async"/> <see cref="Task"/> OnAfterRenderAsync(bool firstRender)
///// {
///// <see langword="await"/> <see langword="base"/>.OnAfterRenderAsync(firstRender);
/////
///// <see langword="if"/>(firstRender)
///// {
///// Module = <see langword="await"/> JSRuntime.LoadUtility();
///// }
///// }
/////
///// private <see langword="async"/> <see cref="Task"/> OnClick()
///// {
///// var result = <see langword="await"/> Module.RemoveMetaAsync("styles.css")
///// }
///// </code>
///// </para>
///// </summary>
///// <param name="module"><see cref="JSModule"/> 实例</param>
///// <param name="content">移除 Meta 内容</param>
///// <returns>A <see cref="ValueTask"/> that represents the asynchronous invocation operation.</returns>
//public static ValueTask<bool> RemoveMetaAsync(this JSModule module, string content) => module.InvokeAsync<bool>("removeMeta", content);
public static async ValueTask<TValue?> Function<TValue>(this JSModule module, string script, params object?[]? args)
{
TValue? ret = default;
if (module != null)
{
ret = await module.InvokeAsync<TValue?>("runFunction", script, args);
}
return ret;
}

/// <summary>
/// 获取当前终端是否为移动设备
Expand Down
33 changes: 18 additions & 15 deletions src/BootstrapBlazor/Utils/JSModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ namespace BootstrapBlazor.Components;
/// <param name="jSObjectReference"></param>
public class JSModule(IJSObjectReference? jSObjectReference) : IAsyncDisposable
{
/// <summary>
/// IJSObjectReference 实例
/// </summary>
[NotNull]
private IJSObjectReference? Module { get; } = jSObjectReference ?? throw new ArgumentNullException(nameof(jSObjectReference));

/// <summary>
/// InvokeVoidAsync 方法
/// </summary>
Expand Down Expand Up @@ -58,7 +52,10 @@ async ValueTask InvokeVoidAsync()
{
try
{
await Module.InvokeVoidAsync(identifier, cancellationToken, [.. paras]);
if (jSObjectReference != null)
{
await jSObjectReference.InvokeVoidAsync(identifier, cancellationToken, [.. paras]);
}
}
catch (JSException)
{
Expand All @@ -79,7 +76,7 @@ async ValueTask InvokeVoidAsync()
/// <param name="identifier"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object?[]? args) => InvokeAsync<TValue>(identifier, CancellationToken.None, args);
public virtual ValueTask<TValue?> InvokeAsync<TValue>(string identifier, params object?[]? args) => InvokeAsync<TValue?>(identifier, CancellationToken.None, args);

/// <summary>
/// InvokeAsync 方法
Expand All @@ -88,11 +85,11 @@ async ValueTask InvokeVoidAsync()
/// <param name="timeout"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual ValueTask<TValue> InvokeAsync<TValue>(string identifier, TimeSpan timeout, params object?[]? args)
public virtual ValueTask<TValue?> InvokeAsync<TValue>(string identifier, TimeSpan timeout, params object?[]? args)
{
using CancellationTokenSource? cancellationTokenSource = ((timeout == Timeout.InfiniteTimeSpan) ? null : new CancellationTokenSource(timeout));
CancellationToken cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None;
return InvokeAsync<TValue>(identifier, cancellationToken, args);
return InvokeAsync<TValue?>(identifier, cancellationToken, args);
}

/// <summary>
Expand All @@ -102,7 +99,7 @@ public virtual ValueTask<TValue> InvokeAsync<TValue>(string identifier, TimeSpan
/// <param name="cancellationToken"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual async ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken = default, params object?[]? args)
public virtual async ValueTask<TValue?> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken = default, params object?[]? args)
{
var paras = new List<object?>();
if (args != null)
Expand All @@ -111,12 +108,15 @@ public virtual async ValueTask<TValue> InvokeAsync<TValue>(string identifier, Ca
}
return await InvokeAsync();

async ValueTask<TValue> InvokeAsync()
async ValueTask<TValue?> InvokeAsync()
{
TValue ret = default!;
TValue? ret = default;
try
{
ret = await Module.InvokeAsync<TValue>(identifier, cancellationToken, [.. paras]);
if (jSObjectReference != null)
{
ret = await jSObjectReference.InvokeAsync<TValue?>(identifier, cancellationToken, [.. paras]);
}
}
catch (JSException)
{
Expand All @@ -143,7 +143,10 @@ protected virtual async ValueTask DisposeAsyncCore(bool disposing)
{
try
{
await Module.DisposeAsync();
if (jSObjectReference != null)
{
await jSObjectReference.DisposeAsync();
}
}
catch { }
}
Expand Down
8 changes: 4 additions & 4 deletions test/UnitTest/Extensions/JSModuleExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public async Task LoadModule_Ok()
public async Task LoadModule_Exception()
{
var jsRuntime = new MockJSRuntime();
var ex = await Assert.ThrowsAsync<Exception>(async () => await jsRuntime.LoadModule("./mock.js", "test"));
Assert.Equal("load ./mock.js?v=test module fail", ex.Message);
var module = await jsRuntime.LoadModule("./mock.js", "test");
Assert.NotNull(module);
}

[Fact]
Expand Down Expand Up @@ -98,12 +98,12 @@ class MockJSRuntime : IJSRuntime
{
public ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, object?[]? args)
{
throw new NotImplementedException();
throw new TaskCanceledException();
}

public ValueTask<TValue> InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
{
throw new NotImplementedException();
throw new TaskCanceledException();
}
}
}
2 changes: 1 addition & 1 deletion test/UnitTest/Services/WebSpeechSynthesizerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task GetVoices_Ok()
{
var service = Context.Services.GetRequiredService<WebSpeechService>();
var synthesizer = await service.CreateSynthesizerAsync();
var voices = await synthesizer.GetVoices();
await synthesizer.GetVoices();
}

[Fact]
Expand Down
2 changes: 0 additions & 2 deletions test/UnitTest/Utils/JSModuleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public void JSModule_Error()
var js = new MockJSObjectReference();
var module = new JSModule(js);
Assert.NotNull(module);

Assert.Throws<ArgumentNullException>(() => new JSModule(null));
}

[Fact]
Expand Down