做Web开发肯定离不开Javascript,Blazor虽然一定程度上可以用c#来替换Javascript的功能,但是完全抛弃Javascript肯定是不可能的,因此必然需要一种机制让C#可以和Javascript互相调用,也可以称之为互操作。
从调用的方向来看可以是C#调用Javascript,也可以是Javascript调用C#。
1. C#调用Javascript
做Web开发的都知道Javascript脚本都是通过<script>标签来引入的,不过Blazor不能直接在Razor组件里面添加该标签,你只能添加到文件"wwwroot/index.html (Blazor WebAssembly)" 或者 "Pages/_Host.cshtml (Blazor Server)"中。
1.1 IJSRuntime
Blazor提供了IJSRuntime接口让开发者可以方便的进行Javascript调用。
以下是她的声明:
namespace Microsoft.JSInterop
{
public interface IJSRuntime
{
ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args);
}
}
同时还有一组扩展方法
namespace Microsoft.JSInterop
{
public static class JSRuntimeExtensions
{
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, params object[] args);
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args);
public static ValueTask<TValue> InvokeAsync<TValue>(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args);
public static ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object[] args);
public static ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args);
public static ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args);
}
}
其实主要还是用扩展方法,她比原始方法更灵活,如果需要取返回值,则使用InvokeAsync方法,不需要返回值则使用InvokeVoidAsync。
使用之前必选先在Razor组件里面声明注入:
@inject IJSRuntime JSRuntime;
1.2 identifier
identifier作为每个函数的第一个必选参数,表示JS里面函数的标识符,如果是全局函数,直接写函数名就行,如果包含范围(命名空间或实例),则使用"{范围}.{函数名}"的形式。
比如如下js:
//全局
function sayHello() {
alert('Hello');
}
//带范围
window.jsUtils = {
sayHello: function () {
alert('jsUtils:Hello');
}
};
可以这样调用:
<!--不带参数调用-->
<button @onclick="SayHello">全局函数</button>
<button @onclick="JsUtilSayHello">带范围的函数</button>
@code {
void SayHello()
{
this.JSRuntime.InvokeVoidAsync("sayHello");
}
void JsUtilSayHello()
{
this.JSRuntime.InvokeVoidAsync("jsUtils.sayHello");
}
}
1.3 args
args,顾名思义,就是参数了,这个是可选的,而且可以是一个,也可以是多个,可以是简单的值,也可以是对象(要求能够使用JSON序列化)。
示例如下:
// javascript 函数
//全局
function sayHelloPerson(person) {
alert('Hello:' + person.name);
}
//带范围
window.jsUtils = {
sayHelloPerson: function (person) {
alert('jsUtils:Hello:' + person.name);
}
};
<!--Razor 带参数调用-->
<button @onclick="SayHelloPerson">全局函数</button>
<button @onclick="JsUtilSayHelloPerson">带范围函数</button>
@code {
void SayHelloPerson()
{
this.JSRuntime.InvokeVoidAsync("sayHelloPerson",new { Name="六十五号腕" });
}
void JsUtilSayHelloPerson()
{
this.JSRuntime.InvokeVoidAsync("jsUtils.sayHelloPerson", new { Name = "六十五号腕" });
}
}
需要注意的是C#的JSON序列化会将属性名的首字母转成小写,所以js里面要使用小写的属性名。
还有如果有日期类型,不管是作为对象的属性,还是单独作为参数传递给js的时候都会变成字符串,并不是原始的Js Date类型,格式参考:2020-08-08T08:08:08.798+08:00
1.4 获取Js的返回值
获取返回值需要使用InvokeAsync方法,参数和前面的InvokeVoidAsync基本一致,但是需要声明返回值的类型,返回的值可以是简单值,也可以是一个对象。
下面的例子就使用InvokeAsync方法从js函数里面返回了一个对象:
// javascript 函数
function getPerson() {
return {
name: '六十五号腕',
actionDate: new Date()
};
}
// c#
public class Person
{
public string Name { get; set; }
public DateTime ActionDate { get; set; }
}
async void GetPerson()
{
var person= await this.JSRuntime.InvokeAsync<Person>("getPerson");
}
可以看到需要先在c#里面声明一个与js返回值同结构的类用来序列化,测试发现js里面的Date是可以正常序列化成c#里面的DateTime的。
1.5 IJSInProcessRuntime
前面的方法都属于异步方法,如果使用的是WebAssembly模式,Blazor提供了一个额外的接口IJSInProcessRuntime用来进行js的同步调用,这样会节省一些异步的开销,同样是上面的例子,可以写成:
void GetPerson()
{
var person= ((IJSInProcessRuntime)this.JSRuntime).Invoke<Person>("getPerson");
}
需要注意的是这个接口只能从IJSRuntime转换过来,不能直接使用inject注入。
2. Javascript调用C#方法
2.1 JSInvokable
如果想从Javascript里面调用C#的方法,首先就得把C#的方法暴露出去,我们需要在每个C#方法上面加上[JSInvokable]属性,如下所示:
[JSInvokable]
void CSharpMethod1()
{
}
[JSInvokable("Method2")]
void CSharpMethod2()
{
}
该属性有两种构造,默认构造会将C#的函数名暴露给Javascript(这里并不会进行首字母大小写转换,所以如果C#里面是首字母大写,Javascript里面的方法名也必须保持首字母大写。), 另一个构造则可以指定其他的暴露名称。
2.2 调用静态方法
如果想从Javascript调用C#的静态方法,可以使用DotNet.invokeMethod 或 DotNet.invokeMethodAsync这两个方法,一个用于同步方法,另一个用于异步。
这两个方法的参数是一样的,第一个参数是程序集的名称,比如Bigname65.Blazor,第二个是使用JSInvokable属性暴露的方法名,请看下面的例子:
//c# 静态方法
[JSInvokable]
public static string GetName()
{
return "六十五号腕";
}
//javascript 调用
function getName() {
var name = DotNet.invokeMethod("你的程序集名称","GetName");
alert(name);
}
需要注意的是第一个参数是你的程序集名称,但是同一个程序集下面难免会出现重名的静态方法,这时候就要考虑使用[JSInvokable]属性指定额外的调用名称了。
以上是同步调用的例子,异步的情况也差不多,如下所示:
//c# 静态方法
[JSInvokable]
public static Task<string> GetNameAsync()
{
return Task.FromResult<string>("六十五号腕");
}
//javascript 调用
function getNameAsync() {
DotNet.invokeMethodAsync('你的程序集名称', 'GetName')
.then(name => {
alert(name)
});
}
额外参数
如果C#函数带有参数,同样也是支持的,从DotNet.invokeMethod(或Asyc)的第三个参数开始按顺序排列就可以了。
2.3 组件内的实例方法
上一节调用的静态方法,可以是组件内的,也可以是组件外的。如果想调用当前组件内的非静态方法应该如何操作呢?
我们可以声明一个静态的委托类型,在组件初始化的时候给其赋值,然后在Javascript调用的时候直接使用委托就可以了。
针对上面的例子我们稍微做一下修改就可以了:
//Razor组件代码
//1. 声明一个静态委托变量
static Func<string> _funcGetInputName;
protected override void OnInitialized()
{
//2. 组件初始化的时候将实例方法赋值给静态委托变量
_funcGetInputName = GetInputName;
base.OnInitialized();
}
private string GetInputName()
{
return "六十五号腕";
}
// 暴露方法给Js
[JSInvokable]
public static string GetName()
{
//3. Js调用该方法的时候直接使用委托
return _funcGetInputName.Invoke();
}
这里只是一个用来获取值的Func委托,你也可以用Action或者其他自定义的委托。
2.4 组件外的实例方法
调用组件外的非静态方法其实可以单独作为一节,因为她就是一种混合操作,具体过程为:
- 创建C#类,并使用[JSInvokable]属性修饰需要暴露的方法。
- 创建对应的实例,并使用[IJSRuntime]接口传递给Javascript。
- 在Javascript中调用C#实例的方法。
以下是参考代码:
//Razor组件代码
//1. 创建Person类
public class Person
{
string _name = "";
public Person(string name)
{
_name = name;
}
//2. 暴露GetName方法
[JSInvokable]
public string GetName()
{
return _name;
}
}
DotNetObjectReference<Person> _personRef = null;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//3. 创建Person实例
var person = new Person("六十五号腕");
//4. 创建Person的Js引用
_personRef = DotNetObjectReference.Create(person);
//5. 调用Js的setCurrentPerson方法
await JSRuntime.InvokeAsync<string>(
"setCurrentPerson",
_personRef);
}
await base.OnAfterRenderAsync(firstRender);
}
public void Dispose()
{
//6. 组件回收的时候释放Js引用
_personRef?.Dispose();
}
C#里面最好在Dispose的时候释放Js引用,以免引起内存泄漏。
//Javascript代码
var person = undefined;
//C#调用,给全局person赋值
function setCurrentPerson(p) {
person = p;
}
//Js调用,显示当前Person的名字
function showCurrentPerson() {
//同样使用invokeMethod或者invokeMethodAsync方法
var name = person.invokeMethod("GetName");
alert(name);
}
这里的javascript也是使用invokeMethod或者invokeMethodAsync方法来调用C#里面的方法。
更多推荐
Blazor中C#与Javascript的互操作
发布评论