KEMBAR78
Writing JavaScript for C# Blazor.pptx
Writing
JavaScript
for C#'s
Blazor
#DOTNETFRONTEND
Ed Charbeneau
Author:
Pr. Developer Advocate
Progress Software,
Telerik UI for Blazor
“Blazor: A Beginner's Guide” (free)
Тwitter: @EdCharbeneau
Тwitch: Twitch.tv/EdCharbeneau
Why do we need it?
Blazor is really, really, new!
C# Doesn’t have a full API surface for the Browser’s Web APIs
Why do don’t we need it?
LocalStorage
◦ dotnet add package Blazored.LocalStorage
MediaQueries
◦ dotnet add package BlazorPro.BlazorSize
File Uploads
◦ dotnet add package Telerik.UI.for.Blazor
• Commercial library
.NET 5, 6+
◦ SEO support, <Head>
What’s the JavaScript Interop
A Blazor app can invoke JavaScript functions from .NET methods
and .NET methods from JavaScript functions.
How do we do it?
C# API
DotNetObjectReference<T>
◦ Value (T instance)
◦ Dispose()
IJSRuntime
◦ .InvokeAsync<T>(fname, p)
◦ .InvokeVoidAsync(fname, p)
JAVASCRIPT API
DotNetObjectReference
◦ number | string (guid)
◦ invokeMethodAsync(fname, p)
◦ dispose
Parameters & Values must be JSON serializeable
How do we do it?
CONVENTION
Use namespaces
window.namespace.function
window.myFoo = {
doFoo: function() {}
}
UNLESS
The function already exists on the
window object.
window.alert
Parameters & Values must be JSON serializeable
InvokeVoidAsync
@inject IJSRuntime js
await js.InvokeVoidAsync("myFunction", "Hello");
window.myFunction = (text) => alert(text);
JS C#
Component Lifecycle Awareness
SetParametersAsync()
OnInitializedAsync()
OnParametersSetAsync()
OnAfterRenderAsync(bool
firstRender)
ShouldRender()
Avoid JavaScript interop during
Initialization. Because Blazor can
utilize Server rendering, JavaScript
may not be available.
Use OnAfterRenderAsync for
initializing JavaScript.
Lazy Loaing Modules
Lazy<Task<IJSObjectReference>>
InvokeAsync<IJSObjectReference>
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
public MyService(IJSRuntime jsRuntime)
{
moduleTask = new(() =>
jsRuntime.InvokeAsync<IJSObjectReference>("import",
"./_content/Namespace/module.js").AsTask());
}
public async Task Foo() {
module.InvokeVoidAsync("method", args)
}
let module = await
import('/modules/module.js');
JS C#
Scenarios
One Way
Round-Trip
Round-Trip Callback
Throttle Events
Scenarios: One Way
Invoke JavaScript from C#
Fire and forget
No return value
private readonly IJSRuntime jsRuntime;
private async ValueTask Cancel() =>
await jsRuntime.InvokeVoidAsync($“namespace.functionName“, parameters);
C# JS window.alert
Void
C#
window.namespace = { function: functionName() } JS
Scenarios: Round Trip (C# <-> JS)
Invoke JavaScript from C#
Return value
C# JS invokeMethod
Value
string name = await js.InvokeAsync<string>("prompt", "What is your name?");
Result = $"Your name is: {name}";
C#
Scenarios: Round Trip (JS <-> C#)
[JSInvokable] Attribute
Invoke C# Static Method from JS
JS C# RaiseEvent
Value
[JSInvokable]
[JSInvokable]
public static Task<int[]> ReturnArrayAsync()
Task.FromResult(new int[] { 1, 2, 3 });
returnArrayAsyncJs: function () {
DotNet.invokeMethodAsync(‘namespace', 'ReturnArrayAsync')
.then(data => {
data.push(4);
console.log(data);
});
},
C#
JS
Scenarios: Round-Trip Callback
Invoke JavaScript from .NET
Invoke C# Instance Method from JS
DotNetObjectReference
Dispose, dispose, dispose
C# JS
Function.callbac
k
Value
R
R
Instance.method
Scenarios: Round-Trip Callback
public class GeoLocation
{
public async ValueTask GetCurrentPosition(...) =>
await js.InvokeVoidAsync
("blazorGeolocation.getCurrentPosition",
DotNetObjectReference.Create(this), options);
[JSInvokable]
public void RaiseOnGetPosition(Position p) =>
[JSInvokable]
public void RaiseOnGetPositionError(Error err) =>
window.blazorGeolocation = {
getCurrentPosition: function (geolocationRef, options) {
navigator.geolocation
.getCurrentPosition(onSuccess, onError, options);
},
function onSuccess(result) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPosition’,
blazorGeolocation.toSerializeable(result));
};
function onError(er) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPositionError’,
er.code);
};
};
JS
C#
Scenarios: Round-Trip Callback
public class GeoLocation
{
public async ValueTask GetCurrentPosition(...) =>
await js.InvokeVoidAsync
("blazorGeolocation.getCurrentPosition",
DotNetObjectReference.Create(this), options);
[JSInvokable]
public void RaiseOnGetPosition(Position p) =>
[JSInvokable]
public void RaiseOnGetPositionError(Error err) =>
window.blazorGeolocation = {
getCurrentPosition: function (geolocationRef, options) {
navigator.geolocation
.getCurrentPosition(onSuccess, onError, options);
},
function onSuccess(result) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPosition’,
blazorGeolocation.toSerializeable(result));
};
function onError(er) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPositionError’,
er.code);
};
};
JS
C#
Scenarios: Round-Trip Callback
public class GeoLocation
{
public async ValueTask GetCurrentPosition(...) =>
await js.InvokeVoidAsync
("blazorGeolocation.getCurrentPosition",
DotNetObjectReference.Create(this), options);
[JSInvokable]
public void RaiseOnGetPosition(Position p) =>
[JSInvokable]
public void RaiseOnGetPositionError(Error err) =>
window.blazorGeolocation = {
getCurrentPosition: function (geolocationRef, options) {
navigator.geolocation
.getCurrentPosition(onSuccess, onError, options);
},
function onSuccess(result) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPosition’,
blazorGeolocation.toSerializeable(result));
};
function onError(er) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPositionError’,
er.code);
};
};
JS
C#
Scenarios: Round-Trip Callback
public class GeoLocation
{
public async ValueTask GetCurrentPosition(...) =>
await js.InvokeVoidAsync
("blazorGeolocation.getCurrentPosition",
DotNetObjectReference.Create(this), options);
[JSInvokable]
public void RaiseOnGetPosition(Position p) =>
[JSInvokable]
public void RaiseOnGetPositionError(Error err) =>
window.blazorGeolocation = {
getCurrentPosition: function (geolocationRef, options) {
navigator.geolocation
.getCurrentPosition(onSuccess, onError, options);
},
function onSuccess(result) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPosition’,
blazorGeolocation.toSerializeable(result));
};
function onError(er) {
return geolocationRef
.invokeMethodAsync('RaiseOnGetPositionError’,
er.code);
};
};
JS
C#
Pub-Sub
JS C#
DOM EventA
DOM EventB
Channel
Component (A)
Component (AB)
Component (B)
Component (AB)
Pub-Sub: Init
JS C#
Channel
Component (A)
.addEventListener
DOM EventA
Pub-Sub: De-dupe
JS C#
Channel
Component (A)
.addEventListener
DOM EventA
DOM EventB Component (AB)
DOM EventA
Pub-Sub: Dispose
JS C#
DOM EventA
DOM EventB
Channel
Component (A)
Component (AB)
Component (B)
Component (AB)
.removeEventListener
.Dispose
Pub-Sub: Developer Experience
JS C#
DOM EventA
DOM EventB
Channel
Component (A)
Component
(AB)
Component (B)
Component
(AB)
<MediaQueryList>
<MediaQuery>
<MediaQuery>
<MediaQuery>
<MediaQuery>
mediaQueryLists: MediaQueryListItem[]
MediaQueryListItem {
id: number | string;
dotnetCallback: (ev: MediaQueryListEvent)
mediaQueries: MediaQueryList[];
}
Scenario: Throttle Events (Blazor
Server)
A B C D E F G
A D G
throttleTime(n_ms)
Scenario: Throttle Events (Blazor
Server)
this.reportRate = 300;
this.throttleResizeHandler = () => {
clearTimeout(this.throttleResizeHandlerId);
this.throttleResizeHandlerId = window.setTimeout(this.resizeHandler, this.options.reportRate);
};
Tools, testing?
Npm
Jest
TypeScript
Rollup
Typescript
DotNetObjectReference type is number | string (int | guid)
Remove .json files from NuGet Packages
{
"compilerOptions": {
"outDir": "./wwwroot",
"target": "ES6",
"moduleResolution": "node",
"lib": [ "ES2016", "DOM"]
},
"exclude": [
"node_modules",
"wwwroot",
"**/*.test.ts"
]
}
tsconfig.json
_content/Namespace/resource.*
Razor Class Library
Assets go in wwwroot
wwwroot becomes => _content/Namespace/*
Do provide minified and standard formats
Example
<script src="_content/BlazorPro.BlazorSize/blazorSize.min.js"></script>
Blazorators?
A project using C# source generators to create JavaScript interop libraries automatically
from web specifications.
https://github.com/IEvangelist/blazorators
https://www.nuget.org/packages?q=dpine+blazor
QUESTIONS
Resources
1. EdCharbeneau.com
• Free Ebook & examples
• GitHub
• Twitter
• Twitch
2. Blogs.Telerik.com

Writing JavaScript for C# Blazor.pptx

  • 1.
  • 2.
    Ed Charbeneau Author: Pr. DeveloperAdvocate Progress Software, Telerik UI for Blazor “Blazor: A Beginner's Guide” (free) Тwitter: @EdCharbeneau Тwitch: Twitch.tv/EdCharbeneau
  • 3.
    Why do weneed it? Blazor is really, really, new! C# Doesn’t have a full API surface for the Browser’s Web APIs
  • 5.
    Why do don’twe need it? LocalStorage ◦ dotnet add package Blazored.LocalStorage MediaQueries ◦ dotnet add package BlazorPro.BlazorSize File Uploads ◦ dotnet add package Telerik.UI.for.Blazor • Commercial library .NET 5, 6+ ◦ SEO support, <Head>
  • 6.
    What’s the JavaScriptInterop A Blazor app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions.
  • 7.
    How do wedo it? C# API DotNetObjectReference<T> ◦ Value (T instance) ◦ Dispose() IJSRuntime ◦ .InvokeAsync<T>(fname, p) ◦ .InvokeVoidAsync(fname, p) JAVASCRIPT API DotNetObjectReference ◦ number | string (guid) ◦ invokeMethodAsync(fname, p) ◦ dispose Parameters & Values must be JSON serializeable
  • 8.
    How do wedo it? CONVENTION Use namespaces window.namespace.function window.myFoo = { doFoo: function() {} } UNLESS The function already exists on the window object. window.alert Parameters & Values must be JSON serializeable
  • 9.
    InvokeVoidAsync @inject IJSRuntime js awaitjs.InvokeVoidAsync("myFunction", "Hello"); window.myFunction = (text) => alert(text); JS C#
  • 10.
    Component Lifecycle Awareness SetParametersAsync() OnInitializedAsync() OnParametersSetAsync() OnAfterRenderAsync(bool firstRender) ShouldRender() AvoidJavaScript interop during Initialization. Because Blazor can utilize Server rendering, JavaScript may not be available. Use OnAfterRenderAsync for initializing JavaScript.
  • 11.
    Lazy Loaing Modules Lazy<Task<IJSObjectReference>> InvokeAsync<IJSObjectReference> privatereadonly Lazy<Task<IJSObjectReference>> moduleTask; public MyService(IJSRuntime jsRuntime) { moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/Namespace/module.js").AsTask()); } public async Task Foo() { module.InvokeVoidAsync("method", args) } let module = await import('/modules/module.js'); JS C#
  • 12.
  • 13.
    Scenarios: One Way InvokeJavaScript from C# Fire and forget No return value private readonly IJSRuntime jsRuntime; private async ValueTask Cancel() => await jsRuntime.InvokeVoidAsync($“namespace.functionName“, parameters); C# JS window.alert Void C# window.namespace = { function: functionName() } JS
  • 14.
    Scenarios: Round Trip(C# <-> JS) Invoke JavaScript from C# Return value C# JS invokeMethod Value string name = await js.InvokeAsync<string>("prompt", "What is your name?"); Result = $"Your name is: {name}"; C#
  • 15.
    Scenarios: Round Trip(JS <-> C#) [JSInvokable] Attribute Invoke C# Static Method from JS JS C# RaiseEvent Value [JSInvokable] [JSInvokable] public static Task<int[]> ReturnArrayAsync() Task.FromResult(new int[] { 1, 2, 3 }); returnArrayAsyncJs: function () { DotNet.invokeMethodAsync(‘namespace', 'ReturnArrayAsync') .then(data => { data.push(4); console.log(data); }); }, C# JS
  • 16.
    Scenarios: Round-Trip Callback InvokeJavaScript from .NET Invoke C# Instance Method from JS DotNetObjectReference Dispose, dispose, dispose C# JS Function.callbac k Value R R Instance.method
  • 17.
    Scenarios: Round-Trip Callback publicclass GeoLocation { public async ValueTask GetCurrentPosition(...) => await js.InvokeVoidAsync ("blazorGeolocation.getCurrentPosition", DotNetObjectReference.Create(this), options); [JSInvokable] public void RaiseOnGetPosition(Position p) => [JSInvokable] public void RaiseOnGetPositionError(Error err) => window.blazorGeolocation = { getCurrentPosition: function (geolocationRef, options) { navigator.geolocation .getCurrentPosition(onSuccess, onError, options); }, function onSuccess(result) { return geolocationRef .invokeMethodAsync('RaiseOnGetPosition’, blazorGeolocation.toSerializeable(result)); }; function onError(er) { return geolocationRef .invokeMethodAsync('RaiseOnGetPositionError’, er.code); }; }; JS C#
  • 18.
    Scenarios: Round-Trip Callback publicclass GeoLocation { public async ValueTask GetCurrentPosition(...) => await js.InvokeVoidAsync ("blazorGeolocation.getCurrentPosition", DotNetObjectReference.Create(this), options); [JSInvokable] public void RaiseOnGetPosition(Position p) => [JSInvokable] public void RaiseOnGetPositionError(Error err) => window.blazorGeolocation = { getCurrentPosition: function (geolocationRef, options) { navigator.geolocation .getCurrentPosition(onSuccess, onError, options); }, function onSuccess(result) { return geolocationRef .invokeMethodAsync('RaiseOnGetPosition’, blazorGeolocation.toSerializeable(result)); }; function onError(er) { return geolocationRef .invokeMethodAsync('RaiseOnGetPositionError’, er.code); }; }; JS C#
  • 19.
    Scenarios: Round-Trip Callback publicclass GeoLocation { public async ValueTask GetCurrentPosition(...) => await js.InvokeVoidAsync ("blazorGeolocation.getCurrentPosition", DotNetObjectReference.Create(this), options); [JSInvokable] public void RaiseOnGetPosition(Position p) => [JSInvokable] public void RaiseOnGetPositionError(Error err) => window.blazorGeolocation = { getCurrentPosition: function (geolocationRef, options) { navigator.geolocation .getCurrentPosition(onSuccess, onError, options); }, function onSuccess(result) { return geolocationRef .invokeMethodAsync('RaiseOnGetPosition’, blazorGeolocation.toSerializeable(result)); }; function onError(er) { return geolocationRef .invokeMethodAsync('RaiseOnGetPositionError’, er.code); }; }; JS C#
  • 20.
    Scenarios: Round-Trip Callback publicclass GeoLocation { public async ValueTask GetCurrentPosition(...) => await js.InvokeVoidAsync ("blazorGeolocation.getCurrentPosition", DotNetObjectReference.Create(this), options); [JSInvokable] public void RaiseOnGetPosition(Position p) => [JSInvokable] public void RaiseOnGetPositionError(Error err) => window.blazorGeolocation = { getCurrentPosition: function (geolocationRef, options) { navigator.geolocation .getCurrentPosition(onSuccess, onError, options); }, function onSuccess(result) { return geolocationRef .invokeMethodAsync('RaiseOnGetPosition’, blazorGeolocation.toSerializeable(result)); }; function onError(er) { return geolocationRef .invokeMethodAsync('RaiseOnGetPositionError’, er.code); }; }; JS C#
  • 21.
    Pub-Sub JS C# DOM EventA DOMEventB Channel Component (A) Component (AB) Component (B) Component (AB)
  • 22.
    Pub-Sub: Init JS C# Channel Component(A) .addEventListener DOM EventA
  • 23.
    Pub-Sub: De-dupe JS C# Channel Component(A) .addEventListener DOM EventA DOM EventB Component (AB) DOM EventA
  • 24.
    Pub-Sub: Dispose JS C# DOMEventA DOM EventB Channel Component (A) Component (AB) Component (B) Component (AB) .removeEventListener .Dispose
  • 25.
    Pub-Sub: Developer Experience JSC# DOM EventA DOM EventB Channel Component (A) Component (AB) Component (B) Component (AB) <MediaQueryList> <MediaQuery> <MediaQuery> <MediaQuery> <MediaQuery> mediaQueryLists: MediaQueryListItem[] MediaQueryListItem { id: number | string; dotnetCallback: (ev: MediaQueryListEvent) mediaQueries: MediaQueryList[]; }
  • 26.
    Scenario: Throttle Events(Blazor Server) A B C D E F G A D G throttleTime(n_ms)
  • 27.
    Scenario: Throttle Events(Blazor Server) this.reportRate = 300; this.throttleResizeHandler = () => { clearTimeout(this.throttleResizeHandlerId); this.throttleResizeHandlerId = window.setTimeout(this.resizeHandler, this.options.reportRate); };
  • 28.
  • 29.
    Typescript DotNetObjectReference type isnumber | string (int | guid) Remove .json files from NuGet Packages { "compilerOptions": { "outDir": "./wwwroot", "target": "ES6", "moduleResolution": "node", "lib": [ "ES2016", "DOM"] }, "exclude": [ "node_modules", "wwwroot", "**/*.test.ts" ] } tsconfig.json
  • 30.
    _content/Namespace/resource.* Razor Class Library Assetsgo in wwwroot wwwroot becomes => _content/Namespace/* Do provide minified and standard formats Example <script src="_content/BlazorPro.BlazorSize/blazorSize.min.js"></script>
  • 31.
    Blazorators? A project usingC# source generators to create JavaScript interop libraries automatically from web specifications. https://github.com/IEvangelist/blazorators https://www.nuget.org/packages?q=dpine+blazor
  • 32.
    QUESTIONS Resources 1. EdCharbeneau.com • FreeEbook & examples • GitHub • Twitter • Twitch 2. Blogs.Telerik.com