I just finished up some unit tests where I was validating that a layer definition was correctly applied to a map in ArcGIS Server and I thought I'd share how this worked out.
This is part of a larger service that creates 1:100,000 scale PDF maps. The main data set of interest is the output of an annual survey. My client changed their data structure so that they now hold 10 years of data in a single feature class. So - in order to make a map showing the 1998 data, I needed to apply a definition query to the layer in question.
In addition, I wrote everything so it's totally generic - there can be as many layer definitions as needed, and all the details are pulled from configuration files.
Since I was updating an existing service, I could have just stuck the definition query code right in the larger block of code, but that would have precluded actually testing it. Instead I put it in it's own method.
/// <summary>
/// Applies the layer definitions.
/// </summary>
/// <param name="definitions">The definitions.</param>
/// <param name="sc">The server context.</param>
/// <param name="mapDataFrame">The map data frame.</param>
public static void ApplyLayerDefinitions(IList<ILayerDefinition> definitions, IServerContext sc, IMap mapDataFrame)
{
if (definitions.Count > 0)
//Loop over the queries
foreach (ILayerDefinition def in definitions)
//Get the layer
IFeatureLayer flayer = AGSUtil.GetFeatureLayerByDatasetName(def.LayerName, mapDataFrame, sc);
IFeatureLayerDefinition fldef = (IFeatureLayerDefinition)flayer;
//apply the defintion to the layer
fldef.DefinitionExpression = def.DefinitionQuery;
}
Note that I'm passing in the definitions as an IList of ILayerDefinitions (which just has a layername and the definition string), as well as a server context, and an IMap. I'll also point out that for ArcGIS Server development, I'm fond of using static classes - simply so that I can't have any COM objects luring around in member variables. This makes cleaning things up a lot simpler.
So, in my test code, I have some work to do spinning everything up... connect to ArcGIS Server, get the context, get an IMap of the correct dataframe etc etc. This can seem onerous, but it's really not that bad. Once everything is created, I can then call the code that applies the Layer Definition.
Which leaves how to validate it... in my case I just selected all the features and checked that the field value matched the criteria set in the definition query. Actually, since there are a few hundred thousand features in there, I only check the first 2000 - figuring if they all match, then it worked. Important to note that I use IFeatureLayer.Search, which respects the Definition Query, and not IFeatureClass.Search, which does not.
[RowTest]
[Row("Main Map", @"client/R6Service", "AGS93", "client.DBO.data_allyears")]
public void apply_layer_defs_test(string tileLayerDataFrame, string contextName, string hostName, string layerName)
using (ComReleaser releaser = new ComReleaser())
AGSServerConnection agsconn = new AGSServerConnection();
agsconn.Host = hostName;
agsconn.Connect();
IServerObjectManager som = agsconn.ServerObjectManager;
IServerContext sc = som.CreateServerContext(contextName, "MapServer");
IMapServer mapServer = (IMapServer)sc.ServerObject;
IMapServerObjects mso = (IMapServerObjects)mapServer;
IMap mapDataFrame = mso.get_Map(tileLayerDataFrame);
IList<ILayerDefinition> defs = new List<ILayerDefinition>();
defs.Add(new LayerDefinition("client.DBO.data_allyears", "SURVEY_YR = 2000"));
//Apply to the map...
MapRequestProcessor.ApplyLayerDefinitions(defs, sc, mapDataFrame);
//Get the feature layer
IFeatureLayer flayer = AGSUtil.GetFeatureLayerByDatasetName(layerName, mapDataFrame, sc);
//Query it for all features...
IQueryFilter qf = sc.CreateObject("esriGeodatabase.QueryFilter") as IQueryFilter;
releaser.ManageLifetime(qf);
IFeatureCursor fc = flayer.Search(qf, false);
IFeature f = fc.NextFeature();
//Ensure that all values in the field match the passed in value
int i = 0;
while (f != null)
int yearVal = Convert.ToInt32(f.get_Value(f.Fields.FindField("SURVEY_YR")));
Assert.AreEqual(yearVal, 2000);
i++;
if (i > 2000) break;
f = fc.NextFeature();
sc.ReleaseContext();
The bigger question here is - should you bother to test this? Realistically, if my code sets the definition query correctly once, then it should continue to work, so why bother? This is a good point - and one I scratch my head about from time to time. In the context of this application, it may not be "worth it", but given that it took all of 5 minutes to write the test, and now I *know* it works, it seems like a reasonable trade off to me.
Interesting Aside:
I added another test which applied a "bad" definition query - basically a field which did not exist. What's interesting is that no exception is thrown when you apply the definition. Rather, the next time you interact with the target feature layer you get a lovely COM Interop Exception - in my case, I saw this on the flayer.Search line. Exception in all it's glory so that Google can index it.
System.Runtime.InteropServices.COMException (0x8004152A): Exception from HRESULT: 0x8004152A\r\n at ESRI.ArcGIS.Carto.IFeatureLayer.Search(IQueryFilter queryFilter, Boolean recycling)\r\n at MapSeries.Core.Tests.MapRequestProcessorTestFixture.apply_layer_defs(String tileLayerDataFrame, String contextName, String hostName, String layerName, String def) in F:\\Web Sites\\FHTET MapSeries\\src\\trunk\\MapSeries.Core.Tests\\MapRequestProcessorTestFixture.cs:line 108
I'm Dave and this is my blog. I'm usually writing about .NET Software Development, ArcGIS, or Agile Practices, but other stuff does creep in from time to time. I hope you find something of use, and feel free to contact me if you have any questions. You can also check out my profile on LinkedIn
dojo.DTSAgile.com is our technology preview / demo site. As I and my team cook up cool things we post them here.
ArcDeveloper.net is a site that hosts a set of open source projects related to ArcGIS. This includes Tile Cache for .NET (TC4N) and Feature Server for .NET (FS4N). Come over and check it out!
Assembla is a free service that provides Subversion source control, wikis and work Tracking. The ArcDeveloper project is run from here. It rocks. Check them out today.
Agilistas is a LinkedIn group focused on discussing and promoting Agile practices. Everyone is welcome to join in the conversation as we evolve the process of creating software to make it more enjoyable for all involved.