Replies: 5 comments 2 replies
-
You should avoid reflection based ViewLocator at any cost, if you want to use trimming. Which most apps need to do.
Yes, registration mechanism is a safe way to do it. But in their/your example there is a problem with Activator.CreateInstance reflection call which is still unsafe. Consider something like this using factory methods in ViewLocator class: private static Dictionary<Type, Func<Control>> Registration = new Dictionary<Type, Func<Control>> ();
public static void Register<TViewModel, TView>() where TView : new()
{
Registration.Add(typeof(TViewModel), () => new TView());
}
public static void Register<TViewModel, TView>(Func<TView> factory)
{
Registration.Add(typeof(TViewModel), factory);
}
public Control Build(object data) {
var type = data.GetType();
if (Registration.TryGetValue(type, out var factory)) {
return factory();
}
else {
return new TextBlock { Text = "Not Found: " + type };
}
} This way there is no reflection, and all types should be statically preserved. I think there should be no other changes in your code, as Initialize method looks good already. |
Beta Was this translation helpful? Give feedback.
-
Thank you! This really helps. I'm using ReactiveCommand to respond to button click and then switch page for user. Before this I'm using reflection to get ViewModel by its full name. Since reflection is not suitable, which means I can't use name string to find viewmodel, what's a better CommandParamter for the click command? I tried to pass the class name of viewmodel, but it just treat it as a string. *edit: Write individual commands for every button seems an easy way, but it's redundant and not flexible if there are many views. |
Beta Was this translation helpful? Give feedback.
-
Problem solved by following guide on https://docs.avaloniaui.net/docs/guides/data-binding/how-to-bind-to-a-command-with-reactiveui, thanks again! |
Beta Was this translation helpful? Give feedback.
-
The problem with reflection is the trimmer has no idea what code you need at compile time. So it will remove any code it's not sure about. If you don't need trimming just turn it off. It's generally only used for AOT/web assembly. That said, there are ways to tell the trimmer about some things it needs to keep. https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/incompatibilities |
Beta Was this translation helpful? Give feedback.
-
@creamIcec please use discussions or telegram for questions rather than opening bug reports. thx. |
Beta Was this translation helpful? Give feedback.
-
Hi I'm new to Avalonia and trying to use view locator for switching views. But I encountered some unexpected consequence when publishing the app.
Describe the bug
I'm using the view locator code available on Avalonia documentation: https://docs.avaloniaui.net/zh-Hans/docs/tutorials/todo-list-app/locating-views. It works good under IDE debugging and running, but when I publish my app with trimming code option checked, the result is that app launched but just displaying not found message on screen. Below are my actions trying to solve this issue, but unfortunately none of them worked:
When I firstly encountered the issue, I believe there must be some difference between debugging in IDE and publishing. So I turned to search engine and added an option to my project file:
<PreserveCompilationContext>true</PreserveCompilationContext>
which means to preserve all things in runtime considered when debugging. But this didn't work, view locator still return the 'not found' textblock.
Then I turned to some apps recorded in Awesome Avalonia for references. I found they are using the registration mechanism which register mapping of viewmodels to views. This may be a better solution and I tried, following are the modified view locator
Build()
method:And below is the registration dictionary, included in
ViewLocator.cs
:And
App.xaml.cs
, the modifiedInitialize()
Method:The type templates are all my viewmodels and views.
Then the result became even worse, as app started to fail to launch. For this I turned on console window for published app, and received the error message below:
Unhandled exception. System.MissingMethodException: Cannot dynamically create an instance of type 'Calmy.Views.HomeView' Reason: No parameterless constructor defined.
Where Calmy.Views is the namespace for my views. Accroding to stack trace, I believe view locator found corresponding view, but due to some reason the app was unable to display it. To get a better understanding, I printed all class names in the assembly with this:and found something strange. The views classes are not printed. Then I realized it may be caused by trmming code option. Views code-behind and xamls are trimmed, since they are accessed by reflection in my previous view locator(the one doesn't use registration). So I turned to find a way to preserve my views not to be trimmed. Finally I found this blog: https://devblogs.microsoft.com/dotnet/customizing-trimming-in-net-core-5/ and followed some steps described. But this time, the condition get even worse, as the xml indicating trimming for preserving didn't work, the view classes names still not printed.
In addition, there was a warning from ViewLocator when publishing:
Using member 'System.Reflection.Assembly.GetTypes()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Types might be removed.
After doing these research, I believe there are two major issues:
whole stacktrace for my issue 2:
is this due to the dependance injection progress happened behind scenes are also trimmed?
Expected behavior
Views display properly with code trimmed.
Environment
As trimming will impressingly reduce my app size from > 300MB to about 130MB, I don't want to abandon it. Please help me.
Beta Was this translation helpful? Give feedback.
All reactions