Over the past few weeks I’ve been exploring the concept of region management using the Managed Extensibility Framework, and for a good reason. I’m working on a project that has several different regions and controls that must be managed effectively and across the boundaries of dynamic XAP files in Silverlight 3.
Sound like a mouthful? In previous posts I demonstrated some methods for handling region management using MEF. This post is an advanced post and I’m assuming you have the fundamentals of MEF down. If not, I would come back to this post at a later time. You’ll also want to be familiar with my previous experiments detailed in this article and another version in this video tutorial.
So the ultimate goal for me is to tag a view with a type, tag a region with a type, then route views to regions and vice versa. This way I can easily add controls (just tag ’em) and regions (again, tag ’em) and have a routing table that handles swapping them in and out for me.
One of the issues in the past has been having to explicitly export the region. In other words, I might have a ContentControl
tagged for a particular region. I have to give it an x:Name
in the XAML, like this:
... <ContentControl x_Name="MyName"/> ...
And then export it with my custom attributes like this:
... [TargetRegion(Region=ViewRegion.MainRegion)] public FrameworkElement MainRegionExport { get { return MyName; } } ...
This assumes I created a custom export attribute with a Region
parameter and a base type of FrameworkElement. Where I want to go is here: eliminate the code behind, and tag my regions like this:
... <ContentControl mef:TargetRegion.Region="MainRegion"/> ...
In the XAML, with no code behind. Can we get there? Of course, this is MEF!
The first step for me is to create to a custom export provider. I spoke briefly about this in an old post: Custom Export Provider for Attached Exports, but that was for static controls. Here, I want to export regions with meta data. The export provider looks like this:
public class RegionExportProvider : ExportProvider { private static readonly Dictionary<ExportDefinition, List<Export>> _exports = new Dictionary<ExportDefinition, List<Export>>(); private static readonly RegionExportProvider _provider = new RegionExportProvider(); public static DependencyProperty RegionProperty = DependencyProperty.RegisterAttached( "Region", typeof(ViewRegion), typeof(RegionExportProvider), null); public static ViewRegion GetRegion(DependencyObject obj) { return (ViewRegion) obj.GetValue(RegionProperty); } public static void SetRegion(DependencyObject obj, ViewRegion value) { obj.SetValue(RegionProperty, value); GetRegionExportProvider().AddExport(value, obj); } private static readonly object _sync = new object(); public static RegionExportProvider GetRegionExportProvider() { return _provider; } public void AddExport(ViewRegion region, object export) { lock (_sync) { string contractName = typeof (FrameworkElement).FullName; var metadata = new Dictionary<string, object> { {"ExportTypeIdentity", typeof (FrameworkElement).FullName}, }; var found = from e in _exports where string.Compare(e.Key.ContractName, contractName, StringComparison.OrdinalIgnoreCase) == 0 select e; if (found.Count() == 0) { var definition = new ExportDefinition(contractName, metadata); Debug.WriteLine("Region Export Provider: Add custom export: " + region); _exports.Add(definition, new List<Export>()); } metadata.Add("Region", region); var wrapper = new Export(contractName, metadata, () => export); found.First().Value.Add(wrapper); } } protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) { var contractDefinition = definition as ContractBasedImportDefinition; IEnumerable<Export> retVal = Enumerable.Empty<Export>(); if (contractDefinition != null) { string contractName = contractDefinition.ContractName; Debug.WriteLine("GetExportsCore: Request for contract: " + contractName); if (!string.IsNullOrEmpty(contractName)) { var exports = from e in _exports where string.Compare(e.Key.ContractName, contractName, StringComparison.OrdinalIgnoreCase) == 0 && definition.IsConstraintSatisfiedBy(e.Key) select e.Value; if (exports.Count() > 0) { Debug.WriteLine("Export satisfied."); retVal = exports.First(); } else { Debug.WriteLine("Export was not satisfied."); } } } return retVal; } }
Let’s step through it. First, I need to keep a catalog of my exports. These are unique type definitions and contract names that we are going to satisfied with our custom export provider. I use the singleton pattern because I’m going to double this class as an attached behavior. Probably violating the single responsibility principle here, and could break it out, but let’s run with it for now. Sometimes it makes sense to keep the code together for readability and maintainability.
The attached property just takes the type of the region. Because we type it to the enumeration, intellisense will work in the XAML when adding the behavior, which is nice, because it makes it less of a “magic string” for us to use.
The key to note is the setter. When we set the value, we add the export. The export itself is going to be the contract name and the type identity. This is why we add the first bit of meta data, the ExportTypeIdentity
, and then see if we’ve already made an entry or not. If not, we add a new entry with an empty list of the actual exports.
Adding the exports is easy. Metadata on an export is really just a dictionary of labels and values. We know this particular export has one label (“Region”) and that it is the enum of the region type. Therefore, once we find or create our export definition, we go ahead and add the export with the region itself (a content control, items control, or other container) and meta data for that region (in this case, the “Region” tag and the enumeration value).
Now we just need to be prepared to supply the exports when our custom provider is queried. This is the GetExportsCore
. Fortunately, we get it easy. Instead of having to keep track of all of the rules, the import definition comes with a IsConstraintSatisfiedBy
method that calls the Constraint
provided to filter the export values. We simply find the definition that matches the contract and the constraint, then provide all of the exports we have. These will then have their meta data parsed and if a capabilities interface is provided, it will be populated with the values so that the Lazy<Type,ITypeCapabilities>
target can be populated. Notice I added some debug lines to help troubleshoot.
Now I can tag my export the way I wanted to:
... <ContentControl mef:RegionExportProvider.Region="MainRegion" .../> ...
There is only one more step I need to take in order for this all to work. Remember back when I showed you how to override the container so we could continue to compose new DeploymentCatalogs
into an AggregateCatalog
as they became available? When we create that special container, we need to add our secret sauce. We do it like this:
... var container = new CompositionContainer(catalogService.GetCatalog(), RegionExportProvider.GetRegionExportProvider()); ...
Now we’ve told the container to query our own custom export provider when trying to satisfy parts.
If you recall from my prior posts, I used a routing class to map view types to regions, and loaded the regions like this:
[ImportMany(AllowRecomposition = true)] public Lazy<FrameworkElement, ITargetRegionCapabilities>[] RegionImports { get; set; }
Our new provider kindly exports the tagged regions, which are then imported with the routing class and used to swap views in and out of the display.
Secure from design through deployment.
Our business is built on ensuring customer environments are secure from design through deployment and to provide solutions which are realistically implementable. We provide managed Information Security (InfoSec) and compliance options across the entire computing stack, from connectivity to applications, with stringent physical and logical security controls.
Define your objectives.
We partner with your team to define the right security profile for your business needs.
Build your foundation.
Together we develop comprehensive best practices encompassing all aspects of security: people, process and technology.
Execute your roadmap.
We collaborate to build and evolve an information security program that bolsters your security profile.
More than technology
All Atmosera customers benefit from processes, policies, training and 24x7x365 technical resources.
Our customers have the peace of mind of knowing an industry expert has performed a thorough risk assessment, identified a remediation plan and provided ongoing audit support to ensure customers stay secure.
Best of all, we understands this level of service is — and will continue to be — required year after year.
We always implement networks which
deliver better security and usability.
All our deployments take into consideration the end-to-end solution and we secure all aspects of the network and connectivity.
- Defense in Depth – Our approach offers a flexible and customizable set of capabilities based on enterprise-grade standards for performance, availability, and response.
- WANs, MANs, LANs, and VPNs – We provide comprehensive networking and high-speed connectivity options from Wide Area Networks (WAN), Metropolitan Area Networks (MAN) and Local Area Networks (LAN), as well as managed Virtual Private Networks (VPNs) and firewalls.
- Stay current – We can be counted on to deliver proactive testing and the most secure network protocols.
- Reduce vulnerabilities – We help your team leverage advanced security features coupled with relentless vigilance from trained experts.