WPF / MVVM uygulamasında bağımlılık enjeksiyonu nasıl işlenir


105

Yeni bir masaüstü uygulaması başlatıyorum ve bunu MVVM ve WPF kullanarak oluşturmak istiyorum.

Ben de TDD'yi kullanmak niyetindeyim.

Sorun şu ki, bağımlılıklarımı üretim koduma enjekte etmek için bir IoC kabını nasıl kullanmam gerektiğini bilmiyorum.

Aşağıdaki sınıfa ve arayüze sahip olduğumu varsayalım:

public interface IStorage
{
    bool SaveFile(string content);
}

public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

Ve sonra IStoragebağımlılığı olan başka bir sınıfım var, bu sınıfın bir ViewModel veya bir işletme sınıfı olduğunu da varsayalım ...

public class SomeViewModel
{
    private IStorage _storage;

    public SomeViewModel(IStorage storage){
        _storage = storage;
    }
}

Bununla, düzgün çalıştıklarından emin olmak için, taklitler kullanarak vb. Birim testleri kolayca yazabilirim.

Sorun, gerçek uygulamada kullanmak söz konusu olduğunda. IStorageArayüz için varsayılan bir uygulamayı birbirine bağlayan bir IoC kabına sahip olmam gerektiğini biliyorum , ancak bunu nasıl yapacağım?

Örneğin, aşağıdaki xaml'e sahip olsaydım nasıl olurdu:

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

Bu durumda WPF'ye bağımlılıkları enjekte etmesini nasıl doğru bir şekilde 'söyleyebilirim'?

Ayrıca, SomeViewModelC # kodumun bir örneğine ihtiyacım olduğunu varsayalım , bunu nasıl yapmalıyım?

Bunda tamamen kaybolduğumu hissediyorum, bununla başa çıkmanın en iyi yolunun nasıl olduğuna dair herhangi bir örnek veya rehberlik için minnettar olurum.

StructureMap'e aşinayım, ancak bir uzman değilim. Ayrıca, daha iyi / daha kolay / kullanıma hazır bir çerçeve varsa lütfen bana bildirin.


Önizlemede .net core 3.0 ile bunu bazı Microsoft nuget paketleriyle yapabilirsiniz.
Bailey Miller

Yanıtlar:


88

Ninject kullanıyorum ve birlikte çalışmanın bir zevk olduğunu gördüm. Her şey kodda ayarlanmıştır, sözdizimi oldukça basittir ve iyi bir dokümantasyona (ve SO'da pek çok yanıta) sahiptir.

Yani temelde şöyle:

Görünüm modelini oluşturun ve IStoragearayüzü yapıcı parametresi olarak alın:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

ViewModelLocatorGörünüm modeli için görünüm modelini Ninject'ten yükleyen bir get özelliği ile bir oluşturun :

class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

Make ViewModelLocatorApp.xaml bir uygulama geniş bir kaynak:

<Application ...>
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

Bağlama DataContextarasında UserControlViewModelLocator karşılık gelen özelliğine.

<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

Gerekli bağlamaları ( IStorageve görünüm modelini) ayarlayacak olan NinjectModule'ı miras alan bir sınıf oluşturun :

class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

Uygulama başlangıcında IoC çekirdeğini gerekli Ninject modülleri ile başlatın (şimdilik yukarıdakiler):

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

IocKernelIoC çekirdeğinin uygulama genelindeki örneğini tutmak için statik bir sınıf kullandım , böylece gerektiğinde ona kolayca erişebilirim:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

    public static void Initialize(params INinjectModule[] modules)
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(modules);
        }
    }
}

Bu çözüm , sınıfın bağımlılıklarını gizlediği için genellikle bir anti-model olarak kabul edilen bir durağan ServiceLocator(the IocKernel) kullanır . Bununla birlikte, UI sınıfları için bir tür manuel hizmet aramasından kaçınmak çok zordur, çünkü bunlar parametresiz bir kurucuya sahip olmalıdır ve yine de başlatmayı kontrol edemezsiniz, bu nedenle VM'yi enjekte edemezsiniz. En azından bu yol, tüm iş mantığının bulunduğu sanal makineyi tek başına test etmenize izin verir.

Daha iyi bir yolu olan varsa lütfen paylaşın.

DÜZENLEME: Lucky Likey, Ninject'in UI sınıflarını başlatmasına izin vererek statik hizmet bulucudan kurtulmak için bir cevap verdi. Cevabın detayları burada görülebilir


13
Bağımlılık ekleme konusunda yeniyim, ancak sizin çözümünüz, statik ViewModel Konumlandırıcıyı kullandığınız için Hizmet Bulucu anti-modelini Ninject ile birleştiriyor. Enjeksiyonun test edilme olasılığı daha düşük olan Xaml dosyasında yapıldığı iddia edilebilir. Daha iyi bir çözümüm yok ve muhtemelen sizinkini kullanacağım - yine de yanıtta bundan bahsetmenin faydalı olacağını düşünüyorum.
user3141326

Adam senin çözüm aşağıdaki Hattı ile yalnızca bir "Sorun" var, sadece büyük: DataContext="{Binding [...]}". Bu, VS-Designer'ın ViewModel'in Yapıcısındaki tüm Program Kodunu yürütmesine neden olur. Benim durumumda Pencere çalıştırılıyor ve VS ile herhangi bir etkileşimi mod olarak engelliyor. Belki de ViewModelLocator, Tasarım Zamanında "gerçek" ViewModellerin yerini belirlememek için değiştirilmelidir. - Diğer bir Çözüm ise "Proje Kodunu Devre Dışı Bırakmak" tır, bu da diğer her şeyin gösterilmesini engelleyecektir. Belki zaten buna düzgün bir çözüm buldunuz. Bu durumda bunu göstermenizi rica ederim.
LuckyLikey

@LuckyLikey d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" kullanmayı deneyebilirsiniz, ancak bunun bir fark yarattığından emin değilim. Ama VM yapıcısı neden / nasıl bir kalıcı pencere başlatıyor? Ve ne tür bir pencere?
sondergard

@son Aslında neden ve nasıl olduğunu bilmiyorum, ancak Çözüm Gezgini'nden bir Pencere Tasarımcısı açtığımda, yeni Sekme açıldığında pencere tasarımcı tarafından görüntüleniyor ve aynı pencere, modal hata ayıklama gibi görünüyor, VS "Micorosoft Visual Studio XAML Designer" dışındaki yeni bir işlemde barındırılır. İşlem kapatılırsa, VS-Designer da yukarıda belirtilen istisna ile başarısız olur. Geçici çözümünüzü deneyeceğim. Yeni Bilgi bulduğumda sizi bilgilendireceğim :)
LuckyLikey

1
@sondergard ServiceLocator Anti-Pattern'den kaçınarak cevabınızda bir iyileştirme yayınladım. Kontrol etmekten çekinmeyin.
LuckyLikey

53

DataContextSorunuzda, XAML'deki görünümün özelliğinin değerini ayarlarsınız . Bu, görünüm modelinizin varsayılan bir kurucuya sahip olmasını gerektirir. Ancak, belirttiğiniz gibi, bu, yapıcıya bağımlılıkları enjekte etmek istediğinizde bağımlılık ekleme ile iyi çalışmaz.

Dolayısıyla özelliği XAML'de ayarlayamazsınızDataContext . Bunun yerine başka alternatifleriniz var.

Uygulamanız basit bir hiyerarşik görünüm modeline dayanıyorsa, uygulama başladığında tüm görünüm modeli hiyerarşisini oluşturabilirsiniz ( StartupUriözelliği App.xamldosyadan kaldırmanız gerekecektir ):

public partial class App {

  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve<RootViewModel>();
    var window = new MainWindow { DataContext = viewModel };
    window.Show();
  }

}

Bu, kökleşmiş görünüm modellerinin bir nesne grafiğine dayanmaktadır, RootViewModelancak bazı görünüm modeli fabrikalarını, yeni alt görünüm modelleri oluşturmalarına olanak tanıyan ana görünüm modellerine enjekte edebilirsiniz, böylece nesne grafiğinin sabitlenmesi gerekmez. Bu aynı zamanda umarım soru cevapları ben bir örneğini gerek varsayalım SomeViewModelbenim dan csbunu nasıl yapmalıyım kod?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

  public void AddChild() {
    Children.Add(_childViewModelFactory.Create());
  }

  ObservableCollection<ChildViewModel> Children { get; private set; }

 }

class ChildViewModelFactory {

  public ChildViewModelFactory(/* ChildViewModel dependencies */) {
    // Store dependencies.
  }

  public ChildViewModel Create() {
    return new ChildViewModel(/* Use stored dependencies */);
  }

}

Uygulamanız doğası gereği daha dinamikse ve belki navigasyona dayanıyorsa, navigasyonu gerçekleştiren koda bağlanmanız gerekecektir. Yeni bir görünüme her gittiğinizde, bir görünüm modeli (DI konteynerinden), görünümün kendisini oluşturmanız ve DataContextgörünümün görünümünü görünüm modeline ayarlamanız gerekir. Bu görünümü önce bir görünüme dayalı bir görünüm modeli seçtiğinizde yapabilir veya bunu önce model görünümü yapabilirsiniz.görünüm modelinin hangi görünümün kullanılacağını belirlediği yer. Bir MVVM çerçevesi, DI kapsayıcınızı görünüm modellerinin oluşturulmasına bağlamanız için bu temel işlevselliği bir şekilde sağlar, ancak bunu kendiniz de uygulayabilirsiniz. Burada biraz belirsizim çünkü ihtiyaçlarınıza bağlı olarak bu işlevsellik oldukça karmaşık hale gelebilir. Bu, bir MVVM çerçevesinden aldığınız temel işlevlerden biridir, ancak kendi işlevinizi basit bir uygulamada kullanmak, size MVVM çerçevelerinin başlık altında ne sağladığını iyi bir şekilde anlamanızı sağlayacaktır.

DataContextXAML'de bildiremediğinizde, bazı tasarım zamanı desteğini kaybedersiniz. Görünüm modeliniz bazı veriler içeriyorsa, tasarım sırasında görünecek ve bu çok yararlı olabilir. Neyse ki, tasarım zamanı özniteliklerini WPF'de de kullanabilirsiniz . Bunu yapmanın bir yolu, aşağıdaki öznitelikleri <Window>öğeye veya <UserControl>XAML'ye eklemektir :

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True}"

Görünüm modeli türünün iki oluşturucusu olmalıdır; varsayılan tasarım zamanı verileri ve diğeri bağımlılık ekleme için:

class MyViewModel : INotifyPropertyChanged {

  public MyViewModel() {
    // Create some design-time data.
  }

  public MyViewModel(/* Dependencies */) {
    // Store dependencies.
  }

}

Bunu yaparak bağımlılık enjeksiyonunu kullanabilir ve tasarım zamanı desteğini iyi koruyabilirsiniz.


13
Tam olarak aradığım şey buydu. "Sadece [ yadde-ya ] çerçevesini kullan" diyen cevapları kaç kez okuduğum beni sinirlendiriyor . Bunların hepsi iyi ve güzel, ancak bunu önce kendim nasıl yapacağımı tam olarak bilmek istiyorum ve sonra benim için ne tür bir çerçevenin gerçekten yararlı olabileceğini öğrenebilirim. Bu kadar net yazdığın için teşekkürler.
kmote

28

Burada yayınladığım şey, sondergard'ın Cevabında bir iyileştirme, çünkü söyleyeceğim şey bir Yoruma uymuyor :)

Fact bir ihtiyacını ortadan kaldırır düzgün bir çözüm, tanıtan am ServiceLocator ve için bir sarıcı StandardKernelSondergard en Çözüm denir -Instance, IocContainer. Neden? Bahsedildiği gibi, bunlar anti-kalıplardır.

Yapımı StandardKernelher yerde kullanılabilir

Ninject'in büyüsünün anahtarı StandardKernel-Method'u kullanmak için gerekli olan .Get<T>()-Instance'tır.

Sondergard'lara alternatif olarak -Class'ın içini IocContaineroluşturabilirsiniz .StandardKernelApp

StartUpUri'yi App.xaml'den kaldırmanız yeterli

<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

Bu, App.xaml.cs içinde Uygulamanın CodeBehind

public partial class App
{
    private IKernel _iocKernel;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get<MainWindow>();
        Current.MainWindow.Show();
    }
}

Şu andan itibaren Ninject yaşıyor ve savaşmaya hazır :)

Enjekte etmek DataContext

Ninject yaşarken, her türlü enjeksiyonu gerçekleştirebilirsiniz, örn. Özellik Belirleyici Enjeksiyonu veya en yaygın olanı Yapıcı Enjeksiyonu .

Bu, içine ViewModel enjekte nasıl Window'sDataContext

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

Elbette IViewModeldoğru ciltlemeleri yaparsanız bir de enjekte edebilirsiniz , ancak bu cevabın bir parçası değildir.

Kernel'e doğrudan erişim

Çekirdekte Yöntemleri doğrudan .Get<T>()çağırmanız gerekirse (örn. -Yöntem), Çekirdeğin kendisini enjekte etmesine izin verebilirsiniz.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

Çekirdeğin yerel bir örneğine ihtiyacınız varsa, bunu Özellik olarak enjekte edebilirsiniz.

    [Inject]
    public IKernel Kernel { private get; set; }

Her ne kadar bu oldukça faydalı olsa da, bunu yapmanızı tavsiye etmem. Sadece bu şekilde enjekte edilen nesnelerin Oluşturucu içinde bulunmayacağını unutmayın, çünkü daha sonra enjekte edilir.

Bu bağlantıya göre , IKernel(DI Konteyner) enjekte etmek yerine fabrika Uzantısını kullanmalısınız .

Bir yazılım sisteminde bir DI konteyneri kullanmak için önerilen yaklaşım, uygulamanın Kompozisyon Kökünün konteynere doğrudan dokunulduğu tek yer olmasıdır.

Ninject.Extensions.Factory'nin nasıl kullanılacağı da burada kırmızı olabilir .


Güzel yaklaşım. Ninject'i hiç bu seviyeye kadar keşfetmedim, ama kaçırdığımı görebiliyorum :)
sondergard

@son thx. Cevabınızın sonunda, eğer birinin daha iyi bir yolu varsa, lütfen paylaşın. Buna bir bağlantı ekleyebilir misiniz?
LuckyLikey

Birisi bunun nasıl kullanılacağıyla ilgileniyorsa, Ninject.Extensions.Factorybunu burada yorumlarda belirtin ve biraz daha bilgi ekleyeceğim.
LuckyLikey

1
@LuckyLikey: Parametresiz kurucusu olmayan XAML aracılığıyla bir pencere veri içeriğine bir ViewModel'i nasıl ekleyebilirim? ServiceLocator ile sondergard'ın çözümüyle bu durum mümkün olacaktır.
Thomas Geulen

Bu yüzden lütfen ekli mülklerde ihtiyaç duyduğum hizmetleri nasıl alacağımı söyleyin? Hem destek DependencyPropertyalanı hem de Get ve Set yöntemleri her zaman statiktir .
springy76

12

Görünüm modelini görünümün yapıcısına (arkasındaki kodda) veri bağlamına atanan bir "önce görünüm" yaklaşımını tercih ediyorum, örneğin

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

Bu, XAML tabanlı yaklaşımınızın yerini alır.

Gezinmeyi yönetmek için Prism çerçevesini kullanıyorum - bazı kodlar belirli bir görünümün görüntülenmesini istediğinde ("gezinerek"), Prism bu görünümü çözecektir (dahili olarak, uygulamanın DI çerçevesini kullanarak); DI çerçeve dönüş kararlılığı görünümü (bizim örneğimizde görünümü modeli) sahip herhangi bir bağımlılıkları içinde, daha sonra giderir olacak onun bağımlılıklarını, vb.

DI çerçevesinin seçimi, hepsi temelde aynı şeyi yaptıkları için hemen hemen alakasızdır, yani, çerçevenin o arayüze bağımlılık bulduğunda somutlaştırmasını istediğiniz somut türle birlikte bir arayüz (veya bir tür) kaydedersiniz. Kayıt için Castle Windsor kullanıyorum.

Prism navigasyonu alışmak için biraz zaman alır, ancak bir kez etrafından dolaştığınızda oldukça iyidir ve uygulamanızı farklı görünümler kullanarak oluşturmanıza olanak tanır. Örneğin, ana pencerenizde bir Prism "bölgesi" oluşturabilir, ardından Prism navigasyonunu kullanarak bu bölge içinde bir görünümden diğerine geçebilirsiniz, örneğin kullanıcı menü öğelerini veya her neyse seçerken.

Alternatif olarak, MVVM Light gibi MVVM çerçevelerinden birine bir göz atın. Bunlarla ilgili hiçbir deneyimim yok, bu yüzden kullanmaktan hoşlandıkları hakkında yorum yapamam.


1
Yapıcı argümanlarını alt görüşlere nasıl iletirsiniz? Bu yaklaşımı denedim, ancak ana görünümde, alt görünümün varsayılan parametresiz bir
Doctor Jones

10

MVVM Light'ı kurun.

Kurulumun bir parçası, bir görünüm modeli bulucu oluşturmaktır. Bu, görünüm modellerinizi özellikler olarak ortaya çıkaran bir sınıftır. Bu özelliklerin alıcıları, daha sonra IOC motorunuzdan örnekler döndürülebilir. Neyse ki, MVVM ışığı, SimpleIOC çerçevesini de içerir, ancak isterseniz başkalarını da bağlayabilirsiniz.

Basit IOC ile bir türe karşı bir uygulama kaydedersiniz ...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);

Bu örnekte, görünüm modeliniz oluşturucusuna göre bir servis sağlayıcı nesnesi oluşturulmuş ve iletilmiştir.

Daha sonra IOC'den bir örnek döndüren bir özellik oluşturursunuz.

public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}

Akıllı kısım, görünüm modeli bulucunun daha sonra app.xaml veya eşdeğerinde bir veri kaynağı olarak oluşturulmasıdır.

<local:ViewModelLocator x:key="Vml" />

Görünüm modelinizi enjekte edilmiş bir hizmetle almak için artık 'MyViewModel' özelliğine bağlanabilirsiniz.

Umarım yardımcı olur. Bir iPad'de bellekten kodlanan herhangi bir kod yanlışlığı için özür dileriz.


Uygulamanın önyüklemesine GetInstanceveya resolvedışına sahip olmamalısınız . DI'nin amacı bu!
Soleil - Mathieu Prévot

Başlangıç ​​sırasında özellik değerini ayarlayabileceğinizi kabul ediyorum, ancak tembel örnekleme kullanmanın DI'ye aykırı olduğunu söylemek yanlıştır.
kidshaw

@kishaw yapmadım.
Soleil - Mathieu Prévot

3

Canonic DryIoc durum

Eski bir gönderiyi yanıtlamak, ancak bunu yapmak DryIocve düşündüğüm şeyi yapmak, DI ve arayüzlerin iyi bir kullanımıdır (somut sınıfların minimum kullanımı).

  1. Bir WPF uygulamasının başlangıç ​​noktası App.xaml, ve orada ilk bakış açısının ne olduğunu söyleriz; bunu varsayılan xaml yerine arkasındaki kodla yapıyoruz:
  2. StartupUri="MainWindow.xaml"App.xaml'de kaldır
  3. codebehind'de (App.xaml.cs) şunu ekleyin override OnStartup:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DryContainer.Resolve<MainWindow>().Show();
    }

bu başlangıç ​​noktasıdır; aynı zamanda aranması resolvegereken tek yer burası .

  1. konfigürasyon kökü (Mark Seeman'ın .NET'e Dependency injection kitabına göre; somut sınıflardan bahsedilmesi gereken tek yer) kurucuda aynı kod arkasında olacaktır:

    public Container DryContainer { get; private set; }
    
    public App()
    {
        DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
        DryContainer.Register<IDatabaseManager, DatabaseManager>();
        DryContainer.Register<IJConfigReader, JConfigReader>();
        DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
            Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
        DryContainer.Register<MainWindow>();
    }

Açıklamalar ve birkaç ayrıntı daha

  • Beton dersini sadece manzara ile kullandım MainWindow;
  • ViewModel için hangi yapıcının kullanılacağını (bunu DryIoc ile yapmamız gerekiyor) belirtmem gerekiyordu, çünkü XAML tasarımcısı için varsayılan kurucunun var olması gerekiyor ve enjeksiyonlu kurucu uygulama için kullanılan asıl yapıcıdır.

DI ile ViewModel yapıcısı:

public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
    _dbMgr = dbmgr;
    _jconfigReader = jconfigReader;
}

ViewModel tasarım için varsayılan yapıcı:

public MainWindowViewModel()
{
}

Görünümün kod arkasındaki:

public partial class MainWindow
{
    public MainWindow(IMainWindowViewModel vm)
    {
        InitializeComponent();
        ViewModel = vm;
    }

    public IViewModel ViewModel
    {
        get { return (IViewModel)DataContext; }
        set { DataContext = value; }
    }
}

ve ViewModel ile bir tasarım örneği almak için görünümde (MainWindow.xaml) gerekenler:

d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"

Sonuç

Bu nedenle, bir DryIoc konteyneri ve DI ile bir WPF uygulamasının çok temiz ve minimal bir uygulamasını elde ederken, görünümlerin ve görünüm modellerinin tasarım örneklerini de mümkün kılıyoruz.


2

Yönetilen Genişletilebilirlik Çerçevesini kullanın .

[Export(typeof(IViewModel)]
public class SomeViewModel : IViewModel
{
    private IStorage _storage;

    [ImportingConstructor]
    public SomeViewModel(IStorage storage){
        _storage = storage;
    }

    public bool ProperlyInitialized { get { return _storage != null; } }
}

[Export(typeof(IStorage)]
public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

//Somewhere in your application bootstrapping...
public GetViewModel() {
     //Search all assemblies in the same directory where our dll/exe is
     string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
     var catalog = new DirectoryCatalog(currentPath);
     var container = new CompositionContainer(catalog);
     var viewModel = container.GetExport<IViewModel>();
     //Assert that MEF did as advertised
     Debug.Assert(viewModel is SomViewModel); 
     Debug.Assert(viewModel.ProperlyInitialized);
}

Genel olarak, yapacağınız şey statik bir sınıfa sahip olmak ve Fabrika Modelini size genel bir kap (önbelleğe alınmış, natch) sağlamak için kullanmaktır.

Görünüm modellerinin nasıl enjekte edileceğine gelince, onları diğer her şeyi enjekte ettiğiniz gibi enjekte edersiniz. XAML dosyasının arkasındaki kodda içe aktaran bir kurucu oluşturun (veya bir özelliğe / alana bir içe aktarma ifadesi yerleştirin) ve ona görünüm modelini içe aktarmasını söyleyin. Sonra bağlama senin Window's DataContexto mülke. Aslında konteynerden kendiniz çıkardığınız kök nesneleriniz genellikle oluşturulmuş Windownesnelerdir. Sadece pencere sınıflarına arayüzler ekleyin ve bunları dışa aktarın, ardından yukarıdaki gibi katalogdan alın (App.xaml.cs'de ... bu WPF önyükleme dosyasıdır).


Herhangi bir örnek oluşturmadan kaçınmak olan önemli bir DI noktasını kaçırıyorsunuz new.
Soleil - Mathieu Prévot

0

ViewModel'i kullanmanızı öneririm - İlk yaklaşım https://github.com/Caliburn-Micro/Caliburn.Micro

bkz: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions

Castle WindsorIOC kapsayıcı olarak kullanın .

Sözleşmeler Hakkında Her Şey

Caliburn.Micro'nun temel özelliklerinden biri, bir dizi konvansiyona göre hareket ederek kazan plakası kodu ihtiyacını ortadan kaldırma kabiliyetinde kendini gösterir. Bazı insanlar gelenekleri sever ve bazıları onlardan nefret eder. Bu nedenle CM'nin kuralları tamamen özelleştirilebilir ve hatta istenmediğinde tamamen kapatılabilir. Kuralları kullanacaksanız ve varsayılan olarak AÇIK olduklarından, bu kuralların ne olduğunu ve nasıl çalıştığını bilmek iyidir. Bu makalenin konusu bu. Çözünürlüğü Görüntüle (ViewModel-First)

Temel bilgiler

CM'yi kullanırken karşılaşacağınız ilk kural, görünüm çözünürlüğü ile ilgilidir. Bu kural, uygulamanızın ViewModel-First alanlarını etkiler. ViewModel-First'te, ekrana dönüştürmemiz gereken mevcut bir ViewModel'imiz var. Bunu yapmak için CM, ViewModel'e ve ekrana bağlanması gereken bir UserControl1 bulmak için basit bir adlandırma modeli kullanır. Peki bu model nedir? Bunu öğrenmek için ViewLocator.LocateForModelType'a bir göz atalım:

public static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) =>{
    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if(context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
        viewTypeName = viewTypeName + "." + context;
    }

    var viewType = (from assmebly in AssemblySource.Instance
                    from type in assmebly.GetExportedTypes()
                    where type.FullName == viewTypeName
                    select type).FirstOrDefault();

    return viewType == null
        ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
        : GetOrCreateViewType(viewType);
};

İlk önce "bağlam" değişkenini göz ardı edelim. Görünümü türetmek için, VM'lerinizin adlandırılmasında "ViewModel" metnini kullandığınızı varsayıyoruz, bu nedenle "Model" kelimesini kaldırarak onu bulduğumuz her yerde "Görüntüle" olarak değiştiriyoruz. Bu, hem tür adlarını hem de ad alanlarını değiştirme etkisine sahiptir. Böylece ViewModels.CustomerViewModel, Views.CustomerView olur. Veya uygulamanızı özelliğe göre düzenliyorsanız: CustomerManagement.CustomerViewModel, CustomerManagement.CustomerView olur. Umarım bu oldukça basittir. İsme sahip olduğumuzda, o isimdeki türleri ararız. CM'ye maruz bıraktığınız herhangi bir derlemeyi AssemblySource aracılığıyla aranabilir olarak ararız. Tipi bulamazsak,

Şimdi, bu "bağlam" değerine geri dönelim. CM'nin aynı ViewModel üzerinde birden çok Görünümü nasıl desteklediği budur. Bir bağlam (tipik olarak bir dize veya bir enum) sağlanırsa, bu değere bağlı olarak adın daha ileri bir dönüşümünü yaparız. Bu dönüşüm, sondan "Görünüm" kelimesini kaldırıp bunun yerine bağlamı ekleyerek farklı görünümler için bir klasörünüz (ad alanı) olduğunu varsayar. Dolayısıyla, "Ana" bağlamı verildiğinde, ViewModels.CustomerViewModel'imiz Views.Customer.Master olacaktır.


2
Gönderinizin tamamı fikirdir.
John Peters

-1

Başlangıç ​​uri'sini app.xaml dosyanızdan kaldırın.

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IoC.Configure(true);

        StartupUri = new Uri("Views/MainWindowView.xaml", UriKind.Relative);

        base.OnStartup(e);
    }
}

Artık örnekleri oluşturmak için IoC sınıfınızı kullanabilirsiniz.

MainWindowView.xaml.cs

public partial class MainWindowView
{
    public MainWindowView()
    {
        var mainWindowViewModel = IoC.GetInstance<IMainWindowViewModel>();

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

App.xaml.cs dışında herhangi bir kapsayıcıya GetInstancesahip resolveolmamalısınız, DI noktasını kaybediyorsunuz. Ayrıca, görünümün kod arka planında xaml görünümünden bahsetmek biraz karmaşıktır. Görünümü saf c # olarak çağırın ve bunu kapsayıcıyla yapın.
Soleil - Mathieu Prévot
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.