dotnet / wpf

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

please add natural dependency injection supported converter #9162

Open ali50m opened 1 month ago

ali50m commented 1 month ago

Converter works like a birdge between the origin data and UI presentation. It is a very important feature in WPF world. Please consider adding DI support to converter or create new one with DI supported.

lindexi commented 1 month ago

@ali50m WPF doesn't prevent you from adding DI mechanisms to converter.

ali50m commented 1 month ago

@lindexi It`s hard to do it natively, without the help of static service locator.

miloush commented 1 month ago

Converters are only interface contracts, so it shouldn't be difficult to use them with any DI framework. If you need to locate an instance, you could do it using custom markup extension.

ali50m commented 1 month ago

@miloush I think it's hard to bind a Dependency Property (WPF's soul IMO) inside a MarkupExtension, right?

miloush commented 1 month ago

Can you give some example on how you want the XAML or code look like? You can certainly bind DP from a ME, that's what the Binding ME does...

ali50m commented 1 month ago

@miloush

for example, I have a converter like below, which is used to perform some converting job on a AccountGroup record and show the translation information on it. I have to use the Ioc.Default from CommunityToolkit to get the service.

namespace Device.Converters;

internal sealed class AccountGroupAuthorityMenuTranslationsConverter : IValueConverter
{
    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is IAccountGroup accountGroup)
        {
            var service1 = Ioc.Default.GetRequiredService<IAccountGroupTranslationService>();
            var translation = service1.GetConfigTranslation(accountGroup);
            if (translation is { })
                return translation;

            var service2 = Ioc.Default.GetRequiredService<ITranslationsService>();
            translation = service2.GetLocalTranslation(accountGroup.Translations);
            return translation;
        }

        throw new Exception($"the input [{value}], is not {nameof(IAccountGroup)}");
    }

    public object ConvertBack(
        object? value,
        Type targetType,
        object? parameter,
        CultureInfo culture
    )
    {
        return Binding.DoNothing;
    }
}
<TextBlock Text="{Binding AccountGroupMenuService.SelectedAccountGroup, Converter={converters2:AccountGroupAuthorityMenuTranslationsConverter}}" />
ali50m commented 1 month ago

In ASP.net, getting injected service is quite easy. I can even use code like app.MapGet("/", (IFoo service) => service.Bar())); to get service.

miloush commented 1 month ago

So if I understand correctly, picking MEF as an example, you would rather have

[Export(typeof(AccountGroupAuthorityMenuTranslationsConverter))]
class AccountGroupAuthorityMenuTranslationsConverter : ConverterBase
{
    [Import]
    IAccountGroupTranslationService _groupTranslationService;

    [Import]
    ITranslationsService _translationService;

    ...
}

And have that somehow be used in XAML, correct? If you do that, an instance will be created with all the imports and then you can request it from the container in the markup extension.

For example, for {Binding Converter={Inject AccountGroupAuthorityMenuTranslationsConverter}} and in the markup extension's ProvideValue, you would get it from the container. You don't even have to do any binding as far as I can tell, because it is a property on the binding ME.

ali50m commented 1 month ago

@miloush Sorry if I didn`t make my question clearly. I modified my code a little to use IValueConverter instead (I copied my source code directly before).

As you see, I have to use 3rd library to get instance from Ioc container, and then use it in the converter. That`s my whole pain point. I believe dependency injection is quite important in nowadays software development world. Morden WPF should have a way to natively bring DI into converter.

miloush commented 1 month ago

Same point though, why wouldn't what I suggested work? Which library are you using?

miloush commented 1 month ago

Otherwise this is duplicate of #499

ali50m commented 1 month ago

@miloush I can't fully understander your previous suggestion. I try to made a converter MarkupExtension like below, but still no luck about how to get the injected service. Could you help to make it clear for me? Thanks!

internal sealed class AccountGroupValueToTranslationConverter2 : MarkupExtension, IValueConverter
{
    [Import] // Where is this attribute from?
    IAccountGroupTranslationService _groupTranslationService;

    [Import]
    ITranslationsService _translationService;

    private AccountGroupValueToTranslationConverter2? _converter;

    private IAccountGroupTranslationService? _service1;
    private ITranslationsService? _service2;

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        _service1 = serviceProvider.GetRequiredService<IAccountGroupTranslationService>(); // can`t get the service from sp
        _service2 = serviceProvider.GetRequiredService<ITranslationsService>();

        _converter = new AccountGroupValueToTranslationConverter2();
        return _converter;
    }

    public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
    {
        if (value is not int accountValue)
            return "Wrong type!";

        var accountGroup = AccountGroup.GetMenu(accountValue);

        var translation =
            _service1!.GetConfigTranslation(accountGroup)
            ?? _service2!.GetLocalTranslation(accountGroup.Translations);

        return translation;
    }

    public object ConvertBack(
        object? value,
        Type targetType,
        object? parameter,
        CultureInfo culture
    )
    {
        return Binding.DoNothing;
    }
}

image

miloush commented 1 month ago

Import and Export attributes are part of MEF. If you are not using that framework, the example is not very helpful to you. Which DI library are you using?

ali50m commented 1 month ago

@miloush it's Microsoft.Extensions.DependencyInjection.

lindexi commented 1 month ago

@ali50m

My friend @h82258652 wrote the Chinese blog that provides a tutorial on injecting dependencies into the Converter, please refer to https://www.cnblogs.com/h82258652/p/12625081.html , and thank you @h82258652

And you can use the translator to translate the blog.

ali50m commented 1 month ago

@lindexi

The blogger's idea is basically the same as what I mentioned before, using a static service locator. I think he can also directly call the static service locator directly in the converter to get the desired service then without adjusting the ME ConverterType in xaml. I used Ioc.Default, the service locator from the Mvvmtoolkit, basically the same working way.

I still hope that Microsoft can officially add DI support to the converter, just like ASP.NET has done.