Monday, June 22, 2009

Dynamic MapResourceManager Part 1: Custom Config Section

This is part 1 of a 2-part post on how I set up a dynamically-configurable ESRI Web ADF MapResourceManager control. Part 1 is how I used a custom web.config section to store the ResourceItem properties for the MapResourceManager.  Part 2 shows how to dynamically initialize a MapResourceManager using the ResourceItem properties I set up here.

I got a great start using the information from James Simmonds' blog.  This MSDN article on custom config sections is helpful also.

I inserted a line like this into my web.config in <configSections>:

<section name="MapResourceItemSettings" type="TNC.GIS.WebControlsLibrary.CustomConfig.MapResourceItemSettings, TNC.GIS.WebControlsLibrary"/>


Note that the class name referenced by the type parameter must be fully-qualified.  I added this custom section farther down inside the web.config <configuration> section:



<MapResourceItemSettings> 
  <LayerCollection> 
    <LayerSettings id="1" layerOrder="0" roles="*" name="Conservation Projects" visible="true" transparency="30" transparentBackground="true" transparentColor="#fffffefd" 
                   dataSourceType="ArcGIS Server Local" dataSourceDefinition="localhost" resourceDefinition="(default)@AllUsers" imageFormat="" height="" 
                   width="" dpi="" returnMimeData="" displayInTableOfContents="" dataSourceShared="" /> 
    <LayerSettings id="2" layerOrder="0" roles="role1,role2" name="Conservation Projects" visible="true" transparency="30" transparentBackground="true" transparentColor="#fffffefd" 
                   dataSourceType="ArcGIS Server Local" dataSourceDefinition="localhost" resourceDefinition="(default)@AuthUsers" imageFormat="" height="" 
                   width="" dpi="" returnMimeData="" displayInTableOfContents="" dataSourceShared="" /> 
    <LayerSettings id="3" layerOrder="1" roles="*" name="Aerial Photography" visible="true" transparency="0" transparentBackground="false" transparentColor="" 
                   dataSourceType="ArcGIS Server Internet" dataSourceDefinition="http://server.arcgisonline.com/arcgis/services" 
                   resourceDefinition="(default)@ESRI_Imagery_World_2D" imageFormat="" height="" width="" dpi="" returnMimeData="" displayInTableOfContents="" dataSourceShared="" /> 
  </LayerCollection> 
</MapResourceItemSettings>


Each LayerSettings entry represents all of the property values to be set on an ESRI ResourceItem instance.  The layerOrder attribute configures the display order in the map, from lowest (on top) to highest (on bottom, e.g., base layers).  The roles attribute allows for dynamic switching of map layers based on user role.  In the code sample above, the top-most layer will have a different display depending on user role, but the base layer will be the same for all users.


Three classes are required to retrieve the values from the MapResourceItemSettings custom configuration section:  MapResourceItemSettings, LayerCollection, and LayerSettings.  Code samples below.



internal class MapResourceItemSettings : ConfigurationSection 
{ 
    [ConfigurationProperty("LayerCollection")] 
    internal LayerCollection Layers 
    { 
        get { return this["LayerCollection"] as LayerCollection; } 
    } 
} 
[ConfigurationCollection(typeof(LayerSettings), AddItemName = "LayerSettings")] 
internal class LayerCollection : ConfigurationElementCollection 
{ 
    protected override ConfigurationElement CreateNewElement() 
    { 
        return new LayerSettings(); 
    } 
    protected override object GetElementKey(ConfigurationElement element) 
    { 
        return ((LayerSettings)element).Id; 
    } 
    internal LayerSettings this[int index] 
    { 
        get { return (LayerSettings)BaseGet(index); } 
    } 
} 
internal class LayerSettings : ConfigurationElement 
{ 
    [ConfigurationProperty("id", IsRequired = true, IsKey = true)] 
    virtual internal int Id 
    { 
        get { return (int)this["id"]; } 
    } 
    [ConfigurationProperty("layerOrder", IsRequired = true)] 
    virtual internal uint LayerOrder 
    { 
        get { return (uint)this["layerOrder"]; } 
    } 
// to save space, I omitted most of the Configuration Properties from this code sample
    [ConfigurationProperty("dataSourceShared")] 
    virtual internal bool? DataSourceShared 
    { 
        get { return (bool?)this["dataSourceShared"]; } 
    } 
}



The LayerSettings class is not ideal, because as far as I could tell, each ConfigurationProperty is limited to a built-in value type.  I made all properties virtual so that LayerSettings can be mocked by Rhino Mocks.  LayerSettings also is difficult to unit-test because it requires a web.config file.  Therefore, I created two adapter classes.  The LayerProperties class adapts LayerSettings, and the LayerPropertiesCollection class adapts LayerCollection:



public class LayerProperties : IComparable<LayerProperties> 
{ 
    public LayerProperties(int id, uint layerOrder, string roles, string name, bool visible, 
        int transparency, bool transparentBackground, string transparentColor, 
        string dataSourceType, string dataSourceDefinition, string resourceDefinition, 
        string imageFormat, int? height, int? width, int? dpi, bool? returnMimeData, 
        bool? displayInTableOfContents, bool? dataSourceShared) 
    { 
        try 
        { 
            _id = id; 
            _layerOrder = layerOrder; 
            SetRoles(roles); 
            _name = name; 
            _visible = visible; 
            _transparency = transparency; 
            _transparentBackground = transparentBackground; 
            SetTransparentColor(transparentColor); 
            _dataSourceType = dataSourceType; 
            _dataSourceDefinition = dataSourceDefinition; 
            _resourceDefinition = resourceDefinition; 
            SetImageFormat(imageFormat); 
            SetHeight(height); 
            SetWidth(width); 
            SetDpi(dpi); 
            SetReturnMimeData(returnMimeData); 
            SetDisplayInTableOfContents(displayInTableOfContents); 
            SetDataSourceShared(dataSourceShared); 
        } 
        catch { throw; } 
    } 
    private int _id; 
    public int Id 
    { 
        get { return _id; } 
    } 
    private uint _layerOrder; 
    public uint LayerOrder 
    { 
        get { return _layerOrder; } 
    } 
// to save space, I omitted most of the Properties from this code sample
    private bool _dataSourceShared; 
    public bool DataSourceShared 
    { 
        get { return _dataSourceShared; } 
    } 
    private void SetDataSourceShared(bool? value) 
    { 
        if (value == null) _dataSourceShared = true; 
        else _dataSourceShared = (bool)value; 
    } 
    #region IComparable<LayerProperties> Members 
    public int CompareTo(LayerProperties other) 
    { 
        if (other == null) 
            throw new ArgumentNullException("other"); 
        return LayerOrder.CompareTo(other.LayerOrder); 
    } 
    #endregion 
}
public class LayerPropertiesCollection : Collection<LayerProperties>
{
    public LayerPropertiesCollection()
        : this("MapResourceItemSettings")
    { }
    public LayerPropertiesCollection(string configSectionName) :
        this(GetMapResourceItemSettingsFromConfigSection(configSectionName))
    { }
    internal LayerPropertiesCollection(MapResourceItemSettings settings)
        : base(AddAllLayerPropertiesFromConfigSection(settings))
    { }
    private static MapResourceItemSettings GetMapResourceItemSettingsFromConfigSection(string configSectionName)
    {
        if (string.IsNullOrEmpty(configSectionName))
            throw new ArgumentNullException("configSectionName");
        try
        {
            return System.Configuration.ConfigurationManager.GetSection(configSectionName) as MapResourceItemSettings;
        }
        catch (Exception ex) 
        { 
            throw new ExplanationException("The provided custom configuration section name (" + configSectionName 
                + ") is invalid", ex); 
        }
    }
    internal static LayerProperties GetLayerPropertiesFromLayerSettings(LayerSettings settings)
    {
        try
        {
            return new LayerProperties(
                        settings.Id,
                        settings.LayerOrder,
                        settings.Roles,
                        settings.Name,
                        settings.Visible,
                        settings.Transparency,
                        settings.TransparentBackground,
                        settings.TransparentColor,
                        settings.DataSourceType,
                        settings.DataSourceDefinition,
                        settings.ResourceDefinition,
                        settings.ImageFormat,
                        settings.Height,
                        settings.Width,
                        settings.Dpi,
                        settings.ReturnMimeData,
                        settings.DisplayInTableOfContents,
                        settings.DataSourceShared
                        );
        }
        catch { throw; }
    }
    private static Collection<LayerProperties> AddAllLayerPropertiesFromConfigSection(MapResourceItemSettings settings)
    {
        if (settings == null)
            throw new ArgumentNullException("settings");
        Collection<LayerProperties> allLayers = new Collection<LayerProperties>();
        try
        {
            for (int i = 0; i < settings.Layers.Count; i++)
                allLayers.Add(GetLayerPropertiesFromLayerSettings(settings.Layers[i]));
        }
        catch (Exception ex)
        {
            throw new ExplanationException("The supplied configuration section instance is invalid, " +
                "or there is an invalid value in one of the LayerSettings values", ex);
        }
        return allLayers;
    }
}



The internal constructor and internal static GetLayerPropertiesFromLayerSettings method allow for unit testing of the LayerPropertiesCollection class.  The final step was to create a factory class to create LayerProperties instances:



public class LayerPropertiesFactory 
{ 
    private Collection<LayerProperties> _allLayers = new Collection<LayerProperties>(); 
    public LayerPropertiesFactory(Collection<LayerProperties> layerPropertiesCollection) 
    { 
        if (layerPropertiesCollection == null) 
            throw new ArgumentNullException("layerPropertiesCollection"); 
        else if (layerPropertiesCollection.Count == 0) 
            throw new ArgumentException("Must not be an empty collection", "layerPropertiesCollection"); 
        try 
        { 
            _allLayers = SortedCollectionFromList(new List<LayerProperties>(layerPropertiesCollection)); 
        } 
        catch { throw; } 
    } 
    public LayerProperties LayerById(int id) 
    { 
        try 
        { 
            return (from layer in _allLayers 
                    where layer.Id == id 
                    select layer).First<LayerProperties>(); 
        } 
        catch { throw; } 
    } 
    public Collection<LayerProperties> AllLayers 
    { 
        get { return _allLayers; } 
    } 
    public Collection<LayerProperties> AllLayersByRole(string role) 
    { 
        if (string.IsNullOrEmpty(role)) 
            throw new ArgumentNullException("role"); 
        List<LayerProperties> lpList = new List<LayerProperties>(); 
        lpList.AddRange(from layer in _allLayers 
                        where ((layer.Roles.Contains("*") == true 
                            & !((from filter in _allLayers 
                                 where filter.Roles.Contains(role) == true 
                                 select filter.LayerOrder).ToArray()).Contains(layer.LayerOrder)) 
                            | layer.Roles.Contains(role) == true) 
                        select layer); 
        return SortedCollectionFromList(lpList); 
    } 
    public Collection<LayerProperties> LayersByRoleAndLayerOrder(string role, params uint[] layers) 
    { 
        if (string.IsNullOrEmpty(role)) 
            throw new ArgumentNullException("role"); 
        if (layers == null) 
            throw new ArgumentNullException("layers"); 
        try 
        { 
            Collection<LayerProperties> lpColl = AllLayersByRole(role); 
            List<LayerProperties> lpList = new List<LayerProperties>(); 
            lpList.AddRange(from layer in lpColl 
                    where layers.Contains(layer.LayerOrder) == true 
                    select layer); 
            return SortedCollectionFromList(lpList); 
        } 
        catch { throw; } 
    } 
    private static Collection<LayerProperties> SortedCollectionFromList(List<LayerProperties> listToSort) 
    { 
        listToSort.Sort(); 
        return new Collection<LayerProperties>(listToSort); 
    } 
}



The code to use the LayersCollection and LayerPropertiesFactory classes looks like this:



//  read from the MapResourceItemSettings custom config section 
LayerPropertiesFactory factory = new LayerPropertiesFactory(new LayerPropertiesCollection()); 
// get a sorted collection of LayerProperties objects for a particular role 
Collection<LayerProperties> layers; 
if (Roles.IsUserInRole("role1")) 
    layers = factory.AllLayersByRole("role1"); 
else 
    layers = factory.AllLayersByRole("*"); 



In Part 2 of this post, I will show the code I used to dynamically manage an ESRI MapResourceManager control.

No comments:

Post a Comment