Wednesday, July 27, 2011

USE OF PRISM IN ARCGIS SILVERLIGHT APPLICATIONS - PART II

Adding Modules to the application

Part I - Starting a new application with PRISM

In this part, we will add a second module to the application. As example module we will add an ArcGis Legend component included in the TocModule. The main pattern we will have to solve is the synchronization between the MapModule and the TocModule concerning the ArcGis Map control. There is no longer the possibility to link different ArcGis controls within the same view with xaml properties. The map control and legend control are located in different xap files.
 In Prism there are different ways to communicate between modules. The method I choose was the use of the EventAggregate method, it is based on the principle of publish and subscribe. In our case the MapModule will be the publisher and the TocModule the subscriber. In this way all modules that need a reference to the Map control can use this communication pattern. As the Map control is central to all ArcGis processing, a lot of modules will need to subscribe to map changes.
Using EventAggregate methods we will respect the loosely coupling of modules. The use of filtering is not used here. When needed, the subscriber can write a method to limit the number of published events to be processed.
Start a new project similar as we did for the MapModule and name it TocModule. Mark the application as host by the existing web configuration. Uncheck ‘Add a test page that references the application” as we do not need this to be a startup xap file.



Delete the app.xaml and mainpage.xaml and create a new view base on the Silverlight UserControl template. Add the ArcGis Legend control to the page as outlined below.

<UserControl x:Class="MyToc.Views.TocView"
   xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:bind="clr-namespace:Helper.RelativeBinding;assembly=Helper"
   mc:Ignorable="d"
   d:DesignHeight="300" d:DesignWidth="250">
   <Grid x:Name="LayoutRoot" Background="White">
     <StackPanel VerticalAlignment="Top">
     <esri:Legend x:Name="LayerLegend" LayerItemsMode="Tree" ShowOnlyVisibleLayers="False"
        Map="{Binding MapView}" Margin="0" Height="400">
     <esri:Legend.MapLayerTemplate>
       <DataTemplate>
        <StackPanel>
          <CheckBox Content="{Binding Label}"
                    IsChecked="{Binding IsEnabled,Mode=TwoWay}"
                    IsEnabled="{Binding IsInScaleRange}">
           </CheckBox>
         </StackPanel>
         </DataTemplate>
       </esri:Legend.MapLayerTemplate>
       <esri:Legend.LayerTemplate>
       <DataTemplate>
          <CheckBox Content="{Binding Label}"
              IsChecked="{Binding IsEnabled,Mode=TwoWay}"
              IsEnabled="{Binding IsInScaleRange}">
          </CheckBox>
       </DataTemplate>
     </esri:Legend.LayerTemplate>
   </esri:Legend>
  </StackPanel>
</Grid>
</UserControl>

Create the module class TocModule implementing the IModule interface. Don’t forget to export the module to the MEF container by adding the needed attribute to the class.

Create a ViewModel class with at least the MapView defined that will expose the map control.

[Export(typeof(TocViewModel))]
public class TocViewModel : NotificationObject
{
    private Map _map;
    public Map MapView
   {
      get { return _map; }
      set
      {
         _map = value;
         this.RaisePropertyChanged(() => this.MapView);
      }
    }
}
 To have the new module to be incorporated in the application we still need to do two things:
  • Add the module to our xaml catalog of modules.
<Modularity:ModuleInfoGroup InitializationMode="WhenAvailable">
   <Modularity:ModuleInfo Ref="MyMap.xap" ModuleName="MapModule"/>
</Modularity:ModuleInfoGroup>
<Modularity:ModuleInfoGroup InitializationMode="WhenAvailable">
   <Modularity:ModuleInfo Ref="MyToc.xap" ModuleName="TocModule"/>
</Modularity:ModuleInfoGroup>

  • Define a region where we will put this legend. Use the Initialize method from the IModule for the region registration.
public void Initialize()
{
     this._regionManager.RegisterViewWithRegion("Region2", typeof(TocView));
}

This results is the layout below:


Region 2 contains the legend module.

First we will add to the MapModule the possibility to publish the changes in the Map control.

To be able to communicate information to a subscriber, we will add a new class to our common Helper project. This class will be used to exchange information to all subscribers.

Add a class MapInfo:

public class MapData
{
   public string BaseMap { get; set; }
   public Map MapView { get; set; }
}
With this class, you now can publish this information. In the ViewModel of the MapModule we will add a reference to the EventAggregator of the container.

[ImportingConstructor]
public MapViewModel(IEventAggregator eventAggregator, IGisOperations gisOperations)
{
   mapDataEvent = eventAggregator.GetEvent<CompositePresentationEvent<MapData>>();
   this._gisOperations = gisOperations;
}

You can call the publish method at different location. Here I opt to do it when the initial extent has been set after the map has been configured.

public void SetInitialExtent(double xMin, double yMin, double xMax, double yMax, int wkid)
{
   SpatialReference sref = new SpatialReference(wkid);
   _initialExtent = new Envelope(xMin, yMin, xMax, yMax);
   _initialExtent.SpatialReference = sref;
   this.wkid = wkid;
   this.RaisePropertyChanged(() => this.Layers);
   this.RaisePropertyChanged(() => this.InitialExtent);
   PublishMapChange();
}

private void PublishMapChange()
{
   MapData mapData = new MapData();
   mapData.MapView = mapView.map;
   _gisOperations.SetMap(mapView.map,baseMap);
   // Broadcast changes
   mapDataEvent.Publish(mapData);
}

Next we will need to handle the subscription to the changes exposed by means of the EventAggregator in the TocModule.

As with the MapModule, we will initialize the subscription in the ViewModel of the TocModule. The most logical place to do it is in the constructor of the module. Using dependency injection we will make the EventAggregator available.

[ImportingConstructor]public TocViewModel(IEventAggregator eventAggregator)
{
   if (eventAggregator != null)
   eventAggregator.GetEvent<CompositePresentationEvent<MapData>>().Subscribe
    (OnMapChanged);
}
The real processing has to done on the handling of the event when something has been changed in. This will be called every time a publishing of the above event happens.
public void OnMapChanged(MapData mapData)
{
   if (mapData != null)
      this.MapView = mapData.MapView;
}

By setting a new value to MapView, a property changed event will occur modifying the map property of the legend control and forcing a refresh of the legend.

Often you will have more to do than simply set a property to set the map control. In the case of the legend control, it could be useful to collapse the legend except for one or several layers or hide the graphic layer in the legend.

This is all we have to do create a communication between two modules that respects the loosely coupling between modules.

The same pattern can be used to create modules based on other ArcGis controls as scalebar, overview map or in case you create your own custom controls.

Monday, July 25, 2011

USE OF PRISM AND MVVM IN ARCGIS SILVERLIGHT APPLICATIONS. PART I

START A SILVERLIGHT APPLICATION

Introduction

With the disappearance of the Web ADF for ArcGis in the long term, the company I worked for was looking for a new architecture that can be used to replace the current Web ADF development. The ESRI Silverlight API is one of the possible solutions. However what most development teams are looking for is a flexible architecture for building application like Web ADF and in the mean time a solid architecture.  Not often I found a certain lack of software quality in ArcGis development. In many developments focus was on the ArcGis development framework, which in itself is huge and not in maintainable development.
In the coming publications I will explain a roadmap how we can achieve the flexible development goals and even create a more modern approach to develop applications.
After some research I came to the PRISM framework for Silverlight development.  It is well known that for Silverlight there is a design & practice pattern MVVM to create applications. However today there is a strong push for the use of containers to create more loosely coupled components.
Some time ago a Microsoft Team developed a framework PRISM for Silverlight/WCF using container technology to make the life easer for developers of Silverlight applications. In the coming sections I will describe how we can use this framework to develop ArcGis Silverlight applications. The benefits of using this kind of framework are:
·         Modular concept. Applications are split into independent components (modules).
·         Separation of UI and business logic. Application development can be done by two separate teams: UI Designers and Developers. In Microsoft terms we speak of ‘Blendable’ development, pointing to the use of Microsoft Blend.
By using PRISM some assumptions are needed to be made, in this study I used the folowing architecture:
  • Container library: We will use MEF as the container library. You can use other libraries, but we will use MEF here because it is embedded in .NET 4.0 and use attributes to pass information to the framework, eliminating to do the coding for interfacing with the PRISM framework.
  • Log library, custom implementation of ILogFacade.

Prerequisites

You must have installed Microsoft .NET 4.0 framework in order to use PRISM 4.0 together with Silverlight 4.0.
You also need Visual Studio 2010 professional or higher in order to use the code; I am not sure all can be done with Visual Studio 2010 Express.
For Silverlight applications the Microsoft Silverlight 4 Tools for Visual Studio 2010 are needed.
You need to install the PRISM 4.0 library that you can find at the location http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=4922

Additional it is useful to install the Nuget Package Manager plug-in. You can use this to get the references imported of the PRISM assembly’s when they are required during design.
  

Create PRISM Application


To start our first PRISM driven application, we will create a simple Silverlight application based on the template you will find in Silverlight in Visual Studio 2010.

For use later, check the Enable WCF RIA Services.  Make sure Silverlight 4 is selected.


We now have a blank Silverlight application that we will adapt for PRISM 4.0 and add ArcGis functionality to it.

Rename the page MainPage.xaml to Shell.xaml as this is the standard name used for the applications or you can delete MainPage.xaml and create an new Silverlight UserControl named Shell.xaml

Using PRISM requires no longer using a XAML file as startup but rather a bootstrapper. The bootstrapper will contain all the pluming needed for the PRISM framework to operate within out Silverlight application.

The final step in the bootstrapper will be the launch of the startup XAML, in our case the Shell.xaml.

Add PRISM assembly’s references to the project. You can use Nuget to import the references from the PRISM package and the MEF extensions.

Create a new Class MyBootstrapper derived from MefBootstrapper.

We need to implement the following methods:
protected override void ConfigureAggregateCatalog()
{
  base.ConfigureAggregateCatalog();
  this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(MyBootstrapper).Assembly));
}

protected override DependencyObject CreateShell()
{
  return this.Container.GetExportedValue<Shell>();
}

protected override void InitializeShell()
{
  base.InitializeShell();
  Application.Current.RootVisual = (UIElement)this.Shell;
}

private const string ModuleCatalogUri = “/Demoapplication;component/ModulesCatalog.xaml”

protected override IModuleCatalog CreateModuleCatalog()
{
  Uri uri = new Uri(ModuleCatalogUri, UriKind.Relative);
  Microsoft.Practices.Prism.Modularity.ModuleCatalog.CreateFromXaml(
          uri);
  return;
}
Here we will use a resource xaml file ModulesCatalog.xaml that contains all the modules we will use. Doing so, we can easy add or remove components to the application. Let’s say we have an overview module we can add or remove this from our application depending if it is needed or not.

An example of a resource file is

<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
  <Modularity:ModuleInfoGroup Ref="MyMap.xap" InitializationMode="WhenAvailable">
     <Modularity:ModuleInfo ModuleName="MapModule"/>
  </Modularity:ModuleInfoGroup>
</Modularity:ModuleCatalog>

Modify the startup of the application in the App.cs to point to our bootstrapper:

public App()
{
   MyBootstrapper bootstrapper = new MyBootstrapper();
   bootstrapper.Run();
}

Create Modules


Before we can run our application, let’s add a module. As almost all ArcGis application will need a map, we will start with the implementation of a Map module.


To create a module, we will add a new project to the solution. We will choose a Silverlight Application from the project templates of Silverlight in Visual Studio 2010.

Make sure to uncheck ‘Add a test page that references the application’ as we do not need another startup page.


A blank Silverlight application is now generated. Delete the App.xaml and MainPage.xaml as we will do not need these files.

Create the MVVM project structure as we will use the MVVM design & practice pattern.

And finally we will create a class that will be the module.

The project should contain folders Model , ModelView and View.

Add the different PRISM references; make sure to mark these references ‘Copy Local’ as false.

Create a module MapModule that implements the IModule interface of the PRISM framework. You need to implement the Initialize method.

Add also the ModuleExport attribute to make it available in the catalog of modules. The MefContainer use attributes to do the registration, in this way you don’t need to registration by code.

Below is an example of the module.

[ModuleExport(typeof(MapModule))]
public class MapModule : IModule
{
  [Import]
  public IRegionManager RegionManager { private get; set; }

  public void Initialize()
  {
    this.RegionManager.RegisterViewWithRegion("Region1", typeof(MapView));
  }
}

By using Dependency injection we can access the RegionManager component to register the view in a region defined in the Shell.xaml

In the folder View we create a Silverlight control MapView that will embed the ArcGis Map component.

Create an empty UserControl.

In the code behind add an export attribute to make it available in the MapModule for the registration of the region.

[Export(typeof(MapView))]

Add in the Shell.xaml the region namespace and the definition.

xmlns:Regions="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"

<ItemsControl x:Name="RegionMap" Regions:RegionManager.RegionName="Region1" />

You can now compile and run the application. The result should be an empty page as for now we do not have initialized the ArcGis map component with an ArcGis MapService.

Add a ArcGis Map to the Application


We will now modify the previous application so that we can add a MapService to the application in the predefined region. Implementation will respect the MVVM design & practice pattern.

First, start with the implementation of the ViewModel that we will need. The ViewModel must be derived from NotificationObject of the PRISM assembly.  This replace the INotifyPropertyChanged interface of the MVVM architecture resulting in less code needed to raise the changed event.

Using a Lambda expression eliminates the use of hardcoded property names.

Simply add some properties needed for the initialization of the Map.

[Export(typeof(MapViewModel))]
public class MapViewModel : NotificationObject
{
  public Map MapView { get; set; }


  private Envelope _initialExtent;
  private Envelope _initialExtent;

  public Envelope InitialExtent
  {
    get { return _initialExtent; }
    set {
    _initialExtent = value;
    }
  }

  private LayerCollection _layers;
  public LayerCollection Layers
  {
   get { return _layers; }
   set
   {
    _layers = value;
    this.RaisePropertyChanged(() => this.Layers);
   }
  }
  public ObservableCollection<string> LayerIds;
}


To make the ViewModel available in the View we can export the class so that it is available in the container. Using Dependency Injection this will be available in the view during the initialization process of the container.

Add export attribute to the ViewModel.

[Export(typeof(MapViewModel))]
public class MapViewModel : NotificationObject


Import the ViewModel in the MapModule :

[Export(typeof(MapView))]
public partial class MapView : UserControl
{
  public MapView()
  {
   InitializeComponent();
  }
  [Import]
  public MapViewModel mapViewModel
  {
   set { this.DataContext = value; }
  }
}


For building the list of layers we use a Model class LayerModel to retrieve the map information needed in the ViewModel.

public static class LayerModel
{
  public static List<string> BaseServiceNames()
  {
   List<string> names = new List<string>();
   names.Add  ("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer");
   return names;
  }
  public static Envelope BaseExtent()
  {
   SpatialReference sref = new SpatialReference(3857);
   Envelope extent = new Envelope(444000, 6636000, 446000, 6638999);
   extent.SpatialReference = sref;
   return extent;
  }
}

This way of working is purely for demonstration purpose. In reality the layers must come from some kind of configuration data. This will be illustrated later.

In the constructor of the ViewModel of MapViewModel we call the Model to retrieve the map information.

_layers = new LayerCollection();
List<string> layerNames = Model.LayerModel.BaseServiceNames();
for (int i = 0; i < layerNames.Count; i++)
{
  ArcGISDynamicMapServiceLayer mapLayer = new ArcGISDynamicMapServiceLayer();
  mapLayer.Url = layerNames[i];
  mapLayer.DisableClientCaching = true;
  _layers.Add(mapLayer);
  Envelope baseExtent = Model.LayerModel.BaseExtent();
  _extent = baseExtent;
}

this.RaisePropertyChanged(() => this.Layers);
this.RaisePropertyChanged(() => this.Extent);

Use a Lambda expression in the RaisePropertyChanged method of the NotificationObject to notify the View of the modification.

In the ESRI Silverlight API in the map control not all properties can be bind to the ViewModel. This is because they are not derived from the dependency property component.

One of these properties is the Map.Extent of the Map control.

To solve this issue, you can add an attached property to replace the extent. This attached property will then be responsible to change the extent of the map.

Create a Helper folder to group all utility classes.

In the helper folder we can implement a MapExtent property:

public class MapViewHelper
{
  public static readonly DependencyProperty MapExtentProperty =
   DependencyProperty.RegisterAttached("MapExtent",typeof(Envelope),
   typeof(MapViewHelper),new PropertyMetadata(new PropertyChangedCallback

   (OnMapExtentChanged)));

  public static Envelope GetMapExtent(DependencyObject depObject)
  {
   return (Envelope)depObject.GetValue(MapExtentProperty);
  }

  public static void SetMapExtent(DependencyObject depObject,Envelope value )
  {
   depObject.SetValue(MapExtentProperty, value);
  }

  private static void OnMapExtentChanged(DependencyObject depObject,
   DependencyPropertyChangedEventArgs e)
  {
   Map map = depObject as Map;
   if (map == null)
   {
     throw new ArgumentException(
      "DependencyObject must be of type ESRI.ArcGis.client.Map");
   }
   Envelope newExtent = GetMapExtent(map);
   if (map.Extent == null)
    map.Extent = newExtent;
   else
    map.ZoomTo(newExtent);
  }
}


In the xaml view add the property to the ESRI Map component:

xmlns:extra="clr-namespace:MyMap.Helper"

<esri:Map Background="White" HorizontalAlignment="Stretch"
x:Name="map" VerticalAlignment="Stretch" Margin="0,0,0,12"
Width="800" Height="500" Layers="{Binding Path=Layers}"
extra:MapViewHelper.MapExtent="{Binding Path=InitialExtent}">

You now have a two way binding for the extent of the Map control.

Compile and run the application. You now have a map displayed at a certain location.

Add Configuration Service


As for now we have used fixed values to initialize the Map control. In the real world almost all applications have a configuration file to make the application customizable. In Silverlight you cannot use configuration files like .ini files or .config files due to security restrictions.

A possible configuration service can be implemented in the following way:

  • Implement a WCF service that retrieves a configuration file based on the application. You can use a database to make it flexible.
  • Create a class configuration, implementing a configuration interface.
  • In the bootstrapper create an instance of the configuration class.
  • Expose the configuration interface through dependency injection.
In the example below we will retrieve the MapService layer from the configuration data that will be retrieved from the web service.

To consume the configuration information we can use the interface that has been injected in the ViewModel.

The major problem that has to been tackled is the fact that WCF services in Silverlight is asynchrony and that you can only handle the map control initialization after the WCF services has terminated. So we have to find some way to know when the call to the WCF service has terminated, and then initialize the map control with the configuration content.

To handle this issue, the following solution I used:

  • Create a separate project with the IConfiguration interface having the methods we need. In particular I need the method for reading the full configuration. I use a Helper project that contains interfaces and classes available for the whole solution. 
public interface IConfiguration
{
  void GetConfiguration();
  object Setting(string key);
  void SetFinishedEvent(EventHandler finishedFillConfiguration);
  void ResetFinishedEvent(EventHandler finishedFillConfiguration);
}

  • Create a configuration class in our main Silverlight project (containing the bootstrapper) that will implement the IConfiguration interface.
[Export(typeof(IConfiguration))]
public class Configuration :IConfiguration
{
  private ObservableCollection<string> baselayers =
   new ObservableCollection<string>();
  private int wkid;
  private Extent extent;
  private ConfigurationServiceClient configService =
    new ConfigurationServiceClient();

  public object Setting(string key)
  {
    string value = string.Empty;
    if (key.Equals("BASE"))
    {
     if (baselayers.Count > 0)
     {
      value = baselayers[0];
     }
     return value;
    }
    if (key.Equals("EXTENT"))
    {
     MyExtent myExtent = new MyExtent();
     myExtent.XMin = extent.XMin;
     myExtent.XMax = extent.XMax;
     myExtent.YMin = extent.YMin;
     myExtent.YMax = extent.YMax;
     myExtent.WKID = wkid;
     return myExtent;
    }
    return null;
  }

  public void GetConfiguration()
  {

    baselayers = new ObservableCollection<string>();
    configService.GetSettingCompleted += (s, ea) =>
    {
      baselayers = ea.Result.BaseLayers;
      extent = ea.Result.BaseExtent;
      wkid = ea.Result.WKID;
      EventArgs eventArgs = new EventArgs();
      OnFinishedFill(eventArgs);
    };
    configService.GetSettingAsync("Demo", "Init");
  }
  private event EventHandler FinishedFillConfiguration;

  public void SetFinishedEvent(EventHandler finishedFillConfiguration)
  {
   this.FinishedFillConfiguration += finishedFillConfiguration;
  }

  public void ResetFinishedEvent(EventHandler finishedFillConfiguration)
  {
   this.FinishedFillConfiguration -= finishedFillConfiguration;
  }

  protected virtual void OnFinishedFill(EventArgs e)
  {
   if (FinishedFillConfiguration != null)
   {
    FinishedFillConfiguration(this, e);
   }
  }
}


  • Add an event to the class that will be raised at the end of the WCF call for the configuration data.
  • Finally, use the MapModule to hook to this event. Because almost all ArcGis applications use a map, we can hook to this event. The call to get the configuration data can be done during the Initialize method of the module. In the Initialize we hook to the end of the configuration service.

When the call to get the configuration data is finished, we can update the ViewModel of the MapModule to make the map displayed.

To initialize the configuration at this level is not conform the loosely coupling of modules but in the case of ArcGis Silverlight application this is an acceptable method.

public void Initialize()
{
   this._regionManager.RegisterViewWithRegion("Region1", typeof(MapView));
   configuration.SetFinishedEvent(new EventHandler(ConfigurationInitialised));
   configuration.GetConfiguration();
}
private void ConfigurationInitialised(object sender,EventArgs e)
{
   string baseMap = string.Empty;
   baseMap = (string) configuration.Setting("BASE");
   configuration.ResetFinishedEvent(ConfigurationInitialised);
   MyExtent myExtent = (MyExtent)configuration.Setting("EXTENT");
   mapViewModel.SetBaseMap(baseMap);
   mapViewModel.SetInitialExtent( myExtent.XMin, myExtent.YMin, myExtent.XMax,    

     myExtent.YMax, myExtent.WKID);
}


In the example I also handle the zoom to an initial extent. The extent property in the map control is not a dependency property; therefore you cannot bind it to the ViewModel. To bypass this issue I use a custom attached property derived from the dependency object. In this way you can set the extent from the map from the ViewModel.