suwoo
5월 14, 2025, 1:23오전
1
먼저 저와 비슷한 경험을 찾았어서 공유부터 드립니다…
열림 08:21PM - 05 Apr 24 UTC
### Is your feature request related to a problem? Please describe.
_No response… _
### Describe the solution you'd like
The generated ```ConfigureServices()``` in ```App.xaml.cs``` can be misleading, since it registers pages as services. When ```NavigationService.NavigateTo()``` is called, a type is passed to ```_frame.Navigate()``` instead of an instance. As I have seen WinUI’s API does not support passing instance to the ```Frame.Navigate()``` method. (it also makes parametrized constructors prohibited)
I found a solution to enable ```Frame.Navigate()``` to instantiate a page via an ```IServiceProvider``` interface. When ```Frame.Navigate()``` is called, it retrieves an ```IXamlType``` from the ```App```, ```IXamlType.ActivateInstance()``` method is responsible for creating the instance from a type. The ```ActivateInstance()``` method can be redirected, and I created a sample app that shows how this redirection is implemented.
I think it would be great that the [`App.DI.cs`](https://github.com/gabor-budai/WinUI-DI-FrameNavigate/blob/master/WinUI.DI.FrameNavigate/App.DI.cs) could be included in the TemplateStudio’s source generator.
### Describe alternatives you've considered
_No response_
### Additional context
[Here](https://github.com/gabor-budai/WinUI-DI-FrameNavigate) is my example app.
### Applies to the following platforms:
- [X] WinUI
- [ ] WPF
- [ ] UWP
WinUI3에는 NavigationView라는 녀석이 있습니다.
윈도 앱 좌측 메뉴와 같은 편리하고 예쁜(?) 모양을 제공해서 이걸 사용하기로 했었는데요,
평소 하던 대로 View와 ViewModel을 ServiceProvider에 맡겨서 쓰고 있었던거죠.
WPF에서 Navigate는 인스턴스를 인자로 넘기고,(애초에 NavigationView라는 녀석도 없구요.)
WinUI 3에서는 해당 Frame
의 인스턴스가 아닌 Type
만 넘기는걸 알고 아차 했습니다.
ServiceProvider.GetService<T>()
로 관리되는 View로는 애초에 Navigate를 통한 화면 전환이 불가능했었던 거죠.
관련 해결책으로 친절히 Nuget으로 공유해주신 분이 있었는데요.
코드 생성기로 Type을 받는 경우 provider를 호출해주는 것으로 판단됩니다.
(고수분들 보시면 설명좀 부탁드립니다 ^^;)
(해당 패키지 링크)
(관련 작성 문서:동일 repo입니다.)
# Why do we need this?
**The codes are representative not the real implementation**.\
The [`Template Studio`](https://github.com/microsoft/TemplateStudio)'s source generators when an empty project is created, it generates some code in the App class.
```App.xaml.cs```:
```csharp
public partial class App : Application
{
public IHost Host { get; }
public T GetService<T>() where T : class => (App)App.Current.Host.Services.GetRequiredService<T>();
public App()
{
Host = Microsoft.Extensions.Hosting.Host
.CreateDefaultBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureServices((context, services) =>
{
// View and ViewModels registration is done by the Template Studio.
This file has been truncated. show original
위 글과 같이, 굳이 View를 등록해서 쓰는 이유 중 하나가
View를 싱글턴으로 쓰고 싶(을 수도있죠!)다거나, 관리를 맡기고 싶다거나 하는 식이라고 생각했습니다.
이걸 수정 해줄지는 모르겠네요 위 이슈도 24년 10월이면 꽤 최근인 듯 한데…
조금 더 찾아보니 제안 된지는 꽤 된 내용인가 보네요.
열림 03:12PM - 13 May 19 UTC
feature proposal
area-Navigation
needs-winui-3
team-Controls
# Proposal: Enable extensibility in Frame navigation
## Summary
Extend … the current WinUI/UWP navigation model to support navigation based on more than just a page Type, and enable custom logic in the creation of the page instance that is navigated to.
This will **enable good coding practices** and **improve support for MVVM and Cross-Platform Frameworks**.
## Rationale
The current WinUI (UWP) navigation model requires specifying navigation based on a `Page` type and requires that pages have parameterless constructors. This forces code in pages to use the ServiceLocator pattern for dependency resolution rather than dependency injection.
By adding a way to specify how a navigation request results in the creation of a page it will allow the use of pages with parameterized constructors. It also enables the possibility of navigating based on more than just page types.
I believe these changes would help in:
- writing code that more clearly indicates dependencies, for ease of support/maintenance.
- writing code with a better separation of concerns/dependencies, to better enable the following of good development practices.
- improve integration/support by 3rd party MVVM Frameworks as some rely on different navigation strategies.
- help in providing consistency in cross-platform scenarios for frameworks that include UWP as a target but don't work well (or at all) with the current page based navigation option.
## Functional Requirements
1. This proposal does not involve changing the current navigation behavior and only includes an optional way to extend it. Any developer who wishes will be able to continue using the current navigation method without issue and this will avoid any unintended consequences for existing code.
2. Remove the need to have a parameterless constructor for the type of page navigating to.
3. Provide a way to write custom code to resolve the page to navigate to.
4. Enable navigation based on an object instance, not just a type.
## Important Notes
Basic overview:
- Add a way to specify how navigation requests are resolved.
- Add new navigation methods.
- When calling one of the new methods, a comparable method in the resolver is called to determine which page to navigate to.
- If calling a new method but a custom resolver hasn't been specified then a NavigationFailed event is raised with a suitable error message. (e.g. exception.Message = "NavigationResolver not specified", SourcePageType = null)
- Make testing the logic within a page easier.
### API ideas
**New property on `Frame`**
`INavigationResolver NavigationResolver { get; set; }`
**New methods on `Frame`**
```csharp
// Similar to existing Navigate and NavigateToType methods.
// 'type' could be any type, not just a page
// other params would work like in the existing methods.
bool TryNavigate(Type type, object parameter = null, NavigationTransitionInfo infoOverride = null)
// For navigation based on an instance of an object. "parameter" not needed as it would be a property of the target object.
bool TryNavigate(object target, NavigationTransitionInfo infoOverride = null)
```
_The naming is to reflect the `TryXxxx` naming pattern and avoid collisions with the existing `Navigate` and `NavigateToType` methods._
The implementation of these methods (without error handling) would be something like this:
```csharp
public bool TryNavigate(Type type, object parameter = null, NavigationTransitionInfo infoOverride = null)
{
if (this.NavigationResolver.TryNavigateByType(type, parameter, infoOverride, out Page pageInstance))
{
return this.NavigateInternal(pageInstance, parameter, infoOverride);
}
return false;
}
public bool TryNavigate(object target, NavigationTransitionInfo infoOverride = null)
{
if (this.NavigationResolver.TryNavigateToObject(target, infoOverride, out Page pageInstance))
{
return this.NavigateInternal(pageInstance, infoOverride);
}
return false;
}
```
where `NavigateInternal()` does the navigation to the created page instance.
**New interface `INavigationResolver`**
```csharp
public interface INavigationResolver
{
bool TryNavigateByType(Type type, object parameter, NavigationTransitionInfo infoOverride, out Page page);
bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page);
}
```
The methods defined in this interface would work in parallel with the new methods on Frame and return the page instance to navigate to.
### Example usage
**Navigate to a page with constructor params**
Set up custom navigation handling. App-wide frame reference set for simplicity, not part of the spec here.
```csharp
App.Frame = new MyNavigationResolver();
```
Set up rules for custom navigation.
```csharp
public class MyNavigationResolver : INavigationResolver
{
public bool TryNavigateByType(Type type, object parameter, NavigationTransitionInfo infoOverride, out Page page)
{
switch (type.Name)
{
// Just a simple example. Reality may involve passing multiple values and/or using a DI container/framework
case nameof(UserPage):
var vm = this.viewModelLocator.GetUserViewModel(parameter);
page = new UserPage(vm);
return true;
break;
// Other cases not shown for brevity
default:
page = null;
return false;
break;
}
}
public bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page)
{
// Not shown - see below
}
}
```
In the above, `this.viewModelLocator` would be set in a constructor but not shown here to keep the example focused on the important parts of the code.
How page being navigated to is defined:
```csharp
public sealed partial class UserPage : Page
{
public UserViewModel ViewModel { get; }
public UserPage(UserViewModel viewModel)
{
InitializeComponent();
this.ViewModel = viewModel;
}
}
```
Triggering navigation with above page and resolver. ("5" is the user Id. hard-coded for demonstration purposes.)
```csharp
App.Frame.TryNavigate(typeof(UserPage), 5);
```
**Navigate based on an object instance**
```csharp
App.Frame.TryNavigate(vm.SelectedItem);
```
pseudo code of resolver implementation
```csharp
public bool TryNavigateToObject(object target, NavigationTransitionInfo infoOverride, out Page page)
{
// Assume `target` is a ViewModel instance (convention in the app)
// Get page type based on VM type (via reflection, hard-coded, or whatever)
// Create Page instance and pass VM (similar to above)
}
```
## Open Questions
- What are the consequences for maintaining the back stack (including [Get|Set]NavigationState) and handling suspend/resume? (possible internal issues that aren't obvious without reviewing the code.)
- If a custom navigation request is canceled (within the custom resolver), should it raise a `NavigationStopped` event? (My thought is no as this is also indicated by the Try method returning false.)
- Should the `FrameNavigating` event be supported in combination with a new `TryNavigate` method? (My inclination is no, as it risks splitting navigation logic into multiple classes. Keep it all in the resolver.)
- This makes navigation parameters potentially available in the page constructor--in addition to the OnNavigatedTo event. This is a good/useful thing but could also result in possible negative consequences if misused. Is this an issue or something that needs extra consideration?
- Should there be async support? (triggering navigation is currently synchronous but this proposal potentially moves some functionality that would otherwise happen after navigation--in `OnNavigatedTo`--to during the process while `TryNavigate` is executing. It's reasonable to assume a desire to make async calls within the resolver but doing so might encourage actions that are time-consuming when navigation should be fast.)
1개의 좋아요
suwoo
5월 16, 2025, 4:33오전
2
이왕 글 남긴 김에 삽질한 케이스를 더 말씀드려보자면,
Windows.UI.Xaml.Controls
네임스페이스, 즉 UWP/WINUI의
Navigate
메서드들 오버라이드들 전부 System.Type
로 인수로 받는다고 말씀드렸는데요,
얘 때문에 또 고생을 했더랩니다.
해당 인수에는 항상 Page 및 그 파생 형식만 지원한다고 명시되어있습니다.
sourcePageType Type
The page to navigate to, specified as a type reference to its partial class type. Must be a Page -derived data type; otherwise, an exception is thrown. (A type reference is given as System.Type for Microsoft .NET, or a TypeName helper struct for Visual C++ component extensions (C++/CX)).
초보적인 실수로 Page가아니라 Frame같은 형식을 넣었었는데…
엉뚱한 개체를 집어넣으면 InvalidArgumentException
또는 그와 비슷한 식의 유추 가능성이 있을 오류가 아니라 언제나 NullReferenceException
을 뱉더군요.
위 사항을 몰랐으니
어디서, 뭐가 문제인지를 알 방도가 없어서 꽤 헤맸습니다.
혹시 비슷한 오류 보신다면 꼭 해당 UI 요소가Page
인지 확인해보세요ㅠㅠ
여기서부터는 그냥 TMI
심지어… 중간에 StackTrace 및 중단점을 찍어봐도
이미 바이너리/최적화 해버린 부분을 거쳐버려서 너무 Deep해져버리더군요.
트레이스 찍어 내려가봤다가 C#의 탈을 쓴 이상한 친구들만 만나서 웩…
internal unsafe static void Start(IObjectReference _obj, global::Microsoft.UI.Xaml.ApplicationInitializationCallback callback)
{
IntPtr thisPtr = _obj.ThisPtr;
ObjectReferenceValue value = default(ObjectReferenceValue);
try
{
value = ApplicationInitializationCallback.CreateMarshaler2(callback);
ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>)(*(IntPtr*)((nint)(*(IntPtr*)(void*)thisPtr) + (nint)7 * (nint)sizeof(delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>))))(thisPtr, MarshalInspectable<object>.GetAbi(value)));
//여기서 뇌정지가..
}
finally
{
MarshalInspectable<object>.DisposeMarshaler(value);
}
}
(찾아보니, ABI 접사를 붙인 네임스페이스, 즉 이미 바이너리화 된 Windows App SDK의 일부라고 합니다)
unsafe
구문을 모르지도 않고 그럭저럭 써본 경험이 있었지만 이건 너무 갔다 싶었지요.
아마… 디스어셈블리 또는 요런 쪽 지식이 조금 더 깊었다면 찾을 수 있었을 지도 모르겠지만요!
처음 질문으로 올리려고 정리하다가 어쩌다 보니 자문자답으로 해결해버려서,
이렇게 마무리하면서 남겨봅니다. ㅎㅎ
혹여나, DI 및 IServiceProvider
를 활용해 View를 좀 더 잘 관리할 방법이 생긴다면 거기다 [해결책] 표시를 하려고 합니다.
1개의 좋아요