Tuesday, September 16, 2008
Posted on Tuesday, September 16, 2008 2:46:58 PM (Mountain Daylight Time, UTC-06:00)  Comments [3] | 
Categories: ArcGIS Server | Unit Testing

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

 

 

 

 

Tuesday, September 16, 2008 6:18:50 PM (Mountain Daylight Time, UTC-06:00)
Dave,
If you want to check out how to create GeoSpatial PDFs vs. regular PDFs using the M2P COM interface, try downloading the software from the TerraGo website. The documentation explains how to integrate it in to your solution. Also, check out this link for a little more info. The interface to read the files back in to ArcGIS is included if you're interested in round-tripping your data.

Adam
Wednesday, September 17, 2008 8:09:16 AM (Mountain Daylight Time, UTC-06:00)
Just yesterday I was thinking "How the hell can I create unit tests for an ArcGIS Server Web ADF application." Had no idea how to create a map instance through code. Now instead of digging through the ESRI support site and help files I was able to do it in 5 minutes with your code sample. Thanks!
Erikk
Wednesday, September 17, 2008 10:02:28 AM (Mountain Daylight Time, UTC-06:00)
Hi Dave,

Two things on my mind:
1) I must write here that this is ain't a unit test I would like to put such tests in a seperate Project (and call it SolutionName.ProjectName.IntegrationTests) which I won't run in my nightly builds. The reason is that it should connect to The AGS and work with the DB, and it must have the right lines in the SDE or other GDB (so it will cause me to make initiations to the DB using a script that will run at after the build of the *.IntegrationTests project)

2) If you test AGS\SDE\ArcObjects - do it like this! and not using mocking frameworks the reason is that this is the only way to figure out that your is _realy_ works.

moreover,
I would be glad to see some more code about the AGSUtil,

Anyway,
I like this code (ESRI-testing) and I hope could add more of my own to my blog in the near future.

Thanks,
Shani.
Comments are closed.