In my last post I introduced the concept of application and session state within a Windows Phone 7 application, and described how the Chrysalis framework makes it easy to share state between multiple pages (and hence view models) within an application, including full tombstoning support. As usual, all the source code is available on the Chrysalis CodePlex site under the “Source Code” tab.
Application Services
To recap, Chrysalis allows multiple view models to share the same state between multiple pages by using one of the two ChrysalisService.GetService methods,
1: public class ChrysalisService
2: {
3: ...
4:
5: public T GetService<T>() { ... }
6: public object GetService(Type serviceType) { ... }
7:
8: ...
9: }
The first time one of these methods is called, a new instance of the specified type is created (calling the default parameter-less constructor). Any subsequent calls will return the same “singleton” instance. As the method names allude to, this can be used not only for general state, but for any services that may wish to be shared throughout the application.
Furthermore, Chrysalis makes it simple for these services to persist state when the application is tombstoned. All that is required is for the service to implement the IHasSessionState interface,
1: public interface IHasSessionState
2: {
3: // *** Methods ***
4:
5: void Initialize(object state);
6: object SaveState();
7: }
The first time the service is created within a session the Initialize method is called with the state parameter set to null. In this instance default values can be set along with any other initialization required by the service. Upon tombstoning the SaveState method is called from which you can return any state you wish to persist. Upon reactivation this state is passed back to the newly created instance of the service (remember – during tombstoning the application process is killed and all objects are lost) via the Initialize method.
Chrysalis and Dependency Injection
Whilst the ChrysalisService class provides the GetService methods, this is only one way in which your view models can obtain such state information. Another method is “dependency injection”. This refers to a situation where external dependencies (in our case the state objects) are injected into our view model, rather than the view model itself having to seek them out.
In Chrysalis this is done by defining public properties for each of the services that we wish to inject into the view model. To indicate that these should be injected we add the [ChrysalisImport] attribute to the property. Finally, in the constructor we check whether Chrysalis is initialised and tell the ChrysalisService to satisfy any imports by calling the SatifyImports(…) method.
For example within the sample CalculatorViewModel included with the source download,
1: public class CalculatorViewModel : ViewModelBase, IHasSessionState
2: {
3: // *** Constructors ***
4:
5: public CalculatorViewModel()
6: {
7: if (ChrysalisService.Current != null)
8: ChrysalisService.Current.SatisfyImports(this);
9: }
10:
11: // *** Properties ***
12:
13: ...
14:
15: [ChrysalisImport]
16: public CalculatorSessionState SessionState
17: {
18: get;
19: set;
20: }
21:
22: ...
23: }
This defines a property called SessionState that is marked with the [ChrysalisImport] attribute. In the constructor we notify Chrysalis that it should inject any dependencies, which when it returns will have set the property to an instance of CalculatorSessionState. As with the GetService calls this will return the same instance across all view models, creating a new object only if this is the first such reference. Note that you can define any number of properties to be injected and they will all be satisfied by the one SatisfyImports(…) call.
Using Interfaces for Dependency Injection
Whilst being able to declaratively inject singleton instances of classes is useful, there are a number of reasons that you would wish to decouple such properties from the concrete implementations – for example to inject mock implementations for testing purposes. This is usually done by specifying an interface rather than a class type, and this too is enabled in the Chrysalis framework.
For example in the ChoosersViewModel sample included in the source code,
1: [ChrysalisImport]
2: public IChooserExecutionService ChooserExecutionService
3: {
4: get;
5: set;
6: }
However, we now need a way to link the interface to the concrete implementation. In the Chrysalis framework this is done by simply attributing the interface with the ExportImplementationAttribute specifying the concrete type to use.
1: [ExportImplementationAttribute(typeof(ChooserExecutionService))]
2: public interface IChooserExecutionService
3: {
4: ...
5: }
Comparing Chrysalis with Other Dependency Injection Frameworks
Before I conclude I would like to take a moment to contrast dependency injection in the Chrysalis framework to other implementations. Dependency injection within Chrysalis was designed to support the very basic injection of services (to enable unit testing, etc.) and integration with the rest of the Chrysalis model – this it performs well. If you require more advanced services then it is recommended that you use a full dependency injection container implementation.
No comments:
Post a Comment