Skip to content

Commit

Permalink
Make all generated APIs for the server-side support generics by defau…
Browse files Browse the repository at this point in the history
…lt. This is a breaking change, but it's better this way.
  • Loading branch information
IEvangelist committed Dec 1, 2024
1 parent 57200a5 commit b2a509d
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
return;
}

_transcript = await SessionStorage.GetItemAsync(TranscriptKey);
_transcript = await SessionStorage.GetItemAsync<string?>(TranscriptKey);
}

async Task OnRecognizeSpeechClick()
Expand Down
8 changes: 4 additions & 4 deletions samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed partial class ReadToMe : IAsyncDisposable
string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types.";
SpeechSynthesisVoice[] _voices = [];
readonly IList<double> _voiceSpeeds =
Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList();
[.. Enumerable.Range(0, 12).Select(i => (i + 1) * .25)];
double _voiceSpeed = 1.5;
string? _selectedVoice;
string? _elapsedTimeMessage = null;
Expand Down Expand Up @@ -48,18 +48,18 @@ protected override async Task OnAfterRenderAsync(bool firstRender)

await GetVoicesAsync();

if (await LocalStorage.GetItemAsync(PreferredVoiceKey)
if (await LocalStorage.GetItemAsync<string?>(PreferredVoiceKey)
is { Length: > 0 } voice)
{
_selectedVoice = voice;
}
if (await LocalStorage.GetItemAsync(PreferredSpeedKey)
if (await LocalStorage.GetItemAsync<string?>(PreferredSpeedKey)
is { Length: > 0 } s &&
double.TryParse(s, out var speed) && speed > 0)
{
_voiceSpeed = speed;
}
if (await SessionStorage.GetItemAsync(TextKey)
if (await SessionStorage.GetItemAsync<string?>(TextKey)
is { Length: > 0 } text)
{
_text = text;
Expand Down
19 changes: 2 additions & 17 deletions samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,13 @@ async Task UpdateTodoItemsAsync()
{
if (key.StartsWith(TodoItem.IdPrefix))
{
var rawValue = await LocalStorage.GetItemAsync(key);
if (rawValue.FromJson<TodoItem>() is TodoItem todo)
var todo = await LocalStorage.GetItemAsync<TodoItem?>(key);
if (todo is not null)
{
todos.Add(todo);
_localStorageItems[key] = todo.ToString();
continue;
}
if (rawValue is not null)
{
_localStorageItems[key] = rawValue;
continue;
}
if (bool.TryParse(rawValue, out var @bool))
{
_localStorageItems[key] = @bool.ToString();
continue;
}
if (decimal.TryParse(rawValue, out var num))
{
_localStorageItems[key] = num.ToString();
continue;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Blazor.LocalStorage/Blazor.LocalStorage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Blazor.Serialization\Blazor.Serialization.csproj" />
<ProjectReference Include="..\Blazor.SourceGenerators\Blazor.SourceGenerators.csproj" OutputItemType="Analyzer" SetTargetFramework="TargetFramework=netstandard2.0" ReferenceOutputAssembly="false" />
</ItemGroup>

Expand Down
7 changes: 4 additions & 3 deletions src/Blazor.LocalStorage/ILocalStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

namespace Microsoft.JSInterop;

[JSAutoInterop(
[JSAutoGenericInterop(
TypeName = "Storage",
Implementation = "window.localStorage",
HostingModel = BlazorHostingModel.Server,
OnlyGeneratePureJS = true,
Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage",
TypeDeclarationSources =
GenericMethodDescriptors =
[
"https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.dom.d.ts"
"getItem",
"setItem:value"
])]
public partial interface ILocalStorageService;
90 changes: 86 additions & 4 deletions src/Blazor.Serialization/Extensions/SerializationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,99 @@ public static class SerializationExtensions
}
};

/// <inheritdoc cref="Deserialize{TValue}(string, JsonSerializerOptions?)" />
/// <inheritdoc cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions?)" />
public static TResult? FromJson<TResult>(
this string? json,
JsonSerializerOptions? options = null) =>
json is { Length: > 0 }
? Deserialize<TResult>(json, options ?? _defaultOptions)
? JsonSerializer.Deserialize<TResult>(json, options ?? _defaultOptions)
: default;

/// <inheritdoc cref="Serialize{TValue}(TValue, JsonSerializerOptions?)" />
/// <inheritdoc cref="JsonSerializer.Deserialize{TValue}(string, JsonTypeInfo{TValue})" />
public static TResult? FromJson<TResult>(
this string? json,
JsonTypeInfo<TResult>? jsonTypeInfo)
{
return json switch
{
{ Length: > 0 } => jsonTypeInfo switch
{
null => JsonSerializer.Deserialize<TResult>(json, _defaultOptions),
_ => JsonSerializer.Deserialize(json, jsonTypeInfo)
},
_ => default
};
}

/// <inheritdoc cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions?)" />
public static async ValueTask<TResult?> FromJsonAsync<TResult>(
this ValueTask<string?> jsonTask,
JsonSerializerOptions? options = null)
{
var json = await jsonTask.ConfigureAwait(false);

return json is { Length: > 0 }
? JsonSerializer.Deserialize<TResult>(json, options ?? _defaultOptions)
: default;
}

/// <inheritdoc cref="JsonSerializer.Deserialize{TValue}(string, JsonTypeInfo{TValue})" />
public static async ValueTask<TResult?> FromJsonAsync<TResult>(
this ValueTask<string?> jsonTask,
JsonTypeInfo<TResult>? jsonTypeInfo)
{
var json = await jsonTask.ConfigureAwait(false);

return json switch
{
{ Length: > 0 } => jsonTypeInfo switch
{
null => JsonSerializer.Deserialize<TResult>(json, _defaultOptions),
_ => JsonSerializer.Deserialize(json, jsonTypeInfo)
},
_ => default
};
}

/// <inheritdoc cref="JsonSerializer.Serialize{TValue}(TValue, JsonSerializerOptions?)" />
public static string ToJson<T>(
this T value,
JsonSerializerOptions? options = null) =>
Serialize(value, options ?? _defaultOptions);
JsonSerializer.Serialize(value, options ?? _defaultOptions);

/// <inheritdoc cref="JsonSerializer.Serialize{TValue}(TValue, JsonTypeInfo{TValue})" />
public static string ToJson<T>(
this T value,
JsonTypeInfo<T>? jsonTypeInfo)
{
return jsonTypeInfo switch
{
null => JsonSerializer.Serialize(value, _defaultOptions),
_ => JsonSerializer.Serialize(value, jsonTypeInfo)
};
}

/// <inheritdoc cref="JsonSerializer.Serialize{TValue}(TValue, JsonSerializerOptions?)" />
public static async ValueTask<string> ToJsonAsync<T>(
this ValueTask<T> valueTask,
JsonSerializerOptions? options = null)
{
var value = await valueTask.ConfigureAwait(false);

return JsonSerializer.Serialize(value, options ?? _defaultOptions);
}

/// <inheritdoc cref="JsonSerializer.Serialize{TValue}(TValue, JsonTypeInfo{TValue})" />
public static async ValueTask<string> ToJsonAsync<T>(
this ValueTask<T> valueTask,
JsonTypeInfo<T>? jsonTypeInfo)
{
var value = await valueTask.ConfigureAwait(false);

return jsonTypeInfo switch
{
null => JsonSerializer.Serialize(value, _defaultOptions),
_ => JsonSerializer.Serialize(value, jsonTypeInfo)
};
}
}
2 changes: 1 addition & 1 deletion src/Blazor.Serialization/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

global using System.Text.Json;
global using System.Text.Json.Serialization;
global using static System.Text.Json.JsonSerializer;
global using System.Text.Json.Serialization.Metadata;
1 change: 1 addition & 0 deletions src/Blazor.SessionStorage/Blazor.SessionStorage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Blazor.Serialization\Blazor.Serialization.csproj" />
<ProjectReference Include="..\Blazor.SourceGenerators\Blazor.SourceGenerators.csproj" OutputItemType="Analyzer" SetTargetFramework="TargetFramework=netstandard2.0" ReferenceOutputAssembly="false" />
</ItemGroup>

Expand Down
9 changes: 7 additions & 2 deletions src/Blazor.SessionStorage/ISessionStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
namespace Microsoft.JSInterop;

/// <summary></summary>
[JSAutoInterop(
[JSAutoGenericInterop(
TypeName = "Storage",
Implementation = "window.sessionStorage",
HostingModel = BlazorHostingModel.Server,
OnlyGeneratePureJS = true,
Url = "https://developer.mozilla.org/docs/Web/API/Window/sessionStorage")]
Url = "https://developer.mozilla.org/docs/Web/API/Window/sessionStorage",
GenericMethodDescriptors =
[
"getItem",
"setItem:value"
])]
public partial interface ISessionStorageService;
5 changes: 3 additions & 2 deletions src/Blazor.SourceGenerators/Builders/SourceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ internal SourceBuilder AppendUsingDeclarations()
{
if (_options is { SupportsGenerics: true })
{
_builder.Append($"using Blazor.Serialization.Extensions;{NewLine}");
_builder.Append($"using System.Text.Json;{NewLine}");
_builder.Append(value: $"using Blazor.Serialization.Extensions;{NewLine}");
_builder.Append(value: $"using System.Text.Json;{NewLine}");
_builder.Append(value: $"using System.Text.Json.Serialization.Metadata;{NewLine}");
}

if (!_options.IsWebAssembly)
Expand Down
9 changes: 4 additions & 5 deletions src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) David Pine. All rights reserved.
// Licensed under the MIT License.

using Blazor.SourceGenerators.Builders;

namespace Blazor.SourceGenerators.CSharp;

internal sealed partial record CSharpTopLevelObject(string RawTypeName)
Expand Down Expand Up @@ -81,7 +79,7 @@ internal string ToInterfaceString(
if (details.IsSerializable)
{
builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},")
.AppendRaw($"JsonSerializerOptions? options = null);")
.AppendRaw($"JsonTypeInfo<{MethodBuilderDetails.GenericTypeValue}>? typeInfo = null);")
.AppendLine();
}
else
Expand Down Expand Up @@ -264,7 +262,7 @@ internal string ToImplementationString(
if (details.IsSerializable)
{
builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},");
builder.AppendRaw($"JsonSerializerOptions? options){genericTypeParameterConstraint} =>");
builder.AppendRaw($"JsonTypeInfo<{MethodBuilderDetails.GenericTypeValue}>? jsonTypeInfo){genericTypeParameterConstraint} =>");
}
else
{
Expand All @@ -288,6 +286,7 @@ internal string ToImplementationString(

builder.IncreaseIndentation()
.AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\",");

// Write method body / expression, and arguments to javaScript.Invoke*
foreach (var (ai, parameter) in method.ParameterDefinitions.Select())
{
Expand All @@ -298,7 +297,7 @@ internal string ToImplementationString(
{
// Overridden to control explicitly
builder.AppendRaw($"{parameter.ToArgumentString(toJson: false)})");
builder.AppendRaw($".FromJson{details.GenericTypeArgs}(options);");
builder.AppendRaw($".FromJson{details.Suffix}{details.GenericTypeArgs}(jsonTypeInfo);");
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/Blazor.SourceGenerators/CSharp/CSharpType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public string ToArgumentString(bool toJson = false, bool asDelegate = false)
: RawName.LowerCaseFirstLetter();

return toJson
? $"{parameterName}.ToJson(options)"
? $"{parameterName}.ToJson(jsonTypeInfo)"
: parameterName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,14 @@ internal static GeneratorOptions GetGeneratorOptions(
var trimmed = values.Trim();
var descriptors = trimmed.Split(',');

return descriptors
return [.. descriptors
.Select(descriptor =>
{
descriptor = descriptor
.Replace("\"", "")
.Trim();
return descriptor;
})
.ToArray();
})];
}

return default;
Expand Down
4 changes: 2 additions & 2 deletions src/Blazor.SourceGenerators/Extensions/ListExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Blazor.SourceGenerators.Extensions;

static class ListExtensions
{
internal static IEnumerable<(Interation Index, T Item)> Select<T>(this List<T> list)
internal static IEnumerable<(Iteration Index, T Item)> Select<T>(this List<T> list)
{
var count = list.Count;
for (var i = 0; i < count; ++i)
Expand All @@ -15,7 +15,7 @@ static class ListExtensions
}
}

readonly record struct Interation(
readonly record struct Iteration(
int Index,
int Count)
{
Expand Down
1 change: 1 addition & 0 deletions src/Blazor.SourceGenerators/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
global using System.Text;
global using System.Text.RegularExpressions;
global using Blazor.SourceGenerators.CSharp;
global using Blazor.SourceGenerators.Builders;
global using Blazor.SourceGenerators.Diagnostics;
global using Blazor.SourceGenerators.Expressions;
global using Blazor.SourceGenerators.Extensions;
Expand Down

0 comments on commit b2a509d

Please sign in to comment.