UWP模板10和服务依赖注入(MVVM),不是WPF

6

我已经花费了两个多星期的时间在谷歌,必应,Stack Overflow和MSDN文档上搜索,试图找出如何对我正在开发的移动应用程序进行正确的依赖注入。需要澄清的是,我每天都会在Web应用程序中进行DI。我不需要关于什么、谁以及为什么DI很重要的速成课程。我知道它很重要,并且一直在接受它。

我需要理解的是,在移动应用程序世界中以及特别是UWP Template 10 Mobile应用程序中,这是如何工作的。

以前,我可以在.net/Asp应用程序中的App_Start.ConfigureServices中注册类型(new XYZ)。Singleton() blah” {请原谅语法;只是一个例子}。在.netcore中,这几乎相同,尽管有一些语法变化。

我的问题现在是,我正在尝试提供我的API给需要处理我的IXYZ服务的UWP应用程序。我绝对不认为他们应该每次都“new”出一个实例。必须有一种方法将其注入到UWP端的容器中;而我感觉我在这个过程中错过了一些非常简单的东西。

这是我拥有的代码:

App.xaml.cs

public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
    {
        // TODO: add your long-running task here

        //if (args.Kind == ActivationKind.LockScreen)
        //{

        //}
        RegisterServices();
        await NavigationService.NavigateAsync(typeof(Views.SearchCompanyPage));

    }

public static IServiceProvider Container { get; private set; }

private static void RegisterServices()
{
    var services = new ServiceCollection();
    services.AddSingleton<IXYZ, XYZ>();
    Container = services.BuildServiceProvider();
}

MainPage.xaml.cs:

public MainPage()
{
   InitializeComponent();
   NavigationCacheMode = NavigationCacheMode.Enabled;
}

MainPageViewModel:

public class MainPageViewModel : ViewModelBase
{
    private readonly IXYZ _xyz;
    public MainPageViewModel(IXYZ xyz)
    {
        //Stuff
        _xyz= xyz;
    }

}

我现在遇到了错误: XAML MainPage...无法构建ViewModel类型。为了能够在XAML中被构造,一个类型不能是抽象的、嵌套的泛型接口或结构体,并且必须有一个公共的默认构造函数。 我愿意使用任何品牌的IoC容器,但我需要一个如何在UWP应用程序中正确使用DI的示例。99.9%关于DI的问题都是关于视图的(例如Prism?),而不仅仅是简单的服务DI(例如DataRepo;又称API/DataService)。
再次强调,我感觉自己缺少一些显而易见的东西,需要在正确的方向上得到帮助。可以有人给我展示一个示例项目、基本代码或基础教程,或者请不要抨击我如何不成为一个程序员...请不要这样做(我不知道我的自尊心是否能承受)。

如果你能等一下,那么目前正在进行大规模的重构工作,这将极大地改善依赖注入。 - mvermef
有一个实现,但它不是您通常在当前版本的nuget中使用的已经预置的。 - mvermef
https://stackoverflow.com/questions/38023604/dependency-injection-using-template-10 - mvermef
@mvermef 我之前看了那篇帖子,但没能让它工作。我决定重试一下,这次不知道为什么就成功了。现在我的问题是,“ResolveForPage”函数虽然起作用,但我有一些带有ViewModels的用户控件,它们会因为“ResolveForPage”抛出错误而出现数据上下文错误。你知道“while”语句在重构等待中需要多长时间吗? - rabidwoo
现在 GitHub 的主分支上有一个可工作的版本。由于 RS3 发布是他们的目标,因此它尚未进行代码修复。我还怀疑很大一部分也将是跨平台的或非常接近跨平台的。DI 核心可根据您的 DI 容器需求进行交换。 - mvermef
2个回答

3
你可以像使用ASP.NET一样尝试Microsoft.Hosting.Extensions。James Montemagno在Xamarin.Forms上实现了它,并且它也可以在UWP中使用。我已经尝试过并且完美地运行。你需要更改一些部分才能使其正常工作。
在OnLaunched方法中添加Startup.Init();
    public static class Startup
    {
        public static IServiceProvider ServiceProvider { get; set; }
        public static void Init()
        {
            StorageFolder LocalFolder = ApplicationData.Current.LocalFolder;
            var configFile = ExtractResource("Sales.Client.appsettings.json", LocalFolder.Path);

            var host = new HostBuilder()
                        .ConfigureHostConfiguration(c =>
                        {
                            // Tell the host configuration where to file the file (this is required for Xamarin apps)
                            c.AddCommandLine(new string[] { $"ContentRoot={LocalFolder.Path}" });

                            //read in the configuration file!
                            c.AddJsonFile(configFile);
                        })
                        .ConfigureServices((c, x) =>
                        {
                            // Configure our local services and access the host configuration
                            ConfigureServices(c, x);
                        }).
                        ConfigureLogging(l => l.AddConsole(o =>
                        {
                            //setup a console logger and disable colors since they don't have any colors in VS
                            o.DisableColors = true;
                        }))
                        .Build();

            //Save our service provider so we can use it later.
            ServiceProvider = host.Services;
        }

        static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services)
        {
            //ViewModels
            services.AddTransient<HomeViewModel>();
            services.AddTransient<MainPageViewModel>();
        }

        static string ExtractResource(string filename, string location)
        {
            var a = Assembly.GetExecutingAssembly();
            
            using (var resFilestream = a.GetManifestResourceStream(filename))
            {
                if (resFilestream != null)
                {
                    var full = Path.Combine(location, filename);

                    using (var stream = File.Create(full))
                    {
                        resFilestream.CopyTo(stream);
                    }
                }
            }
            return Path.Combine(location, filename);
        }
    }

"

注入ViewModel也是可能的,这非常好。

"

0

在@mvermef的帮助下和SO问题Dependency Injection using Template 10的指引下,我找到了解决方案。这个过程中,我遇到了一个又一个问题。

第一个问题是让依赖注入正常工作。一旦我从上述来源中弄清楚了这一点,我就能够将我的服务注入到ViewModels中,并在代码后台将它们设置为DataContext。

然后,我遇到了一个注入问题,即将我的IXYZ服务注入到UserControls的ViewModels中。

页面及其ViewModels运行良好,但我在UserControl的DataContext中遇到了问题,它没有被注入到UserControl的ViewModel中,而是被包含它的页面的ViewModel所注入。

最终的解决方案是确保UserControlXAML中设置了DataContext,而不是像页面那样在代码后台中设置,然后在代码后台创建一个DependencyProperty。

请参阅以下基本解决方案。

为了使它正常工作,我从以下内容开始:

APP.XAML.CS

public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
        // long-running startup tasks go here
       RegisterServices();
       await Task.CompletedTask;
}

private static void RegisterServices()
{
        var services = new ServiceCollection();
        services.AddSingleton<IRepository, Repository>();
        services.AddSingleton<IBinderService, BinderServices>();

        **//ViewModels**
        **////User Controls**
        services.AddSingleton<AddressesControlViewModel, AddressesControlViewModel>();
        services.AddSingleton<CompanyControlViewModel, CompanyControlViewModel>();

        **//ViewModels**
        **////Pages**
        services.AddSingleton<CallListPageViewModel, CallListPageViewModel>();
        services.AddSingleton<CallListResultPageViewModel, CallListResultPageViewModel>();
        etc....

        Container = services.BuildServiceProvider();
}

public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
       **//INJECT THE VIEWMODEL FOR EACH PAGE**
       **//ONLY THE PAGE NOT USERCONTROL**
        if (page is CallListPage)
        {
            return Container.GetService<CallListPageViewModel>();
        }
        if (page is CallListResultPage)
        {
            return Container.GetService<CallListResultPageViewModel>();
        }

        etc...
        return base.ResolveForPage(page, navigationService);
    }

在页面的代码后台

CALLLISTPAGE.XAML.CS

public CallListPage()
    {
        InitializeComponent();
    }

    CallListPageViewModel _viewModel;

    public CallListPageViewModel ViewModel
    {
        get { return _viewModel ?? (_viewModel = (CallListPageViewModel)DataContext); }
    }

在您的XAML中添加您的用户控件

CALLLISTPAGE.XAML

<binder:CompanyControl Company="{x:Bind ViewModel.SelectedCompany, Mode=TwoWay}"/>

在您的 UserControl 中,请确保将 DataContext 添加到 XAML,而不是像我们在页面中所做的那样添加到代码后台。

COMPANYCONTROL.XAML

<UserControl.DataContext>
    <viewModels:CompanyControlViewModel x:Name="ViewModel" />
</UserControl.DataContext>

在用户控件的代码后台添加一个依赖属性。

COMPANYCONTROL.XAML.CS

    public static readonly DependencyProperty CompanyProperty = DependencyProperty.Register(
        "Company", typeof(Company), typeof(CompanyControl), new PropertyMetadata(default(Company), SetCompany));

    public CompanyControl()
    {
        InitializeComponent();
    }

    public Company Company
    {
        get => (Company) GetValue(CompanyProperty);
        set => SetValue(CompanyProperty, value);
    }

    private static void SetCompany(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as CompanyControl;
        var viewModel = control?.ViewModel;
        if (viewModel != null)
            viewModel.Company = (Company) e.NewValue;
    }

最后我不确定这是否是一个优雅的解决方案,但它能够工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接