Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash within designer when using Refit #14282

Closed
mayka-mack opened this issue Jan 19, 2024 · 8 comments · Fixed by #14301
Closed

Crash within designer when using Refit #14282

mayka-mack opened this issue Jan 19, 2024 · 8 comments · Fixed by #14301
Labels

Comments

@mayka-mack
Copy link
Contributor

mayka-mack commented Jan 19, 2024

Describe the bug

I'm encountering a strange issue when using Avalonia and Refit in tandem. I figured I would start here to get assistance pinpointing which library might be causing the issue.

When using Refit within an Avalonia project, invoking a Refit command crashes the designer in both Visual Studio and Rider.

The specific error I'm getting is System.InvalidCastException: Unable to cast object of type 'DesignerCrashIUserApi' to type 'DesignerCrash.IUserApi'. It almost appears like the periods are being stripped from the namespace when Refit is generating its code. Strangely, when running the application normally, everything behaves as expected. So the issue appears limited to the AXAML designer.

Interestingly, when I call the Refit method directly and do not wrap it in a command, the crashing did not occur.

Full crash stack trace:

"C:\Program Files\dotnet\dotnet.exe" exec --runtimeconfig X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\bin\Debug\net8.0\DesignerCrash.runtimeconfig.json --depsfile X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\bin\Debug\net8.0\DesignerCrash.deps.json C:\Users\mmacinkowicz\.nuget\packages\avalonia\11.0.7\buildTransitive\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll --transport tcp-bson://127.0.0.1:53866/ --method avalonia-remote X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\bin\Debug\net8.0\DesignerCrash.dll
Initializing application in design mode
Obtaining AppBuilder instance from DesignerCrash.Program
Sending StartDesignerSessionMessage
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'DesignerCrashIUserApi' to type 'DesignerCrash.IUserApi'.
   at Refit.RestService.For[T](HttpClient client, IRequestBuilder`1 builder) in /_/Refit/RestService.cs:line 20
   at Refit.RestService.For[T](HttpClient client, RefitSettings settings) in /_/Refit/RestService.cs:line 34
   at Refit.RestService.For[T](String hostUrl, RefitSettings settings) in /_/Refit/RestService.cs:line 54
   at Refit.RestService.For[T](String hostUrl) in /_/Refit/RestService.cs:line 65
   at DesignerCrash.UserData.LoadUsersAsync() in X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\UserData.cs:line 35
   at DesignerCrash.AsyncCommand.ExecuteAsync() in X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\UserData.cs:line 113
   at DesignerCrash.TaskUtilities.FireAndForgetSafeAsync(Task task, IErrorHandler handler) in X:\Scratchpad\DesignerCrash\Amended\ExampleSolution\UserData.cs:line 210
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
   at Avalonia.Threading.SendOrPostCallbackDispatcherOperation.InvokeCore()
   at Avalonia.Threading.DispatcherOperation.Execute()
   at Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation job)
   at Avalonia.Threading.Dispatcher.ExecuteJobsCore(Boolean fromExplicitBackgroundProcessingCallback)
   at Avalonia.Threading.Dispatcher.Signaled()
   at Avalonia.Controls.Platform.ManagedDispatcherImpl.RunLoop(CancellationToken token)
   at Avalonia.Threading.DispatcherFrame.Run(IControlledDispatcherImpl impl)
   at Avalonia.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at Avalonia.Threading.Dispatcher.MainLoop(CancellationToken cancellationToken)
   at Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(String[] cmdline)
   at Avalonia.Designer.HostApp.Program.Main(String[] args)
Process terminated with exit code -532462766

To Reproduce

Steps to reproduce the behavior:

  1. Open the attached solution in Visual Studio or Rider : ExampleSolution.zip

  2. Build the project, then go to MainWindow.axaml and open the designer.

  3. Within the designer, click on the "Load Users Method" button. The designer will not crash.

  4. Within the designer, click on the "Load Users Command" button. The designer will crash with the System.InvalidCastException exception.

Environment

  • OS: Windows
  • Avalonia-Version: 11.0.7

Additional context

I did find one reported issue on the Refit GitHub which may be related to what I am seeing, but there was not enough detail provided for the maintainers to determine what would be causing the issue. It doesn't indicate if they were using any external libraries such as Avalonia or MVVM Community Toolkit.
reactiveui/refit#1125

@mayka-mack mayka-mack added the bug label Jan 19, 2024
@stevemonaco
Copy link
Contributor

There is a difference in implementation between your AsyncCommand<T> and Mvvm Toolkit's. The relevant part is how the task is awaited. Mvvm Toolkit implementation

Yours calls a specific handler, but swallows the exception:

public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
{
    try
    {
        await task;
    }
    catch (Exception ex)
    {
        handler?.HandleError(ex);
    }
}

You can get the same Previewer behavior (DesignerCrashManualIUserApi) by removing the try or by re-throwing the exception in the catch. That rules out one part so far.

@mayka-mack mayka-mack changed the title Crash within designer when using Refit and MVVM Community Toolkit Crash within designer when using Refit Jan 19, 2024
@mayka-mack
Copy link
Contributor Author

mayka-mack commented Jan 19, 2024

There is a difference in implementation between your AsyncCommand<T> and Mvvm Toolkit's. The relevant part is how the task is awaited. Mvvm Toolkit implementation

Yours calls a specific handler, but swallows the exception:

public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
{
    try
    {
        await task;
    }
    catch (Exception ex)
    {
        handler?.HandleError(ex);
    }
}

You can get the same Previewer behavior (DesignerCrashManualIUserApi) by removing the try or by re-throwing the exception in the catch. That rules out one part so far.

Good catch! Sounds like the Community Toolkit is not the issue then. I've updated my post accordingly and posted a new example where I rethrow the exception in the catch to replicate the issue with only Avalonia and Refit.

@stevemonaco
Copy link
Contributor

I think the cause is that Assembly.LoadFile doesn't make your app assembly available for reflection within the context of the Previewer. Therefore, Refit's reflection-based generator fails. In which case, this is an issue with the Avalonia previewer where its Assembly.LoadFile usage would need to be changed to Assembly.LoadFrom, if possible. Check reactiveui/refit#1476 for more details.

@timunie
Copy link
Contributor

timunie commented Jan 20, 2024

ReFit seems to call Http stuff, so IO operation. This is not supported in the previewer. Use Design.IsDesignTime to skip the IO stuff in previewer

@stevemonaco
Copy link
Contributor

ReFit seems to call Http stuff, so IO operation. This is not supported in the previewer. Use Design.IsDesignTime to skip the IO stuff in previewer

HttpClient usage works in the VS Previewer in my testing after swapping out Refit:

private async Task LoadUsersAsync()
{
    //var userApi = RestService.For<DesignerCrash.Toolkit.IUserApi>("https://jsonplaceholder.typicode.com");
    //var users = await userApi.GetUsers();

    using var client = new HttpClient();
    UsersString = await client.GetStringAsync(@"https://www.avaloniaui.net");
}

@mayka-mack
Copy link
Contributor Author

mayka-mack commented Jan 21, 2024

@stevemonaco I made the change you suggested as a draft PR and tested locally with the 11.1.999-cibuild0043827-beta package version from the All builds feed, and it works now with Refit without crashing. Can you think of any other areas of the Previewer which I should test that could potentially break by it using Assembly.LoadFrom instead of Assembly.LoadFile?

@stevemonaco
Copy link
Contributor

@mayka-mack I don't know the Previewer / Assembly APIs that well. I was initially concerned about remote code loading, but it appears that LoadFile and LoadFrom have the same behavior in that area (disabled by default) from a quick read.

mayka-mack added a commit to mayka-mack/Avalonia that referenced this issue Jan 22, 2024
@mayka-mack
Copy link
Contributor Author

@mayka-mack I don't know the Previewer / Assembly APIs that well. I was initially concerned about remote code loading, but it appears that LoadFile and LoadFrom have the same behavior in that area (disabled by default) from a quick read.

Okay, in that case, I have marked the pull request as ready for review. If anyone else has any suggestions to test, I am happy to do them. Thank you very much for helping me track this down!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants