dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.22k stars 9.95k forks source link

Blazor SSR error when rendermode is InteractiveAuto #57874

Open desmondinho opened 1 week ago

desmondinho commented 1 week ago

Is there an existing issue for this?

Describe the bug

I am passing a model(s) as a parameter to an interactive component that is rendered on a SSR page and I experience different unexpected behaviors such as exceptions or parameter value reset. Read below for a better understanding.

The Component:

@rendermode InteractiveAuto

<p>@Ok?.Value</p>
<p>@NotOk?.Value</p>
<p>@AnotherNotOk?.Value</p>

@code {
    [Parameter] public Ok? Ok { get; set; }
    [Parameter] public NotOk? NotOk { get; set; }
    [Parameter] public AnotherNotOk? AnotherNotOk { get; set; }
}

The model(s):

public class Ok( string value, string value2 )
{
    public string Value { get; } = value;
    public string Value2 { get; } = value2;
}

public class NotOk
{
    public string Value { get; }
    public string? Value2 { get; }

    public NotOk( string value )
    {
        Value = value;
    }

    public NotOk( string value, string value2 ) : this( value )
    {
        Value2 = value2;
    }
}

public class AnotherNotOk
{
    public string? Value { get; }
    public string? Value2 { get; }

    public AnotherNotOk() { }

    public AnotherNotOk( string value )
    {
        Value = value;
    }

    public AnotherNotOk( string value, string value2 ) : this( value )
    {
        Value2 = value2;
    }
}

The usage on the page:

@page "/"

<Component Ok="@_ok" />
@* <Component NotOk="@_notOk" /> *@
@* <Component AnotherNotOk="@_anotherNotOk" /> *@

@code {
    private Ok _ok = new( "Ok", "" );
    private NotOk _notOk = new( "Not Ok", ""  );
    private AnotherNotOk _anotherNotOk = new( "Another Not Ok", "" );
}

Ok: a model with the primary constructor; works as expected

NotOk: a model with 2 explicit constructors; throws an exception (see the Exceptions field below)

AnotherNotOk: a model with 3 explicit constructors, a fix for the NotOk; a default constructor was added because it fixes the exception thrown with the NotOk; initially, the property Value is correctly shown, however it gets reset after a bit (prerendering stuff?)

[!IMPORTANT]
The exception is thrown only when there are more than 1 property in a model

Maybe 2 and 3 should be reported separately but I thought that they might be somehow related.

Expected Behavior

NotOk should not throw the exception. Why explicitly defined default contstuctor fixes the issue? Shouldn't it work without it since these are auto-generated under the hood anyway?

AnotherNotOk, as I said, "fixes" the NotOk but the property Value gets reset which does not seem ok.

Steps To Reproduce

Minimal reproduction project

Ok behavior:

  1. Run the app
  2. See the 'Ok' output

Not Ok behavior:

  1. Navigate to the Home.razor
  2. Comment the 3rd line and uncomment the 4th
  3. See the exception in the browser's DevTools

Another Not Ok behavior:

  1. Navigate to the Home.razor
  2. Comment the 4th line and uncomment the 5th
  3. See the value of the Value property dissappears

P.S. The exception message does not look good. https://github.com/dotnet/aspnetcore/issues/51749#issuecomment-1831675663

Exceptions (if any)

Error: One or more errors occurred. (Could not parse the parameter value for parameter '{definition.Name}' of type '{definition.TypeName}' and assembly '{definition.Assembly}'.)

.NET Version

8.0.304

Anything else?

Please tell me if you need more info.

Thanks!

javiercn commented 5 days ago

@desmondinho thanks for contacting us.

Under the hood these parameters are serialized using System.Text.Json, so what's likely going on is that the serializer is not able to select a constructor in the second case. Not sure what the reason is for the 3rd one working vs the second.

You can use [JsonConstructor] to disambiguate or make other constructors internal/private and make a single one public.

If you want to further test this out, simply create a console app, serialize the types, and then try deserializing the different combinations.

dotnet-policy-service[bot] commented 5 days ago

Hi @desmondinho. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.