Blazor.SourceGenerators
1.0.0
See the version list below for details.
dotnet add package Blazor.SourceGenerators --version 1.0.0
NuGet\Install-Package Blazor.SourceGenerators -Version 1.0.0
<PackageReference Include="Blazor.SourceGenerators" Version="1.0.0" />
paket add Blazor.SourceGenerators --version 1.0.0
#r "nuget: Blazor.SourceGenerators, 1.0.0"
// Install Blazor.SourceGenerators as a Cake Addin #addin nuget:?package=Blazor.SourceGenerators&version=1.0.0 // Install Blazor.SourceGenerators as a Cake Tool #tool nuget:?package=Blazor.SourceGenerators&version=1.0.0
Blazorators: Blazor C# Source Generators
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!
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
8.0.0 | 1,351 | 11/17/2023 |
8.0.0-rc.2.23480.2 | 71 | 10/13/2023 |
7.0.3 | 357 | 2/15/2023 |
7.0.2 | 297 | 2/7/2023 |
7.0.1 | 299 | 1/24/2023 |
7.0.0 | 297 | 1/11/2023 |
2.0.11 | 474 | 10/10/2022 |
2.0.10 | 438 | 5/19/2022 |
2.0.9 | 458 | 4/18/2022 |
2.0.8 | 449 | 4/14/2022 |
2.0.7 | 450 | 4/14/2022 |
2.0.6 | 477 | 4/7/2022 |
2.0.5 | 428 | 4/5/2022 |
2.0.4 | 449 | 4/5/2022 |
2.0.3 | 413 | 4/5/2022 |
2.0.2 | 491 | 4/4/2022 |
2.0.1 | 414 | 4/4/2022 |
1.5.0 | 426 | 3/29/2022 |
1.4.5 | 466 | 3/26/2022 |
1.4.3 | 414 | 3/25/2022 |
1.4.2 | 441 | 3/23/2022 |
1.4.0 | 437 | 3/22/2022 |
1.3.3 | 414 | 3/18/2022 |
1.3.1 | 476 | 3/16/2022 |
1.3.0 | 469 | 3/16/2022 |
1.2.0 | 439 | 3/13/2022 |
1.1.1 | 483 | 3/8/2022 |
1.1.0 | 446 | 3/5/2022 |
1.0.5 | 417 | 3/4/2022 |
1.0.4 | 670 | 3/3/2022 |
1.0.3 | 430 | 2/24/2022 |
1.0.2 | 445 | 2/22/2022 |
1.0.1 | 427 | 2/22/2022 |
1.0.0 | 442 | 2/22/2022 |