WPF / MVVM 애플리케이션에서 종속성 주입을 처리하는 방법


102

새 데스크톱 응용 프로그램을 시작 중이며 MVVM 및 WPF를 사용하여 빌드하고 싶습니다.

TDD도 사용할 계획입니다.

문제는 생산 코드에 의존성을 주입하기 위해 IoC 컨테이너를 어떻게 사용해야할지 모르겠다는 것입니다.

다음과 같은 클래스와 인터페이스가 있다고 가정합니다.

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

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

그리고 IStorage종속성으로있는 다른 클래스가 있습니다.이 클래스가 ViewModel 또는 비즈니스 클래스라고 가정합니다.

public class SomeViewModel
{
    private IStorage _storage;

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

이를 통해 모의 등을 사용하여 제대로 작동하는지 확인하는 단위 테스트를 쉽게 작성할 수 있습니다.

문제는 실제 응용 프로그램에서 사용할 때입니다. IStorage인터페이스에 대한 기본 구현을 연결하는 IoC 컨테이너가 있어야한다는 것을 알고 있지만 어떻게해야합니까?

예를 들어 다음 xaml이 있으면 어떻게 될까요?

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

이 경우 종속성을 주입하도록 WPF에 올바르게 '알려'하려면 어떻게해야합니까?

또한 SomeViewModelC # 코드 의 인스턴스가 필요하다고 가정 하면 어떻게해야합니까?

나는 이것에서 완전히 길을 잃었다 고 느낍니다. 그것을 처리하는 가장 좋은 방법에 대한 예 또는 지침에 감사드립니다.

StructureMap에 익숙하지만 전문가는 아닙니다. 또한 더 나은 / 쉬운 / 기본 프레임 워크가 있으면 알려주세요.


미리보기에서 .net core 3.0을 사용하면 일부 Microsoft nuget 패키지로 수행 할 수 있습니다.
Bailey Miller

답변:


87

저는 Ninject를 사용해 왔는데 함께 일하는 것이 즐겁다는 것을 알았습니다. 모든 것이 코드로 설정되고 구문은 매우 간단하며 좋은 문서 (SO에 대한 많은 답변)가 있습니다.

따라서 기본적으로 다음과 같이 진행됩니다.

뷰 모델을 만들고 IStorage인터페이스를 생성자 매개 변수로 사용합니다.

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

ViewModelLocatorNinject에서 뷰 모델을로드하는 뷰 모델에 대한 get 속성을 사용하여을 만듭니다 .

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

ViewModelLocatorApp.xaml에서 애플리케이션 전체 리소스를 만듭니다 .

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

인드 DataContextUserControlViewModelLocator에 대응하는 속성.

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

NinjectModule을 상속하는 클래스를 만들어 필요한 바인딩 ( IStorage및 뷰 모델)을 설정합니다.

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

필요한 Ninject 모듈 (현재는 위의 모듈)을 사용하여 애플리케이션 시작시 IoC 커널을 초기화합니다.

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

        base.OnStartup(e);
    }
}

IocKernelIoC 커널의 애플리케이션 전체 인스턴스를 보유하기 위해 정적 클래스를 사용 했으므로 필요할 때 쉽게 액세스 할 수 있습니다.

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

이 솔루션은 클래스의 종속성을 숨기기 때문에 일반적으로 안티 패턴으로 간주되는 정적 ServiceLocator( IocKernel) 을 사용 합니다. 그러나 UI 클래스에 대한 일종의 수동 서비스 조회를 피하는 것은 매우 어렵습니다. UI 클래스에는 매개 변수없는 생성자가 있어야하고 어쨌든 인스턴스화를 제어 할 수 없으므로 VM을 삽입 할 수 없습니다. 최소한이 방법을 사용하면 모든 비즈니스 논리가있는 VM을 격리 상태로 테스트 할 수 있습니다.

더 나은 방법이 있다면 공유 해주세요.

편집 : Lucky Likey는 Ninject가 UI 클래스를 인스턴스화하도록하여 정적 서비스 로케이터를 제거하기위한 답변을 제공했습니다. 답변에 대한 자세한 내용은 여기에서 볼 수 있습니다.


13
저는 의존성 주입을 처음 접했지만 정적 ViewModel Locator를 사용하고 있기 때문에 솔루션이 Service Locator 안티 패턴을 Ninject와 결합하고 있습니다. 인젝션은 Xaml 파일에서 수행되며 테스트 가능성이 낮다고 주장 할 수 있습니다. 나는 더 나은 해결책이 없으며 아마도 당신의 해결책을 사용할 것입니다. 그러나 나는 이것을 대답에서도 언급하는 것이 도움이 될 것이라고 생각합니다.
user3141326

Man your solution is just great, there is only one "Problem"with the following Line : DataContext="{Binding [...]}". 이로 인해 VS-Designer가 ViewModel의 생성자에서 모든 프로그램 코드를 실행합니다. 제 경우에는 Window가 실행 중이며 VS에 대한 모든 상호 작용을 모달로 차단합니다. 디자인 타임에서 "실제"ViewModel을 찾지 않도록 ViewModelLocator를 수정해야 할 수도 있습니다. -또 다른 해결책은 "프로젝트 코드 비활성화"로, 다른 모든 항목이 표시되지 않도록하는 것입니다. 아마도 이것에 대한 깔끔한 해결책을 이미 찾았을 것입니다. 이 경우 보여 주시면 감사하겠습니다.
LuckyLikey

@LuckyLikey d : DataContext = "{d : DesignInstance vm : UserControlViewModel, IsDesignTimeCreatable = True}"를 사용해 볼 수 있지만 차이가 있는지 확실하지 않습니다. 그러나 VM 생성자가 모달 창을 시작하는 이유 / 방법은 무엇입니까? 그리고 어떤 종류의 창문?
sondergard

@son 실제로 이유와 방법을 모르겠지만 솔루션 탐색기에서 창 디자이너를 열면 새 탭이 열리면서 창이 디자이너에 의해 표시되고 모달을 디버깅하는 것처럼 동일한 창이 나타납니다. VS "Micorosoft Visual Studio XAML 디자이너"외부의 새 프로세스에서 호스팅됩니다. 프로세스가 종료되면 앞에서 언급 한 예외와 함께 VS-Designer도 실패합니다. 해결 방법을 시도해 보겠습니다. 나는 새로운 정보를 탐지으로 나는 :)을 알려드립니다
LuckyLikey

1
@sondergard ServiceLocator Anti-Pattern을 피하면서 귀하의 답변에 대한 개선 사항을 게시했습니다. 자유롭게 확인하십시오.
LuckyLikey

52

귀하의 질문 DataContext에서 XAML에서 뷰 의 속성 값을 설정했습니다 . 이를 위해서는 뷰 모델에 기본 생성자가 있어야합니다. 그러나 앞서 언급했듯이 이것은 생성자에 종속성을 주입하려는 종속성 주입에서는 제대로 작동하지 않습니다.

따라서 XAML 에서 DataContext속성을 설정할 수 없습니다 . 대신 다른 대안이 있습니다.

응용 프로그램이 간단한 계층 적 뷰 모델을 기반으로하는 경우 응용 프로그램이 시작될 때 전체 뷰 모델 계층을 구성 할 수 있습니다 ( 파일 에서 StartupUri속성 을 제거해야 함 App.xaml).

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

}

이것은에 뿌리를 둔 뷰 모델의 객체 그래프를 기반으로 RootViewModel하지만 일부 뷰 모델 팩토리를 부모 뷰 모델에 삽입하여 새로운 자식 뷰 모델을 생성 할 수 있으므로 객체 그래프를 수정할 필요가 없습니다. 이것은 또한 코드 의 인스턴스가 필요하다고 가정하는 귀하의 질문에 대한 답변을 희망합니다 . 어떻게해야합니까?SomeViewModelcs

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

}

애플리케이션이 본질적으로 더 동적이고 탐색을 기반으로하는 경우 탐색을 수행하는 코드에 연결해야합니다. 새 뷰로 이동할 때마다 뷰 모델 (DI 컨테이너에서)을 생성하고 뷰 자체 DataContext를 뷰 모델로 설정해야합니다. 당신이 할 수있는 첫 번째보기를 당신이보기에 따라 뷰 - 모델을 선택할 경우 또는 당신이 그것을 할 수 있습니다 뷰 - 모델을 처음뷰 모델이 사용할 뷰를 결정합니다. MVVM 프레임 워크는 DI 컨테이너를 뷰 모델 생성에 연결할 수있는 방법과 함께이 주요 기능을 제공하지만 직접 구현할 수도 있습니다. 필요에 따라이 기능이 매우 복잡해질 수 있기 때문에 여기서는 약간 모호합니다. 이것은 MVVM 프레임 워크에서 얻을 수있는 핵심 기능 중 하나이지만 간단한 애플리케이션에서 직접 롤링하면 MVVM 프레임 워크가 내부에서 제공하는 내용을 잘 이해할 수 있습니다.

DataContextXAML에서 를 선언 할 수 없으므로 일부 디자인 타임 지원이 손실됩니다. 뷰 모델에 일부 데이터가 포함되어 있으면 디자인 타임에 매우 유용 할 수 있습니다. 다행히도 WPF에서도 디자인 타임 특성을 사용할 수 있습니다 . 이를 수행하는 한 가지 방법은 <Window>요소 또는 <UserControl>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}"

뷰 모델 유형에는 디자인 타임 데이터에 대한 기본값과 종속성 주입에 대한 다른 두 개의 생성자가 있어야합니다.

class MyViewModel : INotifyPropertyChanged {

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

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

}

이렇게하면 종속성 주입을 사용하고 좋은 디자인 타임 지원을 유지할 수 있습니다.


12
이것이 바로 제가 찾던 것입니다. "그냥 [ yadde-ya ] 프레임 워크를 사용하십시오."라는 답변을 몇 번이나 읽으면 실망 스럽습니다 . 그게 다 훌륭하고 좋지만, 먼저 어떻게 직접 굴려야하는지 정확히 알고 싶어요. 그리고 나서 실제로 어떤 종류의 프레임 워크가 저에게 유용할지 알 수 있습니다. 철자를 명확하게 써 주셔서 감사합니다.
kmote

28

내가 여기에 게시하는 것은 sondergard의 답변에 대한 개선 사항입니다. 왜냐하면 내가 말할 내용이 댓글에 맞지 않기 때문입니다. :)

사실 나는 ServiceLocatorStandardKernelsondergard의 Solution에서 IocContainer. 왜? 언급했듯이, 그것들은 안티 패턴입니다.

제작 StandardKernel어디서나 사용할 수를

Ninject의 마법의 핵심은 StandardKernel-Method를 사용하기 위해 필요한 .Get<T>()-Instance입니다.

Sondergard의 대안으로 -Class 내부를 IocContainer만들 수 있습니다 .StandardKernelApp

App.xaml에서 StartUpUri를 제거하기 만하면됩니다.

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

App.xaml.cs 내부의 앱 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();
    }
}

이제부터 Ninject는 살아 있고 싸울 준비가되었습니다. :)

당신의 DataContext

Ninject가 살아 있기 때문에 Property Setter Injection 이나 가장 일반적인 Constructor Injection 과 같은 모든 종류의 주입을 수행 할 수 있습니다 .

이것이 ViewModel을 WindowDataContext

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

물론 IViewModel올바른 바인딩을 수행하면 Inject를 사용할 수도 있지만 이것은이 답변의 일부가 아닙니다.

커널에 직접 액세스

커널에서 직접 메소드를 호출해야하는 경우 (예 : .Get<T>()-Method) 커널이 자체적으로 주입하도록 할 수 있습니다.

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

커널의 로컬 인스턴스가 필요한 경우 속성으로 삽입 할 수 있습니다.

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

이것은 매우 유용 할 수 있지만 그렇게하지 않는 것이 좋습니다. 이런 식으로 주입 된 개체는 나중에 주입되기 때문에 생성자 내부에서 사용할 수 없습니다.

링크 에 따르면 IKernel(DI Container) 를 주입하는 대신 factory-Extension을 사용해야합니다 .

소프트웨어 시스템에서 DI 컨테이너를 사용하는 데 권장되는 접근 방식은 애플리케이션의 컴포지션 루트가 컨테이너를 직접 터치하는 단일 위치라는 것입니다.

Ninject.Extensions.Factory를 사용하는 방법도 여기에서 빨간색으로 표시 할 수 있습니다 .


좋은 접근입니다. 이 수준에 Ninject에 탐험,하지만 나는 :) 내가 놓치고하고 있음을 알 수 마십시오
sondergard

@ 아들 thx. 당신의 답변 끝에 당신이 언급 한 사람이 더 나은 방법이 있다면 공유하십시오. 이 링크를 추가 할 수 있습니까?
LuckyLikey

누군가가 Ninject.Extensions.Factory이것을 사용하는 방법에 관심 이 있다면 여기에 의견을 말하면 더 많은 정보를 추가하겠습니다.
LuckyLikey

1
@LuckyLikey : 매개 변수없는 생성자가없는 XAML을 통해 창 데이터 컨텍스트에 ViewModel을 어떻게 추가 할 수 있습니까? ServiceLocator를 사용한 sondergard의 솔루션으로 이러한 상황이 가능할 것입니다.
Thomas Geulen

그렇다면 연결된 속성에서 필요한 서비스를 검색하는 방법을 알려주십시오. 백업 DependencyProperty필드와 해당 Get 및 Set 메서드 모두 항상 정적 입니다.
springy76

12

"뷰 우선"접근 방식을 사용합니다. 여기서 뷰 모델을 뷰의 생성자 (코드 숨김)에 전달하여 데이터 컨텍스트에 할당됩니다.

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

        DataContext = viewModel;
    }
}

이는 XAML 기반 접근 방식을 대체합니다.

저는 Prism 프레임 워크를 사용하여 탐색을 처리합니다. 일부 코드가 특정 뷰를 표시하도록 요청하면 ( "탐색"하여) Prism이 해당 뷰를 해결합니다 (내부적으로 앱의 DI 프레임 워크를 사용). DI 프레임 워크는 차례로 뷰에있는 모든 종속성 (내 예제의 뷰 모델)을 확인한 다음 해당 종속성 을 확인합니다 .

DI 프레임 워크의 선택은 본질적으로 동일한 작업을 수행하기 때문에 거의 관련이 없습니다. 즉, 해당 인터페이스에 대한 종속성을 발견 할 때 프레임 워크가 인스턴스화 할 구체적인 유형과 함께 인터페이스 (또는 유형)를 등록합니다. 기록을 위해 저는 Castle Windsor를 사용합니다.

Prism 탐색은 익숙해지는 데 약간의 시간이 걸리지 만 일단 머리를 숙이면 꽤 괜찮아서 다른보기를 사용하여 응용 프로그램을 구성 할 수 있습니다. 예를 들어, 메인 창에 Prism "영역"을 생성 한 다음 Prism 탐색을 사용하여이 영역 내에서 한보기에서 다른보기로 전환 할 수 있습니다. 예를 들어 사용자가 메뉴 항목을 선택하거나 무엇이든 선택할 수 있습니다.

또는 MVVM Light와 같은 MVVM 프레임 워크 중 하나를 살펴보십시오. 나는 이것들에 대한 경험이 없으므로 그들이 사용하는 것에 대해 언급 할 수 없습니다.


1
생성자 인수를 자식 뷰에 어떻게 전달합니까? 이 접근 방식을 시도했지만 부모보기에서 자식보기에 기본 매개 변수가없는 생성자가 없다는 예외가 발생했습니다
Doctor Jones

10

MVVM Light를 설치합니다.

설치의 일부는 뷰 모델 로케이터를 만드는 것입니다. 뷰 모델을 속성으로 노출하는 클래스입니다. 이러한 속성의 getter는 IOC 엔진에서 반환 된 인스턴스가 될 수 있습니다. 다행히 MVVM 조명에는 SimpleIOC 프레임 워크도 포함되어 있지만 원하는 경우 다른 프레임 워크를 연결할 수 있습니다.

간단한 IOC를 사용하여 유형에 대한 구현을 등록합니다.

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

이 예에서는 뷰 모델이 생성되고 생성자에 따라 서비스 공급자 개체가 전달됩니다.

그런 다음 IOC에서 인스턴스를 반환하는 속성을 만듭니다.

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

영리한 부분은 뷰 모델 로케이터가 app.xaml 또는 이에 상응하는 데이터 소스로 생성된다는 것입니다.

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

이제 'MyViewModel'속성에 바인딩하여 삽입 된 서비스로 뷰 모델을 가져올 수 있습니다.

도움이 되었기를 바랍니다. iPad의 메모리에서 코딩 된 부정확 한 코드에 대해 사과드립니다.


응용 프로그램의 부트 스트랩 GetInstance또는 resolve외부에 있으면 안됩니다 . 그것이 DI의 요점입니다!
Soleil-Mathieu Prévot

시작 중에 속성 값을 설정할 수 있다는 데 동의하지만 지연 인스턴스화를 사용하는 것이 DI에 위배된다는 것을 제안하는 것은 잘못되었습니다.
kidshaw

@kishaw 나는하지 않았다.
Soleil-Mathieu Prévot

3

Canonic DryIoc 케이스

이전 게시물에 답변했지만 DryIocDI와 인터페이스를 사용하는 것이 좋습니다 (구체적인 클래스 사용을 최소화).

  1. WPF 앱의 시작점 App.xaml은입니다. 여기서 사용할 초기 뷰가 무엇인지 알려줍니다. 기본 xaml 대신 코드 숨김으로 수행합니다.
  2. StartupUri="MainWindow.xaml"App.xaml에서 제거
  3. 코드 숨김 (App.xaml.cs)에서 다음을 추가합니다 override OnStartup.

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

그것이 시작 지점입니다. 그것은 또한 resolve호출되어야 하는 유일한 장소 입니다.

  1. 구성 루트 (Mark Seeman의 책 .NET의 Dependency injection에 따르면 구체적인 클래스를 언급해야하는 유일한 위치)는 생성자에서 동일한 코드 숨김에 있습니다.

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

비고 및 기타 세부 사항

  • 나는보기에만 구체적인 클래스를 사용했다 MainWindow.
  • 기본 생성자는 XAML 디자이너에 대해 존재해야하고 주입이있는 생성자가 응용 프로그램에 사용되는 실제 생성자이기 때문에 ViewModel에 대해 사용할 생성자를 지정해야했습니다 (DryIoc를 사용하여 수행해야 함).

DI가있는 ViewModel 생성자 :

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

디자인을위한 ViewModel 기본 생성자 :

public MainWindowViewModel()
{
}

뷰의 코드 비하인드 :

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

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

ViewModel을 사용하여 디자인 인스턴스를 가져 오기 위해 뷰 (MainWindow.xaml)에 필요한 것 :

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

결론

따라서 뷰 및 뷰 모델의 디자인 인스턴스를 가능한 유지하면서 DryIoc 컨테이너와 DI를 사용하여 WPF 응용 프로그램을 매우 깨끗하고 최소한으로 구현했습니다.


2

Managed Extensibility Framework를 사용합니다 .

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

일반적으로 할 일은 정적 클래스를 가지고 팩토리 패턴을 사용하여 전역 컨테이너 (캐시 됨, natch)를 제공하는 것입니다.

뷰 모델을 주입하는 방법은 다른 모든 것을 주입하는 것과 같은 방식으로 주입합니다. XAML 파일의 코드 숨김에서 가져 오기 생성자를 만들고 (또는 속성 / 필드에 import 문을 배치하고) 뷰 모델을 가져 오도록 지시합니다. 그런 다음 귀하 Window의의 DataContext를 해당 속성에 바인딩하십시오 . 실제로 컨테이너에서 직접 꺼내는 루트 개체는 일반적으로 구성된 Window개체입니다. 창 클래스에 인터페이스를 추가하고 내 보낸 다음 위와 같이 카탈로그에서 가져옵니다 (App.xaml.cs에서 WPF 부트 스트랩 파일).


.NET을 사용한 인스턴스 생성을 피하는 DI의 중요한 점이 하나 누락되었습니다 new.
Soleil-Mathieu Prévot

0

ViewModel-First 접근 방식 https://github.com/Caliburn-Micro/Caliburn.Micro 를 사용하는 것이 좋습니다.

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

Castle WindsorIOC 컨테이너로 사용하십시오 .

규칙에 관한 모든 것

Caliburn.Micro의 주요 기능 중 하나는 일련의 규칙에 따라 작업을 수행하여 보일러 플레이트 코드의 필요성을 제거 할 수 있다는 점입니다. 어떤 사람들은 관습을 좋아하고 어떤 사람들은 그것을 싫어합니다. 그렇기 때문에 CM의 규칙을 완전히 사용자 지정할 수 있으며 원하지 않는 경우 완전히 끌 수도 있습니다. 규칙을 사용하려는 경우 기본적으로 켜져 있으므로 이러한 규칙이 무엇이며 어떻게 작동하는지 아는 것이 좋습니다. 이것이이 기사의 주제입니다. 보기 해상도 (ViewModel-First)

기초

CM을 사용할 때 접하게 될 첫 번째 규칙은 뷰 해상도와 관련이 있습니다. 이 규칙은 애플리케이션의 모든 ViewModel-First 영역에 영향을줍니다. ViewModel-First에는 화면에 렌더링해야하는 기존 ViewModel이 있습니다. 이를 위해 CM은 간단한 이름 지정 패턴을 사용하여 ViewModel에 바인딩하고 표시해야하는 UserControl1을 찾습니다. 그렇다면 그 패턴은 무엇입니까? ViewLocator.LocateForModelType을 살펴 보겠습니다.

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

처음에는 "context"변수를 무시합시다. 뷰를 도출하기 위해 VM 이름 지정에 "ViewModel"이라는 텍스트를 사용한다고 가정하므로 "Model"이라는 단어를 제거하여 찾을 수있는 모든 곳에서 "View"로 변경합니다. 이는 유형 이름과 네임 스페이스를 모두 변경하는 효과가 있습니다. 따라서 ViewModels.CustomerViewModel은 Views.CustomerView가됩니다. 또는 기능별로 애플리케이션을 구성하는 경우 : CustomerManagement.CustomerViewModel이 CustomerManagement.CustomerView가됩니다. 바라건대, 그것은 매우 간단합니다. 이름이 있으면 해당 이름으로 유형을 검색합니다. AssemblySource.Instance.2를 통해 CM에 노출 된 어셈블리를 검색 할 수있는 어셈블리를 검색합니다 .2 유형을 찾으면 인스턴스를 만들고 (등록 된 경우 IoC 컨테이너에서 가져옴) 호출자에게 반환합니다. 유형을 찾지 못하면

이제 "컨텍스트"값으로 돌아갑니다. 이것이 CM이 동일한 ViewModel에 대해 여러 뷰를 지원하는 방법입니다. 컨텍스트 (일반적으로 문자열 또는 열거 형)가 제공되면 해당 값을 기반으로 이름을 추가로 변환합니다. 이 변환은 끝에서 "보기"라는 단어를 제거하고 대신 컨텍스트를 추가하여 다양한보기에 대한 폴더 (네임 스페이스)가 있다고 가정합니다. 따라서 "마스터"컨텍스트가 주어지면 ViewModels.CustomerViewModel이 Views.Customer.Master가됩니다.


2
귀하의 전체 게시물은 의견입니다.
존 피터

-1

app.xaml에서 시작 URI를 제거합니다.

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

이제 IoC 클래스를 사용하여 인스턴스를 생성 할 수 있습니다.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

당신은 어떤 용기가 없어야 GetInstanceresolve외부 App.xaml.cs를, 당신은 DI의 지점을 잃고있다. 또한 뷰의 코드 숨김에서 xaml 뷰를 언급하는 것은 다소 복잡합니다. 순수한 C #으로 뷰를 호출하고 컨테이너로이 작업을 수행하십시오.
Soleil-Mathieu Prévot
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.