Megabit / Blazorise

Blazorise is a component library built on top of Blazor with support for CSS frameworks like Bootstrap, Tailwind, Bulma, AntDesign, and Material.
https://blazorise.com/
Other
3.31k stars 534 forks source link

Button Component Failing with "SerializationNotSupportedParentType" | AOT | Android | MAUI Blazor Hybrid 7.0 #4820

Open hitchhiker opened 1 year ago

hitchhiker commented 1 year ago

Describe the bug The button component appears to fail with SerializationNotSupportedParentType when using AOT/Release on Android.

To Reproduce Steps to reproduce the behavior:

<PackageReference Include="Blazorise.Animate" Version="1.2.3" />
<PackageReference Include="Blazorise.Bootstrap5" Version="1.2.3" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.2.3" />
<Button @onclick="OnBack" Color="Color.Primary" Size="Size.Small" Outline>
     <i class="ph-bold ph-caret-left"></i>
     <span>@Localizer["General.Button.Back"]</span>
</Button>

@..
[Parameter]
    public EventCallback OnBack { get; set; }

Inspector output

ConstructorContainsNullParameterNames, <>f__AnonymousType0`1[System.Boolean] SerializationNotSupportedParentType, System.Object Path: $.
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& , NotSupportedException )
   at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(Utf8JsonWriter , Object[]& , JsonSerializerOptions , WriteStack& )
   at System.Text.Json.JsonSerializer.WriteCore[Object[]](Utf8JsonWriter , Object[]& , JsonTypeInfo`1 )
   at System.Text.Json.JsonSerializer.WriteString[Object[]](Object[]& , JsonTypeInfo`1 )
   at System.Text.Json.JsonSerializer.Serialize[Object[]](Object[] , JsonSerializerOptions )
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[IJSVoidResult](Int64 , String , CancellationToken , Object[] )
   at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[Microsoft.JSInterop.Infrastructure.IJSVoidResult, Microsoft.JSInterop, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
   at Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeVoidAsync(IJSObjectReference , String , Object[] )
   at Blazorise.Modules.BaseJSModule.InvokeSafeVoidAsync(String , Object[] )
   at Blazorise.Button.<OnInitialized>b__12_0()
   at Blazorise.BaseAfterRenderComponent.OnAfterRenderAsync(Boolean firstRender)
   at Blazorise.BaseComponent.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task , ComponentState )

Additional context Blazorise is also giving trim warnings [NET 7.0 / MAUI HYBRID]

hitchhiker commented 1 year ago

This may be due to the use of a callback via the @onclick method in another component. I will close this until confirmed. Apologies.

hitchhiker commented 1 year ago

This may be due to the use of a callback via the @OnClick method in another component. I will close this until confirmed. Apologies.

github-actions[bot] commented 1 year ago

Hello @hitchhiker, thank you for your submission. The issue was labeled "Status: Repro Missing", as you have not provided a way to reproduce the issue quickly. Most problems already solve themselves when isolated, but we would like you to provide us with a reproducible code to make it easier to investigate a possible bug.

stsrki commented 1 year ago

Can you try with a Clicked callback?

<Button Clicked="OnBack" Color="Color.Primary" Size="Size.Small" Outline>
     <i class="ph-bold ph-caret-left"></i>
     <span>@Localizer["General.Button.Back"]</span>
</Button>
hitchhiker commented 1 year ago

I had some time to get back to this and begin to debug the issue:

1 - I'm using Blazorise with MAUI (Hybrid) - now on Net8.0 Preview5 - but this applies to NET7 also 2 - Blazorise is not compatible with AOT at the moment (Trimming warnings, call sites are not decorated etc) 3 - Any use of <Button> on a MAUI / Blazor surface will fail when compiled with AOT to a release version.

<Button Color="Color.Danger">Ok</Button>

The above code will cause an error when used with AOT / Maui / Android (I assume also iOS) due to System.Text.Json.Serialization.JsonConverter called via IJSButtonModule to initialize the module (I assume).

With the push to AOT in Net8 and the popularity of Blazor Hybrid, this issue will come up more frequently. Basically the Blazorise library just needs to be reviewed for trimmability.

hitchhiker commented 1 year ago

Just in-case anybody needs a quick solution - a very very rough hack is to disable the module for Button.

services.AddBlazorise();
services.Remove(new ServiceDescriptor(typeof(IJSButtonModule), typeof(JSButtonModule), ServiceLifetime.Scoped));
services.AddScoped<IJSButtonModule, JsButtonModuleFix>();
public class JsButtonModuleFix : IJSButtonModule
{
    public string ModuleFileName => "NONE";
    public Task<IJSObjectReference> Module => default!;
    public ValueTask Destroy(ElementReference elementRef, string elementId) => ValueTask.CompletedTask;
    public ValueTask Initialize(ElementReference elementRef, string elementId, object options) => ValueTask.CompletedTask;
}
David-Moreira commented 1 year ago

We can at least confirm that the usage of JS in the button currently is for a very specific case. As such if you do not need that specific case I guess @hitchhiker's could be a fine solution for now. But fair warning that we might still require js in a few other components.

https://github.com/Megabit/Blazorise/issues/4719 image

Another thing to consider is that if you really want AOT? As far as I know there's a clear disavantage which is a bigger footprint in app size?

image

Anyway isn't AOT and trimming two different things? Trimming is very useful but can also be disabled as far as I know. And I believe you can also do some tricks to make explicit calls to the missing APIs in order for them to not be trimmed?

hitchhiker commented 1 year ago

AOT will likely become a first class citizen in the NET world (next NET8 release will up that a lot). A lot of us are now using Blazor to render the UI surface for apps going to Android / iOS with Blazor Hybrid. As you state, AOT increases app size a bit, but it gives us a strong advantage in app start up times on mobile (which is, as you know, a huge deal). It's relatively new to NET, unfinished and a generally immature stack at this point, but they (MS) seem strongly invested in it, as is the community. It's going to come up a lot.

Trimming and AOT are going hand in hand, and while you can 'play' with it to some degree, it's rapidly becoming essential. Basically due to the app size thing you mentioned.


re: the module / jsinterop -> the reflection in Json is the problem, you could perhaps simply render the json in another way. Most of us are using sourcegen now, but if it's not a huge json object you need, perhaps manually convert the object to a json string and send that through instead of an object to serialize?

Thanks for getting back to me @David-Moreira - appreciated.

David-Moreira commented 1 year ago

AOT will likely become a first class citizen in the NET world (next NET8 release will up that a lot). A lot of us are now using Blazor to render the UI surface for apps going to Android / iOS with Blazor Hybrid. As you state, AOT increases app size a bit, but it gives us a strong advantage in app start up times on mobile (which is, as you know, a huge deal). It's relatively new to NET, unfinished and a generally immature stack at this point, but they (MS) seem strongly invested in it, as is the community. It's going to come up a lot.

Trimming and AOT are going hand in hand, and while you can 'play' with it to some degree, it's rapidly becoming essential. Basically due to the app size thing you mentioned.

re: the module / jsinterop -> the reflection in Json is the problem, you could perhaps simply render the json in another way. Most of us are using sourcegen now, but if it's not a huge json object you need, perhaps manually convert the object to a json string and send that through instead of an object to serialize?

Thanks for getting back to me @David-Moreira - appreciated.

I see thanks for the explanation. I had the feeling that the bandwidth would be always a bigger concern in enabling AOT vs the start up benefit you get. (you know, having to download bigger sizes in mobile and all) Some examples I saw Microsoft giving in the past, was playing with stuff that was actually cpu intensive, they showed something to do with image cropping / processing, so I guessed it was more of a niche thing. But we'll def take a look at it. Thanks for raising the concern.


re: the module / jsinterop -> In theory we're not doing anything special, It's a simple interop. And best case scenario it should have been correctly handled by the .NETs trimming engine.

From the stack trace it seems it's related to the serialization of our options, where we use an anonymous object, I can't quite remember now, but we use anonymous objects here because strongly typed classes weren't working correctly for these use cases, at least as far as I can remember. image image

So in reality maybe it's something we can reach out to the dotnet team and see if there's an issue with trimming vs a serialization happening on an anonymous object? If that is the issue that is... I'm not sure we should specifically have to decorate something here, we aren't using explicit reflection at all.