Use Microsoft.Extensions.DependencyInjection
or other framework that implements IServiceProvider
to instantiate resources to instantiate Resources from XAML..
| Capability | Priority |
| :---------- | :------- |
| This proposal will allow developers to use design time bindings on View Models and other objects created via Dependency Injection | Must |
| This proposal will allow developers to use any dependency injection framework that implements IServiceProvider
. | Must |
| This proposal will allow developers to define an Interface as a Resource since the instance will be obtained from the IServiceProvider
. | Must |
| This proposal will __not__ require dependency injection for all projects. | Must |
<local:IMyResource x:Key="MyResource" ServiceProvider="{x:Bind Provider}" />
Here, ServiceProvider
is any instance of IServiceProvider
that can be resolved via x:Bind
.
@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.