Tuesday, October 07, 2008
Posted on Tuesday, October 07, 2008 7:05:49 AM (Mountain Daylight Time, UTC-06:00)  Comments [3] | 
Categories: .NET | Agile | Ajax | ASP.NET MVC | Dojo

1028209_man_thinking With the recent announcement that jQuery will ship with ASP.NET MVC and Visual Studio, the question of what javascript framework to use just got more complex.

Here's my situation:

1) I need to work with the ESRI Javascript API for the maps in my apps, and this is based on the dojo toolkit

2) I want to work with ASP.NET MVC because it's actually a sane way to built RESTful web apps on the Microsoft platform.

3) I need to send complex data between client and server as json

4) I work in an agile manner, so I need an environment that's flexible - hand coding of client-side "data classes" is a waste of time if they can be generated.

So - what to do?

jQuery is awesome, but if I already have dojo loaded into the browser for the map controls, it does not add much to the mix (besides more bytes to download!)

MSAjax and ASP.NET JSON Web Services make sending complex data across the wire super simple, but should I drag along this library just to save some effort here and there? Sending complex data is possible with dojo, but it requires coding the Json deserialization on the server, which is tedious but not that difficult.

So - what to do?  My initial thinking goes like this - for apps that use Google Maps or Virtual Earth, I'm inclined to use just jQuery. If I need to send complex data across the wire, then adding MSAjax into the mix is likely worth it. For sites using the ESRI Javascript API, I'm torn. Dojo has to be loaded, so I don't see good reason to load up jQuery as well. Again the call on MSAjax will be based on the complexity of the data services.

If you are working with this stack, I'd love to hear your thoughts on this...

Wednesday, October 01, 2008
Posted on Wednesday, October 01, 2008 12:28:18 PM (Mountain Daylight Time, UTC-06:00)  Comments [0] | 
Categories: .NET | Ajax | ASP.NET MVC | Dojo

Next up in my series of posts on using dojo to communicate with a Controller class is POSTing data. In this example, I used dojo.xhrPost to send Json as a form and have the MVC framework parse it into the Create(string name, string age) method on the Services controller. In this case, the data being sent back and forth is simple - a Name and Age from two text boxes.

Dojo Code

function CreateEmployeeMVCPost(){
    var responseNode = dojo.byId("responseMVCPost");
    var request = {"name":dojo.byId("username").value , "age":parseInt(dojo.byId("age").value)};     
         
    dojo.xhrPost({
        url: '<%= Page.ResolveClientUrl("~/Service/Create") %>',
        handleAs: 'json',
        timeout:3000,
        content: request,   
        //Don't set content type to Json
        contentType: "application/x-www-form-urlencoded",
        load: function(person,args){                         
            responseNode.innerHTML = "Response: " + person.Name + " " + person.Age;
        },
        error: function(error,args){ 
            alert(error);               
            responseNode.innerHtml("Error!",error);
        }
    });
}

Controller Code

ASP.NET MVC looks at the form data and matches up form elements which have the same name as the parameters of the method. So - the in-bound json {"name":"steve", "age":34} is parsed into form elements called "name" and "age", which line up with the parameters of the method (shown below). The controller just uses the inputs to create a new instance of an Employee object, which is then Json serialized and returned to the client.

[AcceptVerbs("POST")]

public ActionResult Create(string name, string age)

{

    return Json(new Employee(name, Convert.ToInt32(age)), "text/json-comment-filtered");

}

This is pretty smooth and works very well for simple data. Next time I'll look at trying to POST complex objects.

Monday, September 29, 2008
Posted on Monday, September 29, 2008 8:02:48 PM (Mountain Daylight Time, UTC-06:00)  Comments [2] | 
Categories: .NET | ArcGIS Server

Just ran across this new tool from ESRI, via the ArcObjects Blog - The API Evaluator. Basically this tool digs through your .NET assemblies and tells you all about the ESRI API usage.

ESRI created this tool so that they can get information about how developers use their APIs, without developers actually shipping the code to them. The idea of sending the results to them is that they can then focus their efforts on improving the SDK itself.

I ran it against a recent project that generates PDF files using ArcGIS Server...

 Evaluator

From the developer perspective, you get to see how many calls your code makes into the ESRI API - this can help determine the licensing requirements as well as give you some insight into how well your code is designed - i.e. do you have calls into the API from all over your application? If so, you could refactor your code to centralize those calls.

If you are working with the ESRI APIs, check it out, and know you'll be helping improve things along the way.

Posted on Monday, September 29, 2008 7:28:29 PM (Mountain Daylight Time, UTC-06:00)  Comments [0] | 
Categories: Ajax | ASP.NET MVC | Dojo

My first exploration of using MVC with the dojo toolkit focused on GETting data from a Controller.

I created a simple ServiceController, and added a ListEmployees method. In MVC, this relates to a Url like  http://localhost/mvcajax/Service/ListEmployees. I added the AcceptVerbs attribute to restrict this action to GET's. Code is below:

[AcceptVerbs("GET")]
public ActionResult ListEmployees()
{
    List<Employee> emps = new List<Employee>();
    emps.Add(new Employee("Dave",37));
    emps.Add(new Employee("Kai", 2));
    return Json(emps, "text/json-comment-filtered");          
}

As you can see, this just creates two "employees", adds them to a strongly typed list, and returns them as Json. Super simple.

In the View, I wired things up using dojo.xhrGet, and then converted the json into an array of objects.

What's nice about this is that the json that's returned is automatically turned into objects.

<script type="text/javascript">
function GetEmployeesMVCGet(){
    var responseNode = dojo.byId("responseMVCGet");                       
    dojo.xhrGet({
        url: '<%= Page.ResolveClientUrl("~/Service/ListEmployees") %>',
        handleAs: 'json',
        timeout:3000,                    
        contentType: "application/json; charset=utf-8",
        load: function(person,args){                                     
            responseNode.innerHTML ="Response: " + person[1].Name + " " + person[1].Age;
        },
        error: function(error,args){ 
            alert(error);               
            responseNode.innerHtml("Error!",error);
        }
    });
}
</script>

You may notice that the url passed into xhrGet looks a little funky - well, this code was copied from the View in Visual Studio, so the <%= Page.ResolveClientUrl("~/Service/ListEmployees") %> has not been evaluated. When the page is served, this expression is evaluated, and the full url added in. This is much better than putting in the path manually, as it can be used the same way from any view.

So, this is very usable, and relatively easy to build and consume

Next up - posting Data with dojo.xhrPost...

Sunday, September 28, 2008
Posted on Sunday, September 28, 2008 8:17:02 PM (Mountain Daylight Time, UTC-06:00)  Comments [0] | 
Categories: Ajax | ASP.NET MVC | Unit Testing

I'm becoming more and more reliant on unit testing as a critical part of my development process, and so I'm really excited to get a chance to build some web applications (internal research sites) using the new ASP.NET MVC (model-view-controller) bits. It's not "officially" released, but it's in "Preview 5", there is a "go-live" license, and it means you can build saner web apps without all the ASP.NET Web Forms shinanigans. So lets jump in!

I'm going to be doing a number of posts related to ASP.NET MVC, and this will be the "anchor" post - I will likely be editing it over time to add in more pointers to good resources.

The Basics

I'll point you to the official www.ASP.net/mvc site to get all the gory details, but at a high-level the things I like about this style of development are pretty simple:

  1. absolute control over the HTML and javascript that's sent to the browser,
  2. REST concepts baked in,
  3. separation of the presentation from the data from the logic (separation of concerns),
  4. Framework is test-driven development friendly

In my post on the Model View patterns, I gave some background on the idea of "MVC", so I'll consider that to be covered.

Getting Started

First thing to do is grab the bits. The installer and the source code (yes, Microsoft is publishing the source code for ASP.NET MVC!) are available at codeplex.com. This builds on .NET 3.5 and you will want to use Visual Studio 2008 (I don't think it's 100% required, but it makes the experience smoother).

Once installed, you get a new project type - ASP.NET MVC Application

mvc-project

Immediately after this dialog you are prompted to create a test project with your testing framework of choice. I like MBUnit, so that's what I selected.

mvc-project-test

So - how's that for sweet and different? Defining your unit testing strategy UP FRONT, instead of as an after thought. Tasty.

The Code

At Preview 5, the template kicks out a starter website, that's ready to roll. It's designed to show users how to do some of the common things - membership, roles, login / logout etc.

Not surprisingly the code is laid out as Models, Views and Controllers, with folders for each. Requests are routed to Controllers which then apply logic to the Model, and then determine which View to render to the client.

sol-exp

This is nice in that it helps ensure consistency across MVC projects.

Looking at the default project a little closer, we see some naming conventions at work...

details

We see two controllers - AccountController and HomeController. Then, under Views, we see two folders - Account and Home. This is not a mistake - although not required, setting things up like this simplifies the code you need to write. In the View subfolders, we see aspx pages with names that look like they may be methods, and that's exactly the case. The HomeController has 2 methods, which relate to the 2 View aspx files in /Views/Home. Initially this looks all jumbled in terms of the Url hiearchy, but that's taken care of by routing.

Routing 101

Unlike normal ASP.NET sites, the requested Url in an MVC site does not (usually) map to specific ASPX file on disk. In fact, you usually never see a file extension at all. For example, suppose you had a Url http://foo.com/orders - in traditional ASP.NET, a request to that url would look for a "default.aspx" file in the "orders" folder, and load that. With MVC, the Url is separated from the actual content on disk. Rather, decisions on what to execute are made based on what "routes" are setup for the site.

Essentially, routing is a way to map Urls to Controllers. In the sample site, a request to "/", actually routes to the HomeController, and specifically the Index action on the HomeController.

This is all setup in the Global.asax file, and looks like this:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default",                                              // Route name
        "{controller}/{action}/{id}",                           // URL with parameters
        new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    );
}

The sample site is very simple, so the routing is very simple, but you can get much more creative with this stuff. What's really nice about this is that you can start to build RESTful applications - so getting the details of an order could be accessed via a url like /Orders/Details/123-098. You could also send along more information - either as another element in the url, or as a parameter, and specify the response format - much like how the ArcGIS Server 9.3 REST API works. This adds a tremendous amount of power and flexibility to the ASP.NET toolbox, allowing you to build one application which simultaneously provides a HTML based interface AND a Json interface - just by varying a parameter on the url (and a very minor amount of Controller code!)

Unit Testing

As noted earlier, unit testing is a key (but not mandatory) part of ASP.NET MVC. In the past, anyone concerned with testing usually kept their data classes (essentially the model) in a separate assembly which the web site referenced. This allowed the developer to write unit tests without meddling with classes in App_Code. But there was still all the code-behind logic which was a bear to test. This is where the Controllers come in. They do a lot of the logic that used to be in the code behinds. And they are testable. I'll be getting into this in more detail in upcoming posts, but you can test both the View output, as well as the routing logic.

Javascript: MSAjax, Dojo, jQuery oh my!

Since the developer has complete control over the markup that is emitted into the page, it's much easier to work with non-Microsoft Ajax libraries. This was somewhat complex in ASP.NET as the use of UserControls tended to cause all sorts of issues with respect to the ID of the HTML elements in the page. If you've ever looked at the HTML in a page and seen an id like "ctl1_ctl3_mydiv", that's ASP.NET trying to "help" you. This worked well for WebForms applications, but it's a total pain for Ajax apps where consistent ID's is critical. Why would we want to use another library? Consider that for sites using the ESRI Javascript API, the dojo toolkit is already loaded into the page, and it has all the Ajax plumbing we need to talk to back to the server - wouldn't it be nice to not load MSAjax if we did not have to? Later this week I'll be posting about some experiments I've been working on to see how this works.

 

Resources:

Tutorial Videos @ ASP.NET - good content for getting a handle on the main MVC ideas.

ASP.NET MVC Quick Start pages - high-level documentation for the framework

Thursday, September 25, 2008
Posted on Thursday, September 25, 2008 10:55:38 AM (Mountain Daylight Time, UTC-06:00)  Comments [5] | 
Categories: ArcGIS Server | Unit Testing

Here's a little something to scratch your head about...

As I mentioned in some previous posts, I'm currently working on some code that creates 100k maps based on USGS quad sheet boundaries. And all is working nicely. Except I was seeing a blue halo around some of the quad sheet boundary in the PDF...

edge

I should note that at this point I'm creating the PDF's via unit tests - so I'm always using the same tile and varying other properties. When I zoomed in on the PDF a light went on...

edge-zoom

Why - it's not some weird rendering "halo" artifact - it's the cyan selection box. Except I'm not manipulating the map selection. Scratched my head for a minute and then I opened up my map document...

selbox

Look at that. I had saved the document with a selection, and then when I accessed it via ArcGIS Server, the selection was still there.

Now - I guess this "makes sense" in that selections are stored in the document so they persist over time, but it kind of seems like ArcGIS Server should clear or ignore them? I mean I'd hate to think that someone was building an application that relied on a particular selection set being in a document...

Anyhow - just another thing to add to your "Publishing Maps to ArcGIS Server" checklist.

Thursday, September 18, 2008
Posted on Thursday, September 18, 2008 1:47:24 PM (Mountain Daylight Time, UTC-06:00)  Comments [3] | 
Categories: .NET | ArcGIS Server | Dojo

Just another note on using Dojo to communicate with ASP.NET JSON Services. In my last post on this topic, we did a simple GET request to a service, and that is all well and good, but suppose we want to POST some data?

Well, I ran into this today, and since it was a royal pain to dig the answer out of the internets, I'll share it with you (and Google) now.

Scenario

I have created a web service that cranks out 1:100,000 scale maps as PDFs. Since these can take a while to process, the service kicks up a thread and cooks the map asynchronously, and simply returns a status message to the requesting client. When the map is done, an email is sent to the user telling them that the map is ready to be downloaded.

Here's the web service signature...

    [WebMethod(Description = "Request a map")]

    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]

    public string CreateMapSheet(string emailAddress, string serviceName, string mapSheetId, int year)

    {

        // Do good things and create the map

        return "A message will be sent to " + emailAddress + " with details of how to download the map when it is ready.";

    }

We can see that the service is marked up so that it accepts JSON ([ScriptMethod] attribute) and responds with Json ( ResponseFormat = ResponseFormat.Json). Ok - moving on...

On my test page, I have a very simple form...

post-form

The HTML is below...

<form id="form1" action="" >
<ul>
    <li>
        <label for="emailaddress">Email Address:</label>
        <input type="text" id="emailaddress" value="dbouwman@edats.com"/>            
    </li>
    <li>
        <label for="tileid">Tile Identifier:</label>
        <input type="text" id="tileid" value="73"/>        
    </li>
    <li>
        <label for="service">Service:</label>
        <input type="text" id="service" value="UTM_ZN10"/>        
    </li>
    <li>
        <label for="year">Year:</label>
        <input type="text" id="year" value="2007"/>  
    </li>    
    <input id="Button2" type=button value="Submit via POST" onclick="SubmitMapRequest();" />
</ul>

As we can see, the Submit button calls the SubmitMapRequest function, which is where we actually make the xhr request. The working final Javascript is show below.

function SubmitMapRequest(){
    //debugger;
    var responseNode = dojo.byId("response");
    var maprequest = { "emailAddress":dojo.byId("emailaddress").value , 
        "serviceName":dojo.byId("service").value , 
        "mapSheetId":dojo.byId("tileid").value  , 
        "year": dojo.byId("year").value };                
    
    dojo.rawXhrPost({
        url: "./MapSheets.asmx/CreateMapSheet",
        handleAs: 'json',
        timeout:1000,
        postData: dojo.toJson(maprequest),            
        contentType: "application/json; charset=utf-8",
        load: function(data,args){                
            responseNode.innerHTML = data.d;
        },
        error: function(error,args){                
            responseNode.warn("Error!",error);
        }
    });
}

So - what's going on here...

Packing up Data as JSON

Step one is to pack up the form data into a javascript object which can then be sent to the service. It took a while to locate a sample that actually worked, so it's worth showing below...

var maprequest = { "emailAddress":dojo.byId("emailaddress").value , 
    "serviceName":dojo.byId("service").value , 
    "mapSheetId":dojo.byId("tileid").value  , 
    "year": dojo.byId("year").value };     

Since we are not using the MSAjax framework, we can't rely on just using the web service proxy to make the request. Thus we need to take a little more responsibility for constructing the json, and that's exactly what we are doing here. What's important to note is that the naming of the fields in the javascript must match the naming of the parameters in our web service method. So we send in 'emailAddress' not 'EmailAddress' or 'emailaddress'.

Posting the Data to the Web Service Part 1: dojo.xhrPost

It took a lot of digging, FireBugging and messing with Fiddler to figure this out. In the end I used rawXhrPost as it does not try to get all fancy with the content - it just POSTs the it to the Url. But for the sake of completeness and Google-indexing, here's the deal with dojo.xhrPost

My original code used dojo.xhrPost, and looked like this:

function SubmitMapRequestPOST(){
        
        var responseNode = dojo.byId("response");        
        var maprequest = { "emailAddress":dojo.byId("emailaddress").value , 
            "serviceName":dojo.byId("service").value , 
            "mapSheetId":dojo.byId("tileid").value  , 
            "year": dojo.byId("year").value };                        
        dojo.xhrPost({
            url: "./MapSheets.asmx/CreateMapSheet",
            handleAs: 'json',
            content: maprequest,            
            contentType: "application/json; charset=utf-8",
            load: function(data,args){                
                responseNode.innerHTML = data.d;
            },
            error: function(error,args){                
                responseNode.warn("Error!",error);
            }
        });
    }

Using this I would get a Json error from the service...

{"Message":"Invalid JSON primitive: emailAddress.","Stack Trace":"blah blah blah..."}

When I'd look at the POST in FireBug, it was not JSON... instead it looked like this

emailAddress=me%40foo.com&serviceName=UTM_ZN10&mapSheetId=73&year=2007

Next I tried to use dojo.toJson to force the maprequest to become Json...

        dojo.xhrPost({
            url: "./MapSheets.asmx/CreateMapSheet",
            handleAs: 'json',
            content: dojo.toJson(maprequest),            
            contentType: "application/json; charset=utf-8",
            load: function(data,args){                
                responseNode.innerHTML = data.d;
            },
            error: function(error,args){                
                responseNode.warn("Error!",error);
            }
        });

This got even more jacked up, and this is what was posted:

0=%7B&1=%22&2=e&3=m&4=a&5=i&6=l&7=A&8=d&9=d&10=r&11=e&12=s&13=s&14=%22&15=%3A&16=%22&17=m&18=e&19=%40
&20=f&21=o&22=o&23=.&24=c&25=o&26=m&27=%22&28=%2C&29=%22&30=s&31=e&32=r&33=v&34=i&35=c&36=e&37=N&38=a
&39=m&40=e&41=%22&42=%3A&43=%22&44=U&45=T&46=M&47=_&48=Z&49=N&50=1&51=0&52=%22&53=%2C&54=%22&55=m&56
=a&57=p&58=S&59=h&60=e&61=e&62=t&63=I&64=d&65=%22&66=%3A&67=%22&68=7&69=3&70=%22&71=%2C&72=%22&73=y&74
=e&75=a&76=r&77=%22&78=%3A&79=%22&80=2&81=0&82=0&83=7&84=%22&85=%7D

Ummm yeah.

Posting the Data to the Web Service Part 2: dojo.rawXhrPost

After much more Googling, I stumbled upon an example at the Project Zero forums. The scenario being discussed is similar - they were having issues posting, and they were seeing that the data was being sent as HTML FORM encoding, not Json. And the solution was to use dojo.rawXhrPost.

Using this, we see that the post actually contains the Json we created...

{"emailAddress":"me@foo.com","serviceName":"UTM_ZN10","mapSheetId":"73","year":"2007"}

and everything works smoothly.

Now one might ask - "How was I supposed to know that?" Great question. The Dojo API documentation for dojo.rawXhrPost  is obtuse at best. The Dojo Quick Start site only talks about xhrGet and xhrPost, and the Book of Dojo is the same.

However, the O'Reilly book "Dojo: The Definitive Guide" does actually cover this stuff - but it's still pretty slim.

If I have some time over the next few days I'll roll this into a sample (minus the mapsheet service stuff!)

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 09, 2008
Posted on Tuesday, September 09, 2008 12:43:00 PM (Mountain Daylight Time, UTC-06:00)  Comments [1] | 
Categories: Presentations

Just a quick note that I'll be giving a talk this Thursday, Sept 11, 2008 at the GIS in the Rockies conference. The title of the talk is "Leveraging Virtual Earth to View Fire Risk Data", and I'll be talking about two projects and talking about the "zen" of getting data out of ArcGIS Server (9.2 and 9.3) and into Virtual Earth. This is at 9:00am in the Cross Room. It's part of the "GIS for the Community" session.

I'm also very excited to be giving a talk at the Texas GIS Forum in Austin. The session is currently titled "GEOWEB" but will likely become more specific as we work out the details of the talks. I'll be talking with Kirk Kykendall of AmberGIS and the ESRI forums hall of fame, and Paul  Bissett of WeoGeo, so I think it should be a good session.

Also - for those in the Northern Colorado area, just a note that an Alt.NET chapter has been started up. Things are just getting rolling, but we're sticking to the basic manifesto...

We are a self-organizing, ad-hoc community of developers bound by a desire to improve ourselves, challenge assumptions, and help each other pursue excellence in the practice of software development.

Our movement is new. The conversation just started. All are welcome to shape and form the dialog in blogs and lists and face-to-face gatherings both local and global.

So - if you are a .NET developer who's interested in pushing the envelope, and expanding your repertoire, bounce over to the Google group, and get involved. We are meeting in the DTS offices, and have a less formal meeting approach - basically we decide what people are interested in, and at the next meeting a few people show some code / informally discuss the idea. We'll see how it goes as the first "real" meeting is tonight. On the agenda: ASP.NET MVC overview, and Inversion of Control / Dependency Injection with Castle Windsor.

Wednesday, September 03, 2008
Posted on Wednesday, September 03, 2008 10:47:30 AM (Mountain Daylight Time, UTC-06:00)  Comments [5] | 
Categories: ArcGIS Devt | ArcGIS Server

After a few months of working flawlessly for "normal" web development (ASP.NET + SQL Server + Virtual Earth etc), I finally ran into some weirdness that I could only trace back to Vista 64, and even then not "definitively".

Background

Basically I was porting a web service that created 1:100,000 scale PDF maps, from ArcGIS Server 9.1 to ArcGIS Server 9.3. Along the way I was refactoring the code, and adding more unit tests. Pretty straight forward really.

I started this work on my notebook, which is running the 32bit version of Vista Ultimate. After a few hours, I had the code worked just fine, but was still looking at the refactoring.  For those who've done burly refactoring, you can appreciate that working on a small 13 inch notebook screen is a pain compared to a 3 monitor workstation, so I moved over to it when I was back in the office. Enter the weirdness.

The application itself is pretty simple - given a 100k map sheet name and an email address, create a 1:100,000 scale PDF of the map sheet, drop it in a pickup folder and send a notification email with the url to the file. In code, it's pretty simple:

  1. Using a query filter to locate the 100k tile feature
  2. Zoom the map to the extent of said feature
  3. Draw the feature on an inset map (separate data frame)
  4. Swap out the content of whole bunch of Text Elements in the map surround
  5. Write out the PDF
  6. Swap the Text elements back to their original values (so we can re-use the SOC)

Of course working with ArcGIS Server means that there is a whole mess of Server Context fun in the mix as well. Moving on...

The Problem

Quite simply, at step #2 in my pseudo code list above, IFeature.Shape.Envelope was throwning a COMException, saying that a Class was not registered. Uh-oh.

This is the actual exception in all it's obscure glory:

System.Runtime.InteropServices.COMException (0x80040154): Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))\r\n at ESRI.ArcGIS.Geometry.IGeometry.get_Envelope()\r\n at FHTET.MapSeries.MapRequestProcessor.CreateMapSheet(MapRequest request) in F:\\Web Sites\\FHTET MapSeries\\src\\trunk\\FHTET.MapSeries.Core\\MapRequestProcessor.cs:line 83

Nifty - apparently IEnvelope was not registered. Only ArcMap worked just fine, so it was registered. Kinda. Sorta. WTF?

I had run into some registry silliness with MbUnit and TestDriven.net on Vista 64, and this smelled of something similar. And of course the same code worked just fine on XP and Vista 32. I hacked around at it for a few hours, trying all sorts of stuff, but regardless, no matter how I'd try to get an IEnvelope, I'd get this COMException.

I also posted to the illustrious ArcGIS Server Forums, with zilch in terms of a response. I guess no one else is running Vista 64?

One Last Try

At that time I had the ArcGIS Desktop 9.3 and the Server 9.3 ADF installed on my workstation. On my notebook I also had the Desktop Developer Kit - so I tried installing this on my workstation. Not good. After that, I started getting the same COMInterop Exception on IFeature. Doh! More registry scrambling. ArcMap still played nice, so it looks like the issue is related to the RCW's. Who knows - the bottom line for me was that the same code was still working smoothly on Vista 32 and I have to solve MY problems for MY clients, and not spend time sleuthing out ESRI related problems. Thus, I decided to pack it in and re-pave my workstation back to Vista 32.

The upside of a kickin workstation is that installs happen really fast. In this case I had the OS installed, and the box back on our domain with MSOffice Everything, Visual Studio 2008 and various helpful tools all loaded in less than 2 hours. I've got my ESRI installers on a network share so they should fly, but I have to head out to the airport now, so that will happen on Friday.