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.

Javascript Validation for Visual Studio

Posted by Dave Bouwman | Posted in Dojo, Javascript, Visual Studio 2008 | Posted on 22-10-2008

3

One big hurdle for developers moving to javascript is the lack of “compile time checking”. The only way to see if your code is syntactically correct is to load it into a browser and run it. But, sometimes small syntax errors can cause the javascript parser to fail, and the file will not load. This is a huge pain, and a great way to waste hours trying to find the smallest of syntax errors.

Enter Javascript Validation. There are some on-line tools for validating javascript, and Douglas Crockford’s JSLint is likely the most widely known. And while these tools can be handy, copy/pasting code into a text box, validating it, making changes, re-validating etc etc is tiresome.

Luckily for all of us Predrag Tomasevic created a Visual Studio add-in that uses JSLint to validate javascript from within Visual Studio 2005/2008. It’s called JSLint.VS, and the code is over at CodeProject. Get it.

The simplest way to use it is to run JSLint on a file from the context menu…

jslint1

But, if you look at the options (Tools –> JSLint.VS options) you can integrate this with your build.

jslint2

You can see the options that I have checked – running with minimal items checked tones things down to just checking for syntax – which end up being the most irritating bugs to catch. For example here is some code that’s catching an event, and then propagating that to another method along with some customized event arguments.

onClick: function(evt){
    // stub for event propagation
    console.log("DashboardMenu::onCommandItemClick");
    this.onCommandItemClick({
        action: this._menuConfig.action,
        type: this._menuConfig.type
    });
},

The customized arguments being passed over to onCommandItemClick are created via object notation – JSON if you will. Since I’m refactoring the code quite frequently, small little things can become problems. For example, I decided I did not need to pass “type” along anymore. Ok, I just went in an deleted that line resulting in this code…

onClick: function(evt){
    // stub for event propagation
    console.log("DashboardMenu::onCommandItemClick");
    this.onCommandItemClick({
        action: this._menuConfig.action,
    });
},

And as usual I refreshed FireFox, and followed along in FireBug and all was well and good. An hour or so (and a dozen or more changes) later I tried the page in IE…

ie-msg

Oh sweet. Without getting into dojo.require etc etc, this message is basically saying that IE could not load the class “dts.DashboardController” from the file because of some javascript syntax error. What’s not really clear is that the error could exist in that file, or any other class that’s dojo.require’d by the specified class. In my case, the code shown above is in dts.DashboardMenu, which is required in dts.DashboardController as shown below…

dojo.provide("dts.DashboardController");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dts.DashboardMenu"); //<-- DashboardMenu is pulled in here

dojo.declare("dts.DashboardController",
    [dijit._Widget, dijit._Templated, dijit._Container],
    {
        //class definition here...
    }
);

So – back to the problem – those who work with javascript and JSON every day ,and have sharp eyes might have noticed the error in the second code block – there is an extra comma in the JSON. Firefox accepts this, and works just fine. However, in IE the file fails to parse and we get that error dialog.

Turning on JSLint.VS, I get a notice about the missing comma as soon as I build… with the line number… how easy is that!

jslint3

This will clearly save me a LOT of time over the long-haul – if you are using Visual Studio for your javascript development, this should be a “must-have” addin.