Exercise 3: Switch to a DI/IoC container and inject our dependency into our code. (XAM250)
This final exercise will replace the Factory pattern used in the first exercise with a Dependency Injection (DI) container that will supply the quote loader as part of the constructor. You'll then optionally, remove the Service Locator as well and just use the DI/IoC container injection approach.
To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.
Open the starter solution
Open the starter solution from the Exercise 3 > Start folder in your copy of the cloned or downloaded course materials in either Visual Studio on Windows or Visual Studio for Mac.
Adding the DI/IoC container
-
In the Assets folder under Exercise 3 you will find a
SimpleContainer
class. This is a very simple implementation of an IoC container that allows you to register and create types. Add it into your PCL.You'll use this as an example - but keep in mind that it is deliberately simplified so you can see the concepts involved and trace through the code if you like. For non-sample applications you should consider a real IoC container.
-
Examine the code; it has two methods (with two variations on each):
Method Description Register
This is used to register known abstractions with the container. You register a creator function with the abstraction type so it may be created, or just pass the implementation type if it has a default constructor. It allows the container to resolve interfaces to types similar to a Service Locator. You could also use a Service Locator for this purpose if you wanted to. FactoryFor<T>
This method is used to create a factory method which will create the T type. It returns a Func<T>
where the implementation goes through the container to generate the type. This can be used to create factory methods so consumers do not have to have a reference to the container itself.Create
This method is used to create types. If the type is a registered type, then the associated registration method is used. Otherwise, if the type has a public, default constructor, then it will be used to create the object. If the type does not, then the code will pick the first public constructor and attempt to create each of the required parameters by recursively calling Create
and then finally construct the final object passing each of the parameters. If it is unable to create the given type, anull
is returned.
Modify the Quote Manager to take constructor parameters
Next, change the code in the QuoteManager
class to inject the IQuoteLoader
dependency as a constructor parameter.
- Open the
QuoteManager
and add a new parameter to the constructor of typeIQuoteLoader
. Assign the passed parameter to a field. Make sure to remove the existing code that uses the Factory approach. - Change the constructor to be
public
so you can create it externally. -
Change the static
Instance
property to have a private setter and remove the existingLazy
field. Instead, assign theInstance
property in the constructor - make sure to add a check to ensure it's not set more than once to enforce the singleton pattern being used.public class QuoteManager { public static QuoteManager Instance { get; private set; } readonly IQuoteLoader loader; public IList<GreatQuote> Quotes { get; private set; } public QuoteManager(IQuoteLoader loader) { if (Instance != null) { throw new Exception("Can only create a single QuoteManager."); } Instance = this; this.loader = loader; Quotes = new ObservableCollection<GreatQuote>(loader.Load()); } ... }
Setup the DI container
Finally, you need to create the container in each of your platform-specific projects and register the IQuoteLoader
implementation with it. Then you'll use the container to create our QuoteManager
so that it properly "injects" in the constructor parameters required. You'll repeat the same basic steps in each project (iOS, Android and Windows).
- Open the file which sets up the factory used to locate the
IQuoteLoader
implementation. In iOS, this is AppDelegate.cs, Android usesApp.cs
and Windows has App.xaml.cs. - Add a new
SimpleContainer
field to the application-level class and new up an instance of the container. - Remove the current factory registration code - this is in
FinishedLaunching
in iOS,OnCreate
in Android and in theApp
constructor in Windows. - Next, register the
IQuoteLoader
abstraction with the proper platform-specific implementations using the container'sRegister<T, TImpl>
method. - Finally, create the
QuoteManager
instance with the container'sCreate
method and assign it to the existing field you are using now. - Run the application on one of the supported platforms and make sure it all still works - try putting a breakpoint into the
QuoteManager
constructor to see the injected parameter - look at the call stack to see how you got it.
iOS code
// AppDelegate.cs
public partial class AppDelegate : UIApplicationDelegate
{
readonly SimpleContainer container = new SimpleContainer();
public override UIWindow Window { get; set; }
public override void FinishedLaunching(UIApplication application)
{
//QuoteLoaderFactory.Create = () => new QuoteLoader();
container.Register<IQuoteLoader, QuoteLoader>();
container.Create<QuoteManager>();
}
...
}
Android code
// App.cs
public class App : Application
{
readonly SimpleContainer container = new SimpleContainer();
...
public override void OnCreate()
{
//QuoteLoaderFactory.Create = () => new QuoteLoader();
container.Register<IQuoteLoader, QuoteLoader>();
container.Create<QuoteManager>();
ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();
base.OnCreate();
}
}
Windows code
// App.xaml.cs
sealed partial class App : Application
{
readonly SimpleContainer container = new SimpleContainer();
public App()
{
//QuoteLoaderFactory.Create = () => new QuoteLoader();
container.Register<IQuoteLoader, QuoteLoader>();
container.Create<QuoteManager>();
ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();
this.InitializeComponent();
this.Suspending += OnSuspending;
}
...
}
Extra challenge: Refactor
The container is being used to create the QuoteManager
with a passed IQuoteLoader
to load the quotes. The QuoteManager
also uses the ITextToSpeech
implementation which is located through the Service Locator you added in the last lab. What if you wanted to remove this dependency and instead have it injected as well? There is a final Challenge completed solution in the Exercise 3 folder of your copy of the cloned or downloaded course materials.
Exercise summary
In this exercise, you've taken an existing application and utilized a DI/IoC container to loosely couple dependencies together.
You can view the completed solution in the Exercise 3 > Completed folder of your copy of the cloned or downloaded course materials.