【UWP】通过 MarkupExtension 实现 ValueConveter 的依赖注入
最近是真的比较闲,花了点时间算是把我自己的微博库的 nuget 包的坑填上了(https://github.com/h82258652/HN.Social.Weibo 欢迎大佬来 Star)。dino 大佬也一直忽悠我弄动画,可惜我没啥艺术细胞而且 Composition API 也不太熟悉,就只能逃了(哈哈哈 )。闲着无事就刷刷 Github,看到 wpf repo 的一个 issue(https://github.com/dotnet/wpf/issues/499),确实目前的 XAML 跟控制反转这块几乎都没啥结合。控件层面由于要求无参构造函数,所以目前来看难以实现了。但 ValueConverter 这玩意,想了下,好像可以耶,于是做了下实验,成功并且写下了这篇 blog。
UWP 的 MarkupExtension 是在 16299 版本引入的,所以我们的项目必须要 target 16299 或以上。
以一般 MVVM 模式为例,创建 ViewModelLocator.cs,这里 IoC 容器我就使用最常用的 Autofac 好了,引用 Autofac.Extras.CommonServiceLocator 包。
public class ViewModelLocator { static ViewModelLocator() { var autofacServiceLocator = new AutofacServiceLocator(CreateAutofacContainer()); ServiceLocator.SetLocatorProvider(() => autofacServiceLocator); } private static IContainer CreateAutofacContainer() { var containerBuilder = new ContainerBuilder(); // TODO Register services return containerBuilder.Build(); } }
并修改 App.xaml
<Application x:Class="ConverterIocDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewModels="using:ConverterIocDemo.ViewModels"> <Application.Resources> <ResourceDictionary> <viewModels:ViewModelLocator x:Key="Locator" /> </ResourceDictionary> </Application.Resources> </Application>
接下来添加一些测试代码吧。
namespace ConverterIocDemo.Models { public class Person { public string Name { get; set; } public int Age { get; set; } } }
using ConverterIocDemo.Models; namespace ConverterIocDemo.Services { public interface IPersonService { string GetHello(Person person); } }
using System; using ConverterIocDemo.Models; namespace ConverterIocDemo.Services { public class PersonService : IPersonService { public string GetHello(Person person) { if (person == null) { throw new ArgumentNullException(nameof(person)); } var now = DateTime.Now; if (now.Hour >= 9 && now.Hour <= 21 && now.DayOfWeek != DayOfWeek.Sunday) { return $"大家好,我叫 {person.Name},今年 {person.Age} 岁"; } else { return "996 大法好(mmp)"; } } } }
using ConverterIocDemo.Models; namespace ConverterIocDemo.ViewModels { public class MainViewModel { public MainViewModel() { Person = new Person { Name = "justin liu", Age = 18 }; } public Person Person { get; } } }
using ConverterIocDemo.Services; using System; using Windows.UI.Xaml.Data; using ConverterIocDemo.Models; namespace ConverterIocDemo.Converters { public class PersonSayHelloConverter : IValueConverter { private readonly IPersonService _personService; public PersonSayHelloConverter(IPersonService personService) { _personService = personService; } public object Convert(object value, Type targetType, object parameter, string language) { return _personService.GetHello((Person)value); } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } } }
修改一下 ViewModelLocator,把这堆玩意注册上去。
using Autofac; using Autofac.Extras.CommonServiceLocator; using CommonServiceLocator; using ConverterIocDemo.Converters; using ConverterIocDemo.Services; namespace ConverterIocDemo.ViewModels { public class ViewModelLocator { static ViewModelLocator() { var autofacServiceLocator = new AutofacServiceLocator(CreateAutofacContainer()); ServiceLocator.SetLocatorProvider(() => autofacServiceLocator); } public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>(); private static IContainer CreateAutofacContainer() { var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterType<PersonService>().As<IPersonService>(); containerBuilder.RegisterType<MainViewModel>(); containerBuilder.RegisterType<PersonSayHelloConverter>().SingleInstance(); return containerBuilder.Build(); } } }
接下来就是本文关键,通过 MarkupExtension 消费这个 PersonSayHelloConveter 了。这里我就叫 ConverterProviderExtension。
using CommonServiceLocator; using System; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Markup; namespace ConverterIocDemo.Converters { [MarkupExtensionReturnType(ReturnType = typeof(IValueConverter))] public class ConverterProviderExtension : MarkupExtension { public Type ConverterType { get; set; } protected override object ProvideValue() { if (ConverterType == null) { throw new ArgumentException("转换器类型没有设置"); } return ServiceLocator.Current.GetInstance(ConverterType); } } }
接下来修改 MainPage 看看效果了
<Page x:Class="ConverterIocDemo.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="using:ConverterIocDemo.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:ConverterIocDemo" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{Binding Source={StaticResource Locator}, Path=Main}" mc:Ignorable="d"> <Grid> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=Person, Converter={converters:ConverterProvider ConverterType=converters:PersonSayHelloConverter}}" /> </Grid> </Page>
运行起来:
改个时间再跑起来:
还行。
理论上可以修改 MarkupExtensionReturnTypeAttribute 的 ReturnType 为 typeof(object) 然后从 IoC 容器获取任意已经注册了的东西就是了。但写完 blog 发现好像满满的伪需求的样子。ε=ε=ε=┏(゜ロ゜;)┛