【问题标题】:Navigation and DI导航和 DI
【发布时间】:2018-05-18 20:11:16
【问题描述】:

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

await Navigation.PushAsync(new SecondPageView());

对于 DI:

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

我知道实现 Di 的正确方法是创建一个界面并从该步骤开始,但问题是当我尝试时,我未能尝试拥有一个良好的导航系统(例如在文件分开)。

谁有我可以看的示例?或者也许有一些迹象可以继续?

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

提前致谢!

【问题讨论】:

  • 我不明白你到底想要什么。您想像 Prism 那样实现自己的 ViewModelLocator 吗?
  • @DiegoRafaelSouza 是的,我想知道哪种方法是实现 DI 和导航结构(例如 MvvmCross)的最佳方法,但不使用它(如果可能的话)
  • 看看这两个帖子mallibone.com/post/xamarin.forms-navigation-with-mvvm-lightalexdunn.org/2017/06/01/…。我知道您不想使用框架,但是 MVVM light 非常简单和小巧,您可以看看它
  • @Johannes 谢谢你的回复,我会检查两个链接
  • 这很有可能,但我认为不值得。这些框架太轻了,已经处理了很多问题,一旦你开始开发和/或使用就会让你头疼。

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


【解决方案1】:

(我会尽量简化所有代码示例)。

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.我们需要一个对象来负责为特定的ViewModel 检索Page(视图),反之亦然。第二种情况在设置应用程序的根/主页面时可能很有用。为此,我们应该同意一个简单的约定,即所有ViewModels 都应该在ViewModels 目录中,Pages(Views) 应该在Views 目录中。换句话说,ViewModels 应该存在于 [MyApp].ViewModels 命名空间中,Pages(Views) 应该存在于 [MyApp].Views 命名空间中。除此之外,我们应该同意WelcomeView(Page) 应该有一个WelcomeViewModel 等等。这是一个映射器的代码示例:

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

3.对于设置根页面的情况,我们需要ViewModelLocator,它会自动设置BindingContext

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>

4.最后,我们需要一个支持ViewModel First Navigation 方法的NavigationService

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 的方法,这些方法将在导航后立即执行。下面是一个导航示例:

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

据您了解,这种方法更复杂、更难调试并且可能会造成混淆。但是有很多优点,而且您实际上不必自己实现它,因为大多数 MVVM 框架都支持它开箱即用。 github 上提供了此处演示的代码示例。

有很多关于 ViewModel First Navigation 方法的好文章,还有一本免费的 Enterprise Application Patterns using Xamarin.Forms 电子书详细解释了这个和许多其他有趣的主题。

【讨论】:

  • 干得好!我向您的出色回答致敬,这就是我热爱社区的原因:)
  • 非常感谢!我已经检查了 Xamarin.Forms 电子书,但是当我尝试在我的代码中实现时我迷失了。无论如何,非常感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-31
  • 1970-01-01
  • 1970-01-01
  • 2011-01-09
相关资源
最近更新 更多