Cách xử lý chèn phụ thuộc trong ứng dụng WPF / MVVM


102

Tôi đang bắt đầu một ứng dụng máy tính để bàn mới và tôi muốn xây dựng nó bằng MVVM và WPF.

Tôi cũng đang có ý định sử dụng TDD.

Vấn đề là tôi không biết mình nên sử dụng vùng chứa IoC như thế nào để đưa các phần phụ thuộc vào mã sản xuất của mình.

Giả sử tôi có lớp và giao diện sau:

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

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

Và sau đó tôi có một lớp khác có IStoragevai trò phụ thuộc, cũng giả sử rằng lớp này là ViewModel hoặc lớp doanh nghiệp ...

public class SomeViewModel
{
    private IStorage _storage;

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

Với điều này, tôi có thể dễ dàng viết các bài kiểm tra đơn vị để đảm bảo rằng chúng đang hoạt động bình thường, sử dụng mocks, v.v.

Vấn đề là khi sử dụng nó trong ứng dụng thực. Tôi biết rằng tôi phải có vùng chứa IoC liên kết triển khai mặc định cho IStoragegiao diện, nhưng tôi sẽ làm điều đó như thế nào?

Ví dụ, nó sẽ như thế nào nếu tôi có xaml sau:

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

Làm cách nào để tôi có thể 'yêu cầu' WPF chèn các phụ thuộc một cách chính xác trong trường hợp đó?

Ngoài ra, giả sử tôi cần một phiên bản SomeViewModeltừ mã C # của mình, tôi nên làm như thế nào?

Tôi cảm thấy mình hoàn toàn lạc lối trong việc này, tôi sẽ đánh giá cao bất kỳ ví dụ hoặc hướng dẫn nào về cách tốt nhất để xử lý nó.

Tôi quen thuộc với StructureMap, nhưng tôi không phải là chuyên gia. Ngoài ra, nếu có một khuôn khổ tốt hơn / dễ dàng hơn / vượt trội hơn, vui lòng cho tôi biết.


Với .net core 3.0 trong bản xem trước, bạn có thể làm điều đó với một số gói nuget của Microsoft.
Bailey Miller

Câu trả lời:


87

Tôi đã và đang sử dụng Ninject và thấy rằng rất vui khi được làm việc cùng. Mọi thứ đều được thiết lập bằng mã, cú pháp khá đơn giản và nó có một tài liệu tốt (và rất nhiều câu trả lời trên SO).

Vì vậy, về cơ bản nó diễn ra như thế này:

Tạo mô hình chế độ xem và lấy IStoragegiao diện làm tham số hàm tạo:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Tạo một thuộc ViewModelLocatortính get cho mô hình chế độ xem, sẽ tải mô hình chế độ xem từ Ninject:

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

Tạo ViewModelLocatortài nguyên rộng rãi cho ứng dụng trong App.xaml:

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

Ràng buộc DataContextthuộc tính UserControltương ứng trong ViewModelLocator.

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

Tạo một lớp kế thừa NinjectModule, lớp này sẽ thiết lập các ràng buộc cần thiết ( IStoragevà mô hình chế độ xem):

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

Khởi tạo nhân IoC khi khởi động ứng dụng bằng các mô-đun Ninject cần thiết (hiện tại là mô-đun ở trên):

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

        base.OnStartup(e);
    }
}

Tôi đã sử dụng một IocKernellớp tĩnh để chứa phiên bản rộng của ứng dụng của nhân IoC, vì vậy tôi có thể dễ dàng truy cập nó khi cần:

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

Giải pháp này sử dụng static ServiceLocator(the IocKernel), thường được coi là anti-pattern, bởi vì nó ẩn các phụ thuộc của lớp. Tuy nhiên, rất khó để tránh một số kiểu tra cứu dịch vụ thủ công cho các lớp UI, vì chúng phải có một hàm tạo không tham số và dù sao thì bạn cũng không thể kiểm soát việc khởi tạo, vì vậy bạn không thể đưa VM. Ít nhất thì cách này cho phép bạn kiểm tra VM một cách riêng biệt, đó là nơi chứa đựng tất cả logic nghiệp vụ.

Nếu ai có cách tốt hơn, xin vui lòng chia sẻ.

CHỈNH SỬA: Lucky Likey đã đưa ra câu trả lời để loại bỏ bộ định vị dịch vụ tĩnh, bằng cách cho phép Ninject khởi tạo các lớp giao diện người dùng. Chi tiết câu trả lời có thể xem tại đây


13
Tôi chưa quen với việc tiêm phụ thuộc, nhưng về cơ bản thì giải pháp của bạn là kết hợp chống mẫu Service Locator với Ninject vì bạn đang sử dụng ViewModel Locator tĩnh. Người ta có thể tranh luận việc tiêm được thực hiện trong tệp Xaml, tệp này ít có khả năng được kiểm tra hơn. Tôi không có giải pháp nào tốt hơn và có thể sẽ sử dụng giải pháp của bạn - nhưng tôi nghĩ cũng sẽ hữu ích nếu đề cập vấn đề này trong câu trả lời.
user3141326

Man giải pháp của bạn chỉ là tuyệt vời, chỉ có một "vấn đề" với các dòng sau: DataContext="{Binding [...]}". Điều này khiến VS-Designer thực thi tất cả Mã chương trình trong Bộ tạo của ViewModel. Trong trường hợp của tôi, Cửa sổ đang được thực thi và chặn theo phương thức bất kỳ tương tác nào với VS. Có lẽ người ta nên sửa đổi ViewModelLocator để không xác định ViewModels "thực" trong Design-Time. - Một Giải pháp khác là "Tắt mã dự án", điều này cũng sẽ ngăn mọi thứ khác hiển thị. Có lẽ bạn đã tìm thấy một giải pháp gọn gàng cho điều này. Trong trường hợp này, tôi vui lòng cho bạn xem nó.
LuckyLikey

@LuckyLikey Bạn có thể thử sử dụng d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" nhưng tôi không chắc nó tạo ra sự khác biệt. Nhưng tại sao / làm thế nào mà hàm tạo VM lại khởi chạy một cửa sổ phương thức? Và loại cửa sổ nào?
sondergard

@son Thực ra tôi không biết tại sao và làm thế nào, nhưng khi tôi mở Trình thiết kế cửa sổ từ Trình khám phá giải pháp, khi Tab mới đang được mở, cửa sổ đang được trình thiết kế hiển thị và cửa sổ tương tự xuất hiện như thể phương thức gỡ lỗi, được lưu trữ trong một quy trình mới bên ngoài VS "Micorosoft Visual Studio XAML Designer". Nếu quá trình bị tắt, VS-Designer cũng không thành công với ngoại lệ được đề cập trước đó. Tôi sẽ thử cách giải quyết khác của bạn. Tôi sẽ thông báo cho bạn khi tôi phát hiện thông tin mới :)
LuckyLikey

1
@sondergard Tôi đã đăng một cải tiến cho câu trả lời của bạn, tránh được ServiceLocator Anti-Pattern. Kiểm tra nó thoải mái.
LuckyLikey

52

Trong câu hỏi của bạn, bạn đặt giá trị thuộc DataContexttính của chế độ xem trong XAML. Điều này yêu cầu mô hình khung nhìn của bạn phải có một hàm tạo mặc định. Tuy nhiên, như bạn đã lưu ý, điều này không hoạt động tốt với việc tiêm phụ thuộc mà bạn muốn đưa các phụ thuộc vào hàm tạo.

Vì vậy, bạn không thể đặt thuộc DataContexttính trong XAML . Thay vào đó bạn có những lựa chọn thay thế khác.

Nếu ứng dụng của bạn dựa trên một mô hình xem phân cấp đơn giản, bạn có thể xây dựng toàn bộ phân cấp mô hình xem khi ứng dụng khởi động (bạn sẽ phải xóa thuộc StartupUritính khỏi App.xamltệp):

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

}

Điều này dựa trên một biểu đồ đối tượng của các mô hình chế độ xem bắt nguồn từ RootViewModelnhưng bạn có thể đưa một số nhà máy sản xuất mô hình chế độ xem vào các mô hình chế độ xem mẹ cho phép họ tạo các mô hình chế độ xem con mới để biểu đồ đối tượng không cần phải cố định. Điều này cũng hy vọng trả lời câu hỏi của bạn, giả sử tôi cần một phiên bản SomeViewModeltừ csmã của mình , tôi nên làm như thế nào?

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

}

Nếu ứng dụng của bạn có bản chất năng động hơn và có lẽ dựa trên điều hướng, bạn sẽ phải gắn vào mã thực hiện điều hướng. Mỗi lần bạn điều hướng đến một chế độ xem mới, bạn cần tạo một mô hình chế độ xem (từ vùng chứa DI), chính chế độ xem đó và đặt DataContextchế độ xem thành mô hình xem. Bạn có thể thực hiện chế độ xem này trước khi bạn chọn một mô hình chế độ xem dựa trên một chế độ xem hoặc bạn có thể thực hiện chế độ xem này trướcnơi mô hình chế độ xem xác định chế độ xem sẽ sử dụng. Khung MVVM cung cấp chức năng chính này với một số cách để bạn kết nối vùng chứa DI của mình vào việc tạo mô hình chế độ xem nhưng bạn cũng có thể tự triển khai nó. Tôi hơi mơ hồ ở đây vì tùy thuộc vào nhu cầu của bạn, chức năng này có thể trở nên khá phức tạp. Đây là một trong những chức năng cốt lõi mà bạn nhận được từ khuôn khổ MVVM nhưng việc tự sử dụng trong một ứng dụng đơn giản sẽ giúp bạn hiểu rõ những gì mà khung công tác MVVM cung cấp.

Khi không thể khai báo DataContexttrong XAML, bạn sẽ mất một số hỗ trợ về thời gian thiết kế. Nếu chế độ xem của bạn chứa một số dữ liệu, nó sẽ xuất hiện trong thời gian thiết kế, điều này có thể rất hữu ích. May mắn thay, bạn có thể sử dụng các thuộc tính thời gian thiết kế cũng trong WPF. Một cách để làm điều này là thêm các thuộc tính sau vào <Window>phần tử hoặc <UserControl>trong XAML:

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

Loại mô hình chế độ xem phải có hai hàm tạo, mặc định cho dữ liệu thời gian thiết kế và một cấu trúc khác để chèn phụ thuộc:

class MyViewModel : INotifyPropertyChanged {

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

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

}

Bằng cách này, bạn có thể sử dụng phương pháp tiêm phụ thuộc và duy trì sự hỗ trợ tốt về thời gian thiết kế.


12
Điều này thật đúng với gì mà tôi đã tìm kiếm. Tôi đã làm tôi thất vọng bao nhiêu lần khi đọc những câu trả lời có nội dung "Chỉ cần sử dụng khung [ yadde-ya ]". Đó là tất cả tốt và tốt, nhưng tôi muốn biết chính xác làm thế nào để tự mình cuộn điều này trước và sau đó tôi có thể biết loại khung thực sự có thể sử dụng cho tôi. Cảm ơn vì đã đánh vần nó rất rõ ràng.
kmote

28

Những gì tôi đăng ở đây là một cải tiến cho Câu trả lời của sondergard, bởi vì những gì tôi sắp kể không phù hợp với một Bình luận :)

Trên thực tế, tôi đang giới thiệu một giải pháp gọn gàng, tránh sự cần thiết của ServiceLocator và một trình bao bọc cho StandardKernel-Instance, mà trong Giải pháp của sondergard được gọi là IocContainer. Tại sao? Như đã nói, đó là những mẫu chống.

Làm cho StandardKernelkhả dụng ở mọi nơi

Chìa khóa cho phép thuật của Ninject là StandardKernel-Instance cần thiết để sử dụng .Get<T>()-Method.

Ngoài ra với sondergard, IocContainerbạn có thể tạo StandardKernelbên trong- AppClass.

Chỉ cần xóa StartUpUri khỏi App.xaml của bạn

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

Đây là CodeBehind của ứng dụng bên trong App.xaml.cs

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

Từ bây giờ, Ninject vẫn còn sống và sẵn sàng chiến đấu :)

Tiêm của bạn DataContext

Như Ninject còn sống, bạn có thể thực hiện tất cả các loại thuốc tiêm, ví dụ như tài sản Setter tiêm hoặc một phổ biến nhất Constructor tiêm .

Đây là cách bạn tiêm ViewModel của bạn thành của bạn Window'sDataContext

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

Tất nhiên bạn cũng có thể Inject một IViewModelnếu bạn làm đúng các ràng buộc, nhưng đó không phải là một phần của câu trả lời này.

Truy cập trực tiếp vào Kernel

Nếu bạn cần gọi các Phương thức trên Kernel trực tiếp (ví dụ .Get<T>()-Method), bạn có thể để Kernel tự tiêm.

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

Nếu bạn cần một phiên bản cục bộ của Kernel, bạn có thể đưa nó vào làm Thuộc tính.

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

Mặc dù điều này có thể khá hữu ích nhưng tôi không khuyên bạn nên làm như vậy. Chỉ cần lưu ý rằng các đối tượng được đưa vào theo cách này, sẽ không có sẵn bên trong Khối mã lệnh, vì nó được đưa vào sau.

Theo liên kết này, bạn nên sử dụng Factory-Extension thay vì tiêm IKernel(DI Container).

Cách tiếp cận được khuyến nghị để sử dụng vùng chứa DI trong hệ thống phần mềm là Gốc thành phần của ứng dụng là nơi duy nhất mà vùng chứa được chạm trực tiếp.

Cách sử dụng Ninject.Extensions.Factory cũng có thể có màu đỏ ở đây .


Cách tiếp cận tốt. Không bao giờ khám phá Ninject đến mức này, nhưng tôi có thể thấy rằng tôi đang bỏ lỡ :)
sondergard

@son thx. Ở cuối câu trả lời của bạn, bạn đã nêu Nếu ai có cách tốt hơn, xin vui lòng chia sẻ. Bạn có thể thêm một liên kết này?
LuckyLikey

nếu ai đó quan tâm đến cách sử dụng Ninject.Extensions.Factorytrên này, hãy nêu nó ở đây trong phần nhận xét và tôi sẽ bổ sung thêm một số thông tin.
LuckyLikey

1
@LuckyLikey: Làm cách nào để tôi có thể thêm ViewModel vào văn bản dữ liệu cửa sổ thông qua XAML không có hàm tạo không tham số? Với giải pháp từ sondergard với ServiceLocator, tình huống này có thể xảy ra.
Thomas Geulen

Vì vậy, xin vui lòng cho tôi biết làm thế nào để truy xuất các dịch vụ mà tôi cần trong các thuộc tính đính kèm? Chúng luôn ở trạng thái tĩnh, cả trường hỗ trợ DependencyPropertyvà các phương thức Get and Set của nó.
springy76

12

Tôi chọn phương pháp tiếp cận "xem trước", nơi tôi chuyển mô hình chế độ xem đến phương thức khởi tạo của chế độ xem (trong mã phía sau của nó), được gán cho ngữ cảnh dữ liệu, ví dụ:

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

        DataContext = viewModel;
    }
}

Điều này thay thế cách tiếp cận dựa trên XAML của bạn.

Tôi sử dụng khung Prism để xử lý điều hướng - khi một số mã yêu cầu một chế độ xem cụ thể được hiển thị (bằng cách "điều hướng" đến nó), Prism sẽ giải quyết chế độ xem đó (nội bộ, sử dụng khung DI của ứng dụng); khung DI sẽ lần lượt giải quyết bất kỳ phụ thuộc nào mà khung nhìn có (mô hình khung nhìn trong ví dụ của tôi), sau đó giải quyết các phụ thuộc của nó , v.v.

Lựa chọn khung DI không liên quan nhiều vì tất cả chúng về cơ bản giống nhau, tức là bạn đăng ký một giao diện (hoặc một kiểu) cùng với kiểu cụ thể mà bạn muốn khung khởi tạo khi nó tìm thấy sự phụ thuộc vào giao diện đó. Để ghi lại, tôi sử dụng Castle Windsor.

Điều hướng lăng kính mất một số thời gian để làm quen nhưng khá tốt khi bạn đã hiểu rõ về nó, cho phép bạn soạn ứng dụng của mình bằng các chế độ xem khác nhau. Ví dụ: bạn có thể tạo một "vùng" Prism trên cửa sổ chính của mình, sau đó sử dụng điều hướng Prism, bạn sẽ chuyển từ chế độ xem này sang chế độ xem khác trong vùng này, ví dụ như khi người dùng chọn các mục menu hoặc bất cứ thứ gì.

Ngoài ra, hãy xem một trong các khung công tác MVVM như MVVM Light. Tôi không có kinh nghiệm về những thứ này nên không thể bình luận về những gì họ muốn sử dụng.


1
Làm cách nào để bạn chuyển các đối số của hàm tạo cho các khung nhìn con? Tôi đã thử cách tiếp cận này, nhưng nhận được ngoại lệ trong chế độ xem gốc nói với tôi rằng chế độ xem con không có hàm tạo ít tham số mặc định
Bác sĩ Jones

10

Cài đặt MVVM Light.

Một phần của cài đặt là tạo bộ định vị mô hình chế độ xem. Đây là một lớp hiển thị các mô hình xem của bạn dưới dạng thuộc tính. Sau đó, bộ nhận của các thuộc tính này có thể được trả về các phiên bản từ công cụ IOC của bạn. May mắn thay, ánh sáng MVVM cũng bao gồm khuôn khổ SimpleIOC, nhưng bạn có thể kết nối với những người khác nếu muốn.

Với IOC đơn giản, bạn đăng ký một triển khai chống lại một loại ...

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

Trong ví dụ này, mô hình dạng xem của bạn được tạo và chuyển một đối tượng nhà cung cấp dịch vụ theo phương thức khởi tạo của nó.

Sau đó, bạn tạo một thuộc tính trả về một thể hiện từ IOC.

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

Phần thông minh là bộ định vị mô hình chế độ xem sau đó được tạo trong app.xaml hoặc tương đương như một nguồn dữ liệu.

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

Bây giờ bạn có thể liên kết với thuộc tính 'MyViewModel' của nó để có được mô hình xem của bạn với một dịch vụ được đưa vào.

Hy vọng rằng sẽ giúp. Xin lỗi vì bất kỳ sai sót mã nào, được mã hóa từ bộ nhớ trên iPad.


Bạn không nên có GetInstancehoặc resolvengoài bootstrap của ứng dụng. Đó là quan điểm của DI!
Soleil - Mathieu Prévot

Tôi đồng ý rằng bạn có thể đặt giá trị thuộc tính trong quá trình khởi động, nhưng gợi ý rằng việc sử dụng tính năng khởi tạo lười biếng chống lại DI là sai.
xe kéo

@kishaw Tôi đã không.
Soleil - Mathieu Prévot

3

Canonic DryIoc trường hợp

Trả lời một bài viết cũ, nhưng làm điều này với DryIocvà làm những gì tôi nghĩ là sử dụng tốt DI và giao diện (sử dụng tối thiểu các lớp cụ thể).

  1. Điểm khởi đầu của ứng dụng WPF là App.xaml, và ở đó chúng tôi cho biết đâu là chế độ xem ban đầu để sử dụng; chúng tôi làm điều đó với mã phía sau thay vì xaml mặc định:
  2. xóa StartupUri="MainWindow.xaml"trong App.xaml
  3. trong codebehind (App.xaml.cs) thêm cái này override OnStartup:

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

đó là điểm khởi động; đó cũng là nơi duy nhất resolvenên được gọi.

  1. gốc cấu hình (theo cuốn sách Dependency injection trong .NET của Mark Seeman; nơi duy nhất mà các lớp cụ thể nên được đề cập đến) sẽ nằm trong cùng một đoạn mã, trong hàm tạo:

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

Nhận xét và một số chi tiết khác

  • Tôi chỉ sử dụng lớp cụ thể với khung nhìn MainWindow;
  • Tôi đã phải chỉ định phương thức xây dựng nào sẽ sử dụng (chúng ta cần làm điều đó với DryIoc) cho ViewModel, vì phương thức khởi tạo mặc định cần tồn tại cho trình thiết kế XAML và phương thức khởi tạo có tiêm là phương thức thực sự được sử dụng cho ứng dụng.

Hàm tạo ViewModel với DI:

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

Hàm tạo mặc định của ViewModel cho thiết kế:

public MainWindowViewModel()
{
}

Phần mã phía sau của khung nhìn:

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

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

và những gì cần thiết trong dạng xem (MainWindow.xaml) để có được một phiên bản thiết kế với ViewModel:

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

Phần kết luận

Do đó, chúng tôi đã triển khai ứng dụng WPF rất gọn gàng và tối thiểu với vùng chứa DryIoc và DI trong khi vẫn giữ được các thể hiện thiết kế của khung nhìn và mô hình xem.


2

Sử dụng Khung hỗ trợ mở rộng được quản lý .

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

Nói chung, những gì bạn sẽ làm là có một lớp tĩnh và sử dụng Factory Pattern để cung cấp cho bạn một vùng chứa toàn cục (cache, natch).

Đối với cách tiêm các mô hình xem, bạn tiêm chúng giống như cách bạn tiêm mọi thứ khác. Tạo một phương thức khởi tạo nhập (hoặc đặt một câu lệnh nhập trên một thuộc tính / trường) ở phần sau của tệp XAML và yêu cầu nó nhập mô hình dạng xem. Sau đó, ràng buộc của bạn WindowDataContextđể tài sản đó. Các đối tượng gốc của bạn mà bạn thực sự tự kéo ra khỏi vùng chứa thường là Windowcác đối tượng có cấu trúc . Chỉ cần thêm giao diện vào các lớp cửa sổ và xuất chúng, sau đó lấy từ danh mục như trên (trong App.xaml.cs ... đó là tệp WPF bootstrap).


Bạn đang thiếu một điểm quan trọng của DI là tránh bất kỳ việc tạo phiên bản nào với new.
Soleil - Mathieu Prévot

0

Tôi khuyên bạn nên sử dụng ViewModel - Cách tiếp cận đầu tiên https://github.com/Caliburn-Micro/Caliburn.Micro

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

sử dụng Castle Windsorlàm IOC container.

Tất cả về các quy ước

Một trong những tính năng chính của Caliburn.Micro thể hiện ở khả năng loại bỏ nhu cầu về mã tấm lò hơi bằng cách hành động dựa trên một loạt các quy ước. Một số người yêu thích các quy ước và một số ghét chúng. Đó là lý do tại sao các quy ước của CM hoàn toàn có thể tùy chỉnh và thậm chí có thể tắt hoàn toàn nếu không muốn. Nếu bạn định sử dụng các quy ước và vì chúng được BẬT theo mặc định, bạn nên biết những quy ước đó là gì và chúng hoạt động như thế nào. Đó là chủ đề của bài báo này. Xem độ phân giải (ViewModel-First)

Khái niệm cơ bản

Quy ước đầu tiên bạn có thể gặp phải khi sử dụng CM liên quan đến độ phân giải chế độ xem. Quy ước này ảnh hưởng đến bất kỳ khu vực ViewModel-First nào trong ứng dụng của bạn. Trong ViewModel-First, chúng tôi có một ViewModel hiện có mà chúng tôi cần hiển thị ra màn hình. Để làm điều này, CM sử dụng một mẫu đặt tên đơn giản để tìm một UserControl1 mà nó sẽ liên kết với ViewModel và hiển thị. Vậy, mô hình đó là gì? Chúng ta hãy nhìn vào ViewLocator.LocateForModelType để tìm hiểu:

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

Đầu tiên, hãy bỏ qua biến "ngữ cảnh". Để tạo ra chế độ xem, chúng tôi giả định rằng bạn đang sử dụng văn bản “ViewModel” trong việc đặt tên cho máy ảo của mình, vì vậy chúng tôi chỉ thay đổi nó thành “View” ở mọi nơi mà chúng tôi tìm thấy nó bằng cách xóa từ “Model”. Điều này có tác dụng thay đổi cả tên kiểu và không gian tên. Vì vậy, ViewModels.CustomerViewModel sẽ trở thành Views.CustomerView. Hoặc nếu bạn đang tổ chức ứng dụng của mình theo tính năng: CustomerManagement.CustomerViewModel sẽ trở thành CustomerManagement.CustomerView. Hy vọng rằng, đó là khá thẳng về phía trước. Khi đã có tên, chúng tôi sẽ tìm kiếm các loại có tên đó. Chúng tôi tìm kiếm bất kỳ assembly nào bạn đã tiếp xúc với CM dưới dạng có thể tìm kiếm thông qua AssemblySource.Instance.2 Nếu chúng tôi tìm thấy loại, chúng tôi tạo một phiên bản (hoặc lấy một phiên bản từ IoC container nếu nó đã được đăng ký) và trả lại cho người gọi. Nếu chúng tôi không tìm thấy loại,

Bây giờ, quay lại giá trị "ngữ cảnh" đó. Đây là cách CM hỗ trợ nhiều Chế độ xem trên cùng một ViewModel. Nếu một ngữ cảnh (thường là một chuỗi hoặc một enum) được cung cấp, chúng tôi sẽ thực hiện chuyển đổi thêm tên, dựa trên giá trị đó. Sự chuyển đổi này giả định một cách hiệu quả rằng bạn có một thư mục (không gian tên) cho các dạng xem khác nhau bằng cách xóa từ “Chế độ xem” ở cuối và thay vào đó thêm ngữ cảnh vào. Vì vậy, với ngữ cảnh là “Master” ViewModels.CustomerViewModel của chúng tôi sẽ trở thành Views.Customer.Master.


2
Toàn bộ bài viết của bạn là ý kiến.
John Peters

-1

Xóa tiểu khởi động khỏi app.xaml của bạ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);
    }
}

Bây giờ bạn có thể sử dụng lớp IoC của mình để xây dựng các thể hiện.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

Bạn không nên có bất kỳ container GetInstancecủa resolveapp.xaml.cs bên ngoài, bạn đang mất điểm của DI. Ngoài ra, việc đề cập đến chế độ xem xaml trong phần mã của chế độ xem là khá phức tạp. Chỉ cần gọi chế độ xem trong c # thuần túy và thực hiện việc này với vùng chứa.
Soleil - Mathieu Prévot
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.