ClientUI 7 Preview Part 2: View Discovery and View Injection with Region Manager
In my previous post, I’ve discussed about dependency injection and IoC container, and how the upcoming ClientUI release makes it easy for developers to build good quality software. Today I’ll continue to explore the new exciting framework-related features so you can preview these features and try them in your apps.
In this blog post, I’ll particularly focus on the challenges in view extensibility, and discuss the architectural patterns that can be applied to address these challenges.
Understanding View Extensibility
First, let’s get on the same page for the view extensibility definition. Assume that you have an application that shows a list of quick tasks such as illustrated below.
Initially, your application might contain only a few modules such as Customers, Products, and Reports – then define them in the navigation list such as shown above. As you added more modules, you’d have to revisit and modify the navigation list, add the new links and so forth. There are no extensibility points.
In the above case, the ability to add the navigation links when new modules become available without modifying the original codebase is the essence of view extensibility. As you’re becoming more familiar with extensible application development, you’ll realize the real benefits in the way it accelerates your development and minimizes code changes. Not only will your application become easier to maintain and extend, you’ll find yourself building software more fun and enjoyable.
There are two well-known patterns to achieve view extensibility called View Injection and View Discovery.
View injection is a pattern that dynamically inject an element to the target at runtime. The view injection is usually done manually by the controller at a certain event, such as page load. In contrast to view injection, the view discovery pattern performs the injection automatically when the element becomes available in the application life cycle. View discovery is usually implemented with metadata (also known as attribute in .NET) to allow the view to be discovered at runtime. More details on the patterns implementation are discussed in the section below.
Both patterns are commonly implemented with so called Region Manager, which acts as the controller that reside in the application’s shell. A region manager can consist of multiple regions which represent the target placeholder for injection. A well-known library that already provide region manager functionality is Prism from Microsoft, which does the job well but with several limitations.
At a glance, the following illustration shows how the view injection/discovery works together with region manager.
Page-aware Region Manager
As I mentioned earlier, Prism provides several functionality related to region manager. I won’t discuss Prism’s region manager concept in details here, but would like to point out the general principles and overview its differences with ClientUI’s region manager.
Prism’s region manager is built around the concept that centralizes on shell (also known as Root View). This means that you will normally define the region manager in the shell, and define the regions within the shell as well. See the following picture for more details.
While this single region manager concept works well in common scenarios, it falls short on certain scenarios such as multi-page applications. You cannot define the regions within the shell in multi-page application, because the view/content is defined in separate pages, not in the shell. There might be several ways to workaround this using the existing library but that would be less-than-ideal due to the extensive additional code.
With multi-page applications in mind, we built our own region manager to facilitate view extensibility supporting both view injection and view discovery methods. The page-aware region manager is perhaps the most interesting feature in ClientUI’s view extensibility design.
It enables you to simply define the region manager and the associated regions in the pages without needing to know when the regions need to be instantiated or activated. When the page is activated (either through browser journal navigation or navigation link), the defined regions in the page will be automatically filled with the injected view content. This allows view extensibility to be achieved with loose coupling approach.
The following illustration shows the region manager concept implemented in ClientUI.
With page-aware design, our goal is to make modular application development easier and more maintainable. It allows you to simply define the region scope and the regions in each page without concerning when to instantiate or disposing the regions – the framework does it all for you behind the scene.
Implementing View Injection
Now that you’ve got the idea what region manager is about, let’s go further and see how to implement the view injection in ClientUI.
First of all, you define the region manager and regions to the containers that will participate in the view extensibility. The containers can be a simple content control, items control, tab controls, window controls – or any control types that serve as containers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<Intersoft:UXPage <strong>Intersoft:RegionManager.IsRegionScope</strong>="True" <strong>Intersoft:RegionManager.ScopeName</strong>="Home"> ... <Intersoft:GroupBox Header="Quick Tasks"> <Intersoft:UXItemsControl <strong>Intersoft:Region.RegionName</strong>="QuickTasks"> <Intersoft:UXHyperlinkButton Content="View Top Customers"/> <Intersoft:UXHyperlinkButton Content="Manage Products"/> ... </Intersoft:UXItemsControl> </Intersoft:GroupBox> </Intersoft:UXPage> |
The example above shows the region manager and scope definition in the UXPage, followed with the region definition for the target container which is the UXItemsControl in this example.
Once the regions are defined, you can now access to these regions using the region manager API. This allows you to instantiate the view element and inject it to the target region from either code behind or ViewModel. Often times, you might want to do the injection in the bootstrapper where the application module is first loaded. Here’s an example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class AppInitializer : IApplicationInitializer { #region IApplicationInitializer Members public void Initialize(ApplicationPackage package) { // add new links to the Quick Tasks region on-demand ("view injection") when this XAP is loaded RegionManager manager = RegionManagerService.FindRegionManager("Home") as RegionManager; manager.AddViewToRegion("QuickTasks", new UXHyperlinkButton() { Content = "New Link 1", Foreground = new SolidColorBrush(Colors.Red) }); manager.AddViewToRegion("QuickTasks", new UXHyperlinkButton() { Content = "New Link 2", Foreground = new SolidColorBrush(Colors.Red) }); } #endregion } |
As the results, when the XAP is loaded, notice that the new hyperlinks will be added to the Quick Tasks control which can be seen in the following illustration.
Implementing View Discovery
Similar to the view injection, you always need to define the region manager and regions in the views that will be extended.
The difference with the view injection is that you don’t write code to inject the element to the target region. Instead, you simply add decorator attributes to the class that will be injected to the target region. When the target region is loaded, it will discover all views that should be injected based on the metadata, hence the injection is processed seamlessly and automatically.
Here’s an example of the view discovery implementation.
1 2 3 4 5 6 7 8 |
<strong>[ViewPart("Home", "Overview")]</strong> public partial class Overview : UserControl { public Overview() { InitializeComponent(); } } |
The ViewPart attribute above basically states that the Overview class should participate in the view discovery process which target the Home region scope and Overview region.
And here’s the screenshot that shows the application before and after the view is discovered.
Comparing both view extensibility approaches, it’s obvious that the view discovery is the easiest and most efficient way to achieve the view extensibility goal. I’d recommend to use view discovery pattern whenever possible, unless you need to inject the view based on certain conditions or complex business logics.
More Advanced Features
Of course, we’ve only seen the basics of the view extensibility so far. The region manager in ClientUI also provides many advanced features that I couldn’t share in this single post alone. These advanced features include:
- Custom region adapter which enables you to extend the region manager service to your own containers
- Selection control support
- View activation support
- Integration with Navigation Framework
Download Sample Code
At this point, you have learnt the basics of view extensibility and how you can implement it using the API available in the next version of ClientUI. The view extensibility can be very useful if you build applications that allow third-party developers to create add-ons on top of your standard functionality. For an instance, consider a standard customer form shown in the screenshot below.
With view extensibility, third-party developers can extend the existing form by adding new view elements without changing or even recompiling the original apps, see the below illustration.
To preview these new exciting features, we’ve created a complete SDK sample that demonstrates the view injection and view discovery implementation using ClientUI preview builds. The sample also demonstrates how IoC container can be used along with region manager to facilitate really powerful extension capability to your apps. You can download the reference sample here.
In the next series of my blog post, I will do some previews on the upcoming new UI controls and top-requested enhancements. Stay tuned!
Best,
Jimmy
Comments