导航和DI

时间:2018-05-18 20:11:16

标签: c# xamarin dependency-injection xamarin.forms navigation

我试图制作一个标准代码,以便在我的xamarin.forms应用程序中实现。我想要做的是有一种方法在viewmodels和wey之间导航,以正确实现依赖注入。 我目前正在做的导航:

await Navigation.PushAsync(new SecondPageView());

对于DI:

 var test = DependencyService.Get<ITestService>();
 WelcomeMessage = test.GetSystemWelcome();

我知道实现Di的正确方法是创建一个接口并从该步骤开始,但问题是当我尝试时,我没有尝试拥有一个好的导航系统(比如注册视图和视图模型)文件分开)。

有没有人有一个示例我可以看看?或者可能有一些迹象表明要继续?

PD:我试图避免像MvvMcross那样的框架。

提前致谢!

1 个答案:

答案 0 :(得分:3)

(我将尝试尽可能简化所有代码示例)。

1.首先,我们需要一个可以注册所有对象并可选择定义其生命周期的地方。对于这个问题,我们可以使用IOC容器,您可以自己选择一个。在这个例子中,我将使用Autofac(它是速度最快的之一)。我们可以在App中保留对它的引用,以便全局可用(不是一个好主意,但需要简化):

public class DependencyResolver
{
    static IContainer container;

    public DependencyResolver(params Module[] modules)
    {
        var builder = new ContainerBuilder();

        if (modules != null)
            foreach (var module in modules)
                builder.RegisterModule(module);

        container = builder.Build();
    }

    public T Resolve<T>() => container.Resolve<T>();
    public object Resolve(Type type) => container.Resolve(type);
}

public partial class App : Application
{
    public DependencyResolver DependencyResolver { get; }

    // Pass here platform specific dependencies
    public App(Module platformIocModule)
    {
        InitializeComponent();
        DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
        MainPage = new WelcomeView();
    }

    /* The rest of the code ... */
}

2.我们需要一个负责检索特定Page的{​​{1}}(视图)的对象,反之亦然。第二种情况可能在设置应用程序的根/主页面时很有用。为此,我们应该就一个简单的约定达成一致,即所有ViewModel都应该在ViewModels目录中,ViewModels(视图)应该在Pages目录中。换句话说,Views应该位于ViewModels命名空间中的[MyApp].ViewModelsPages(视图)中。除此之外,我们应该同意[MyApp].Views(Page)应该有WelcomeView等。以下是映射器的代码示例:

WelcomeViewModel

3.对于设置根页面的情况,我们需要一些public class TypeMapperService { public Type MapViewModelToView(Type viewModelType) { var viewName = viewModelType.FullName.Replace("Model", string.Empty); var viewAssemblyName = GetTypeAssemblyName(viewModelType); var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName); return Type.GetType(viewTypeName); } public Type MapViewToViewModel(Type viewType) { var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels."); var viewModelAssemblyName = GetTypeAssemblyName(viewType); var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName); return Type.GetType(viewTypeModelName); } string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName; string GenerateTypeName(string format, string typeName, string assemblyName) => string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName); } 来自动设置ViewModelLocator

BindingContext

4.最后,我们需要一个支持public static class ViewModelLocator { public static readonly BindableProperty AutoWireViewModelProperty = BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged); public static bool GetAutoWireViewModel(BindableObject bindable) => (bool)bindable.GetValue(AutoWireViewModelProperty); public static void SetAutoWireViewModel(BindableObject bindable, bool value) => bindable.SetValue(AutoWireViewModelProperty, value); static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>(); static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue) { var view = bindable as Element; var viewType = view.GetType(); var viewModelType = mapper.MapViewToViewModel(viewType); var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType); view.BindingContext = viewModel; } } // Usage example <?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewmodels="clr-namespace:MyApp.ViewModel" viewmodels:ViewModelLocator.AutoWireViewModel="true" x:Class="MyApp.Views.MyPage"> </ContentPage> 方法的NavigationService

ViewModel First Navigation

您可能会看到public class NavigationService { TypeMapperService mapperService { get; } public NavigationService(TypeMapperService mapperService) { this.mapperService = mapperService; } protected Page CreatePage(Type viewModelType) { Type pageType = mapperService.MapViewModelToView(viewModelType); if (pageType == null) { throw new Exception($"Cannot locate page type for {viewModelType}"); } return Activator.CreateInstance(pageType) as Page; } protected Page GetCurrentPage() { var mainPage = Application.Current.MainPage; if (mainPage is MasterDetailPage) { return ((MasterDetailPage)mainPage).Detail; } // TabbedPage : MultiPage<Page> // CarouselPage : MultiPage<ContentPage> if (mainPage is TabbedPage || mainPage is CarouselPage) { return ((MultiPage<Page>)mainPage).CurrentPage; } return mainPage; } public Task PushAsync(Page page, bool animated = true) { var navigationPage = Application.Current.MainPage as NavigationPage; return navigationPage.PushAsync(page, animated); } public Task PopAsync(bool animated = true) { var mainPage = Application.Current.MainPage as NavigationPage; return mainPage.Navigation.PopAsync(animated); } public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel => InternalPushModalAsync(typeof(TViewModel), animated, parameter); public Task PopModalAsync(bool animated = true) { var mainPage = GetCurrentPage(); if (mainPage != null) return mainPage.Navigation.PopModalAsync(animated); throw new Exception("Current page is null."); } async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter) { var page = CreatePage(viewModelType); var currentNavigationPage = GetCurrentPage(); if (currentNavigationPage != null) { await currentNavigationPage.Navigation.PushModalAsync(page, animated); } else { throw new Exception("Current page is null."); } await (page.BindingContext as BaseViewModel).InitializeAsync(parameter); } } - 所有BaseViewModel的抽象基类,您可以在其中定义ViewModels之类的方法,这些方法将在导航后立即执行。以下是导航示例:

InitializeAsync

如您所知,这种方法更复杂,更难调试,可能会让人感到困惑。然而,由于大多数MVVM框架都支持开箱即用,因此您实际上不必自己实现它有许多优点。此处演示的代码示例可在github上找到。

有很多关于public class WelcomeViewModel : BaseViewModel { public ICommand NewGameCmd { get; } public ICommand TopScoreCmd { get; } public ICommand AboutCmd { get; } public WelcomeViewModel(INavigationService navigation) : base(navigation) { NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>()); TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>()); AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>()); } } 方法的好文章,还有一本免费的Enterprise Application Patterns using Xamarin.Forms电子书,它详细解释了这个以及许多其他有趣的主题。