Skip to content

Repository files navigation

JsBind.Net

NugetGitHub Workflow StatusSonar TestsSonar Quality Gate

A package for creating binding from .Net to JavaScript.

How to use this package

This package can be used in two ways.

Create a binding library

This is the case when a binding library can be created to be consumed by other projects or published to NuGet. You would want to do this if the binding is for common JS libraries (e.g. jQuery) or JavaScript API (e.g. File API and Storage API).

First thing that you will need to do is decide on what is the strategy that you would want to publish your library.

The best approach is to separate the library into multiple packages:

  1. WebAssembly bindings
  2. WebAssembly dependency injection extension
  3. Server bindings
  4. Server dependency injection extension

Of course this can also be separated into just 2 packages:

  1. WebAssembly bindings (with or without dependency injection extension)
  2. Server bindings (with or without dependency injection extension)

To get started with creating a WebAssembly or Server binding project:

  1. Create a new class library project.

  2. Install JsBind.Net from Nuget.

  3. Add PrivateAssets="contentfiles" to the package reference in the csproj project file. For example:

    <PackageReference Include="JsBind.Net" Version="x.x.x" PrivateAssets="contentfiles" />

    This is so that the projects consuming your binding library will also include the build assets in the JsBind.Net package.

  4. Start creating bindings. You can refer to the section How to create bindings below.

To create a dependency injection extension project:

  1. Create a new class library project.
  2. Install JsBind.Net.Extensions.DependencyInjection from Nuget.
  3. Create the extension method for registering the binding services.

Create bindings in a web project directly

When you want to interop to the JavaScript libraries in your project from .Net, you can simply create the bindings required and use it.

  1. Install JsBind.Net (if you intend to use the binding without dependency injection) or JsBind.Net.Extensions.DependencyInjection from Nuget.
  2. Create the bindings, you can refer to the section How to create bindings below.
  3. Register the bindings in the dependency container (if you are using dependency injection).
    services.AddJsBind();// if you need this class to be injected to be usedservices.AddTransient<MyBindingClass>();
  4. Add the script tag as shown in the section below to import the JsBind.Net JavaScript file.

For projects using the binding without Blazor

The projects using the binding from a binding library or from the project itself will need to include the JavaScript file from JsBind.Net with

<scriptsrc="_content/JsBind.Net/JsBindNet.js"></script>

How to create bindings

The test binding project showcases how to create bindings for:

  1. WebAssembly (synchronous)
  2. Server (asynchronous)
  3. WebAssembly dependency injection extension
  4. Server dependency injection extension

For simplicity the test binding project is not separated into multiple projects as advised in the project separation strategy above.

Binding classes

You can start creating binding from the root object that needs to be bound, for example the storage API:

publicclassLocalStorage:ObjectBindingBase{publicLocalStorage(IJsRuntimeAdapterjsRuntime){SetAccessPath("localStorage");Initialize(jsRuntime);}publicstringGetItem(stringkey)=>Invoke<string>("getItem",key);publicstringSetItem(stringkey,stringvalue)=>Invoke<string>("setItem",key,value);publicstringRemoveItem(stringkey)=>Invoke<string>("removeItem",key);publicvoidClear()=>InvokeVoid("clear");}

As for the asynchronous version of the binding, all the return type has to be wrapped in ValueTask and the Async version of Invoke, InvokeVoid and GetProperty has to be used instead.

publicclassLocalStorage:ObjectBindingBase{publicLocalStorage(IJsRuntimeAdapterjsRuntime){SetAccessPath("localStorage");Initialize(jsRuntime);}publicValueTask<string>GetItem(stringkey)=>InvokeAsync<string>("getItem",key);publicValueTask<string>SetItem(stringkey,stringvalue)=>InvokeAsync<string>("setItem",key,value);publicValueTask<string>RemoveItem(stringkey)=>InvokeAsync<string>("removeItem",key);publicValueTaskClear()=>InvokeVoidAsync("clear");}

The simplest way is to inherit from ObjectBindingBase class which offers the following APIs:

APIDescription
SetAccessPathSets the access path of the object relative to the globalThis variable.
GetProperty/GetPropertyAsyncGets a property value from the JavaScript object with the specified property name.
SetProperty/SetPropertyAsyncSets a property value to the JavaScript object with the specified property name.
Invoke/InvokeAsyncInvoke a function matching the specified function name to the JavaScript object with return value.
InvokeVoid/InvokeVoidAsyncInvoke a function matching the specified function name to the JavaScript object without return value.
ConvertToType/ConvertToTypeAsyncConverts the current object to the specified type.

Binding class constructor

If the binding class can be used directly to perform interop, meaning they can be accessed from the top level globalThis (e.g. globalThis.document/globalThis.window/globalThis.jQuery), the binding class needs a constructor that receives the IJsRuntimeAdapter to be able to interop to JavaScript. The constructor has to use the SetAccessPath API to set the path relative to the globalThis variable. Example of constructor:

publicLocalStorage(IJsRuntimeAdapter jsRuntime){SetAccessPath("localStorage");Initialize(jsRuntime);}publicWindow(IJsRuntimeAdapter jsRuntime){SetAccessPath("window");Initialize(jsRuntime);}

If the binding class represents the structure of objects that can be returned from a JavaScript interop, it needs to have an parameterless constructor, or a constructor that can be deserialized (e.g. decorated with JsonConstructor attribute).

// Location class representing the object returned from window.location
public class Location
{
// Initialized from JSON deserialization
public Location()
{
}
[JsonPropertyName("href")]
public string Href { get; set; }
}

Binding attributes

You should use the binding attributes to define the behaviour of the interop and serialization/deserialization.

Attributes from the System.Text.Json can be used for the serialization and deserialization behaviour.

Attributes that can be used for binding are

AttributeUsageDescription
BindDeclaredPropertiesAttributeClassInclude the public properties with setter that are declared in the class for binding.
BindAllPropertiesAttributeClassInclude all properties from the JavaScript object for binding.
BindIncludePropertiesAttributeClassInclude the specified properties from the JavaScript object for binding.
BindExcludePropertiesAttributeClassInclude all properties except for the specified properties from the JavaScript object for binding.
BindIgnoreAttributePropertyIgnore this property from binding (Only when the class is decorated with BindDeclaredPropertiesAttribute).

Properties decorated with JsonIgnoreAttribute and BindIgnoreAttribute will be excluded from binding.

Dynamic binding class

There are cases where you may want to create a binding dynamically, either from an existing ObjectBindingBase instance, or just simply from an access path. For example, to achieve this in JavaScript:

constnewDiv=document.createElement("div");document.body.append(newDiv);

The equivalent code would be:

vardocument=Any.From("document",jsRuntime);varnewDiv=document.InvokeFunction<Any>("createElement","div");document["body"].InvokeFunctionVoid("append",newDiv);

Object reference disposal

Objects returned from function invocation are stored as object references, and delegates passed in as parameter to function invocation are stored as delegate references. The object references are stored in the JavaScript, whereas delegate references are stored in both JavaScript and DotNet.

If your library or the consuming project invokes a lot of functions, it will be good to dispose the object and delegate references. If the object reference is an instance of the BindingBase and is the root of the object reference, either the Dispose or DisposeAsync method can be used to dispose it in JavaScript. Otherwise, the JsObjectManager can be used with the following APIs:

MethodDescription
DisposeObjectReference/DisposeObjectReferenceAsyncDisposes the object reference, if the object (can be enumerable) is the root object reference.
DisposeRootObjectReference/DisposeRootObjectReferenceAsyncDisposes the root object reference.
DisposeDelegateReference/DisposeDelegateReferenceAsyncDisposes the delegate reference in both JavaScript and DotNet.
DisposeSession/DisposeSessionAsyncDisposes all the references for the session.

Any disposed object reference and delegate reference can no longer perform any operation such as GetProperty or InvokeFunction.

If the consuming project is a Blazor server, the references in the server needs to be cleared everytime a session ends. In order to do this, you can create a component.

publicclassSessionController:ComponentBase,IDisposable{[Inject]publicIJsRuntimeAdapterJsRuntime{get;set;}publicvoidDispose(){JsObjectManager.DisposeSession(JsRuntime);}}

And in App.razor, add this component next to the <Router> component.

<RouterAppAssembly="@typeof(Program).Assembly">
// ...
</Router>
<SessionController />

Customize build

The following MSBuild properties can be specified in your project file or when running dotnet build command.

PropertyDefault valueDescription
IncludeJsBindAssetstrueIf set to false, the JavaScript files will not be added to the project.
JsBindModuleAssettrueIf set to false, the JavaScript library initializer module will not be added to the project.
JsBindScriptAssetfalseIf set to false, the JavaScript script file will not be added to the project.
LinkJsBindAssetsfalseIf set to false, the JavaScript files are added as static web assets instead of linked files.
LinkJsBindAssetsPathwwwroot\_content\JsBind.NetThe root folder where the JavaScript files should be added as link.

Example projects

About

Create binding from .Net to JavaScript

Topics

Resources

Contributing

Stars

17 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages