0

Is it possible to automatically register a view model with a view without a class library like Prism on the OnStartUp method of App.xaml.cs

I have something like this on my previous Prism Xamarin project on the application startup.

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    //Registered Navigation Containers
    containerRegistry.RegisterForNavigation<LoginPage>("Login");
    containerRegistry.RegisterForNavigation<RegisterPage>("Register");
    containerRegistry.RegisterForNavigation<ProfilePage>("Profile");
    containerRegistry.RegisterForNavigation<CreateAppointmentPage>("CreateAppointment");
    containerRegistry.RegisterForNavigation<NotificationPage>("Notification");
    //Dependency Services
    containerRegistry.RegisterSingleton<IConnectivityService, ConnectivityService>();
    containerRegistry.RegisterSingleton<IAuthenticationService, AuthenticationService>();
    containerRegistry.RegisterSingleton<IAppAPIService, AppAPIService>();
    containerRegistry.RegisterSingleton<IPushNotificationManager, PushNotificationManager>();
}

My current company wants to avoid using libraries. My goal is to have a clean code-behind for all views. I would like to avoid something like in my code-behinds:

public MainWindow()
{
     InitializeComponent();
     DataContext = new MainWindowViewModel();
}

I hope my question is clear. Thank you.

thatguy
  • 21,059
  • 6
  • 30
  • 40
  • You can always implement your own library. .NET Core comes with its own built-in IoC library [Dependency injection in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetimes). If you avoid using the container like a Service Locator e.g. by making it static and let components reference it, it's quite simple to encapsulate and move the dependency on the library behind an interface (e.g., Repository pattern). If it is important to be able to replace the library you can consider this extra efforts. – BionicCode Aug 07 '20 at 07:50
  • It should be clear that there is no "automatic" in software development. If not you yourself, then somebody else has implemented this automatism for you - the library. – BionicCode Aug 07 '20 at 07:51
  • @SamuelMarvinAguilos: You need to ask yourself how you intend to map a view to a view model if you don't want to do it explicitly and not use a framework? WPF won't do this for you. – mm8 Aug 07 '20 at 12:07

2 Answers2

1

No, there is no built-in feature for that in WPF. That is what libraries like Prism are for, so you do not have to reinvent the wheel. In the background, Prism uses multiple mechanisms to achieve that.

  • A dependency injection container to register and resolve types
  • Sets to track which view belongs to which view model
  • Discovery of view models via reflection and naming conventions
  • An attached property to assign a view model automatically to a view.
  • ...

Depending on your requirements you would have to write parts of that yourself or use alternative concepts that are more lightweight, like a view registry type and a factory to create them, for exapmle:

public interface IViewRegistry
{
   void Register<TView, TViewModel>()
      where TView : FrameworkElement
      where TViewModel : INotifyPropertyChanged;

   Type GetViewModelType<TView>()
         where TView : FrameworkElement;
}

public interface IViewFactory
{
   TView Create<TView>()
      where TView : FrameworkElement;
}

The actual view registry implemenatation would have a dictionary to keep track of the mapping between views and view models that can is exposed by GetViewModelType. The type constraint FrameworkElement ensures that you can set a DataContext.

public class ViewRegistry : IViewRegistry
{
   private Dictionary<Type, Type> _mappings;

   public ViewRegistry()
   {
      _mappings = new Dictionary<Type, Type>();
   }

   public void Register<TView, TViewModel>()
      where TView : FrameworkElement
      where TViewModel : INotifyPropertyChanged
   {
      _mappings.Add(typeof(TView), typeof(TViewModel));
   }

   public Type GetViewModelType<TView>()
      where TView : FrameworkElement
   {
      return _mappings.ContainsKey(typeof(TView)) ? _mappings[typeof(TView)] : null;
   }
}

The factory would either create new instances of a view and its view model using reflection or via a dependency injection container and assign the DataContext. I really recommend you to use a dependency container, because your view models might have lots of dependnecies and you would have to resolve or create them yourself. See @BionicCode's comment on that.

public class ViewFactory : IViewFactory
{
   private readonly IViewRegistry _viewRegistry;

   public ViewFactory(IViewRegistry viewRegistry)
   {
      _viewRegistry = viewRegistry;
   }

   public TView Create<TView>() where TView : FrameworkElement
   {
      var viewModelType = _viewRegistry.GetViewModelType<TView>();
      var view = // ...resolve the view via dependency injection or create it via reflection using its type
      var viewModel =  // ...resolve the view model via dependency injection or create it via reflection using its type

      view.DataContext = viewModel;
      return view;
   }
}

In your application, the process of registering an resolving views now looks similar to Prism.

var viewRegistry = new ViewRegistry();
var viewFactory = new ViewFactory(viewRegistry);

viewRegistry.Register<MainWindow, MainWindowViewModel>();
var mainWindow = viewFactory.Create<MainWindow>();
thatguy
  • 21,059
  • 6
  • 30
  • 40
  • I don't quite get what you mean here: // ...resolve the view via dependency injection or create it via reflection using its type and here: // ...resolve the view model via dependency injection or create it via reflection using its type – Samuel Marvin Aguilos Aug 13 '20 at 13:27
  • @SamuelMarvinAguilos You somehow need to create a new instance of a view or a view model. Normally you would do `new MyView(...)`, but you cannot do that with a `Type` object. To create an object from a `Type`, you have to use reflection, like expained [here](https://stackoverflow.com/questions/752/how-to-create-a-new-object-instance-from-a-type). However, if this type depends on other types, those need to be instantiated, too, and passed to the constructor of the view or view model. – thatguy Aug 13 '20 at 15:05
  • @SamuelMarvinAguilos That is where dependency injection comes into play, which can manage dependencies and instantiation for you. See related posts for reference: [1](https://stackoverflow.com/questions/130794/what-is-dependency-injection), [2](https://stackoverflow.com/questions/14301389/why-does-one-use-dependency-injection/14301496). – thatguy Aug 13 '20 at 15:07
  • I guess this is too advance for me to understand right now. I tried to read the references you gave me but I still can't make it work. Thanks for your support. – Samuel Marvin Aguilos Aug 14 '20 at 03:28
1

There's nothing automatic within the net framework.

You could mitigate the issue using viewmodel first and datatemplates. This is how I would usually work in a single window app.

I have a resource dictionary merges in app.xaml and that associates a usercontrol with a viewmodel by type.

You can see a very basic version used here:

https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx

<DataTemplate DataType="{x:Type local:LoginViewModel}">
    <local:LoginView/>
</DataTemplate>

Navigation is done by setting a CurrentView property in mainwindowviewmodel to an instance of a viewmodel. That is bound to the content of a contentcontrol in MainWindow. The datatemplate will then template out the appropriate usercontrol.

No navigationservice and viewmodels that need to be singletons can be cached in an object mainwindowviewmodel instantiates.

You then "only" have mainwindowviewmodel. There are some plusses to manually instantiating mainwindowviewmodel. That way you have control of when expensive instantiation occurs. It's not all automatically done as that viewmodel demands many services via it's ctor.

You can use any DI you like within that to resolve dependencies whilst instantiating the various viewmodels. Or just a mediator pattern with a lazy singleton provides instances cached inside itself.

You could write something of course.

The thing is you're probably going to be re-writing code which is very similar to something someone else already wrote. But since you likely don't have 6 months set aside for framework replacement work your version is likely to be rather simpler.

Caliburn micro uses naming conventions to associate viewmodels and views. You could write something works like that and uses reflection.

Or maybe you could go back to your boss and discuss what his issue is. Personally, I'm not a fan of Prism due to it's complexity. Maybe this is the sort of issue he has. Maybe a simpler framework like mvvmlight would be more acceptable.

Andy
  • 11,864
  • 2
  • 17
  • 20