ArcDeveloper REST: Windsor Brings the Party That Rocks the Body

Tue, Mar 11, 2008 7-minute read
NOTE: Dave has blogged about the REST API, including a demo of it in action! Check it out!
In part III of my series on the ArcDeveloper REST API, I want to focus on how we use Windsor to configure the service. The best way to to that is to take a step back and look at why Windsor exists. Windsor is an Inversion of Control (IoC) container that provides a robust Dependency Injection (DI) framework. Both of those phrases have been blogged about by just about every human in existence, so if the Fowler link doesn't help, then hit Google.

Background

In a nutshell, when you have classes that depend on other classes (and, really, what project doesn't have that? Answer: Maintenance nightmare projects) a good pattern to follow is to supply the dependencies as opposed to instantiating them from within the class. The consummate example is:
public class ClassA{

  private ClassB _classB;

  public ClassA()

  {

    _classB=new ClassB();  //This is pure evil

  }

}
The above code violates so many design patterns and principles that I had to have my 7-year old actually type it (he is a TOTAL hacker) b/c I couldn't bring myself to do it. A better way to do it is:
public class ClassA{

  private ClassB _classB;

  public ClassA(ClassB injectedClassB)

  {

    _classB=injectedClassB;  //This is sunshine and puppy dogs

  }

}
That code is an example of constructor injection, b/c the dependency must be provided to the constructor or the class cannot be instantiated. There is also setter injection, where public properties are exposed and the dependencies are suppled there. Both approaches have their pros and cons, and I am of the opinion that, if you are simply using DI in either form, you are WAY ahead of the game. Before I leave the background, I would be remiss if I didn't point you to the Bitter Coder's Wiki, where the best tutorials on Windsor live and party.

Great, So How'd Does the ArcDeveloper Rest Stuff Use Windsor?

Well, the Windsor container can use an external configuration section, say, you the web.config file.

<configSections>

<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>

</configSections>

Once that is defined, you have three major configuration areas that you can play with: facilities, properties, and components (NOTE: You'll find a separate .config file for each of these sections in the web project of our REST stuff.) Facilities are, basically, an extension to the Windsor container. There are many existing facilities, but the easiest to grok is probably the LoggingFacility. You register facilities on the container like so (from our facilities.config file):

<facilities>

<facility

id="logging"

type="Castle.Facilities.Logging.LoggingFacility, Castle.Facilities.Logging"

loggingApi="log4net"

customLoggerFactory="Castle.Services.Logging.Log4netIntegration.Log4netLogger"

/>

</facilities>

Here I have registered the Log4Net implemenation of the logging facility, which I could change to a different logging implementation by merely changing the "loggingApi" value (NOTE: I omit the logging.config that is required to configure Log4Net as it's an, erm, implementation detail.) Once defined, any components registered on the container that have an ILogger public property will automagically get an instance of my Log4Net logger. That is just cool, man. The rest of our time together will be spent working with components (we aren't currently using properties right now, but they rock and the tutorials cover them well.) The ArcDeveloper REST API has services that provide the query functionality (like an ArcGIS Server provider) and formatters that take the results from the providers and transform them into the requested format. So, this means that we can register any provider and any formatter we might want to use with Windsor, and then all we have to do is tell the RESTServiceManager (our web service implementation class) about them. Here's a picture (not quite 1000 words worth, but maybe a quick fin's worth) High Level Architecture of ArcDeveloper REST API So, let's quickly register the ArcGIS Service provider, which is implemented in the AGSMapService class.

<component id="ags.service" lifestyle="pooled" initialPoolSize="2" maxPoolSize="2"

service="ArcDeveloper.REST.Core.Interfaces.IRESTService, ArcDeveloper.REST.Core"

type="ArcDeveloper.REST.ArcGIS.AGSMapService, ArcDeveloper.REST.ArcGIS">

<parameters>

<name>TestService</name>

<description>Base map</description>

<connectionString>http://65.101.234.201/arcgis/services/gains/gains/MapServer</connectionString>

</parameters>

</component>

There you go. The AGSMapService class implements the IRESTService interface, and we provide the pertinent parameters. The "name" parameter (value="TestService") will be what is provided in the URI in order to specify that we want to use this service. The "connectionString" parameter is the URL of our ArcGIS Server Map Server object. Look closely at the attributes of the component, and you will see that we are pooling 2 instances of the service, which allows us to connect on first request and keep the object around for other requests. Since connecting to the server is a very expensive operation, we only have to live with it once. Windsor does this all for you! What do YOU do for Windsor? HMMM? That's what I thought. Anyway, lets show the formatter:

<component id="geojson.formatter" lifestyle="transient"

service="ArcDeveloper.REST.Core.Interfaces.IFormatter, ArcDeveloper.REST.Core"

type="ArcDeveloper.REST.Core.Services.GeoJSONFormatter, ArcDeveloper.REST.Core">

<parameters>

<name>geoJSON</name>

</parameters>

</component>

So, our GeoJSONFormatter implements the IFormatter interface. We name this one "geoJSON" which, whenever we write a second formatter, will be how the URI will refer to it when requesting the GeoJSON format. Oh, and the lifestyle of this bad boy is "transient", meaning it's created on request and disposed after request. This is not an expensive item, so that's how we roll. Finally, let's take a look at the RESTServiceManager:

<component id="rest.service" lifestyle="singleton"

service="ArcDeveloper.REST.Core.Interfaces.IRESTServiceManager, ArcDeveloper.REST.Core"

type="ArcDeveloper.REST.Core.RESTServiceManager, ArcDeveloper.REST.Core">

<parameters>

<services>

<list>

<item>${ags.service}</item>

<!-- <item>${other.service}</item>-->

</list>

</services>

<formatters>

<list>

<item>

${geojson.formatter}

</item>

</list>

</formatters>

</parameters>

</component>

You can see that our RESTServiceManager implements our IRESTServiceManager interface and we register our AGSMapService and GeoJSONFormatter with the service manager. The "services" and "formatters" properties of the service manager are lists, so we can add more services and more formatters simply by registering them on the container and adding them as an <item> to the list. Let me say it another way, just to drive the point home: If you wanted to register another ArcGIS Service map service with the RESTServiceManager, you would register with Windsor (so, copy the "ags.service", give it a new id and change the <connectionString>) and then add an <item> to the <services> parameter with the component id (oh, you may have to restart your web app, as changes to the external config files do not kick off an app unload. If you don't like it, pull all the config sections into web.config). After that, you can issue REST queries against the map service. It's just that easy. One final caveat. The IRESTServiceManager is not only our service manager, but it is also our WCF service contract. When you host a WCF service in IIS, it is created by the WCF Service Host factory, which means there is no way to register it with the Windsor container. That is bad, because if we can't register the service manager, then we can register the services, and pretty much we have a web service that does bugger all. In order to get around this, the (man and legend) Ayende wrote a component that will allow you to register your WCF services with Windsor. The documentation for it is here, and is so eloquently written that I am stunned that I, er, I mean, the author didn't get at least a Pulitzer nomination. The long and short of it is; - Change the .svc file to use a custom ServiceFactory (from our rest.svc file)
<%@ ServiceHost Service="rest.service"
 Factory="Castle.Facilities.WcfIntegration.WindsorServiceHostFactory, Castle.Facilities.WcfIntegration" %> (GRRR....CopyAsHTML doesn't help me in .svc files)
- Instantiate the container at application start up, using the Global.Application_Start method (from our Global.asax.cs file)

protected void Application_Start(object sender, EventArgs e)

{

container = new WindsorContainer(new XmlInterpreter());

WindsorServiceHostFactory.RegisterContainer(container.Kernel);

_log = container[typeof(ILogger)] as ILogger;

_log.Info("Services app started successfully.");

}

That's it. Now the WCF service will use the "rest.service" component we registered in the config file above. So, this post got a bit wordy on me. I am young in my blogging ways, so I have trouble focusing. Also, I am easily distracted by shiny objects. In future posts, I plan to show how we leverage the Json.NET stuff for the GeoJSON formatter and maybe write a blog about something besides REST.