Zen and the Art of ServerObjectExtensions

Posted by Dave Bouwman | Posted in .NET, ASP.NET MVC, ArcGIS Server, ESRI, Visual Studio 2008 | Posted on 14-12-2009

4

I was going to cook up a full SOE example, but that was going to take way more time that I don’t have, so here’s the abridged version – just the major points…

 

Projects in the Solution

SOE’s have a lot of moving parts, so let’s think about all the parts in terms of servers & assemblies.

First – the SOE project. We’ll create the actual SOE’s in here. We can have references to all the ArcObjects / ADF goop we want in here, since this will of course be running on the SOC machine.

In the “client” application (ASP.NET MVC in my case) we’ll use an interface to access the SOE. Since we’ll want the interface definition on both the SOC machine(s) and the “client” machine, we put this in it’s own assembly. Remember that anything that moves between the SOE and the “client” is really doing DCOM under the covers, so all types need to be serializable.

Next up is the Utility assembly. We have a mess of static classes in here that are pure ArcObjects. This assembly will be used on the ArcGIS SOC box, by the SOEs. An upside of keeping this code “pure” ArcObjects (by that I mean there is no ServerObject cruft in here) we can use these same utility classes in ArcEngine or Desktop development.

Finally, we’ll want that “client” application I’ve be talking about. I’m a big fan of ASP.NET MVC, so I’ve been using that to host services. It’s worth noting that you can put standard ASMX SOAP web services into an MVC project. This is nice because you can expose your SOE methods as SOAP or JSON from the same project. Remember, this project will be using the ESRI ADF library to create the connection into the SOC and get access to the SOE, so it will need to run on a box that has these libraries accessible. Note that at 9.4 the ADF components are re-distributable with out incurring additional licensing.

 

Building

The build process can be a little hairy. First off, make sure you have ArcGIS Server on your development box – this way your build/test cycle will be manageable. If you need to copy dll’s to another server as part of your debugging process you will lose your mind.

So – we need to register all the COM assemblies – that would be the SOE, the SOEInterface, and the COMUtility assemblies at minimum. While you *CAN* tell visual studio to register these assemblies for COM interop, DON’T! That will register the assembles in \bin\debug.

A better plan is to copy these dll’s to a separate folder, and install them there. We call ours _install_ComUtilities. We have a post-build event that copies the SOE and it’s dependencies into that folder, and does all the dirty work. Jeff Germain just threw up a good post on a Post-Build Event that does all this, but here’s the basic process:

  1. Stop the ArcGIS SOM Process (SC STOP ArcServerObjectManager)
  2. Un-register the dll’s (regasm /unregister…)
  3. delete the type libraries (*.tlb)
  4. delete the dlls
  5. copy the new dll’s from the build output folder into _install_ComUtilities
  6. run regasm on them to create the type libraries
  7. re-start the ArcGIS SOM process (SC START ArcServerObjectManager)

Now, of course you don’t want to do this every time your solution builds, as it takes ~15 seconds, so use the visual studio configuration manager to not build the SOE project automatically.

Registering the SOE with the Map Service

Get Vish’s SOExplorer app, and use that. You can do this manually or write some code to do it, but just use Vish’s tool. It rocks. Moving on…

Logging

Since you’ll likely have a bug or two in your code, having some logging is really important. We use Log4Net on the client side, but have never gotten this to work smoothly in COM land. Again, Jeff has saved the day with a ComLogger utility that he whipped up for this. I’ve asked him to post about this, but he has not gotten to it yet. If you want this, go to his blog and hassle him.

The upside is that when debugging the app, I’ve got two logs to watch – the log on the client (MVC) and from the SOC (via ComLogger). The ComLogger simply creates a \log folder next to the SOE dll – so you have to watch for permissions on this – make sure that the ArcSOC process has write permission to the folder where you install your dll’s!

Here’s an example of what I get in my SOE Log:

12/14/2009 3:11:37 PM   INFO   LrsSOE::GetRoute   LrsSOE::GetRoute called.
12/14/2009 3:11:37 PM   INFO   LrsSOE::GetRoute   params: routeId: 002B
12/14/2009 3:11:37 PM   INFO   LrsSOE::GetRoute   whereclause: Route = ‘002B’

What shows up in here is up to you, but make sure you stuff all exceptions into the log because that’s where this will save you a ton of time. If you don’t you’ll just get an InteropException on the client side, and that’s completely useless. Also worth mentioning, I use NotePad++ and just keep these two files open in it. When they change, and you change focus to Notepad++, it will ask if you want to refresh the files. Vish is a big fan of BareTail, but from what we’ve see, it’s got wacky behavior on multi-monitor setups. Moving on…

SOAP and JSON in MVC

As noted above I needed to provide services to different clients – one is a Flex app where JSON is convenient, and the other is another MVC app that can speak SOAP fluently. To do this, I just created a standard MVC project, and created a /SOAP folder, inside of which I created a standard asmx web service. By default MVC maps all urls to routes, and so /SOAP would normally route to a class called SOAPController. However, we don’t want that behavior, so we tell MVC to ignore anything under /SOAP (this is in global.asax.cs)

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("SOAP/{*pathInfo}");
            routes.MapRoute(
                "Default",
                "{controller}/{action}/{id}",
                new { controller = "Home", action = "Index", id = "" }
            );
        }

For the Json stuff, I setup a JsonController and thus we have routes like http://server/json/getroute?routeId=25b, which of course maps to a the GetRoute method on the JsonController, passing in a routeId of 25b. The logic in the JsonController is pretty simple – we just pass the arguments over to the SOE, and return the response.

Getting the SOE

Getting the SOE itself is a two step process. First you need to get a server context – ideally one that has your SOE registered with it, and then get the SOE from that. We’ve written a lot of these things, so we have lots of handy utilities to handle configuration sections, Identity etc etc. But it boils down to two methods:

public static IServerContext GetServerContext(string arcGISServerName,
                                            string serviceName,
                                            string serviceType,
                                            ESRI.ArcGIS.ADF.Identity identity)
{
    //Connect to ags
    ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsConnection
        = new ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection(arcGISServerName, identity);
    agsConnection.Connect();
    if (!agsConnection.IsConnected)
    {
        agsConnection.Dispose();
        return null;
    }

    //Get the som
    IServerObjectManager som = agsConnection.ServerObjectManager;

    //Get the server context and the mapserver
    IServerContext serverContext = som.CreateServerContext(serviceName, serviceType);

    return serverContext;
}

and…

private ILrsSOEInterface GetSOE(IServerContext serverContext, string soeName)
{
    //Get the server object extension
    IServerObjectExtensionManager serverObjExtMgr = serverContext.ServerObject as IServerObjectExtensionManager;
    if (serverObjExtMgr == null)
    {
        throw new ApplicationException("Failed to get the ServerObjectExtensionManager from the server context.");
    }

    IServerObjectExtension serverObjExt = serverObjExtMgr.FindExtensionByTypeName(soeName);
    if (serverObjExt == null)
    {
        throw new ApplicationException("Failed to get the ServerObjectExtension by extension type name" );
    }

    ILrsSOEInterface lrsSOE = (ILrsSOEInterface)serverObjExt;
    if (lrsSOE == null)
    {
        throw new ApplicationException("Failed to create a reference to the {0} server object extension.", soeName));
    }

    return lrsSOE;
}

Notice that the second method returns the SOE typed as the interface we defined in the interfaces assembly.

Serialization

The last step is to get the results from our SOE back to the client. This is easy for simple types, but as usual Geometry is a pain. The Geometry we get back from our SOE is the SOAP Value type geometry. And as far as we know there is no open API for converting this into a Json geometry. Of course, using Reflector you can *see* that ESRI has all the handy methods you may want to use for this in the REST assembly, but alas they are all internal. What’s a dev to do? I’ve asked ESRI to provide public methods for this, and Morten mentioned being able to do this using the WPF components, but again, the needed methods are internal (and they don’t handle the SOAP value types). If you are working with GeoJSON, the Vish (did I mention Vish rocks?) whipped up GeoJson.NET, which you can get from SVN at Assembla (http://svn2.assembla.com/svn/GeoJSON/) . It cleanly handles the ESRI –> GeoJson formatting. All hail Vish. ;-)

So – that’s a pretty shot-gun style round up of SOE related stuff. If you have questions or ideas, just drop me a line and I’ll see what I can do.

Re-mixing the Flex Sample Viewer

Posted by Dave Bouwman | Posted in ArcGIS Server, ESRI, Flex, Usability | Posted on 18-11-2009

16

The ESRI Flex Sample Viewer is a great starting point for creating map centric RIA’s. The widgetized nature of the starter kit facilitates significant re-use of components, and there are a couple dozen plug-and-play components available from the Flex Code Gallery. Woot!

The downside of this ease of re-use is that we are seeing a new wave of cookie cutter sites out there. It’s great that these sites can the whipped up so quickly, but it’s pretty clear that (for the most part) little or no thought is given to how the user should interact with the site. We see hordes of menus and tons of widgets, all gloriously trying to pseudo replicate ArcMap. Let’s be clear – if your users need ArcMap, give them ArcMap. If they actually know the data model to the point that an ad-hoc query tool will be useful, chances are they will need more functionality than you can build into the Flex app anyhow. Right tools, right place and all that…

Where was I… oh yeah… remixing the sample viewer. When I’m designing an application, I want to build it as focused as possible. Every mouse click and menu drop down I can remove, the better it is for the user. So – here are a few of the things that I wanted from a UI/behavior perspective…

  • I want top level menu items that do something on-click. I don’t want a pull down with a single item on it – I want a print icon that opens the print dialog when it’s clicked.
  • The shortcut menu is a great idea, but I want to have things that “only” appear on the short cut menu. Having something in two places in a UI is counter intuitive in most cases, and wastes UI space.
  • I want to have a limited set of base maps available, and I want them to be displayed as top level icons on the menu. Clicking them will turn on the layer. Active layer should have a “glow” indicating that it’s… well… active.
  • I want widgets that can’t be closed. One thing I’ve seen when letting clients run a Flex app is when they close widgets they are confused about how to re-open it. Thus I want a sub-type of widget that can only be minimized and not closed.
  • I want a full width banner across the top of the page – providing more room for top level items.
  • I want a search box in the top banner so it’s easy to search.
  • I want a zoom to / book mark tool on the top bar so it’s easy to jump to commonly accessed extents

And I want all the same level of flexibility in terms of configuration, and the ability to leverage existing widgets. Now. ;-)

Design Sketches

In coming up with this list of things I wanted, I did some sketches…  (apparently my scanner is crap!)

remix-sketch1

remix-sketch2

In the past I’ve used visio or balsamiq to create wireframes, but more recently I’ve been getting back into sketching simply because it’s so fast. I’ve been using Jason Robb’s wireframe templates as a starting point (article and direct download of pdf).

More Tile Caches…

Since ESRI is changing their caches over to Web Mercator, I wanted to start working with some web mercator tile caches. ESRI has *some* services available now (google search here), but realistically most of these are pretty weak for base maps. So, I created tile layers for OpenStreetMap and CloudMade. Once I finish a few tweaks to these layers (need to display the copyright statements on the map) I’ll release them – likely to the ESRI Code Gallery, but I also want them on a SCM somewhere. Maybe GitHub? Thoughts?

Let’s take a peek…

Things are not fully dialed in yet, and I really need to focus on detailing this out for my client, but this is the generic starting point. I’ll post some live links when my client’s site is ready for beta testing.

remix-mc

This shows the full width banner, the address search, zoom to drop down (needs skinning), an “Always Open” widget, the two base map icons, and the top level print widget. The tiles are from the CloudMade Midnight Commander cache.

remix-2

This one shows the locate dialog that’s opened by running a search from the banner bar. As you can see there are still a few issues with this widget. Since “locate” is not the key focus of the app, this widget can be closed. I’m also going to work on the actual search logic – right now it’s parsing out the address based on commas, and then sending to the ESRI geocoder. I may look at using another service that has more matching logic on the back end so I don’t have to figure out how to de-construct the address.

remix-3

This shows the print widget, accessed by just clicking the printer icon (no drop down required). Oh, and the Open Street map cache in the background.

What’s nice about this is that the user does not need to go digging around in menus to access the functions they need. This template will be used for very focused applications – do one thing, and do it well is the mantra for the project – and so keeping things up front will really help out.

Behind the Scenes

So – the real question is – can we share this? Good question! I’ll be blogging more about how we (big shout out to Jeff Germain who whipped up the “toolbars” stuff) created various aspects of this, and at the very least, you can re-implement most of this stuff pretty quickly. We had to hack at the core of the Sample Viewer (specifically ControllerManager.as) and added a bunch of additional controls for our “toolbars”. At this point things are “working” for the scenarios we need right now, but I suspect we’ll have some refactoring in our future. I’ve got at least 3 apps that will build on this, and by the time all 3 are released, we should have tracked down all the weirdness, and have something that supports all the out-of-the-box Sample Viewer stuff, as well as our extensions. Stay tuned!

Usability for Emergency Response Applications

Posted by Dave Bouwman | Posted in ArcGIS Server, Conferences, Usability | Posted on 17-11-2009

2

I finally got around to loading a video of my talk on Usability in Emergency Response Applications, given at the ESRI South West User Group (SWUG) meeting. In this talk I give a walk-through of an app we built using the ESRI Javascript API, focusing on design aspects that helped streamline the end-user experience. I also discuss the performance impact of token secured services and SSL.

It’s up on Vimeo at http://vimeo.com/7557517 or you can just watch it below…

Usability for Emergency Response Applications from Dave Bouwman on Vimeo.

ESRI Silverlight API: Defining Unique Value Renderer in XAML

Posted by Dave Bouwman | Posted in ArcGIS Server, Silverlight | Posted on 08-04-2009

2

I’ve been creating some proof of concept applications in Silverlight, and one thing I needed to do was apply a unique value renderer using PictureMarkerSymbols to some points on a FeatureLayer.

Initially I looked in the Samples and Reference sections of the ESRI Silverlight Resource Center. However, I was some what stymied. The Samples had a simple example of loading a feature layer, but it used the built-in clustering. I could find all sorts of details in the API Reference, but that does not help sort out how to setup a renderer in XAML.

Finally I stumbled into the Concepts section. I had figured that this section would be some very basic stuff – but there are a number of very useful code examples in there – specifically an example creating renderers in XAML. The example is for polygons, and is pretty comprehensive, but I thought I’d share a code sample for a unique value renderer using a PictureMarkerSymbol, as this is very much akin to slapping push-pins into other map canvasses like Google Maps or Virtual Earth.

The idea is pretty simple – in the Grid.Resources section of your XAML, define a bunch of PictureMarkerSymbols with names, then create a unique value renderer, and add in unique value items, which tie the attribute value to the specific marker symbol using data binding to the symbols we just defined. Follow that? It makes more sense in XAML…

<Grid.Resources>
    <esriSymbols:PictureMarkerSymbol x:Name="HelicopterSymbol" Height="40" Width="40" Source="Assets/images/i_helicopter.png" />
    <esriSymbols:PictureMarkerSymbol x:Name="DozerSymbol" Height="40" Width="40"  Source="Assets/images/i_truck.png" />
    <esriSymbols:PictureMarkerSymbol x:Name="PumperSymbol"  Height="40" Width="40" Source="Assets/images/i_firetruck.png"/>
    <esriSymbols:PictureMarkerSymbol x:Name="JumperSymbol"  Height="40" Width="40" Source="Assets/images/i_fireman.png"/>
    <esri:UniqueValueRenderer x:Name="FireResourcesRenderer" Attribute="Type" >
        <esri:UniqueValueRenderer.Infos>
            <esri:UniqueValueInfo Value="HELO" Symbol="{StaticResource HelicopterSymbol}" />
            <esri:UniqueValueInfo Value="DOZER" Symbol="{StaticResource DozerSymbol}" />
            <esri:UniqueValueInfo Value="PUMPER" Symbol="{StaticResource PumperSymbol}" />
            <esri:UniqueValueInfo Value="JUMPER" Symbol="{StaticResource JumperSymbol}" />
        </esri:UniqueValueRenderer.Infos>
    </esri:UniqueValueRenderer>
</Grid.Resources>

Then in the FeatureLayer definition we bind the renderer property to the renderer we defined in the Grid.Resources.

<esri:FeatureLayer
      
ID=”FireResources”
      
Url=”http://yourserver/ArcGIS/rest/services/yourmapeservice/MapServer/0″
      
Where=”1=1″
      
ClusterFeatures=”False”                     
      
Renderer=”{StaticResource FireResourcesRenderer}”>
    <
esri:FeatureLayer.OutFields>
        <
sys:String>Type</sys:String>                               
    </
esri:FeatureLayer.OutFields>
</esri:FeatureLayer>

And that’s it!

Displaying Large Selection Sets with the ESRI REST API

Posted by Dave Bouwman | Posted in Ajax, ArcGIS Server, Google Maps, Javascript, jQuery | Posted on 20-02-2009

2

On my current project, we need to select project locations (points) on a map, create a list of the unique “Projects” which can then be used for reporting. We are using the ArcGIS Server Google Map Extension as that’s the best fit for this project. Overall, this sounds simple enough.

Here’s the wrinkle – there are 1800+ project location points in the dataset. And, these 1800 points actually represent only 33 projects. Ah, 1 to Many’s… this will be “fun”…

500 is the Magic Number

By default, the REST API (which delegates to the SOAP API behind the scenes) only returns the first 500 features when doing a query. This setting can be changed on the server, but it’s there for a reason – you want to limit the number of features that are sent back to the browser. This limits the amount of data on the wire, and keeps your application responsive. In addition, drawing more than a few hundred features on any javascript canvas will make most browsers come to a crawl. Flex and Silverlight have less issues with this, and for applications where you are dealing with large sets of data.

Ok – but how are we going to handle this? We need to select features inside polygon, but we are only going to get 500 features back.

We did some poking at the data, and found that the distribution of project locations to projects is heavily skewed… this graph shows the number of project locations per project.

graph

As we can see, there is one project with nearly 1700 project locations. Most of the rest of the projects have 4 or less locations.

So – even getting just 500 features back, we’ll likely cover off the project in question.  Of course this is not 100% guaranteed – in the long run we may revisit this, and if the client want’s to invest time/money in some back-end ArcObjects development and we can solve the issue that way.

Doing the Query

In any event, in the callback from the GPolygon enableDrawing() method, takes the geometry, and does a query against the project locations layer. Since we don’t want to actually try to draw 500 points, we do not have the geometry returned – just the ProjectID of the features. Remember that returning the smallest set of information is always a good idea.

We then loop over the features, collecting a list of unique the ProjectId’s. At the same time, I’m creating a WHERE clause that can be used in a definition query.

var itemList = new Array();
var defQuery = "PROJECTID IN (";
for(i=0;i<fset.features.length;i++){
    var feature = new esri.arcgis.gmaps.Feature();
    feature = fset.features[i];
    if($.inArray(feature.attributes.PROJECTID, itemList) == -1){
        itemList.push(feature.attributes.PROJECTID);
        defQuery += '\'' + feature.attributes.PROJECTID + '\',';
    }
}

Layer Definitions

Instead of showing the selected project locations as push-pins, I’m using a definition query against a separate “Project Location Selection” layer in the map. Once I have all the PROJECTID’s, I update the layer definition for this selection layer…

defQuery = defQuery.substring(0,defQuery.length -1);
defQuery += ")";
layerDefs[0] = defQuery;
dynamicMap.setLayerDefinitions(layerDefs);

From here, I now use some jQuery to loop over the array of ProjectID’s, make Ajax calls to a controller to get the details of the project to populate the results list area. Since that get’s long, I’m going to skip those details – suffice to say I create a mess of <li> tags and float them so I get a pretty list.

Now, there is some possibly un-expected behavior with this – since the definition query is based on the “one” side of the “one to many”, the selection we end up showing is “all the project locations related to the projects in the polygon”.

This makes more sense when you see it…drawing-poly

This is the map right before we finish the polygon. The Project Locations are the gray dots. The Results area is currently empty, and the reporting tools are disabled.

after-draw

After the polygon is finished, we get the unique list of project ID’s, set the “selection layer” definition (resulting in the green dots), and fetch the project details from the controller and fill up the results area.

As you can see, there are a lot of selected (green) dots outside the selection polygon – this is mainly because of that one project that has ~1700 locations – if we get just one of those back from the spatial query, we’ll be “selecting” all these points.

Regardless of the 1 to Many issues on this project, using a definition query against a dynamic map layer can be a good way to show the selection of a large set of features, or of features with very complex geometries.

ESRI Javascript Starter Kit…

Posted by Dave Bouwman | Posted in ArcGIS Server, Dojo, Javascript | Posted on 16-01-2009

5

The Javascript starter kit has been released, and I have to say it’s pretty sweet. We have been working with some pre-release versions and I wanted to give a shout out to the main developers of the kit – Simon Biickert, Chris Macleod and Rachel O’Neil, all of ESRI Canada. As it stands the kit is a really slick starting point for a map-centric client side application. That said, if you want to extend it, you will need to get your Dojo kung-fu on. Now that it’s out we’ll start posting more examples on how to create/extend these widgets.

Speaking of extending, we have expanded on the kit so it supports some extra services as well as pulling the entire configuration from a service, based on a user’s identity.

Thus far we’ve used this for two map-centric web apps.

The first is for a “City GIS Services” group and basically allows public users to locate public records information and print maps. This site is nearly live – we are just polishing up the UI and building out the tile caches. I’ll post about it when it’s up and on the city’s servers so people can poke it.

image

 

The second is an emergency operations center dash-board application that is still in active development. This will have some interesting event management tools and a catalog of layers that can be added into the map as well

image

Both sites use a combination of the ArcGIS Server REST API, and custom json services. They are also both built on the ASP.NET MVC framework.

Setting Spatial Reference on a Folder of MrSID Files…

Posted by Dave Bouwman | Posted in .NET, ArcGIS Devt, ArcGIS Server | Posted on 10-11-2008

4

So I’m getting ready to cook up a tile cache, and I want to use the ESRI ArcGIS Online tile scheme so I can use their tiles for the first 10 or so zoom levels. To do this, you need to have your map’s spatial reference set to WGS84, which was not a problem when I was setting up the “Road Map”, but I ran into a snag with the “Hybrid” (roads + parcels + orthos) – the Ortho images did not have any spatial reference defined – which means that ArcMap could not project them. Doh!

Now – I should point out that there is likely a neat-o way to do this with some Geoprocessing Python, but I’m a .NET guy, so I used my .NET hammer to solve this.

Ive done enough with images over the years to know that the spatial reference will be stored in the .aux.xml files that share the name of the raster. But for one set of images, there were no .aux.xml files. Solution: Open all the files in ArcMap and it will create these files. Sweet.

Next up, for one raster, I manually set the correct spatial reference in ArcCatalog. Easy, but there are several hundred files, so it’s not going to work as a solution. Enter System.Xml…

I then opened up the .aux.xml file (in notepad) for the raster that I manually assigned a spatial reference to, and simply copied the projection information from the <SRS> element. I then cooked up a simple loop over all the files, opened them up in an XmlDocument, injected or updated the <SRS> element, saved the file and shazam! all done. The pertinent function is below.

 

        private void UpdateSpatialRef(string folderPath, string srs)

        {

            //Loop over all the xml files, open as xml doc, inject the SRS node, save

            string[] fileNames = Directory.GetFiles(folderPath, “*.sid.aux.xml”);

            for (int i = 0; i < fileNames.Length; i++)

            {

                string fileName = fileNames[i];

                //Open the file

                XmlDocument xDoc = new XmlDocument();

                xDoc.Load(fileName);

                //find the SRS element if it’s here

                XmlNodeList srsNodes = xDoc.SelectNodes(“//SRS”);

                XmlElement srsEl = xDoc.CreateElement(“SRS”);

                srsEl.InnerText = srs;

                if (srsNodes.Count > 0)

                {

                    //Already set… replace                   

                    if (xDoc.DocumentElement.FirstChild.Name == “SRS”)

                    {

                        xDoc.DocumentElement.ReplaceChild(srsEl, xDoc.DocumentElement.FirstChild);

                        xDoc.Save(fileName);

                    }//if it’s somewhere else in your doc, it’s likely bad & you’ve got other problems!

                }

                else

                {

                    xDoc.DocumentElement.PrependChild(srsEl);

                    xDoc.Save(fileName);

                }

                Console.WriteLine(“Fixed: “ + fileName);

            }

        }

 

While more over the top than may be necessary, it was quick to write and solved my problem… now to kick off the cache creation!

ESRI API Evaluator

Posted by Dave Bouwman | Posted in .NET, ArcGIS Server | Posted on 30-09-2008

2

Just ran across this new tool from ESRI, via the ArcObjects BlogThe 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.

ArcGIS Server Weirdness…

Posted by Dave Bouwman | Posted in ArcGIS Server, Unit Testing | Posted on 25-09-2008

5

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.

Posting data to ASP.NET JSON Services with Dojo

Posted by Dave Bouwman | Posted in .NET, ArcGIS Server, Dojo | Posted on 18-09-2008

3

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!)