I have Login window and a Home window, inside the login class the Home is opened and the Login is closed:
Home home = new Home(user, _gitApiService);
home.Show();
Close();
Because the Home class relies on a dependency of IGitApiService, I am passing the dependency via the window class's constructor:
public partial class Home : Window
{
private readonly IGitApiService _gitApiService;
public Home(User user, IGitApiService gitApiService)
{
_gitApiService = gitApiService;
...etc
This seems like bad practice to me, is there any cleaner way of accessing/instaniating the IGitApiService?
(For context the GitApiService is just a class with api calls using HttpClient)
Assuming that there are only a few dependencies then such poor man's/pure DI isn't something really bad.
But if it is a common scenario and there are many dependencies, then by all means register a factory for the Home page (as user seems to be some domain object that can't be registered in CompositionRoot):
services.Register<Func<User, Home>>(context =>
user => new Home(user, context.Resolve<IGitApiService>());
or however explicitly or implicitly it is done in the DI framework used in the application.
Slight design change to Home window
public partial class Home : Window {
private readonly IGitApiService _gitApiService;
public Home(IGitApiService gitApiService) {
_gitApiService = gitApiService;
}
public User User { get; set; }
//...
}
I would have a window service responsible for showing a desired window
public interface IWindowService {
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window;
}
public class WindowService : IWindowService {
private readonly IServiceProvider services;
public WindowService(IServiceProvider services) {
this.services = services
}
public void Show<TWindow>(Action<TWindow> configure = null) where TWindow : Window {
var window = services.GetService<TWindow>();
if(configure != null) {
configure(window);
}
window.Show();
}
}
With that in place you inject your window service and use it like
windowSevie.Show<Home>(window => window.User = user);
Close();
Any explicit dependencies are injected when the window is resolved, and the configure delegate allows flexibility to populate any other members as needed
Related
I used Template Studio for WinUI to create a new application. I want to stick to the dependency injection approach which is created from the template.
The template provide the following code for App.xaml.cs:
public partial class App : Application {
private static readonly IHost _host = Host
.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
// Services
// ...
services.AddTransient<IFileOpenPickerService, FileOpenPickerService>();
// Core Services
services.AddSingleton<IFileService, FileService>();
// Views and ViewModels
services.AddTransient<SolutionListDetailsViewModel>();
services.AddTransient<SolutionListDetailsPage>();
services.AddTransient<MainViewModel>();
services.AddTransient<MainPage>();
// Configuration
})
.Build();
public static T? GetService<T>()
where T : class {
return _host.Services.GetService(typeof(T)) as T;
}
// ...
}
For a control in the main project the viewmodel creation looks like this in the template:
public sealed partial class SolutionListDetailsPage : Page {
public SolutionListDetailsViewModel ViewModel {
get;
}
public SolutionListDetailsPage() {
ViewModel = App.GetService<SolutionListDetailsViewModel>();
InitializeComponent();
}
//...
}
However, I want to implement a control in a second project, which is referenced and used by the main project, and here the line
ViewModel = App.GetService<MyOwnViewModelWhichIRegisteredAsAService>();
does not work anymore, because I do not have access to App.GetService() in the second project. Furthermore, I cannot let the service provider provide the control and pass the view model in its constructor, since it is created within XAML code. How do I access the service provider to get the view model?
One solution is to create the view model somewhere else and bind it to the DataContext-property. But then I have a different viewmodel-creation-mechanism for the new control than for the old one, which I would like to avoid. (And I am not sure if it works with WinUI's x:Bind.)
If you want to access to the registerd services from other projects, you can use the Ioc class from the CommunityTookit.
var service = Ioc.Default.GetRequiredServices<ISecondProjectClass>();
Also, regarding to several projects use cases, you can also register your services from your second project by creating an extension method:
namespace SecondProject;
public static class SecondProjectHostBuilderExtensions
{
public static IHostBuilder AddSecondProjectServices(this IHostBuilder hostBuilder)
=> hostBuilder.ConfigureServices((context, services) =>
{
_ = services
.AddSingleton<ISecondProjectClass, SecondProjectClass>();
});
}
Call it at App.xaml.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
this.InitializeComponent();
_host = CreateHost();
Ioc.Default.ConfigureServices(_host.Services);
}
private static IHost CreateHost()
{
return Host
.CreateDefaultBuilder()
.AddSecondProjectServices()
.Build();
}
Then if you want to get these services at your second project:
namespace SecondProject;
public class SomeClassAtSecondProject
{
public SomeClassAtSecondProject()
{
var secondProjectClass = Ioc.Default.GetRequiredService<ISecondProjectClass>();
}
}
My current solution is to add a static class in the second project like this:
public static class ServiceProviderWorkaround {
private static Func<Type, object?>? getService;
public static void Init(Func<Type, object?> getter) {
if (getService != null)
throw new InvalidOperationException("Provider already initialized");
getService = getter;
}
internal static T? GetService<T>() where T : class
=> getService == null
? throw new InvalidOperationException("Provider not initialized")
: getService(typeof(T)) as T;
internal static T GetRequiredService<T>() where T : class
=> getService == null
? throw new InvalidOperationException("Provider not initialized")
: (T)getService(typeof(T))!;
}
It is initialized in the constructor of the Application-class:
public App() {
InitializeComponent();
UnhandledException += App_UnhandledException;
ServiceProviderWorkaround.Init(_ => _host.Services.GetService(_));
}
and can be used like this:
var service = ServiceProviderWorkaround.GetService<MyOwnViewModelWhichIRegisteredAsAService>();
Any better solution is still welcome.
So just some background on how the current UI automation solution works -
Our application is a Windows WPF app, so we utilize WinAppDriver for our automated testing needs. The solution for this is very similar to your typical UI automation page object design. We have page objects that reference elements, and then in our tests we call the methods from these page objects to perform actions on the host. The page objects make use of the C# partial classes. One class to store elements, one class to use these elements and perform actions
The test classes all inherit from a TestClassBase that handles the StartUp and TearDown login. So current design for something like a Login page and a test class that interacts with it looks like this
Login.Elements.cs
namespace UITesting
{
public partial class Login
{
public WindowsElement usernameField => _session.FindElementByAccessibilityId("UserName");
public WindowsElement passwordField => _session.FindElementByAccessibilityId("Password");
public WindowsElement signInButton => _session.FindElementByAccessibilityId("Sign In");
}
}
Login.Actions.cs
namespace UITesting
{
public partial class Login
{
// Driver Setup
private readonly WindowsDriver<WindowsElement> _session;
public Login(WindowsDriver<WindowsElement> session) => _session = session;
// Enter Username
public void EnterUsername(string username)
{
usernameField.SendKeys(username);
}
// Enter Password
public void EnterPassword(string password)
{
passwordField.SendKeys(password)
}
// Click 'Sign In'
public void SignIn()
{
signInButton.Click();
}
}
}
LoginTests.cs
namespace UITesting.Test
{
[Category("Login Tests")]
class LoginTests : TestClassBase
{
[Test]
public void Login()
{
// Login
login.EnterUsername("TestUser1");
login.EnterPassword("Password");
login.ClickSignIn();
}
}
}
TestClassBase
namespace UITesting
{
[TestFixture]
public class TestClassBase
{
// Declare Page Ogjects
public Login login;
// Declare WinAppDriver Session
private WindowsDriver<WindowsElement> session;
[SetUp]
public void SetUp()
{
// Instantiate Page Objects
login = new Login(session);
// Additional SetUp Logic here...
}
[TearDown]
public void TearDown()
{
// TearDown Logic here...
}
}
}
This all works well and great, but what I am trying to do is evolve this into is something that can run the exact same test using the same code on a different host.
We also have a Web version of the app that utilizes the Uno platform. The app is pretty much identical on the web, but to automate it we need to use Selenium. What I don't want to do is to have to manage two separate UI automation solutions, and since the two versions of the app are pretty much identical, I want to be able to toggle the target platform that the tests run on in our CI/CD pipeline and this will ultimately change what code is getting executed.
So it seems like utilizing Interfaces is probably the way to go here, and I understand that using them it would be possible to now have a Page Object class structure like below
ILogin.cs
LoginWeb.Actions.cs
LoginWeb.Elements.cs
LoginWPF.Actions.cs
LoginWPF.Elements.cs
This way, I now have 4 partial classes where the Actions classes inherit the interface and they use the elements from their corresponding Elements class.
The part that I don't understand is how I can get the test class to now execute the code from the desired Actions class. The part where I instantiate the page objects is key, as in this example both the WPF and Web page object would need to share the name login. Would I have to create two different TestClassBase classes and some sort of Interface for them and have the tests inherit both? Or am I just going about this the completely wrong way..
This might be a larger refactoring job, but it will be worth the effort.
First, you'll need to create interfaces for each page model. I recommend keeping the interfaces as simple as possible in order to provide a complete and flexible abstraction. Instead of three separate methods (EnterUsername, EnterPassword and ClickSignIn) which must be called in a specific order, consider a single method called SignIn which accepts a username and password as arguments. The method will internally handle entering the username, password and clicking the appropriate button.
Really, if you go this route, think hard about the interfaces. Try to avoid any situation where the order methods are called matters. Try to focus on the use case, and not the steps required to satisfy that use case.
public interface ILoginPage
{
void SignIn(string username, string password);
}
Next, implement this interface on two different classes. Each class will specialize in Selenium or WinAppDriver. Consider using a naming convention where page models that deal with the web application are prefixed with "Web" and page models for the desktop app are prefixed with "Windows" or "Desktop".
public class WebLoginPage : ILoginPage
{
private readonly IWebDriver driver;
public WebLoginPage(IWebDriver driver)
{
this.driver = driver;
}
public void SignIn(string username, string password)
{
// Enter username
// Enter password
// Click sign-in button
}
}
public class DesktopLoginPage : ILoginPage
{
private readonly WindowsDriver<WindowsElement> session;
public DesktopLoginPage (WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public void SignIn(string username, string password)
{
// Enter username
// Enter password
// Click sign-in button
}
}
Once you have a proper abstraction, you will need an interface for a factory class that creates page models, and then two implementing classes:
public interface IPageModelFactory
{
ILoginPage CreateLoginPage();
}
public class WebPageModelFactory : IPageModelFactory
{
private readonly IWebDriver driver;
public PageModelFactory(IWebDriver driver)
{
this.driver = driver;
}
public ILoginPage CreateLoginPage()
{
return new WebLoginPage(driver);
}
}
public class DesktopPageModelFactory : IPageModelFactory
{
private readonly WindowsDriver<WindowsElement> session;
public DesktopPageModelFactory(WindowsDriver<WindowsElement> session)
{
this.session = session;
}
public ILoginPage CreateLoginPage()
{
return new DesktopLoginPage(session);
}
}
This is an implementation of the Abstract Factory Pattern, and is an approach you can take without resorting to class reflection. While class reflection would probably take less code, it is much more difficult to understand. Just for giggles, here is an attempt at class reflection to generate page models:
public class PageModelFactory
{
private readonly object client;
public PageModelFactory(object client)
{
this.client = client;
}
public ILoginPage CreateLoginPage()
{
var pageModelType = GetPageModelType<ILoginPage>();
var constructor = pageModelType.GetConstructor(new Type[] { client.GetType() });
return (ILoginPage)constructor.Invoke(new object[] { client });
}
private Type GetPageModelType<TPageModelInterface>()
{
return client.GetType()
.Assembly
.GetTypes()
.Single(type => type.IsClass && typeof(TPageModelInterface).IsAssignableFrom(type));
}
}
You can use it with either driver:
// Selenium
var driver = new ChromeDriver();
// WinApDriver (however you initialize it)
var session = new WindowsDriver<WindowsElement>();
PageModelFactory webPages = new PageModelFactory(driver);
PageModelFactory desktopPages = new PageModelFactory(session);
ILoginPage loginPage = null;
loginPage = webPages .CreateLoginPage();
loginPage.SignIn("user", "...");
loginPage = desktopPages.CreateLoginPage();
loginPage.SignIn("user", "...");
Unless you or your team are comfortable with class reflection, I would recommend the abstract factory pattern approach, just because it is easier to understand.
Either way, you will need to determine which client you are using (web versus desktop). This should be done in a the setup method for your test. Refactoring your tests into a base class to centralize this decision making code is advised.
This question already has an answer here:
Why my Subscribe method is not called when using Prism EventAggregator?
(1 answer)
Closed 3 years ago.
In my solution I am using Autofac, and Prism as well. Below is a simplified project that explains what happens.
I am registering my views, view models and EventAggregator in Autofac's container class like that:
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>().SingleInstance();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<ChildView1>().AsSelf();
builder.RegisterType<MainViewModel>().AsSelf();
builder.RegisterType<Child1ViewModel>().AsSelf();
return builder.Build();
}
}
Note, that when registering view models as a singletons, effect was the same. I am injecting EventAggregator into my VM like that:
public class MainViewModel
{
private IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateName("Name1");
}
public void UpdateName(string name)
{
ChildView1 win1 = new ChildView1(); //in the backend Child1ViewModel is assigend to its DataContext
win1.Show();
_eventAggregator.GetEvent<UpdateNameEvent>().Publish(name); //this does not work
}
}
Code above does not work. Because of some reason (I hope that you will tell me why), when executing UpdateName method, this dependency does not work, and inside of Child1ViewModel class UpdateName method is not executed:
public class Child1ViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public Child1ViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
private void UpdateName(string name)
{
this.Name = name; //debug does not hit this code line
}
}
Constructor of Child1ViewModel is hiten during debug, just UpdateName is not executed. BUT, if I use direct call of the EventAggregator, like this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
or this:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Publish(name);
it works! When assuming, that Utility class looks like:
public class Utility
{
public static EventAggregator EventAggregator { get; set; }
static Utility()
{
EventAggregator = new EventAggregator();
}
}
I suspect, that there is some problem with registering the aggregator in Autofac, but I have no idea what is the problem, I just used it as per odl exaples I found.
Resolving Child1ViewModel and MainViewModel:
public partial class ChildView1 : Window
{
public ChildView1()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
Child1ViewModel vm = container.Resolve<Child1ViewModel>();
InitializeComponent();
DataContext = vm;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
MainViewModel vm = container.Resolve<MainViewModel>();
InitializeComponent();
DataContext = vm;
}
}
I'm assuming you are using Prism's ViewModelLocator mechanism. My assumption is based on that you are manually instantiating ChildView1 rather than resolving it with dependency injection container and state in the comment that viewModel is resolved in the background.
You must change the container of Prism ViewModelLocator used for resolving the ViewModel instances as shown below.
ViewModelLocationProvider.SetDefaultViewModelFactory(viewModelType) =>
{
return YourAutofacContainer.Resolve<viewModelType>();
});
For further information see Control how ViewModels are Resolved.
Keep in mind that for keeping single instance lifetime scope of object instances consistent throughout your whole application, you must use the same dependency injection container instance for resolving objects and this objects' parent objects all the way up to the root object.
I would love to find a simple and MVVM compliant way,
to open a new view from the MainWindow.
I have already worked through some instructions and tutorials.
But somehow none really worked or it was a mixture of code behind.
I would like to open a view after pressing a button and edit an ObservableCollection in it.
I have already created DataTemplates in App.xaml and mapped the ViewModels with the respective views.
But I don't know how to cleanly open a separate window from the MainWindow (MainViewModel) via an ICommand for another ViewModel.
You should't open a window directly from the MainWindow nor the MainWindowViewModel. But the view model may use a service to open a window:
public class MainWindowViewModel
{
private readonly IWindowService _service;
public MainWindowViewModel (IWindowService service)
{
_service = service;
}
//...
public void OpenWindowExecuted()
{
_service.ShowWindow();
}
}
Service:
public interface IWindowService
{
void ShowWindow();
}
public class WindowService : IWindowService
{
public void ShowWindow();
{
Window window = new Window()
window.Show();
}
}
You could easily mock out the service in your unit tests.
I am currently learning Catel+Orchestra using MahApps Metro.
I am doing the Authentication example from the Catel.Examples project using the MetroUI.
My problem is when i create a new MainWindow in my MahAppsService
public FrameworkElement GetMainView()
{
return new MainWindow();
}
The constructor of the MainWindowViewModel is never called
public MainWindowViewModel(UIVisualizerService uiVisualizarService, IAuthenticationProvider authenticationProvider)
{
_uiVisualizerService = uiVisualizarService;
_authenticationProvider = authenticationProvider;
RoleCollection = new ObservableCollection<string>(new[] { "Read-Only", "Administrator" });
ShowView = new Command(OnShowViewExecute, OnShowViewCanExecute, "ShowView");
}
I have narrowed it down to the 2 dependencies of the constructor. If i remove the UIVisualizerService and IAuthenticacionProvider dependencies the constructor is properly called but the ModelView needs those two services later on.
I am lost at what can i do to get this working.
You must register the IAuthenticationProvider in the ServiceLocator:
var serviceLocator = ServiceLocator.Default;
serviceLocator.RegisterType<IAuthenticationProvider, MyAuthenticationProvider>();
Note that all services inside Catel are automatically registered for you, but you must register your own services yourself (for example, by using ModuleInit or another entry point in your assembly).
I solved the problem by adding a explicit injection of the viewmodel into the mainwindow constructor.
public MainWindow(MainWindowViewModel _mainwindowviewmodel):base(_mainwindowviewmodel)
{
InitializeComponent();
}
Declaring the field for the AuthenticationProvider interface to the MahAppsService class.
private readonly IAuthenticationProvider _authenticationProvider;
Also adding the dependency of the AuthenticationProvider interface to the constructor.
public MahAppsService(ICommandManager commandManager, IMessageService messageService, IUIVisualizerService uiVisualizerService, IAuthenticationProvider authenticationProvicer)
{
Argument.IsNotNull(() => commandManager);
Argument.IsNotNull(() => messageService);
Argument.IsNotNull(() => uiVisualizerService);
Argument.IsNotNull(() => authenticationProvicer);
_commandManager = commandManager;
_messageService = messageService;
_uiVisualizerService = uiVisualizerService;
_authenticationProvider = authenticationProvicer;
}
And the last step is creating an instance of the viewmodel in the GetMainView in the MahAppsService class.
public FrameworkElement GetMainView()
{
var mainwindowViewModel = TypeFactory.Default.CreateInstanceWithParametersAndAutoCompletion<MainWindowViewModel>(_uiVisualizerService, _authenticationProvider);
return new MainWindow(mainwindowViewModel);
}
Please note that this might not be the best way to do it but it gets the work done. If someone has better way feel free to share it.