Шаблон 10 UWP и Внедрение отклонений в обслуживании (MVVM) не WPF

Я потратил более двух недель на поиск в google, bing, переполнении стека и в документах msdn, пытаясь выяснить, как правильно внедрить зависимости для мобильного приложения, которое я разрабатываю. Чтобы было ясно, я делаю DI каждый день в веб-приложениях. Мне не нужен ускоренный курс о том, что, кто и почему важен DI. Я знаю, что это так, и всегда принимаю это.

Что мне нужно понять, так это то, как это работает в мире мобильных приложений, в частности в мобильном приложении UWP Template 10.

Из моего прошлого, в приложении.net/Asp я могу "RegisterType(новый XYZ).Singleton() blah" {пожалуйста, прости синтаксис; просто пример} в App_Start.ConfigureServices. Это работает почти одинаково в.netcore, предоставлены некоторые синтаксические изменения.

Теперь моя проблема в том, что я пытаюсь обеспечить, чтобы мой API-интерфейс шел к приложению UWP, которое должно переварить мой сервис IXYZ. Я ни в коем случае не думаю, что они должны каждый раз "обновлять" экземпляр. Должен быть способ внедрить это в контейнер на стороне 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 любого бренда, но мне нужен пример того, как правильно использовать DI для сервисов в приложении UWP. 99,9% вопросов о DI касаются Views (то есть Prism?), А не просто DI для службы (например, DataRepo; он же API/DataService).

Опять же, я чувствую, что упускаю что-то очевидное и нуждаюсь в толчке в правильном направлении. Может кто-нибудь показать мне пример проекта, базовый код или базовую порку о том, как я не должен быть программистом... пожалуйста, не делайте этого (я не знаю, сможет ли мое эго это принять).

1 ответ

Вы можете попробовать Microsoft.Hosting.Extensions так же, как ASP.NET, есть реализация на 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, что довольно приятно.

С помощью @mvermef и SO-вопроса Injection Dependency Injection с использованием шаблона 10 я нашел решение. Это оказалась кроличья нора, где на каждом шагу я сталкивался с проблемой.

Первой проблемой было просто заставить Dependency Injection работать. Как только я смог понять это из приведенных выше источников, я смог начать внедрять свои сервисы в ViewModels и устанавливать их в DataContext в коде.

Затем я столкнулся с проблемой инъекции, связанной с внедрением моих служб IXYZ в ViewModels UserControls.

Страницы и их ViewModel работали отлично, но у меня были проблемы с тем, что DataContext UserControl не внедрялся с помощью ViewModel UserControl. Вместо этого они были введены ViewModel Пейджа, который держал его.

Окончательное решение оказалось в том, чтобы убедиться, что в UserControl был задан DataContext в XAML, а не код позади, как мы сделали со страницами, а затем создать свойство 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 добавьте свой UserControl

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>

В коде UserControl Behind добавить свойство зависимости

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

В конце концов, я не уверен, что это элегантное решение, но оно работает.

Другие вопросы по тегам