When you develop Silverlight client application you face problems of its architecture organization and interaction between separate blocks within it. This structure should be understandable. It should be set up easily during the whole application life cycle. The Prism project also knows as Composite Application Guidance provides us with a great variety of tools to build enterprise level architecture of the application.
The Prism project is available in a form of source code (license "MICROSOFT PATTERNS & PRACTICES LICENSE" weblink http://compositewpf.codeplex.com/).
Prism 2.2. (for Silverlight 4) released on May'22 is the current version. However the new Prism v4 Drop 10 version is available in beta stage. The review is based on the latest stable version of the project.
Using Prism set of tools you can create application as a group of independent modules. Each module can be expanded and changed independently. Each module can be easily tested due to often use of the Inversion of Control pattern.
Simple implementation of loader enables users to register basic services, download modules in the necessary order, set dependence of the one module on the other, and much more. User interface is based on regions where different module views are embedded in when needed. It gives more flexibility in grouping visual elements of the application. Let us quickly go through them.
Inversion of Control. Unity
The Inversion of Control (IoC) pattern is a main term for arranging application expansibility. This design pattern is a linking element of all the application blocks in Prism. One of the main forms of this pattern is Dependency Injection (DI) and Unity (DI realization used in Prism). The Unity Application Block (http://unity.codeplex.com/) project is a separate solution independent of Prism, however used in it. There is a new IoC implementation on the basis of Managed Extensibility Framework in the latest version of Prism (v4).
This design pattern helps to create objects and set their dependencies. It means that if a class depends on the other classes, then instances of these classes are automatically filled by the DI container. DI implementation based on the Unity project is used in Prism. If needed DI implementation can be changed to another one.
There are two ways of identifying object dependencies in Unity:
- Using constructor. External objects (the instances depend on them) are enumerated in object constructor. The instances can be classes, however more often they are interfaces.
- Using properties. With the help of the DependencyAttribute attribute properties of the instance are marked. The properties should be defined by DI during dependency injections.
Simplicity of testing classes created with the help of DI container is the main advantage. The class usually depends on the interface; the interfaces implemented by the classes can be easily changed for testing purposes. Set of dependencies can be easily changed and this won’t lead to a code change in the places of class instance creation.
The Service Locator pattern solves the same tasks as DI, however does it in a slightly different way. It allows classes to get access to the services without given information on who and how the services are implemented. Often it is used as an alternative to DI. There are cases when it's necessary to use this design pattern, for example, when we need to have multiple service implementations. Unity is acting as SL implementation.
The first thing to take care about when creating applications using Prism is logic of UnityBootstrapper inheritor. The UnityBootstrapper class contains logic of registration services used in Prism, modules download and initialization, region managers, etc. The UnityContainer instance (acting as a IoC implementation) is created here.
In the latest Prism versions the abstract Bootstrapper class was added. On the basic of the class it is possible to create class inheritors based on other IoC implementations. However, in the version 2.2. one can create own version of this class as the "bootstrapper" just aggregate the actions to be made while application launch. The actions can be made without using this class.
All applications on the basis of Prism consist of the independent modules set. The modules are located in separate assemblies. This way of organization gives the following advantages:
- a high level of block independencies;
- different teams can develop different blocks;
- independent development of modules;
- high application flexibility level.
Each module usually places into a separate assembly. Before all, it is necessary to create implementation for the IModule interface. In the Initialize method of module class one can register services that are used in module. Here with the help of the IRegionManager service, views (used by modules) are injected into regions. Handler methods for executing global commands can be also added.
Stages of the module launch:
- The list of modules is in IModuleCatalog and used in IModuleManager;
- IModuleManager manages modules load based on the modules description;
- After module is loaded it creates IModuleInitializer and calls for the Initialize method of the module.
One of the substantial advantages given by modules configuration is an option of separate modules download when needed. This feature is demanded by big web RIA applications.
Module organization of the application for sure does not cancel common assemblies available in most modules. They usually have application infrastructure: common controls, converters, event classes, common service interfaces, global commands, resources, etc.
IRegionManager is one more service registered in "bootstrapper". The service realizes mechanism of user interface composition. Region markings are done in shell with the help of attached features. Then based on the named locations (regions), views of the different modules are assigned to them.
Shell is usually created in the main module near the "bootstrapper" class. Elements which will be containers for module views (regions) are added in XAML.
The following classes can be the regions:
- System.Windows.Controls.ContentControl – can display only one view;
- System.Windows.Controls.ItemsControl – can display views one by one;
- System.Windows.Primitives.Selector – similar to the previous one can display multiply views.
The same situation is with the inheritors of the above mentioned classes. For example, the System.Windows.Controls.TabControl view is placed to a separate TabItem. Different elements-containers define how module views added to the region will display.
This way of region and view connection is more simple, however not always applicable. The IRegionManager service is used for such purposes. It creates new view instance by type and adds it to a region with a certain name. IRegionManager automatically manages view instance creation and its addition to the region.
This way is preferred in several cases:
- when it is necessary to programmatically manage process of addition and deletion view from the region.
- when it is necessary to add view instances of same type but with different data to the region.
- when it is necessary to define to what region instance the view has been added (in case a view is inside a region).
To do so, from the IRegionManager.Regions collection you get the IRegion region instance by name and add/delete/activate view.
Due to module application organization and its high independency level a question of transferring information between blocks appears. For example, when in the region on the left there is a list of some objects and in the region on the right there is detailed information. If views are added to the left and right regions by different modules, then one can't subscribe to the event of selected item changings on the left to change the data in the detailed information on the right. Below we describe 4 ways of transferring data between modules. Per se all they are common classes accessible for data exchange modules.
Commands are convenient when it is necessary to react on user actions (button clicks, menu item choice, etc.) and when accessibility of the action must be defined by business logic.
Prism libraries give two classes which implement the ICommand interface. The first one is the DelegateCommand class which calls for delegate method when the command is executing. The second one is the CompositeCommand class that has multiple child commands; when composite command is executed, all the child commands are executed too. Accessibility of the composite command depends on accessibility of its child commands.
Command classes, in the first place, allow executing of all the necessary actions on business logic by data binding (without subscription to the events of user interface elements).
Besides, the CompositeCommand can be used for modules interaction. To do so, global property with composite command instance (for example, Save, Load, Open, etc.) is defined as a value in class accessible for interactive modules. Then modules can register their child commands in the defined composite command and execute their methods when executing composite commands.
Important moment for Silverlight is that the static property can't be used in data binding. However this restriction can be easily lifted by creation a wrapper class with properties (not global ones) which call for global commands.
If it is needed to transfer an event between modules without getting a response back, then it is convenient to use the EventAggregator class.
This class supports both multiply places for events calling and multiply places for events processing.
To use this means one needs to create a new event class (inheritor of the CompositePresentationEvent<T>) in the common assembly. The T type defines the parameter type, which transfers when calling for event to the handler.
Implementation of IEventAggregator is registered while the “bootstrapper” is launching, that is why the instance of the class will be available during dependency injections or with the help of Service Locator. You can get an event instance in the IEventAggregator service and subscribe to the event or publish it (the Subscribe and Publish methods correspondently).
RegionContext can be used with Prism to transfer data between views that contain regions and views added to the region.
Context can be set in XAML with the help of the attached RegionManager.RegionContext property, the same as with the region name.
Another variant, you may set (as well as get) a value of the Context property in the code by receiving the IRegion instance by name from the RegionManager.Regions collection.
The IRegion instance has the PropertyChanged event that can be used for tracking value changes of the Context property.
One more variant is a usage of the static GetObservableContext method of the RegionContext class; view instance is as the method parameter. The method returns an object of the ObservableObject<object> type. Using its Value property one can get the RegionContext value for a certain view.
In case any of the above mentioned variants do not suit the requirements one can use the following mechanism. One creates a service in an assembly available for interactive modules. This service should give an event, by subscription to which, any module is able to react on data changers in the service.
Modules do not "know" about the service realization as they call for interface. Service locator at the moment of service registration "knows" about certain realization only. The realization may be in any module or in common assembly, the main thing is that this service registration is done before calling for its members.
MVVM (Model – View – ViewModel) Pattern
The process of creation Silverlight applications based on Prism does not oblige usage of any certain separated data, logic and presentation pattern. One may use MVC, MVP, MP or MVVM architecture patterns.
However, lately the Model-View-ViewModel pattern gains popularity in such development environments as WPF, Silverlight and Windows Phone 7. It takes a place, firstly, due to options and aims of separation code from XAML. XAML forms only look-and-feel and some parts of visual elements behavior. These can be done by a designer (without a developer) with the help of the Expression Blend tool.
The latest version of Prism already has a set of instances with this pattern implementation and a number of features frequently used with the pattern. The feature set will be expanded in the future.
Data display in the MVVM pattern is done due to binding to the properties of the ViewModel instance (ViewModel has no dependencies on View). Converters also play an important part as they convert data during binding.
Making changes in the model in response to user actions is done due to command bindings. Data attachment can be one-side (labels) and two-side (input field), when changes in View display in ViewModel.
All changes in Model are done by the ViewModel class that is why usually it has dependency on the Model service. Inverse process (changes in Model) is also displayed in ViewModel.
Working with Data
The Composite Application Guidance based applications development doesn’t contain many development aspects. It also doesn’t include client-server interaction process. However after you have implemented the Model service from the MVVM pattern you should create a class of this service implementation. In a simple case, data can be uploaded to hard drive in a form of file on the client side, these are usually small and highly specific applications. In most cases your application needs to call for data bases and different services. This is a wide topic and below you can read about some ways of data receiving only (read more in other articles).
Windows Communication Foundation Services (WCF services).
WCF services are main means of creation network-distributed services in Silverlight.
Briefly speaking the process of service creation is the following. To the application server part project you should add a new "WCF Service" or "Silverlight-enabled WCF Service" element that already includes correct settings to be used in Silverlight applications. Then methods provided by service are added to service class. Class should be marked by attributes for contract creation. Service methods should contain calls for data base, other services and server resources. Settings for correct service publication should be put in "web.config" if they are not there yet.
During service publication an error may occur if protocol, path or port of the service differs from the one used in Silverlight application. This is a cross domain call. To make system security allow this, one should put the “clientaccesspolicy.xml” file to the server part root where the service is.
To access the service on the client side one should create proxy-class for calling the server. Most actions on its creation are done by Visual Studio. Service Reference is added to Silverlight project on the service created by us or any other service we have access to or would like to use. Then when creating proxy-class instance we may call for service methods and get the data. Necessary to say, that Silverlight have only asynchronous calls for service.
WCF Rich Internet Application Services (WCF RIA services).
These services are in Silverlight 4 Tools. These services simplify development of data transfer from data base on server to client application. RIA services make logic on server accessible on client. They are built on WCF service base described above.
On server side data is received based on Entity Framework (EF), LINQ2SQL, NHibernate, etc. For example, to create a call on basis of EF you should add the EF model to the project. The model is generated by environment and it allows getting data from the base. Then one should add the DomainService element. This is a special WCF that allows making requests to model, updating it, checking data, etc. After DomainService is added to server, the DomainContext class is generated on client. Then when creating an instance of this type one can make requests and download data from the base.
The Composite Application Guidance development isn’t a universal panacea and can’t help in all cases. However, its tools and services accelerate application development. Modules that form it can be easily extended and tested. Settings of different stages of system work are rather flexible. When there is a task to add changings to the interface, it is solved rather simply.
One more convenience is multi-targeting support, that is development for Silverlight and WPF. Most parts of the code can be shared and changers in one project will be reflected in another one.
Project is rapidly developing and new versions appear. In the latest Prism versions a new Windows Phone 7 mobile platform library is included.
Do you like the article and want us to develop your project? Feel free to contact us here!