Open sharpninja opened 5 years ago
@sharpninja to make sure I understand, would your goal be to replace codebehind like this that would register a service instance (which should work with Xaml types today even with non-default constructors):
var services = new ServiceCollection();
services.AddSingleton<IMyResource>(MyResource);
this.Provider = services.BuildServiceProvider();
with your example markup?
@jebris - You would indeed still need to register the object as you listed so that XAML gets instances of that object from the IServiceProvider specified in the XAML. And this really can extend beyond just resources to all objects such that an entire object try in XAML could be constructed from DI.
So is your proposal that this markup:
<local:IMyResource x:Key="MyResource" ServiceProvider="{x:Bind Provider}" />
would automatically register that IMyResource
instance with the referenced ServiceProvider
?
Would that ServiceProvider
instance be created by the framework or the app? If it was created by the app, how would you propose that the Xaml framework get the associated ServiceCollection
and rebuild the ServiceProvider
?
A more complete example might also help clarify if you could provide one 😊
You would still need this:
Startup.cs
var services = new ServiceCollection();
services.AddTransient<SomeTemplate, SomeTemplate>();
services.AddSingleton<IMainPage, MainPage>();
services.AddSingleton<IMainPageViewModel, MainPageViewModel>();
services.AddSingleton<ISomeDataModel, SomeDataModel>();
App.Provider = services.BuildServiceProvider();
Once all of the DI is defined, your code can look like this.
SomeDataModel.cs
public ILogger Logger { get; }
public SomeDataModel(ILogger<SomeDataModel> logger)
{
logger.LogDebug("Constructing SomeDataModel ...");
Logger = logger;
}
MainPageViewModel.cs
private IServiceProvider _serviceProvider = null;
public ILogger Logger { get; }
public IConfiguration Configuration { get; }
public IServiceProvider Provider = _serviceProvider ??= App.Current.Resources["Provider"] as IServiceProvider;
public MainPageViewModel(ILogger<MainPageViewModel> logger
, IConfiguration configuration)
{
logger.LogDebug("Constructing MainPageViewModel ...");
Logger = logger;
Configuration = configuration;
}
public void LoadData()
{
Logger.LogDebug("Loading Data ...");
// use Configuration to load SomeData
var current = Provider.GetService<ISomeDataModel>();
var current.Populate(...);
SomeData.Add(current)
}
public ObservableCollection<ISomeDataModel> SomeData { get; } =
new ObservableCollection<ISomeDataModel>();
That would allow this:
App.xaml
<system:IServiceProvider x:Key="Provider" ServiceProvider="{x:Bind ServiceProvider}" />
<local:SomeTemplate x:Key="SomeTemplate" ServiceProvider="{StaticResource Provider}" />
App
must expose an instance of IServiceProvider
named ServiceProvider
that can be resolved by x:Bind
once.
MainPage.xaml
<Page.DataContext>
<local:MainPageViewModel ServiceProvider="{StaticResource Provider}"/>
</Page.DataContext>
...
<ListView ItemSource="{x:Bind SomeData}" ItemTemplate="{StaticResource SomeTemplate}" />
SomeTemplate.xaml
<DataTemplate x:DataType="local:SomeDataModel">
...
</DataTemplate>
SomeTemplate.xaml.cs
public ILogger Logger { get; }
public SomeTemplate(ILogger<SomeTemplate> logger)
{
logger.LogDebug("Constructing SomeTemplate ...");
Logger = logger;
InitializeComponent();
}
public SomeDataModel DataModel
{
get => DataContext as SomeDataModel;
set => DataContext = value;
}
Then in your code you can have all of your Pages and custom Controls be instantiated with Dependency Injection. In the case of MainPage
, it can now be created with a constructor that is populated from dependency injection instead of a default constructor. No initialization method would be necessary.
MainPage.xaml.cs
private MainPageViewModel _viewModel = null;
public MainPageViewModel ViewModel => _viewModel ??= DataContext as MainPageViewModel;
public MainPage(ILogger<MainPage> logger)
{
logger.LogDebug("Constructing MainPage ...");
InitializeComponent();
Loaded += (s, a) => ViewModel.LoadData();
}
Thanks for the full example! That's much clearer.
This seems worth considering. It's unlikely we'll be able to get to this for the first WinUI 3.0 release, so we'll keep it in the backlog for a future iteration.
Thanks! If any tasks become available where I could assist I'd be happy to.
Maui has taken an approach that uses the Microsoft.Extensions.Hosting
pattern. I thought that something similar would have been done for WinUI 3.0.
Allowing developers to wire up services, background services, view models etc. Has there been any further internal discussions around how a WinUI 3.0 .NET Project is bootstrapped?
Allowing developers to wire up services, background services, view models etc. Has there been any further internal discussions around how a WinUI 3.0 .NET Project is bootstrapped?
I've actually been doing this by setting the App.xaml file to a Page and adding Program.cs to the project and building up a default host. I wrap the Application in a simple IHostedService and register it with the Host which manages the GUI lifecycle.
Allowing developers to wire up services, background services, view models etc. Has there been any further internal discussions around how a WinUI 3.0 .NET Project is bootstrapped?
I've actually been doing this by setting the App.xaml file to a Page and adding Program.cs to the project and building up a default host. I wrap the Application in a simple IHostedService and register it with the Host which manages the GUI lifecycle.
Nice, do you have a simple skeleton to share by any chance?
Nice, do you have a simple skeleton to share by any chance?
Proposal: Instantiate Resources from Dependency Injection
Summary
Use
Microsoft.Extensions.DependencyInjection
or other framework that implementsIServiceProvider
to instantiate resources to instantiate Resources from XAML..Rationale
Scope
IServiceProvider
.IServiceProvider
.Important Notes
Example
Here,
ServiceProvider
is any instance ofIServiceProvider
that can be resolved viax:Bind
.