Microsoft-ui-xaml: Proposal: Instantiate Resources from Dependency Injection

Created on 17 Nov 2019  路  6Comments  路  Source: microsoft/microsoft-ui-xaml

Proposal: Instantiate Resources from Dependency Injection

Summary

Use Microsoft.Extensions.DependencyInjection or other framework that implements IServiceProvider to instantiate resources to instantiate Resources from XAML..

Rationale

  • Dependency Injection allows for creating loosely coupled resources such as View Models that are defined in separate or distant (not in same solution) projects.
  • Current methods require requiring a "dummy instance" on a default constructor that can be instantiated by Xaml, and then removing and replacing it at runtime from the Resources.
  • Will greatly improve maintainability and quality of code.

Scope

| 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 |

Important Notes

Example

<local:IMyResource x:Key="MyResource" ServiceProvider="{x:Bind Provider}" />

Here, ServiceProvider is any instance of IServiceProvider that can be resolved via x:Bind.

area-CoreFramework feature proposal needs-winui-3

All 6 comments

@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.

Was this page helpful?
0 / 5 - 0 ratings