General-purpose C# assistance library.
Includes support for WPF with Caliburn.Micro.
Wingman is on NuGet.
To install the latest version, open PowerShell in your project directory, and simply run:
Install-Package Wingman
Wingman has several sub-packages. The main package installs all of them, but you can install any of the sub-packages individually:
Install-Package Wingman.DI
Install-Package Wingman.Services
Install-Package Wingman.WPF
The packages have inter-dependencies. For example, Wingman.WPF will always install Wingman.DI.
Supported .NET Framework Versions
We target a varying range of .NET Framework versions from v4.0 to v4.8, so all of your projects from April 2010 forwards should work with Wingman.
For frameworks v4.5 - v4.7, the base release and latest release are targeted, however not release v4.x.1. This means that for these frameworks, v4.x.1 projects will target the v4.x release of Wingman.
Please keep in mind that .NET 4.0 projects cannot be mocked with Moq, so the release target isn't tested, however it compiles.
Release Date | Framework Version | Supported | Targeted | Tested |
---|---|---|---|---|
13th February 2002 | v1.0 | ❌ | ❌ | ❌ |
24th April 2003 | v1.1 | ❌ | ❌ | ❌ |
07th November 2005 | v2.0 | ❌ | ❌ | ❌ |
06th November 2006 | v3.0 | ❌ | ❌ | ❌ |
19th November 2007 | v3.5 | ❌ | ❌ | ❌ |
12th April 2010 | v4.0 | ✅ | ✅ | ❌ |
15th August 2012 | v4.5 | ✅ | ✅ | ✅ |
17th October 2013 | v4.5.1 | ✅ | ❌ | ❌ |
05th May 2014 | v4.5.2 | ✅ | ✅ | ✅ |
20th July 2015 | v4.6 | ✅ | ✅ | ✅ |
30th November 2015 | v4.6.1 | ✅ | ❌ | ❌ |
02nd August 2016 | v4.6.2 | ✅ | ✅ | ✅ |
05th April 2017 | v4.7 | ✅ | ✅ | ✅ |
17th October 2017 | v4.7.1 | ✅ | ❌ | ❌ |
30th April 2018 | v4.7.2 | ✅ | ✅ | ✅ |
18th April 2019 | v4.8 | ✅ | ✅ | ✅ |
The .NET Standard, and .NET Core and Mono are not currently supported.
Wingman currently includes basic support for Caliburn.Micro.
The author includes a BootstrapperBase
base implementation which allows bootstrapping with a minimal amount of code.
The most basic bootstrapper might looks like this:
using Wingman.Bootstrapper;
using Wingman.Container;
class Bootstrapper : BootstrapperBase<IShellViewModel>
{
protected override void RegisterViewModels(IDependencyRegistrar dependencyRegistrar)
{
dependencyRegistrar.Singleton<IShellViewModel, ShellViewModel>();
}
}
As you may have noticed, one of the BootstrapperBase
classes accepts a single generic parameter - the root ViewModel to use for DisplayRootViewModel
.
RegisterViewModels
is an abstract method because the root ViewModel (IShellViewModel
in this case) must always be registered with the dependency registrar, otherwise the root view cannot be displayed by Caliburn. The BootstrapperBase
will throw if the root ViewModel isn't registered!
BootstrapperBase
will also register two additional dependencies for you automatically:
- An
IWindowManager
implementation (from Caliburn)- This serves to spawn windows and is always necessary.
- An
IServiceFactory
implementation- This is a convenience general-purpose factory, provided by Wingman, described in its own section.
Caliburn's container bootstrapping methods have been overridden and sealed by Wingman, and they delegate all calls to IDependencyRetriever
.
The Configure
method has also been sealed, and calls these methods in order:
RegisterViewModels(IDependencyRegistrar dependencyRegistrar);
RegisterServices(IDependencyRegistrar dependencyRegistrar);
RegisterFactoryViewModels(IServiceFactoryRegistrar dependencyRegistrar);
RegisterFactoryServices(IServiceFactoryRegistrar dependencyRegistrar);
The first two methods are simply used to register dependencies with the default container. TRootViewModel
must be registered in RegisterViewModels
, however, apart from that, there is nothing to stop you from registering services in the first method, and ViewModels in the second. The convention was created to make registration easier to read, as there are usually two separate clusters of registrations in the Configure
method.
The Factory versions of these methods relate to registering with IServiceFactory
, which is described in its own section.
Wingman also overrides OnStartup
using the "template method" pattern, and displays the root view for the ViewModel templated when inheriting BootstrapperBase
.
You can override these two method to take action before or after displaying the root view, respectively:
OnStartupBeforeDisplayRootView(object sender, StartupEventArgs e);
OnStartupAfterDisplayRootView(object sender, StartupEventArgs e);
All other BootstrapperBase
methods have been left intact as per Caliburn's implementations.
Wingman's BootstrapperBase
implementation aims to reduce hassle when setting up a new project, ensuring that the app has been bootstrapped properly by throwing descriptive exceptions early on during the startup phase.
As you may have noted, Wingman includes two dependency interfaces - IDependencyRegistrar
and IDependencyRetriever
. These serve to register and retrieve dependencies from a dependency container. The interfaces have largely been based on the SimpleContainer
implementation in Caliburn.
The interfaces are split due to the fact that registrations and retrieval from the dependency container often happen separately, which means that too much information is passed to consumers that do not use both concepts.
Wingman comes with a default implementation of these two interfaces (DependencyContainer
), which can be instantiated via DependencyContainerFactory.Create()
. DependencyContainer
also implements IDependencyActivator
, a simple event that is raised when an instance is created from the container, which isn't used by Wingman specifically, but is present in Caliburn's SimpleContainer
implementation.
Any custom implementations of these interfaces can either manually implement IDependencyRegistrar
and/or IDependencyRetriever
, or derive the DependencyContainerBase
class which implements both interfaces and the convenience generic methods defined in the interfaces. IDependencyActivator
is optional.
For custom container implementations, you must pass instances of your registrar and retriever to the base constructor of BootstrapperBase
:
using Wingman.Bootstrapper;
using Wingman.Container;
class Bootstrapper : BootstrapperBase<IShellViewModel>
{
public Bootstrapper() : base(MyCustomContainerRegistrar.Instance, MyCustomContainerRetriever.Instance)
{
}
protected override void RegisterViewModels(IDependencyRegistrar dependencyRegistrar)
{
dependencyRegistrar.Singleton<IShellViewModel, ShellViewModel>();
}
}
Custom implementations may be necessary when using different dependency containers, such as Castle Windsor and Ninject.
Wingman may introduce custom implementations for these containers in the future. If you happen to implement one of these, feel free to send a pull request! (Include unit tests!)
Wingman provides a general-purpose factory, which can generate implementations with dependencies as well as arguments. Unfortunately, to make the process simple, arguments are weakly-typed, passed as an object[]
.
The ServiceFactory is split into two interfaces:
IServiceFactoryRegistrar
- Registers dependency creation strategies with the factory.
IServiceFactory
- Generates instances of registered services via the appropriate strategy.
The default implementations of these interfaces are ServiceFactoryRegistrar
and ServiceFactory
(respectively), and they can both be instantiated via ServiceFactoryFactory.Create(IDependencyRegistrar, IDependencyRetriever)
. A pair of objects are returned, IServiceFactoryRegistrar Registrar
and IServiceFactory Factory
. The purpose of the registrar being a separate object is so that the registrar is allocated once at startup, all the dependencies are swiftly registered, and the registrar is garbage-collected, releasing all registration-related class instances.
The BootstrapperBase
class already takes care of instantiating and registering the factory with the default dependency container.
Creating a service instance is as simple as this:
class MyService
{
private readonly ISomeService _someService;
public MyService(IServiceFactory serviceFactory)
{
_someService = serviceFactory.Create<ISomeService>(somePieceOfData);
}
}
There are two strategies for registering services:
- FromContainer
- PerRequest
The FromContainer strategy will simply retrieve the service from the container, letting the container do any dependency resolution. This means that you cannot pass any arguments to the service being created (an exception will throw!).
FromContainer dependencies only need an interface registration with the factory, and must be registered with the dependency container beforehand:
using Wingman.Bootstrapper;
using Wingman.Container;
using Wingman.ServiceFactory;
class Bootstrapper : BootstrapperBase<IShellViewModel>
{
protected override void RegisterViewModels(IDependencyRegistrar dependencyRegistrar)
{
dependencyRegistrar.Singleton<IShellViewModel, ShellViewModel>();
}
protected override void RegisterServices(IDependencyRegistrar dependencyRegistrar)
{
// Any registration strategy applies here, Singleton is just used by default
dependencyRegistrar.Singleton<IService, ServiceImpl>();
}
protected override void RegisterFactoryServices(IServiceFactoryRegistrar serviceFactoryRegistrar)
{
serviceFactoryRegistrar.FromContainer<IService>();
}
}
The default IServiceFactoryRegistrar
will throw if the FromContainer
service hasn't been registered with the IDependencyRegistrar
!
The above registration means a use like this applies:
serviceFactory.Create<IService>();
However, this will throw:
serviceFactory.Create<IService>(argument1, argument2); // Cannot pass arguments to FromContainer registrations
The PerRequest strategy will resolve all dependencies, and then inject constructor parameters.
Take the following service:
class ServiceImpl : IService
{
public ServiceImpl(IDependency dependency, IDependency2 dependency2, string myName)
{
// blah
}
}
You can create it like this:
serviceFactory.Create<IService>("You are called ServiceImpl.");
The dependencies will be resolved automagically.
The above usage implies this kind of registration:
using Wingman.Bootstrapper;
using Wingman.Container;
using Wingman.ServiceFactory;
class Bootstrapper : BootstrapperBase<IShellViewModel>
{
protected override void RegisterViewModels(IDependencyRegistrar dependencyRegistrar)
{
dependencyRegistrar.Singleton<IShellViewModel, ShellViewModel>();
}
protected override void RegisterServices(IDependencyRegistrar dependencyRegistrar)
{
// Any registration strategy applies here, Singleton is just used by default
dependencyRegistrar.Singleton<IDependency, DependencyImpl>();
dependencyRegistrar.Singleton<IDependency2, Dependency2Impl>();
}
protected override void RegisterFactoryServices(IServiceFactoryRegistrar serviceFactoryRegistrar)
{
serviceFactoryRegistrar.PerRequest<IService, ServiceImpl>();
}
}
ServiceFactory can be used outside of WPF and Caliburn.Micro projects, however the default implementation depends on IDependencyRetriever
, which may be a slightly restrictive interface, due to its Caliburn-oriented nature.
Should you wish to use the ServiceFactory in other contexts, it will still work equally as well, however you might need custom implementations of IDependencyRetriever
for your specific dependency container (see the DependencyContainer section).
Feel free to post pull requests or issues of such usages, informing the author of ways the library can be shaped to facilitate ease of such use.