Blazor.LocalStorage.WebAssembly
1.0.1
See the version list below for details.
dotnet add package Blazor.LocalStorage.WebAssembly --version 1.0.1
NuGet\Install-Package Blazor.LocalStorage.WebAssembly -Version 1.0.1
<PackageReference Include="Blazor.LocalStorage.WebAssembly" Version="1.0.1" />
paket add Blazor.LocalStorage.WebAssembly --version 1.0.1
#r "nuget: Blazor.LocalStorage.WebAssembly, 1.0.1"
// Install Blazor.LocalStorage.WebAssembly as a Cake Addin #addin nuget:?package=Blazor.LocalStorage.WebAssembly&version=1.0.1 // Install Blazor.LocalStorage.WebAssembly as a Cake Tool #tool nuget:?package=Blazor.LocalStorage.WebAssembly&version=1.0.1
Blazorators: Blazor C# Source Generators
A C# source generator that creates extensions methods on the Blazor WebAssembly JavaScript implemenation of the IJSInProcessRuntime
type. This library is comprised of two NuGet packages:
Design goals đ¯
I was hoping to use the TypeScript lib.dom.d.ts bits as input. This input would be read, parsed, and cached within the generator. The generator code would be capable of generating extension methods on the IJSRuntime
. Additionally, the generator will create object graphs from the well know web APIs.
Using the lib.dom.d.ts file, we could hypothetically parse various TypeScript type definitions. These definitions could then be converted to C# counterparts. While I realize that not all TypeScript is mappable to C#, there is a bit of room for interpretation.
Consider the following type definition:
/**
An object can programmatically obtain the position of the device.
It gives Web content access to the location of the device. This allows
a Web site or app to offer customized results based on the user's location.
*/
interface Geolocation {
clearWatch(watchId: number): void;
getCurrentPosition(
successCallback: PositionCallback,
errorCallback?: PositionErrorCallback | null,
options?: PositionOptions): void;
watchPosition(
successCallback: PositionCallback,
errorCallback?: PositionErrorCallback | null,
options?: PositionOptions): number;
}
This is from the TypeScript repo, lib.dom.d.ts file lines 5,498-5,502.
Example consumption of source generator âī¸
Ideally, I would like to be able to define a C# class such as this:
[JSAutoInterop(
TypeName = "Geolocation",
PathFromWidow = "window.navigator.geolocation",
Url = "https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API",
OnlyGeneratePureJS = false)]
public static partial class GeolocationExtensions { }
The source generator will expose the JSAutoInteropAttribute
, and consuming libraries will decorate their classes with it. The generator code will see this class, and use the TypeName
from the attribute to find the corresponding type to implement.
With the type name, the generator will generate the corresponding methods, and return types. The method implementations will be extensions of the IJSRuntime
.
The following is an example resulting source generated GeolocationExtensions
object:
using Microsoft.JSInterop;
namespace Microsoft.JSInterop.Extensions;
public static partial class GeolocationExtensions
{
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition"></a>.
/// </summary>
public static ValueTask GetCurrentPositionAsync<T>(
this IJSRuntime jsRuntime,
T dotnetObject,
string successMethodName,
string? errorMethodName = null,
PositionOptions? options = null)
where T : class
{
return jsRuntime.InvokeVoidAsync(
"blazorator.getCurrentLocation",
DotNetObjectReference.Create(dotnetObject),
successMethodName,
errorMethodName,
options
);
}
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition"></a>
/// </summary>
public static ValueTask<double> WatchPositionAsync<T>(
this IJSRuntime jsRuntime,
T dotnetObject,
string successMethodName,
string? errorMethodName = null,
PositionOptions? options = null)
where T : class
{
return jsRuntime.InvokeAsync<double>(
"blazorator.watchPosition",
DotNetObjectReference.Create(dotnetObject),
successMethodName,
errorMethodName,
options
);
}
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch"></a>
/// </summary>
public ValueTask ClearWatchAsync(this IJSRuntime jsRuntime, double id)
{
return jsRuntime.InvokevoidAsync(
"navigator.geolocation.clearWatch", id
);
}
}
The generator will also produce the corresponding APIs object types. For example, the Geolocation API defines the following:
PositionOptions
GeolocationCoordinates
GeolocationPosition
GeolocationPositionError
using System.Text.Json.Serialization;
namespace Microsoft.JSInterop.Extensions;
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition"></a>
/// </summary>
public record GeolocationPosition(
[property: JsonPropertyName("coords")] GeolocationCoordinates Coordinates,
[property: JsonPropertyName("timestamp")] DOMTimeStamp TimeStamp
);
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates"></a>
/// </summary>
public record GeolocationCoordinates(
[property: JsonPropertyName("latitude")] double Latitude,
[property: JsonPropertyName("longitude")] double Longitude,
[property: JsonPropertyName("altitude")] double Altitude,
[property: JsonPropertyName("altitudeAccuracy")] double? AltitudeAccuracy,
[property: JsonPropertyName("heading")] double? Heading,
[property: JsonPropertyName("speed")] double Speed
);
/// <summary>
/// See <a href="https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError"></a>
/// </summary>
public record GeolocationPositionError(
[property: JsonPropertyName("code")] short Code,
[property: JsonPropertyName("message")] string Message
);
// Additional models omitted for brevity...
In addition to this GeolocationExtensions
class being generated, the generator will also generate a bit of JavaScript. Some methods cannot be directly invoked as they define callbacks. The approach the generator takes is to delegate callback methods on a given T
instance, with the JSInvokable
attribute. Our generator should also warn when the corresponding T
instance doesn't define a matching method name that is also JSInvokable
.
const getCurrentLocation =
(dotnetObj, successMethodName, errorMethodName, options) =>
{
if (navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
dotnetObj.invokeMethodAsync(
successMethodName, position);
},
(error) => {
dotnetObj.invokeMethodAsync(
errorMethodName, error);
},
options);
}
};
// Other implementations omitted for brevity...
// But we'd also define a "watchPosition" wrapper.
// The "clearWatch" is a straight pass-thru, no wrapper needed.
window.blazorator = {
getCurrentLocation,
watchPosition
};
The resulting JavaScript will have to be exposed to consuming projects. Additionally, consuming projects will need to adhere to extension method consumption semantics. When calling generated extension methods that require .NET object references of type T
, the callback names should be marked with JSInvokable
and the nameof
operator should be used to ensure names are accurate. Consider the following example consuming Blazor component:
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Microsoft.JSInterop.Extensions;
namespace Example.Components;
// This is the other half of ConsumingComponent.razor
public sealed partial class ConsumingComponent
{
[Inject]
public IJSRuntime JavaScript { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JavaScript.GetCurrentPositionAsync(
this,
nameof(OnCoordinatesPermitted),
nameof(OnErrorRequestingCoordinates));
}
}
[JSInvokable]
public async Task OnCoordinatesPermitted(
GeolocationPosition position)
{
// TODO: consume/handle position.
await InvokeAsync(StateHasChanged);
}
[JSInvokable]
public async Task OnErrorRequestingCoordinates(
GeolocationPositionError error)
{
// TODO: consume/handle error.
await InvokeAsync(StateHasChanged);
}
}
Pseudocode and logical flow âšī¸
- Consumer decorates a
static partial class
with theJavaScriptInteropAttribute
. - Source generator is called:
JavaScriptInteropGenerator.Initialize
JavaScriptInteropGenerator.Execute
- The generator determines the
TypeName
from the attribute of the contextual class.- The
TypeName
is used to look up the corresponding TypeScript type definition. - If found, and a valid API - attempt source generation.
- The
NuGet packages đĻ
This repository will expose two NuGet packages:
- The source-generated
IJSRuntime
extension methods for a select few well-defined APIs. - The source generator itself, as a consumable analyzer package.
References and resources đ
- MDN Web Docs: Web APIs
- TypeScript DOM lib generator
- ASP.NET Core Docs: Blazor JavaScript interop
- Jared Parsons - GitHub Channel 9 Source Generators
- .NET Docs: C# Source Generators
- Source Generators Cookbook
- Source Generators: Design Document
Contributors â¨
Thanks goes to these wonderful people (emoji key):
<table> <tr> <td align="center"><a href="https://www.cnblogs.com/weihanli"><img src="https://avatars.githubusercontent.com/u/7604648?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Weihan Li</b></sub></a><br /><a href="https://github.com/IEvangelist/blazorators/commits?author=WeihanLi" title="Code">đģ</a></td> <td align="center"><a href="https://www.microsoft.com"><img src="https://avatars.githubusercontent.com/u/7679720?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Pine</b></sub></a><br /><a href="https://github.com/IEvangelist/blazorators/commits?author=IEvangelist" title="Code">đģ</a> <a href="#design-IEvangelist" title="Design">đ¨</a> <a href="https://github.com/IEvangelist/blazorators/pulls?q=is%3Apr+reviewed-by%3AIEvangelist" title="Reviewed Pull Requests">đ</a> <a href="#ideas-IEvangelist" title="Ideas, Planning, & Feedback">đ¤</a> <a href="https://github.com/IEvangelist/blazorators/commits?author=IEvangelist" title="Tests">â ī¸</a></td> </tr> </table>
This project follows the all-contributors specification. Contributions of any kind are welcome!
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Microsoft.Extensions.DependencyInjection (>= 6.0.0)
- Microsoft.Extensions.Primitives (>= 6.0.0)
- Microsoft.JSInterop (>= 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (3)
Showing the top 3 popular GitHub repositories that depend on Blazor.LocalStorage.WebAssembly:
Repository | Stars |
---|---|
Azure-Samples/azure-search-openai-demo-csharp
A sample app for the Retrieval-Augmented Generation pattern running in Azure, using Azure Cognitive Search for retrieval and Azure OpenAI large language models to power ChatGPT-style and Q&A experiences.
|
|
IEvangelist/signalr-chat
A chat app built with Blazor WebAssembly, hosted on ASP.NET Core, with the latest C# and SignalR -- need I say more?
|
|
IEvangelist/learning-blazor
The application for the "Learning Blazor: Build Single Page Apps with WebAssembly and C#" O'Reilly Media book by David Pine.
|
Version | Downloads | Last updated |
---|---|---|
8.0.0 | 43,099 | 11/17/2023 |
8.0.0-rc.2.23480.2 | 3,293 | 10/13/2023 |
7.0.3 | 24,239 | 2/15/2023 |
7.0.2 | 296 | 2/7/2023 |
7.0.1 | 360 | 1/24/2023 |
7.0.0 | 6,488 | 1/11/2023 |
2.0.11 | 3,611 | 10/10/2022 |
2.0.10 | 6,716 | 5/19/2022 |
2.0.9 | 589 | 4/18/2022 |
2.0.8 | 440 | 4/14/2022 |
2.0.7 | 431 | 4/14/2022 |
2.0.6 | 461 | 4/7/2022 |
2.0.5 | 853 | 4/5/2022 |
2.0.3 | 436 | 4/5/2022 |
2.0.2 | 432 | 4/4/2022 |
2.0.1 | 434 | 4/4/2022 |
1.5.0 | 451 | 3/29/2022 |
1.4.5 | 476 | 3/26/2022 |
1.4.3 | 428 | 3/25/2022 |
1.4.2 | 457 | 3/23/2022 |
1.4.0 | 442 | 3/22/2022 |
1.3.3 | 448 | 3/18/2022 |
1.3.1 | 428 | 3/16/2022 |
1.3.0 | 441 | 3/16/2022 |
1.2.0 | 532 | 3/13/2022 |
1.1.1 | 556 | 3/8/2022 |
1.1.0 | 438 | 3/5/2022 |
1.0.5 | 446 | 3/4/2022 |
1.0.4 | 458 | 3/3/2022 |
1.0.3 | 481 | 2/24/2022 |
1.0.2 | 458 | 2/22/2022 |
1.0.1 | 444 | 2/22/2022 |
1.0.0 | 678 | 2/22/2022 |