dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.08k stars 1.17k forks source link

Make Future WPF IoC Friendly #499

Open TrabacchinLuigi opened 5 years ago

TrabacchinLuigi commented 5 years ago

Something that i really miss on WPF is the ability to avoid using service locators and control how nested controls and pages are created, i'd really like to have some object injected in the UserControl constructors, and a way to tell WPF how to construct them (and maybe provide some mocks for the IDE), instead of the empty constructor. Maybe since a lot is being rewritten to be core it is a good time to support IoC. I'm aware that we can achieve something similar with MVVM but... it's not the same...

stevenbrix commented 5 years ago

This sounds like an interesting idea, although I'm not exactly sure what you're trying to accomplish. Can you provide more information (like a code example) on what you are envisioning this to look like? How would the xaml parser know which constructor to call and with which parameters?

TrabacchinLuigi commented 5 years ago

One of the best API i ever worked with is the one from asp.net core, so i'm inspiring from that. I'm imagining that WPF would work just as it does now, but when it finds in the tree an object that does not have an empty constructor would instruct the user to change the default entry point with something that looks like this:

namespace WPFApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWPFBuilder(args).Build().Run();
        }

        public static IWPFBuilder CreateWPFBuilder(string[] args) 
        {
             // WPF.CreateDefaultBuilder(args); // here all the default way of constructing Controls, FrameworkElements 
             // WPF.CreateDefaultBuilder(args, (builder) => { /* Add custom registrations here, shared between IDE and runtime*/ });
             WPF.CreateDefaultBuilder(args, (ideBuilder, builder) => { /* Add custom registrations here*/ });
         }
    }
}
hippieZhou commented 5 years ago

Yeh,I think this will be a good feature for wpf with dotnetcore 3.0,I vote you.

Valks commented 5 years ago

I've thrown a preliminary (working) example together. It's bare bones but works surprisingly well.

Only issue is it has trouble shutting down. Not sure what's keeping the app alive.

https://github.com/Valks/DotnetCore.GenericHost.WPF.Demo

It would be nice to take it that step further as per @TrabacchinLuigi 's example but it isn't that bad as is.

miloush commented 5 years ago

@Valks Make sure your async stuff is running on a background thread, an app stays running as long as any non-background threads are running.

Valks commented 5 years ago

I needed to call _host.Dispose();. Seems it has a background thread or two (sample updated).

stevenbrix commented 5 years ago

This is awesome, thank you for the discussion! Once we've formalized our API submission and review process, we'll be able to share that and then get the ball moving a bit. Just so we are on the same page, it's unlikely we'll be ready to accept these changes for .NET Core 3.0 RTM, but we should be able to look at them soon after. Thanks for the great contributions!

MeikTranel commented 5 years ago

I have been exploring this in the Context of the Stylet MVVM Framework (https://github.com/canton7/Stylet - this library also exposes a similar thing called "BootStrapper" which is basically a more strictly typed version of an OWIN startup class). Build myself a similar construct that @Valks is showing in his sample. One issue that came up with using the ASP.NET IoC Extension Interfaces is the fact that it is pretty tightly coupled to the idea of PageLoads. Theres 3 types of lifecycles: Singleton, Scoped & Transient -> Singleton and Transient can be translated to the WPF use case no problem. Scoped i'm not sure. In ASP.NET Scoped means it creates an Object once per Connection (thus optimistically said per pageload). Whether this can be translated to Client Applications is questionable - and without using we'd lose alot of good pre-existing libraries building great extension method for ServiceCollection.

As for the greater picture: When designing this i urge everyone to look at this from the MVVM viewpoint and less from UserControl UseCase. IoC is especially useful where pre-configuration has to be done. Its far more likely that ViewModels would gain from Dependency Injection automation than UserControls, because UserControls tend to pull in less side-effect rich dependencies.

weltkante commented 5 years ago

@MeikTranel Classically window lifetime is something which could be a good fit, in our application its pretty common requirement for services to have distinct instances for distinct windows. On the other hand there are lightweight windows which don't require a scope at all ("message box" style windows) or dialogs which want to inherit the scope from their invocing window.

Also I do understand that not everyone likes multi-window applications and some people prefer having different views, basically inlining the "window" scopes into the main window.

So at the end you have to allow the application programmer to decide where scope boundaries are, you can just try to give reasonable defaults to make the common cases easier.

Lakritzator commented 5 years ago

I would like to add my repository to this discussion, it contains multiple UI samples build on the generic host, including WPF: https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting

It has samples with o.a.:

Besides there are services to prevent your application for being started multiple times and also something to load plugins.

The main challenge was to offload the UI onto its own thread, otherwise the background services got into problems. So I actually spent quite a lot of time to make the whole life-cycle work.

But the whole idea of using DI for a UI application makes sense, and it does feel very natural to me. The code is still very experimental, and the API probably needs improvement, but it can be used as a base to continue with.

Actually @onovotny also tweeted about using the generic host for UI, but I think he is to much occupied to react to my answers.

laurentkempe commented 5 years ago

I don't know about @onovotny tweet but I wrote a recent blog post "WPF and .NET Generic Host with .NET Core 3.0" which shows one solution which is working for me!

mhDuke commented 5 years ago

services.AddSingleton, and AddTransient is understandable within any context. what about AddScoped? in ASP scoped lifetime is a request, what scoped lifetime would be in WPF?

weltkante commented 5 years ago

@MHDuke I already "answered" that above by listing a bunch of scenarios which make it clear that there cannot be a single right reponse to that question. The framework has to provide some primitives and the application has to be able to control the scoping.

LeoYang06 commented 5 years ago

@Lakritzator The Dapplo.Microsoft.Extensions.Hosting repository is a good example of using DI for WPF programs, and I'll try to understand and use it.

Hopefully, future versions of WPF will integrate these apis and make the program more elegantly structured.

MeikTranel commented 5 years ago

The framework has to provide some primitives and the application has to be able to control the scoping.

@weltkante Thinking about our earlier conversations i am more and more a fan of tackling this issue. The question of what exactly scope means was never really asked because no one needed to ask; either you were using DI as part of ASP.NET Core or using the Extensions package where my experience has shown me that most people just avoided the scoped lifecycle alltogether. The latter part is definitely not a good trend. Going beyond WPF one must acknowledge that we're probably about to see another candidate pop up where the ASP.NET Core preset of what scoped stands for might also not quite fit the requirements of the programming model at hand: Blazor. Client-Side Blazor will bring in a programming model where "Scoped = Request Lifecycle" will be at least a very narrow fit.

The focus should be on easing APIs and improving documentation related to setting up "Scope" to the needs of the application.

BrainSlugs83 commented 4 years ago

I would love to see the ability to dependency inject models and viewmodels that are created from XAML (i.e. if your model has dependencies, and a non-parameterless constructor, you can configure WPF to still be able to create it from XAML).

ScarletKuro commented 3 years ago

Just for side note. Even MAUI is using Host Builder to bootstrap the app. So it would be nice to see such feature in WPF too. The biggest advantage would be an easy access to Microsoft.Extensions.DependencyInjection and nowadays a lot of 3rd party libraries provides integration with it. Want automapper in your wpf application? type AddAutoMapper. Need MediatR type AddMediatR etc.

TrabacchinLuigi commented 3 years ago

Even if I opened this thread and I still think it would be cool, i now think the mvvm approach is better than this, once I realized I could use ContentControl and a datatemplate instead of a usercontrol with just the ViewModel DependencyProperty i don't feel like this could be very useful ...

ScarletKuro commented 3 years ago

I now think the mvvm approach is better than this

I kinda don't see the connection how MVVM approach is killing the IoC idea and the IHost in general. Since I use MVVM together with DI. I bootstrap all my ViewModels via DI and inject all the things that I need: logging, toaster, services, httpclient, mapping, config and etc. This is like a really standard way with MVVM. Not even saying that many MVVM frameworks have their own IoC (but it's usually a service locator which i find as an anti pattern).

nvmkpk commented 3 years ago

And also, imagine if you can inject all the view model and services a window/control needs via its constructor and the XAML reader using DI to instantiate them will be so awesome.

TrabacchinLuigi commented 3 years ago

because in the mvvm you can inject everything that you need in the viewmodel, and if you don't... weeel i'm afraid that's not the right way. you can already inject viewmodels in windows, and you don't need em in controls, controls should interact only with their bindings. the error i found myself doing was making controls to display viewmodels. A datatemplate is more suitable to the abstraction the pattern dictate... I'm not saying this should not be done, ever, it would still be nice, but i feels it's not that important, at least for me.