For part 2 of this series I am going to talk about the overall design of the Maestro API.
Having used
FDO for quite some time now, a lot of its API design has influenced my design of the Maestro API. Those who have used FDO may notice many parallels in the design when working with the Maestro API.
The first similarity is the heavy use of
interfaces. FDO models its connections and commands via interfaces. In the Maestro API, we model the following in the same manner using interfaces:
- MapGuide Server connections
- MapGuide Service APIs
- MapGuide Resource classes and message types
By having these types as interfaces, it allows the
implementation of these interfaces to be totally different, and I will show later on in this post where having this quality is extremely beneficial.
The MapGuide Server connection is represented by the
IServerConnection interface. This is the main point of entry in the Maestro API.
This contains the
ResourceService and
FeatureService interfaces to provide resource manipulation and feature querying capabilities. Additional MapGuide services are available via
GetService method and you can interrogate the
Capabilities property to determine what you can and can't do with this particular connection.
Now the reason this is an interface is because in the Maestro API, there are currently 2 implementations of this connection:
- One that communicates with the mapagent via http. (Maestro.Http)
- One that wraps the official MapGuide API. (Maestro.LocalNative)
Because this is an interface, the client application can use the same code regardless of implementation. As there is an implementation that wraps the official MapGuide API, you can even use MapGuide Maestro
without even needing a web tier because MapGuide Maestro works against the interfaces defined in the Maestro API.
Now given that
IServerConnection is an interface, how does one go about creating instances of it? Enter the
ConnectionProviderRegistrySimply call
CreateConnection passing in the desired provider and the parameters needed to initialize it. The
ConnectionProviderRegistry reads from a
ConnectionProviders.xml file which contains all the registered connection providers. Each connection provider is an implementation of the
IServerConnection interface.
So having acquired a connection, we can now interact with the services provided by MapGuide. Once again, these services are modeled as interfaces
Each service is defined in a corresponding interface. For reference, they are:
- IFeatureService
- IResourceService
- IDrawingService
- IMappingService (also contains rendering service APIs)
- IFusionService
- ITileService
- ISiteService
Each service interface contains methods which correspond to what you see in the mapagent test page. These services are obtained via the
GetService method on
IServerConnection. For
IResourceService and
IFeatureService, these already exist as properties so you can access them directly. Once again because these are interfaces, the implementations can be wildly different:
- For the http connection provider, these interfaces wrap http requests to the mapagent and automatically processes the XML responses from them.
- For the local connection provider, these interfaces wrap the offical MapGuide service classes (eg. The implementation of IFeatureService wraps the methods of the MgFeatureService class, and does automatic conversion of MgByteReader values)
Finally, there is MapGuide's resource classes and message types. The MapGuide Server returns many different types of XML data. But with the Maestro API, instead of working with raw XML content and having to write a lot of boilerplate code, you get to work with strongly typed classes and interfaces, with XML serialization automatically handled for you
Now there's a lot of
XML Schemas for all the various forms of XML data that MapGuide returns. Actually building the classes that represent this data by hand would've taken an eternity, so I've taken a shortcut for this particular case and used the
xsd2code code generator to generate all the required classes from their XML Schemas. I used xsd2code for generating the classes instead of using the default xsd.exe tool for
reasons I have already explained. The set of generated classes produced by xsd2code a much cleaner (relative to xsd.exe anyway) and gave me a very useful foundation to work with.
In the case of resource classes, using the generated classes as-is was still not good enough. Additional tweaking of this generated code was required. The reason is that each particular
version of a resource was its own separate class when generated. Each particular version of a resource in its generated class form had nothing in common. Once again, interfaces come to save the day.
Now you may notice that there is only one interface for Layer Definitions, but there's been 3 additional revisions to date in the Layer Definition XML schema (ref
1,
2,
3,
4 and
5). So how were 4 separeately generated classes able to be reconciled into a single interface?
One of the fortunate things thus far, that
Autodesk did when they introduced a new version of a given resource was that the schema revisions have been
incremental and additive. Nothing is actually removed or altered in every schema revision thus far.
Because of this, the design of these interfaces is such that they represent the 1.0.0 version of the respective resource type each interface is modeling. Elements introduced in newer versions of the schema are exposed through a new version of the interface that extends the original.
So with this design, we have a consistent baseline interface for all resource types and we can interrogate the resource version to determine if we can cast these interfaces (or its child properties, which are also interfaces) to the newer versions to tap into the extra functionality exposed in these newer interfaces.
As is the case with the Web Layout in the above diagram.
IWebLayout2 extends
IWebLayout and maps to the 1.1.0 version of the Web Layout schema that introduced
a new "ping server" property. However both can be accessed via the
IWebLayout interface. We can check if the resource version is 1.1.0 to determine if it can be cast into the
IWebLayout2 interface.
It is through this design that the Resource Editors in MapGuide Maestro are able to edit most, if not all known versions of a given resource type, and have the necessary intelligence to enable/disable certain UI elements for elements not supported by the edited resource version. This design allows Maestro to keep up to date as newer resource types or schema versions are made available, providing that these schema revisions stay true to their additive form.
This incremental quality of the XML schema revisions is also reflected in the generated code. Thus with the magic of
partial classes and careful use of the
C# pre-processor, getting different versioned classes to implement the same baseline interface was actually an easy task. Then it was a case of extracting the newer properties and methods introduced in newer schema versions into their own separate interface that extends the original.
The end result, is a consistent object-oriented API that is (mostly :) ) free of boilerplate code that you would normally write to deal with raw XML content, MgByteReaders and HTTP requests/responses. Any developer who has worked with the official MapGuide API would appreciate
So that's the high-level overview of the design of the Maestro API. Stay tuned for part 3, where I actually show some concrete examples of how to use this library.