I am developing a C#/WPF/MVVM/UWP app which uses a ViewModelLocator which looks like this:
public class ViewModelLocator
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainPageViewModel MainPage
{
get
{
return ServiceLocator.Current.GetInstance<MainPageViewModel>();
}
}
static ViewModelLocator()
{
// DEBUG LINE: var test = Views.ViewLocator.MainPageKey;
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainPageViewModel>();
}
}
I have another class, a ViewLocator, for navigation purposes which looks like this:
public class ViewLocator
{
public static readonly string MainPageKey = typeof(MainPage).Name;
public static readonly string WorkPageKey = typeof(WorkPage).Name;
static ViewLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
var navigationService = new NavigationService();
navigationService.Configure(MainPageKey, typeof(MainPage));
navigationService.Configure(WorkPageKey, typeof(WorkPage));
SimpleIoc.Default.Register<INavigationService>(() => navigationService);
SimpleIoc.Default.Register<IDialogService, DialogService>();
}
}
Before, both classes were combined in ViewModelLocator, however i figured that the ViewModelLocator as part of the "ViewModels-Side-of-things" should be unaware of the views and it's types, which is why I refactored that code into two classes.
My MainPageView then has a button, which triggers a navigation command in the MainPageView.cs
public class MainPageViewModel : ViewModelBase
{
private INavigationService _navigationService;
public RelayCommand CreateNewImageCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
CreateNewImageCommand = new RelayCommand(CreateNewImage, () => true);
}
public void CreateNewImage()
{
_navigationService.NavigateTo(Views.ViewLocator.WorkPageKey);
}
}
For completeness, here is my App.xaml
<Application
...>
<Application.Resources>
<v:ViewLocator x:Key="ViewLocator" />
<vm:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>
Now what happens is this: if I don't have the DEBUG LINE in ViewModelLocator, at the point at which MainPage requests its ViewModel from the ViewModelLocator, the constructor of ViewLocator has not been called yet and return ServiceLocator.Current.GetInstance<MainPageViewModel>(); throws an exception. If I include the DEBUG LINE, this forces the ViewLocator Constructor to be run first, and everything works fine.
How can I achieve this behaviour without that odd debug line?
You are running into the reason most developers do not use static classes and a service locator.
As suggested in your comments try using dependency injection. This allows inversion of control for the component that manages this wiring.
Look into MEF or Unity as a framework to use rather than rolling your own IoC. Both have advantages and disadvantages but either will work. They also allow for easier and cleaner testing.
MEF
Unity
Despite of all the comments and other answers, I think that I was on the track after all. To solve or rather bypass this problem, I have created added bootstrapping of the SimpleIoC Container in the Startup of the App and move the code from the ViewLocator's constructor there.
I also removed the ServiceLocator calls, which was sort of redundant and rely on SimpleIoC.Default now directly.
The Navigator is still injected into the ViewModels by the SimpleIoC Container. The ViewModelLocator however is necessary work in conjuction with the XAML code.
I recommend this answer as a starting point for others who are also struggling with this issue.
https://stackoverflow.com/a/25524753/2175012
After all, I have taken the side that ServiceLocators are no anti pattern but have to be used carefully.
Related
The problem is moving from the ServiceLocator anti-pattern to Dependency Injection. In view of my inexperience, I can't shift the DI principle to the code implemented now.
Summary
The Summary section is optional for read. You may want to comment on something, advise.
The main purpose of the program is the process of merging placeholder fields of specific information. Number of information makes need to have infrastructure around. Such as forms, services, database. I have some experience with this task. I managed to create a similar program based on WinForms. And it even works! But in terms of patterns, maintenance, extensibility, and performance, the code is terrible. It is important to understand that programming is a hobby. It is not the main education or job.
The experience of implementing the described task on WinForms is terrible. I started studying patterns and new platform. The starting point is UWP and MVVM. It should be noted that the binding mechanism is amazing.
The first problem on the way was solved independently. It is related to navigation in the UWP via the NavigationView located in the ShellPage connected with ShellViewModel. Along with creating a NavigationService. It is based on templates from Windows Template Studio.
Since there is a working WinForms program and its anti-pattern orientation, there is time and a desire to do everything correctly.
Now I'm facing an architecture problem. Called ServiceLocator (or ViewModelLocator). I find it in examples from Microsoft, including templates from Windows Template Studio. And in doing so, I fall back into the anti-pattern trap. As stated above, I don't want this again.
And the first thing that comes as a solution is dependency injection. In view of my inexperience, I can't shift the DI principle to the code implemented now.
Current implementation
The start point of app UWP is app.xaml.cs. The whole point is to transfer control to ActivationService. Its task is adding Frame to Window.Current.Content and navigated to default page - MainPage. Microsoft documentation.
The ViewModelLocator is a singleton. The first call to its property will call constructor.
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
// Constructor
private ViewModelLocator(){...}
Using ViewModelLocator with View (Page) is like this, ShellPage:
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
Using ViewModelLocator with ViewModel is similar, ShellViewModel:
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
Moving to DI
ShellViewModel has NavigationService from ViewModelLocator as shown above. How can I go to DI at this point? In fact, the program is small. And now is a good time to get away from anti-patterns.
Code
ViewModelLocator
private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
private ViewModelLocator()
{
// Services
SimpleIoc.Default.Register<NavigationService>();
// ViewModels and NavigationService items
Register<ShellViewModel, ShellPage>();
Register<MainViewModel, MainPage>();
Register<SettingsViewModel, SettingsPage>();
}
private void Register<TViewModel, TView>()
where TViewModel : class
where TView : Page
{
SimpleIoc.Default.Register<TViewModel>();
NavigationService.Register<TViewModel, TView>();
}
public ShellViewModel ShellViewModel => SimpleIoc.Default.GetInstance<ShellViewModel>();
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
public SettingsViewModel SettingsViewModel => SimpleIoc.Default.GetInstance<SettingsViewModel>();
public NavigationService NavigationService => SimpleIoc.Default.GetInstance<NavigationService>();
ShellPage : Page
private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;
public ShellPage()
{
InitializeComponent();
// shellFrame and navigationView from XAML
ViewModel.Initialize(shellFrame, navigationView);
}
ShellViewModel : ViewModelBase
private bool _isBackEnabled;
private NavigationView _navigationView;
private NavigationViewItem _selected;
private ICommand _itemInvokedCommand;
public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand<NavigationViewItemInvokedEventArgs>(OnItemInvoked));
private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;
public bool IsBackEnabled
{
get => _isBackEnabled;
set => Set(ref _isBackEnabled, value);
}
public NavigationViewItem Selected
{
get => _selected;
set => Set(ref _selected, value);
}
public void Initialize(Frame frame, NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
NavigationService.Frame = frame;
NavigationService.Navigated += Frame_Navigated;
NavigationService.NavigationFailed += Frame_NavigationFailed;
}
private void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
NavigationService.Navigate(typeof(SettingsViewModel));
return;
}
var item = _navigationView.MenuItems.OfType<NavigationViewItem>().First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageKey = GetPageKey(item);
NavigationService.Navigate(pageKey);
}
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
NavigationService.GoBack();
}
private void Frame_Navigated(object sender, NavigationEventArgs e)
{
IsBackEnabled = NavigationService.CanGoBack;
if (e.SourcePageType == typeof(SettingsPage))
{
Selected = _navigationView.SettingsItem as NavigationViewItem;
return;
}
Selected = _navigationView.MenuItems
.OfType<NavigationViewItem>()
.FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw e.Exception;
}
private bool IsMenuItemForPageType(NavigationViewItem item, Type sourcePageType)
{
var pageKey = GetPageKey(item);
var navigatedPageKey = NavigationService.GetNameOfRegisteredPage(sourcePageType);
return pageKey == navigatedPageKey;
}
private Type GetPageKey(NavigationViewItem item) => Type.GetType(item.Tag.ToString());
Update 1
Am I wrong about the equality between ServiceLocator and ViewModelLocator?
Called ServiceLocator (or ViewModelLocator)
Essentially, the current task is to connected View and ViewModel. NavigationService is beyond the scope of this task. So shouldn't it be in ViewModelLocator?
As #Maess notes, the biggest challenge you face (right now) is refactoring the static dependencies into constructor injection. For example, your ShellViewModel should have a constructor like:
public ShellViewModel(INavigationService navigation)
Once you have done that, you can then set up a DI framework (like NInject) with all your dependencies (kind of like your SimpleIoC thing), and, ideally, pull one root object from the container (which constructs everything else). Usually that's the main view model of the application.
I've done this successfully on multiple projects, both WPF and UWP, and it works great. Only thing you have to be careful of is when creating view models at runtime (as you often do), do it by injecting a factory.
I really made a search for this topic and did not find anything, and because of that, I am asking the question here.
I have a WPF application with Prism installed.
I have wired the view-model with the view automatically by name convention
<UserControl x:Class="Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
and the model in the 'Model' like this
public class ViewAViewModel {
public ViewAViewModel () {
// time-resource consuming operations
}
}
the automatic binding work perfectly without a problem and the view and its corresponding view-model is matching, but the problem here.
I have a lot of those views say (50) and for every one of them, the view-model will be created with constructor exhausting the processes. This will make the startup of the application longer and also it will create a lot of view-models objects and put them in the RAM without being sure that they will be used at all.
What I need is to create the view-model class when the view is activated (I mean when the view is navigated to). Is this possible and if yes how?
Update
here is how I register the view with the Module, this is causing all the views to be created when the startup of the module.
public class Module1 : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("region1", typeof(View1));
regionManager.RegisterViewWithRegion("region1", typeof(View2));
is there any way to delay the creating of the views, until the navigation request come?
You could use navigation, for each view.
Or you must create an interfaces for your view and view model.
An example:
public interface IMyView
{
IMyViewModel ViewModel { get; set; }
}
public interface IMyViewModel
{
}
In the module or app.cs, in the method RegisterTypes you should register these.
containerRegistry.Register<IMyView, MyView>();
containerRegistry.Register<IMyViewModel, MyViewModel>();
You must implement IMyView interface in your MyView.cs class.
public partial class MyView : UserControl, IMyView
{
public MyView(IMyViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IMyViewModel ViewModel
{
get => DataContext as IMyViewModel;
set => DataContext = value;
}
}
After you could use it:
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var firstView = containerProvider.Resolve<IMyView>();
regionManager.AddToRegion(RegionNames.MainRegion, firstView);
}
In such case you shouldn't use ViewModelLocator.AutoWireViewModel in your view.
I want to take advantage of dependency injection in my Xamarin project but can't get constructor injection to work in C# classes behind XAML views. Is there any way to do it ?
I've seen guides how to setup dependency injections in View Models, to later use them as repositories but that doesn't work for me.
So far I tried Ninject and Unity.
Code:
This is the service I want to use inside of my PCL project:
public class MyService : IMyService
{
public void Add(string myNote)
{
//Add Note logic
}
}
Interface:
public interface IMyService
{
void Add(string myNote);
}
Unity setup in App.Xaml:
public App ()
{
InitializeComponent();
var unityContainer = new UnityContainer();
unityContainer.RegisterType<IMyService, MyService>();
var unityServiceLocator = new UnityServiceLocator(unityContainer);
ServiceLocator.SetLocatorProvider(() => unityServiceLocator);
MainPage = new MainMasterMenu(); //<-- feel that I'm missing something here as I shouldn't be creating class instances with DI, right ?
}
Usage that I'd like to see. This is .CS file behind a XAML starting page:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainMasterMenu : MasterDetailPage
{
private IMyService _myService;
public MainMasterMenu(IMyService myService)
{
_myService = myService
}
private void SomeFormControlClickEvent(object sender, ItemChangedEventArgs e)
{
_myService.Add("hi");
}
}
For that simple example creating the MainMasterMenu directly would be no issue, but you would have to pass the reference to your service
MainPage = new MainMasterMenu(unityContainer.Resolve<IMyService>());
But this would mean that you'll have to change that line every time the constructor of MainMasterMenu changes. You could circumvent this by registering the MainMasterMenu, too.
unityContainer.RegisterType<MainMasterMenu>();
...
MainPage = unityContainer.Resolve<MainMasterPage>();
Anyway, anytime you want to navigate to another page, which needs any dependency registered with unity, you'll have to make sure to resolve its dependencies properly, which requires (at least indirect) access to the unity container. You could pass a wrapper that encapsules the access to unity
interface IPageResolver
{
T ResolvePage<T>()
where T : Page;
}
and then implement that resolver with unity
public class UnityPageResolver
{
private IUnityContainer unityContainer;
public UnityPageResolver(IUnityContainer unityContainer)
{
this.unityContainer = unityContainer;
}
public T ResolvePage<T>()
where T : Page // do we need this restriction here?
{
return unityContainer.Resolve<T>();
}
}
This gets registered with unity
unityContainer.RegisterInstance<IUnityContainer>(this);
unityContainer.RegisterType<IPageResolver, UnityPageResolver>();
But you should have a look at the Prism library (see here) that solves many of the issues (e.g. it provides an INavigationService that lets you navigate to other pages without caring about the dependencies and it provides facilities to resolve viewmodels automatically, including dependencies).
My application is going on a break mode after hitting a user button that loads the app setup. I have registered the component in the bootstrapper class.
How can I register the constructor of the user controller in bootstrap class so as to avoid the break?
public class Bootstrapper
{
public IContainer Bootstrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<LoginView>().AsSelf();
builder.RegisterType<SchoolSectionDataService>().As<ISchoolSectionDataService>();
builder.RegisterType<AdminView>().AsSelf();
builder.RegisterType<School>().AsSelf();
builder.RegisterType<MainSchoolSetupViewModel>().AsSelf();
return builder.Build();
}
}
and the user control is:
private MainSchoolSetupViewModel _viewModel;
public School(MainSchoolSetupViewModel schoolSetupViewModel)
{
InitializeComponent();
_viewModel = schoolSetupViewModel;
DataContext = _viewModel;
Loaded += UserControl_Loaded;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
_viewModel.Load();
}
Unfortunately passing viewmodel into user control's constructor is not possible but there few ways around it. The main thing usually is when building combining DI and XAML and MVVM is that only the view models are registered into the container.
Couple options are mentioned in the comments:
Add a static IContainer property in your Bootstrap. Call it in you user control's constructor to get the VM:
public School()
{
InitializeComponent();
_viewModel = Bootstrap.Container.Resolve<MainSchoolSetupViewModel>();
...
Skip DI and instead create the viewmodel instance in XAML:
<UserControl.DataContext>
<local:SchoolViewModel/>
</UserControl.DataContext>
But it's quite likely that you want to there's other possibilities:
Use ViewModelLocator to help out with DI. This is well documented in this answer: https://stackoverflow.com/a/25524753/66988
The main idea is that you create a new ViewModelLocator class:
class ViewModelLocator
{
public SchoolViewModel SchoolViewModel
{
get { return Bootstrap.Container.Resolve<SchoolViewModel>(); }
}
}
And create a static instance of it in App.xaml and use it to create the data context of your user control:
DataContext="{Binding SchoolViewModel, Source={StaticResource ViewModelLocator}}">
For other solutions, one option is to check out source code of some of MVVM Frameworks, like Caliburn.Micro. From Caliburn.Micro you can find ViewModelLocator and ViewLocator which might interest you.
I've been using MVVM for WPF quite a while now but I've always been doing it this way:
ExampleView.xaml.cs (namespace: Example.Views)
public partial class ExampleView
{
public ExampleView()
{
InitializeComponent();
var viewModel = new ExampleViewModel();
DataContext = viewModel;
}
}
The ExampleView.xaml has no code concerning the ExampleViewModel except for bindings to properties.
ExampleViewModel.cs (namespace: Example.ViewModels)
public ExampleViewModel()
{
// No important code in here concerning this topic. Code here is only used in this class.
}
Below is a simplified MainWindowView.xaml.
<Window ...
xmlns:views="clr-namespace:Example.Views">
<Grid>
<views:ExampleView />
</Grid>
</Window>
The MainWindowView.xaml.cs is similar to the ExampleView.xaml.cs. The MainWindowViewModel.cs has no important code concerning this topic.
Lastly, the App.xaml contains the StartupUri="Views/MainWindowView.xaml".
If this is a good pattern or not, I got my application to work. Since the application is not maintainable by me alone anymore, 2-3 people are now working on it creating some problems. One person is doing the majority of the coding (ViewModels basically), one person is doing the GUI (Views) and one person is doing the "framework" coding. (Using "" because this is not really a framework but I can't think of a better word for it.)
Now, I'm the guy that is doing the framework coding and I've been reading up on several subjects like dependency injection and the code below is what I came up with using UnityContainer from Windows.
ExampleView.xaml.cs (namespace: Example.Views)
public partial class ExampleView
{
public ExampleView()
{
InitializeComponent();
}
}
The ExampleView.xaml has no code concerning the ExampleViewModel except for bindings to properties.
ExampleViewModel.cs (namespace: Example.ViewModels)
public string MyText { get; set; }
public ExampleViewModel(ILocalizer localizer)
{
MyText = localizer.GetString("Title");
}
Below is a simplified MainWindowView.xaml.
<Window ...
xmlns:views="clr-namespace:Example.Views">
<Grid>
<views:ExampleView DataContext="{Binding ExampleViewModel}" />
</Grid>
</Window>
The MainWindowView.xaml.cs is similar to the ExampleView.xaml.cs.
MainWindowViewModel.cs
ExampleViewModel ExampleViewModel { get; set; }
private readonly ILocalizer _localizer;
private readonly IExceptionHandler _exHandler;
public MainWindowViewModel(ILocalizer localizer, IExceptionHandler exHandler)
{
_localizer = localizer;
_exHandler = exHandler;
ExampleViewModel = new ExampleViewModel(localizer);
}
Lastly, the App.xaml does not contains the StartupUri="..." anymore. It's now done in App.xaml.cs. It's also here where the `UnityContainer is initialized.
protected override void OnStartup(StartupEventArgs e)
{
// Base startup.
base.OnStartup(e);
// Initialize the container.
var container = new UnityContainer();
// Register types and instances with the container.
container.RegisterType<ILocalizer, Localizer>();
container.RegisterType<IExceptionHandler, ExceptionHandler>();
// For some reason I need to initialize this myself. See further in post what the constructor is of the Localizer and ExceptionHandler classes.
container.RegisterInstance<ILocalizer>(new Localizer());
container.RegisterInstance<IExceptionHandler>(new ExceptionHandler());
container.RegisterType<MainWindowViewModel>();
// Initialize the main window.
var mainWindowView = new MainWindowView { DataContext = container.Resolve<MainWindowViewModel>() };
// This is a self made alternative to the default MessageBox. This is a static class with a private constructor like the default MessageBox.
MyMessageBox.Initialize(mainWindowView, container.Resolve<ILocalizer>());
// Show the main window.
mainWindowView.Show();
}
For some reason I need to initialize the Localizer and ExceptionHandler classes myself. The Localizer and ExceptionHandler constructors are found below. Both have constructors with all arguments that have a default value. Adding constructors without arguments like
public ExceptionHandler() : this(Path.Combine(Directory.GetCurrentDirectory(), "ErrorLogs", DateTime.Now.ToString("dd-MM-yyyy") + ".log")) { }
doesn't change a thing.
public Localizer(ResourceDictionary appResDic = null, string projectName = null, string languagesDirectoryName = "Languages", string fileBaseName = "Language", string fallbackLanguage = "en")
{
_appResDic = appResDic ?? Application.Current.Resources;
_projectName = !string.IsNullOrEmpty(projectName) ? projectName : Application.Current.ToString().Split('.')[0];
_languagesDirectoryName = languagesDirectoryName.ThrowArgNullExIfNullOrEmpty("languagesFolder", "0X000000066::The languages directory name can't be null or an empty string.");
_fileBaseName = fileBaseName.ThrowArgNullExIfNullOrEmpty("fileBaseName", "0X000000067::The base name of the language files can't be null or an empty string.");
_fallbackLanguage = fallbackLanguage.ThrowArgNullExIfNullOrEmpty("fallbackLanguage", "0X000000068::The fallback language can't be null or an empty string.");
CurrentLanguage = _fallbackLanguage;
}
public ExceptionHandler(string logLocation = null, ILocalizer localizer = null)
{
// Check if the log location is not null or an empty string.
LogLocation = string.IsNullOrEmpty(logLocation) ? Path.Combine(Directory.GetCurrentDirectory(), "ErrorLogs", DateTime.Now.ToString("dd-MM-yyyy") + ".log") : logLocation;
_localizer = localizer;
}
My big question now is if I'm approaching dependency injection correctly and if having several static classes that I initialize once are bad. Several topics I've read state that static classes are a bad-practice because of bad testability and tightly coupled code, but right now the tradeoffs of dependency injection are bigger than having static classes.
Doing dependency injection correctly would be a first step in having less tightly coupled code though. I like the approach with the static MyMessageBox I can initialize once and that it's globally available in the application. This is mainly for "easy usage" I guess cause I can simply call MyMessageBox.Show(...) instead of injecting this all the way down to the smallest element. I have a similar opinion about the Localizer and ExceptionHandler because these will be used even more.
A last concern I have is the following. Lets say I have a class with several arguments and one of the arguments is the Localizer (because this will be used in nearly any class). Having to add ILocalizer localizer every time
var myClass = new MyClass(..., ILocalizer localizer);
feels very annoying. This would push me towards a static Localizer I initialize once and having never to care about it anymore. How would this problem be tackled?
If you have a bunch of "Services" which are used in many classes, you can create a facade class which encapsulates the required services and inject the facade into your classes.
Advantage of doing so is, you can easily add other services to that facade and they'd be available in all other injected classes, without modifying the constructor parameters.
public class CoreServicesFacade : ICoreServicesFacade
{
private readonly ILocalizer localizer;
private readonly IExceptionHandler excaptionHandler;
private readonly ILogger logger;
public ILocalizer Localizer { get { return localizer; } }
public IExceptionHandler ExcaptionHandler{ get { return exceptionHandler; } }
public ILogger Logger { get { return logger; } }
public CoreServices(ILocalizer localizer, IExceptionHandler exceptionHandler, ILogger logger)
{
if(localizer==null)
throw new ArgumentNullException("localizer");
if(exceptionHandler==null)
throw new ArgumentNullException("exceptionHandler");
if(logger==null)
throw new ArgumentNullException(logger);
this.localizer = localizer;
this.exceptionHandler = exceptionHandler;
this.logger = logger;
}
}
Then you can pass it to your classes:
var myClass = new MyClass(..., ICoreServicesFacade coreServices);
(which you shouldn't do anyway when using Dependency Injection, you shouldn't use new keyword, except for factories and models).
As for your ILocalizer and IExceptionHandler implementations... if your ExceptionHandler requires the Localizer and the localizer requires the string parameter, you have two options, depending on if the file name needs to be determined at a later point at run time or only once during the Application initialization.
Important
Don't use optional constructor parameters if you want to use dependency injection. For DI, constructor parameters should declare the dependencies in constructor and constructor dependencies are always considered as mandatory (don't use ILocalizer localizer = null within the constructor).
If you only create the logfile during the Applications initialization, it's quite easy
var logFilePath = Path.Combine(Directory.GetCurrentDirectory(), "ErrorLogs", DateTime.Now.ToString("dd-MM-yyyy") + ".log");
var localizer = new Localizer(...);
var exceptionHandler = new ExceptionHandler(logFilePath, localizer);
container.RegisterInstance<ILocalizer>(localizer);
container.RegisterInstance<IExceptionHandler>(exceptionHandler);
Basically in your bootstrapper you instantiate and configure your Localizer and ExceptionHandler, then register it as instance with the container.
If for some reason, you need to determine the name of log filename or language at a later point (after Bootstrapper configuration & initialization), you need to use a different approach: You need a factory class.
The factory will be injected into your classes rather than the instance of ILocalizer/IExceptionHandler and create the instance of it when the parameters are known.
public interface ILocalizerFactory
{
ILocalizer Create(ResourceDictionary appResDic, string projectName);
}
public class ILocalizerFactory
{
public ILocalizer Create(ResourceDictionary appResDic, string projectName)
{
var localizer = new Localizer(appResDic, projectName, "Languages", "Language", "en");
return localizer;
}
}
Using the facade Example from above:
public class CoreServicesFacade : ICoreServicesFacade
{
private readonly ILocalizer localizer;
public ILocalizer Localizer { get { return localizer; } }
public CoreServices(ILocalizerFactory localizerFactory, ...)
{
if(localizer==null)
throw new ArgumentNullException("localizerFactory");
this.localizer = localizerFactory.Create( Application.Current.Resources, Application.Current.ToString().Split('.')[0]);
}
}
Caveats & tips
Move Default configuration outside of the classes itself
Don't use such code inside your Localizer/ExceptionHandler classes.
_appResDic = appResDic ?? Application.Current.Resources;
_projectName = !string.IsNullOrEmpty(projectName) ? projectName : Application.Current.ToString().Split('.')[0];
_languagesDirectoryName = languagesDirectoryName.ThrowArgNullExIfNullOrEmpty("languagesFolder", "0X000000066::The languages directory name can't be null or an empty string.");
_fileBaseName = fileBaseName.ThrowArgNullExIfNullOrEmpty("fileBaseName", "0X000000067::The base name of the language files can't be null or an empty string.");
_fallbackLanguage = fallbackLanguage.ThrowArgNullExIfNullOrEmpty("fallbackLanguage", "0X000000068::The fallback language can't be null or an empty string.");
CurrentLanguage = _fallbackLanguage;
This pretty much makes it untestable and puts the configuration logic in the wrong place. You should only accept and validate the parameters passed into the constructor and determine the values and fall backs in either a) factory's create method or b) inside your bootstrapper (if runtime parameters aren't required).
Don't use View-related type inside your interfaces
Don't use ResourceDictionary in your public interfaces, this will leak View knowledge into your ViewModels and require you to have a reference to the assembly containing View/Application related code (I know I used it above, based on your Locator constructor).
If you need it, pass it as constructor Parameter and implement the class in Application/View assembly, while having your Interface in your ViewModel assembly). Constructors are implementation detail, and can be hidden (by implementing the class in a different assembly which allows reference to the class in question).
Static classes are evil
As you already realized, static classes are bad. Inject them is the way to go. Your Application will most likely need a navigation too. So you can put Navigation (Navigate to a certain View), MessageBoxes (display an information) and opening of new Windows (a kind of navigation too) into either one service or a navigation facade (similar to above one) and pass all services related to navigation as a single Dependency into your objects.
Passing parameters to ViewModel
Passing parameters can be a bit of a pain in "home-brew" frameworks and you shouldn't pass parameters via ViewModel constructors (prevents DI from resolving it or forcing you to use a factory). Instead consider writing a navigation service (or using exiting framework). Prims has it solved pretty nicely, you got a navigation service (which will do the navigation to a certain View and it's ViewModel and also offers INavigationAware interface with NavigateTo and NavigateFrom methods, which are called when one navigates to a new view (one of this methods parameters can be used to provide parameters to the ViewModel) and when navigating from a ViewModel (i.e. to determine if navigating from a view is viable or to cancel the navigation if necessary, for example: Asking the user to save or discard the data before navigating to the other ViewModel).
But that's bit off-topic.
Example:
public class ExampleViewModel : ViewModelBase
{
public ExampleViewModel(Example2ViewModel example2ViewModel)
{
}
}
public class Example2ViewModel : ViewModelBase
{
public Example2ViewModel(ICustomerRepository customerRepository)
{
}
}
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel(ExampleViewModel example2ViewModel)
{
}
}
// Unity Bootstrapper Configuration
container.RegisterType<ICustomerRepository, SqlCustomerRepository>();
// You don't need to register Example2ViewModel and ExampleViewModel unless
// you want change their container lifetime manager or use InjectionFactory
To get an resolve instance of your MainWindowViewModel simply do
MainWindowViewModel mainWindowViewModel = container.Resolve<MainWindowViewModel>();
and Unity will resolve all other dependencies (it will inject ICustomerRepository into Example2ViewModel, then inject Example2ViewModel into ExampleViewModel and finally inject ExampleViewModel into your MainWindowViewModel and return an instance of it.
The catch is: You can't use the container inside your ViewModels (though using it in View's code-behind is okay in your use case. However it's better to use navigation Service or a ViewModel Locator within your XAML (see Prism on how they did it)) .
So you need a navigation service of a kind, if you need to do it from ViewModels.