Шаблон 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;
}
В конце концов, я не уверен, что это элегантное решение, но оно работает.