-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



martedì 1 marzo 2011

SOE (Server Object Extension) Rest

SOE consente di estendere le funzionalità base di ArcGIS Server. Le funzionalità sono sviluppate prevalentemente utilizzando gli ArcObjects, che sono i mattoncini con i quali è costruito ArcGIS e che consentono la massima flessibilità nello sviluppo di funzionalità.
Poichè lo sviluppo di una SOE richiede conoscenze di sviluppo relative a diversi ambienti, è importante valutare bene se è necessario svilupparla o trovare alternative ai propri scopi. Normalmente la si sviluppa quando non sono presenti funzionalità out-of-box esposte da ArcGIS Server o dal geoprocessing (funzionalità ad esempio di segmentazione dinamica o tracing su geometry network).
La SOE estende un tipo di servizio. Per ora SOE esposta via REST è solo disponibile con servizi di tipo MapServer.
Considerando poi che la 10.1 permetterà solo la pubblicazione di msd (Servizi di mappa ottimizzati) è importante sviluppare SOE Rest che supportino gli msd. Questo può creare qualche problema nell'utilizzo diretto dei fine-grained arcobjects dalla libreria Carto, ma è comunque possibile utilizzare nuove interfacce che abilitano il MapServer ad utilizzare gli object.
Per lo sviluppo occorre conoscere gli ArcObjects, .NET o Java e tecnologie di comunicazione web service come REST o SOAP. Inoltre, se occorre gestire delle proprietà della SOE, è possibile sviluppare in ArcCatalog nella pagina delle capabilites del servizio una windows form.
Per completezza è possibile esporre la SOE anche via SOAP.
Come abbiamo già detto, occorre valutare bene se sviluppare una SOE o se ad esempio creare un modello con il Model Builder e concatenare i singoli tool che ci interessano per eseguire i nostri task. Questi tool potranno essere personalizzati con Python (ora nella 10 potenziato con arcpy). Si potrà poi esporre questi geoprocessi come web service per essere utilizzati dalle nostre applicazioni. Inoltre è possibile creare strumenti di geoprocessing con .NET e incorporare arcobjects in tool utilizzabile nei tool o nei modelli. E' chiaro che utilizzando tool out-of-box e costruendo modelli evitiamo la scrittura di codice.
L'aspetto negativo di questa soluzione è che richiede più memoria e spesso è più lenta della SOE. E' chiaro che una soluzione di questo tipo si adatta bene se il servizio è chiamato solo poche volte al giorno. Se il servizio è chiamato molte volte e soprattutto da utenti concorrenti, optare per la SOE.
Infine, prima di iniziare lo sviluppo di una SOE, valutare anche se le funzionalità del geometry service con le funzionalità di interrogazione esposte da ArcGIS Server non coprono già le nostre esigenze.

Una SOE è un oggetto COM sviluppato in .NET o Java che implementa IServerObjectExtension più altre interfacce necessarie all'implementazione.

Questa interfaccia è utilizzata dal server object (servizio) per gestire il ciclo di vita della SOE. L'Init è chiamato una sola volta quando si crea l'istanza, mentre lo ShutDown informa la SOE che il context è in shut down cosicchè la SOE rilascia il suo riferimento sul server object helper.

La SOE può opzionalmente implementare l'interfaccia IObjectConstruct, se occorre recuperare proprietà di configurazione della SOE o occorre eseguire logiche di inizializzazione. Questa interfaccia ha un solo metodo che è chiamato una sola volta dopo l'Init.

Attenzione a non mettere logiche di inizializzazione nel costruttore della SOE o nell'Init, ma utilizzare IObjectConstruct.Construct(), che è chiamato dopo il ciclo di vita della SOE e così assicura che la SOE è creata e puoi così generare log sin dalla tua logica di inizializzazione.

IObjectActivate and ILogSupport sono due interfacce opzionali da non utilizzare poichè sono utilizzabili con SOE che usano connessioni ArcGIS Server Local. Poichè con la 10.1 non ci sarà supporto su DCOM è meglio evitare di utilizzarle e comunque non sono utilizzate con SOE Web Services.

Per il logging utilizzeremo l'oggetto ESRI.ArcGIS.SOESupport.ServerLogger.

Per le SOE Rest implementeremo l'interfaccia IRESTRequestHandler: il client invia richieste su http al web service che restituisce una risposta. L'interfaccia IRESTRequestHandler facilita questa comunicazione.

I passi da seguire per sviluppare una SOE sono:
1) Sviluppare la SOE. E' presente un template in Visual Studio che crea un primo scheletro con le interfacce da implementare.
2) Registrare la SOE su ogni macchina SOC
3) Registrare la SOE in ArcGIS Server SOM
4) Facoltativo: sviluppare una pagina di proprietà della SOE per ArcCatalog
5) Abilitare la SOE su un servizio
6) Utilizzare il servizio con abilitata la SOE da un client (per esempio da Silverlight, Flex, Javascript).

Per lavorare con i Web services REST SOE, una volta indicato l'url ed appeso alcuni parametri di input (coordinate, nomi di layer ecc.), il server elabora e restituisce la risposta, come ad esempio, dati o stream di byte (immagini), al browser. Normalmente questo scambio avviene attraverso JSON.

Esempio:

http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Earthquakes/EarthquakesFromLastSevenDays/MapServer/0/query?where=magnitude>6&f=pjson

Questo è l'url inviato al server. I parametri che passiamo sono dopo il ? e cioè where=magnitude>6&f=pjson

La risposta restituita dal server in JSON è
{
"displayFieldName" : "eqid",
"fieldAliases" : {
"eqid" : "Earthquake ID"
},
"geometryType" : "esriGeometryPoint",
"spatialReference" : {
"wkid" : 4326
},
"fields" : [
{
"name" : "eqid",
"type" : "esriFieldTypeString",
"alias" : "Earthquake ID",
"length" : 50
}
],
"features" : [
{
"attributes" : {
"eqid" : "c0000f0y"
},
"geometry" : {
"x" : 148.95690000000002,
"y" : -6.0207999999999515
}
}
]
}

Il client potrà poi leggere e "parsare" la risposta in JSON e fare qualcosa nell'applicazione: ad esempio visualizzare le geometrie nella propria mappa.

Il precedente esempio è una chiamata rest ad un servizio ArcGIS Server. In questo caso non è stata chiamata una SOE Rest.


Il seguente link richiede la quota ad una certa latitudine e longitudine. E' una SOE chiamata ElevationSOE
http://sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/exts/ElevationsSOE/ElevationLayers/1/GetElevationAtLonLat?lon=-123&lat=47&f=pjson

Vediamo come è composto l'url:
- http://sampleserver4.arcgisonline.com/ArcGIS/rest/services : è l'URL dell'ArcGIS Server root per i servizi rest
- Elevation: nome della cartella in ArcGIS Server
- ESRI_Elevation_World: nome del servizio
- MapServer: tipo di servizio
- exts: url della root per tutte le SOE esposte da questo servizio
- ElevationsSOE: nome della SOE
- ElevationLayers: risorsa esposta dalla SOE rappresentante una lista di layer abilitati per restituire quote
- 1: indice del layer da utilizzare nella richiesta della risorsa ElevationLayers
- GetElevationAtLonLat: operazione sulla SOE che prende la quota data una lat/lon
- lon=-123: parametro logitudine per GetElevationAtLonLat
- lat=47: parametro latitudine per GetElevationAtLonLat
- f=pjson: formato per la risposta dell'operazione. In questo caso è stato indicato il pjson ovverosia il json formattato per essere facilmente leggibile.

Il server restituisce un oggetto Json (chiamato elevation).
{
"elevation" : 83.003478588350134
}

Il server in questo caso ha eseguito ArcObjects per restituire questo risultato, ma l'applicazione client non necessita di avere conoscenze ArcObjects. Il client deve solo conoscere l'url e gestirsi la risposta.

Prima di sviluppare una SOE Rest occorre introdurre la definizione di risorsa e di operazione.
Una volta stabilito quali sono le informazioni che vogliamo inviare e ricevere dal nostro servizio occorre pensare alla SOE Rest come risorse e operazioni.
Le risorse sono delle informazioni che richiediamo al server. Esempio: una lista di layer della mappa o un insieme di livelli di scala disponibili nella cache della mappa. Può essere vista come una proprietà di sola lettura.
Le operazioni sono richieste al server su una determinata risorsa. Dopo aver effettuato la richiesta possiamo farci restituire informazioni testuali, immagini od altro. Le operazioni possono essere viste come dei metodi.

E' importante progettare il diagramma delle risorse e delle operazioni (con input ed output) per poter creare lo schema poichè quest'ultimo deve essere creato da programmazioni e, se dovessimo creare  risorse multiple con operazioni, potrebbe diventare ardua la gestione da codice senza un diagramma definito. Inoltre, poichè i dati in input ed output andranno serializzati o deserializzati (si passerà da Json ad ArcObjects e viceversa), conoscere nel dettaglio i parametri aiuta lo sviluppo di queste operazioni. La ESRI ha introdotto oggetti e metodi (nella libreria ESRI.ArcGIS.SOESupport) che aiutano i passaggi di serializzazione e deserializzazione.

Una volta che abbiamo installato SDK ArcGIS.NET, abbiamo a disposizione in Visual Studio 2008 e 2010 dei templates che ci aiutano a creare una SOE Rest. Il template è disponibile sono in C# con framework 3.5.


Una volta selezionato il template, ci verrà generata una classe che rappresenta la nostra SOE Rest.

Come abbiamo accennato precedentemente, una SOE è una classe COM che risiede sul nostro server. Il template ha già impostato per noi la GUID e la visibilità a COM.
Inoltre la classe implementa una serie di interfacce alcune delle quali sono nuove:

ServicedComponent: è richiesta come classe base dalla SOE che usa COM.
IServerObjectExtension: contiene i metodi Init() e Shutdown() che vengono eseguiti quando si avvia e si ferma la nostra SOE. Normalmente non vengono utilizzati a meno che occorra 'ripulire' qualcosa sullo shutdown. Utilizzare il metodo IObjectConstruct.Construct() per inizializzare qualcosa una volta che la SOE è abilitata - non il costruttore della classe o l'Init.
IObjectConstruct: contiene un metodo (Construct()) che viene eseguito una volta che la SOE è abilitata. Qui è il posto corretto dove mettere logiche business di inizializzazione che non necessitano di essere eseguite ad ogni richiesta. Ad esempio, potresti creare geodatabase temporanei che precalcolano dati che poi verranno utilizzati e dipendono dal servizio sottostante, oppure impostare variabili che verranno poi utilizzate perchè non cambiano più durante il ciclo di vita ecc.
IRESTRequestHandler: abilita richieste e risposte REST per il servizio. Questi metodi creano lo schema e manipolano le richieste. Normalmente possiamo lasciare il codice generato così com'è.

Nel costruttore possiamo notare la classe SoeRestImpl che implementa l'interfaccia IRESTRequestHandler.

reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler;


La SoeRestImpl valida lo schema SOE, convalida il nome delle risorse e delle operazioni delle chiamate HandleRESTRequest, valida le capabilities della SOE, gestisce gli errori e  registra le chiamate e le risposte del servizio.
La classe SOE normalmente contiene una sola istanza della classe SoeRestImpl.
Il resto del template contiene delle funzioni per mostrare che cosa la SOE Rest può fare e come ogni richiesta alla SOE può essere gestita. CreateRestSchema() definisce le risorse e le operazioni disponibili nella nostra SOE. Ognuna di queste risorse e operazioni ha associato una funzione che descrive che cosa deve fare la risorsa e l'operazione quando è chiamata. Il template contiene, a scopo dimostrativo, una funzione per la risorsa ( RootResHandler() ) ed una per una operazione ( SampleOperHandler() ).

private RestResource CreateRestSchema()
{
     RestResource rootRes = new RestResource(soe_name, false, RootResHandler);
     RestOperation sampleOper = new RestOperation("sampleOperation",
                                                      new string[] { "parm1""parm2" },
                                                      new string[] { "json" },
                                                      SampleOperHandler);
 
     rootRes.operations.Add(sampleOper);
 
     return rootRes;
}

In questo caso possiamo vedere come lo schema del template è molto semplice, ovvero abbiamo una risorsa (risorsa di root) e una operazione che viene aggiunta alla risorsa di root ( rootRes.operations.Add(sampleOper)).
Come abbiamo detto precedentemente, poichè lo schema verrà creato via codice è importante definire bene le risorse e le operazioni che si vogliono supportare.

La funzione CreateRestSchema() è il cuore della nostra SOE Rest.
Qui è dove noi definiamo e creiamo lo schema del servizio dicendo quali risorse e operazioni intediamo supportare.

Per ogni risorsa che definiamo, utilizziamo RestResource, mentre per ogni operazione che definiamo utilizziamo RestOperation.
Una volta definite tutte le risorse e le operazioni, utilizziamo il metodo Add() sulla risorse per costruire lo schema.

Nel precedente schema, alla risorsa rootRes è stata aggiunta una operazione (sampleOper).

Quando creiamo una RestResource o una RestOperation, possiamo, opzionalmente, passare il nome di una capabilities come uno dei parametri del metodo e/o per le operation se si può chiamare esclusivamente via post (postOnly).

La risorsa può essere una collezione o meno. Il secondo parametro indica se la risorsa è una collezione.

Esempio di risorsa di tipo collection
RestResource customLayerResource = new RestResource("customLayers"true, CustomLayer, c_CapabilityGetInfo);

Ora, se una risorsa od una operazione è aggiunta in una risorsa di tipo collezione, quando chiameremo la nostra operazione/risorsa per recuperare il/i valore/i delle risorse di tipo collezione dei livelli superiori utilizzeremo la lista boundVariable contenente tutti valori di ogni risorsa di tipo collezione dei livelli superiori rispetto alla risorsa od operazione che si sta analizzando. Ogni valore è recuperato passando la chiave così composta "NomeRisorsa" + ID

//customLayers/{customLayersID}
        //returns json with simplified layerinfo (name, id, extent)
        private byte[] CustomLayer(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
        {
            responseProperties = null;
 
            //layerID
            int layerID = Convert.ToInt32(boundVariables["customLayersID"]);



RestOperation sampleOper = new RestOperation("sampleOperation",
                                                      new string[] { "parm1""parm2" },
                                                      new string[] { "json" },
                                                      SampleOperHandler, "GetSampleOper");

In questo esempio abbiamo definito una capability per l'operazione sampleOper di nome, ad esempio, GetSampleOper.
Le capabilities sono gruppi di risorse o operazioni che l'amministratore del gis server può abilitare o disabilitare per esporre o meno un certo sottoinsieme di funzionalità.

Queste capabilities sono chiamate "Operations Allowed" e vengono visualizzate automaticamente in ArcCatalog.

Nel progetto di registrazione della SOE impostare le proprietà seguenti:

// Capabilities
serverObjectExtensionType.Info.SetProperty("DefaultWebCapabilities"string.Format("{0}""GetSampleOper"));
serverObjectExtensionType.Info.SetProperty("AllWebCapabilities"string.Format("{0}""GetSampleOper"));


Abbiamo precedentemente accennato che ogni risorsa ed ogni operazione che aggiungiamo al nostro schema necessita di una funzione. Essa contiene la logica da eseguire quando si chiama la risorsa o l'operazione.
Queste funzioni sono dove noi scriveremo la maggior parte del nostro codice ArcObjects.



Se abilitiamo il nostro servizio a questa extension possiamo notare nella sezione Supported Extensions la nostra SOE Rest extension.
Selezionandola  vedremo la risposta del resource di root



Cliccando su rest vedremo la rappresentazione JSON della risorsa

{
  "hello" : "world"
}

Nell'esempio del template la risorsa ha una funzione chiamata RootResHandler(). L'esempio creato automaticamente dal template restituisce un semplice JSON hello : world.
La definizione della funzione che gestisce la risorsa è definita da un delegato .NET nella libreria ESRI SOESupport.
Questo significa che tutte le risorse hanno una firma uguale alla seguente:

public delegate byte[] ResourceHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties);

Il REST SOE template contiene anche la funzione per una operazione (chiamata SampleOperHandler()). In questo semplice esempio la funzione deserializza due parametri stringa (parm1 e parm2) e restituisce gli stessi come output in formato JSON. Puoi copiare e modificare questa funzione per ospitare le  operazioni del tuo schema.

Come per la risorsa, la firma della funzione per gestire un'operazione è definita da un delegato. Essa è simile a quella della risorsa eccetto il fatto di contenere un oggetto Json come input (operationInput). Questo oggetto Json ha dei parametri richiesti dall'operazione; per esempio, quando chiamiamo un'operazione  di buffer, l'oggetto Json potrebbe contenere un punto e la distanza di buffer.

public delegate byte[] OperationHandler(NameValueCollection boundVariables, JsonObject operationInput, string outputFormat, string requestProperties, out string responseProperties);

Gli elementi principali sui quali occorre concentrarsi in una funzione che gestisce una operazione sono: la deserializzazione dell' input JSON, lavorare con gli ArcObiects o con le proprie librerie e serializzare l'output in JSON. Deserializzare e serializzare il JSON può essere la parte più delicata.

Come possiamo notare, nelle firme abbiamo la responseProperties. Questa potrebbe essere utilizzata quando vogliamo restituire qualcosa che non può essere rappresentato da JSON, come ad esempio una immagine. In questo caso dobbiamo impostare il Content-type header dell' HTTP nelle proprietà della risposta.

Innanzitutto, quando definiamo la nostra risorsa o operazione, impostiamo i formati supportati. In questo esempio definiamo un'operazione che restituisce un PNG. Nella RestOperation è il terzo parametro che riceve un array di stringhe con i formati supportati.

RestOperation pngOper = new RestOperation("generatePng"nullnew string[]{"png"} , PngHandler);
soeResource.operations.Add(pngOper);

Nella funzione che gestisce l'operazione impostiamo come ultimo parametro (responseProperties) un JSON rappresentante il Content-Type header che desideriamo. Nel caso specifico image/png

private byte[] PngHandler(System.Collections.Specialized.NameValueCollection
                          boundVariables, ESRI.ArcGIS.SOESupport.JsonObject
                          operationInput, string outputFormat, string
                          requestProperties, out string responseProperties)
{
         responseProperties = "{\"Content-Type\" : \"image/png\"}";
         // Add code to generate and return a PNG.
}

altrimenti impostiamo a null se non dobbiamo impostare il Content-Type header.

Il JSON è un formato altamente strutturato per trasferire dati tra applicazioni e per i web services.
Gli ArcObjects però non capiscono il JSON, pertanto le nostre funzioni che gestiscono le risorse e le operazioni devono deserializzare l'input JSON;  ovverosia, estrarre i valori che occorrono per la logica di business. Una volta eseguita la business logic, il codice avrà la necessità di creare un JSON con i risultati così da serializzare la risposta.

La libreria SOESupport è stata creata da ESRI per essere di supporto a queste operaioni, serializzare e deserializzare JSON. Quando un client fa una richiesta, JSON è portato nella funzione che gestisce la risorsa o l'operazione come istanza della classe SOESupport.JsonObject; inoltre, possiamo inviare i nostri risultati dalla funzione come istanza di JsonObject.

Ma entriamo nel dettaglio: vedendo il codice generato nel template possiamo notare il pattern utilizzato nella funzione di gestione di una operazione.

private byte[] SampleOperHandler(NameValueCollection boundVariables,
                                                  JsonObject operationInput,
                                                      string outputFormat,
                                                      string requestProperties,
                                                  out string responseProperties)
        {
            responseProperties = null;
 
            string parm1Value;
            bool found = operationInput.TryGetString("parm1"out parm1Value);
            if (!found || string.IsNullOrEmpty(parm1Value))
                throw new ArgumentNullException("parm1");

L'istanza della classe JsonObject è il parametro operationInput. Il nostro codice dovrà provare a prendere i valori utilizzando i metodi TryGet inclusi in JsonObject.
Abbiamo a disposizione:
  • TryGetArray
  • TryGetAsBooolean
  • TryGetAsData
  • TryGetAsDouble
  • TryGetAsLong
  • TryGetJsonObject (gestione di oggetti di oggetti nidificati)
  • TryGetObject
  • TryGetString
Questi metodi permettono di estrarre il valore dei parametri JSON ed assegnarli a variabili nel nostro progetto. Successivamente potremo utilizzare queste variabili per creare gli ArcObjects che ci occorrono.

Per deserializzare le geometrie possiamo utilizzare il metodo  SOESupport.Conversion.ToGeometry()  che prende un oggetto JSON o una stringa di input e restituisce una IGeometry. Abbiamo anche ToSpatialReference che, data una stringa, restituisce uno ISpatialReference e ToJson che serializza in stringa Json UTF8 come array di byte un IRecordSet o una IGeometry.

In questo esempio vediamo un extension method per convertire oggetti JsonObjects in IGeometry (nota: alla fine è stato posto un try/catch perchè con la cultura IT è possibili aver problemi con la funzione TryGetDouble per il separatore decimale. L'alternativa è impostare nel web.config dell'applicazione rest ESRI la cultura IT):

        /// <summary>
        /// restituisce la Geometry corrispondente
        /// </summary>
        /// <param name="jsonObjectGeometry">oggetto JsonObject</param>
        /// <returns>restituisce la IGeometry</returns>
        internal static IGeometry ConvertAnyJsonGeometry(this JsonObject jsonObjectGeometry)
        {
            object[] objArray;
            ////double? nullable;
            if (jsonObjectGeometry.TryGetArray("rings"out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPolygon);
            }
 
            if (jsonObjectGeometry.TryGetArray("paths"out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPolyline);
            }
 
            if (jsonObjectGeometry.TryGetArray("points"out objArray))
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryMultipoint);
            }
 
            try
            {
                return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryPoint);
            }
            catch
            {
                try
                {
                    return Conversion.ToGeometry(jsonObjectGeometry, esriGeometryType.esriGeometryEnvelope);
                }
                catch
                {
                    return null;
                }
            }
        }


Chiaramente un controllo sull'oggetto è sempre essenziale per questi metodi. Se l'oggetto non contiene i dati necessari per costruire il punto, avremo un'eccezione.

Ma ora vediamo l'operazione inversa . Una volta completata la nostra business logic dobbiamo serializzare i nostri risultati in JSON o in stream indietro al client.
Vediamo come serializzare i risultati in JSON.

Per generare una risposta JSON possiamo creare  un'istanza della classe JsonObject ed aggiungere dati utilizzando i metodi che ci mette a disposizione  JsonObject.

JsonObject jsonPoint;
if (!operationInput.TryGetJsonObject("location"out jsonPoint))
                throw new ArgumentNullException("location");
IPoint location = Conversion.ToGeometry(jsonPoint,
                                                    esriGeometryType.esriGeometryPoint) as
                                                    IPoint;


Nell'esempio creaimo un oggetto JsonObject ed aggiungiamo una stringa con la proprietà parm1 e con il valore parm1Value. Se parm1Value ha il valore 'myFirstParameter' il risultato del JSON sarà:

JsonObject result = new JsonObject();
            result.AddString("parm1", parm1Value);

{
"parm1" : "myFirstParameter"
}

Come visto per i metodi di deserializzazione, abbiamo i seguenti metodi di serializzazione:
  • AddArray
  • AddBoolean
  • AddData
  • AddDouble
  • AddJsonObject (consente di aggiungere oggetti nidificati)
  • AddLong
  • AddObject
  • AddString
Alcune geometrie possono essere serializzate perchè contengono oggetti nidificati e array. Il modo più semplice è utilizzare  SOESupport.Conversion.ToJsonObject()  per serializzare le geometrie.
Passando un oggetto che implementa IGeometry prenderemo l'oggetto serializzato in Json.

In questo esempio serializziamo delle IGeometry e creiamo un oggetto Json con una proprietà geometries che contiene un array di oggetti Json (le nostre geometrie serializzate).

            // Creiamo una lista vouta di JsonObjects.
            List<JsonObject> jsonGeometries = new List<JsonObject>();
            
            JsonObject jsonResultsGeometry = Conversion.ToJsonObject(resultsGeometry);
            jsonGeometries.Add(jsonResultsGeometry);
            JsonObject resultJsonObject = new JsonObject();
            resultJsonObject.AddArray("geometries", jsonGeometries.ToArray());
            byte[] result = Encoding.UTF8.GetBytes(resultJsonObject.ToJson());
            return result;

Json prodotto:

"geometries" : [
    {
      "rings" : [
        [
          [
            537677.56250619888,
            4900994.4999926779
          ],
          [
            537952.21783445403,
            4900502.2883762196
          ],
          [
            537942.24243737175,
            4900503.3471435569
          ],
          etc. . .
        ]
      ]
    },
    {
      "rings" : [
        [
          [
            537952.21783445403,
            4900502.2883762196
          ],
          [
            537677.56250619888,
            4900994.4999926779
          ],
          [
            537826.87501833774,
            4901122.9999607969
          ],
          etc . . .
        ]
      ]
    }
  ]

Lato client: ad esempio, utilizzando le API ESRI Javascript, possiamo ciclare attraverso queste geometrie e creare un Polygon da visualizzare in mappa.

      var geometries = response.geometries;
      // Loop through all graphics in the JSON.
      for (var i = 0, il = geometries.length; i < il; i++) {
          // Make a new polygon.
          var polygon = new esri.geometry.Polygon(sr);
          // Loop through all rings in the JSON and add to polygon.
          for (var j = 0, jl = geometries[i].rings.length; j < jl; j++) {
              polygon.addRing(geometries[i].rings[j]);
          }
          // Create a graphic from the polygon and add it to the map.
          var currentGraphic = new esri.Graphic(polygon, symbol, attr, infoTemplate);
          map.graphics.add(currentGraphic);
      }

E' importante abilitare la nostra SOE a supportare l'utilizzo di servizi basati su MSD. Innanzitutto, perchè l'engine di drawing è più veloce rispetto ai servizi tradizionali basati su mxd, e poi perchè nella versione ArcGIS Server 10.1 i servizi MSD saranno i soli supportati.
Questo però, poichè l'engine di drawing è completamente differente, non supporta completamente tutti gli ArcObjects della libreria Carto e quindi, se il servizio si basa su un MSD, non possiamo utilizzare direttamente gli ArcObjects  relativi al documento di mappa come IMap, ILayer, IFeatureLayer o IMapServerObjects.

A questo punto, il consiglio è utilizzare la classe MapServer e classi/interfaccie collegate, che rappresentano il servizio, nella libreria Carto per prendere informazioni sui layer o per capire come eseguire interrogazioni sul servizio.

In questo esempio vediamo come accedere ai datasource dei layer della mappa del servizio. Una volta che arriviamo ai datasource, possiamo utilizzare gli ArcObjects per lavorare con i cursori o per eseguire operazioni topologiche. Innazitutto, come abbiamo detto, utilizziamo la classe MapServer per prendere il layer e poi utilizziamo IMapServerDataAccess per prendere i dati sottostanti.

        /// <summary>
        /// restituisce la featureclass dal mapserver
        /// </summary>
        /// <param name="mapServer">oggetto map server</param>
        /// <param name="layerID">identificativo del layer</param>
        /// <returns>restituisce la feature classe del layer</returns>
        internal static IFeatureClass GetFeatureClass(this IMapServer3 mapServer, int layerID)
        {
            IMapServerDataAccess dataAccess = (IMapServerDataAccess)mapServer;
            return dataAccess.GetDataSource(mapServer.DefaultMapName, layerID) as IFeatureClass;
        }

L'interfaccia IMapServer3 ha metodi che restituiscono i dati serializzati in JSon utilizzando da IQueryResultOptions la proprietà Format

 // Summary:
    //     Query Result Format.
    [Guid("6B819851-6A36-4A6F-8759-58CEF9915B36")]
    public enum esriQueryResultFormat
    {
        // Summary:
        //     Indicates RecordSet.
        esriQueryResultRecordSetAsObject = 0,
        //
        // Summary:
        //     Indicates KML.
        esriQueryResultKMLAsMime = 1,
        //
        // Summary:
        //     Indicates KML.
        esriQueryResultKMLAsURL = 2,
        //
        // Summary:
        //     Indicates JSON.
        esriQueryResultJsonAsMime = 3,
        //
        // Summary:
        //     Indicates JSON.
        esriQueryResultJsonAsURL = 4,
        //
        // Summary:
        //     Indicates AMF.
        esriQueryResultAMFAsMime = 5,
        //
        // Summary:
        //     Indicates AMF.
        esriQueryResultAMFAsURL = 6,
    }

Qui vediamo un esempio di come serializzare il risultato di una query in formato json mediante un stream di byte come risposta della nostra operazione. C'è da sottolineare come possiamo anche contestalmente effettuare una trasformazione di datum mediante la proprietà GeoTransformation della classe QueryResultOptionsClass.

private byte[] FindNearFeatures(int layerID, IPoint location, double distance)
        {
            if (layerID < 0)
                throw new ArgumentOutOfRangeException("layerID");
 
            if (distance <= 0.0)
                throw new ArgumentOutOfRangeException("distance");
 
            IMapServer3 mapServer = serverObjectHelper.ServerObject as IMapServer3;
            if (mapServer == null)
                throw new Exception("Unable to access the map server.");
 
            IGeometry queryGeometry = ((ITopologicalOperator)location).Buffer(distance);
 
            ISpatialFilter filter = new SpatialFilterClass();
            filter.Geometry = queryGeometry;
            filter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
 
            IQueryResultOptions resultOptions = new QueryResultOptionsClass();
            resultOptions.Format = esriQueryResultFormat.esriQueryResultJsonAsMime;
            
            AutoTimer timer = new AutoTimer(); //starts the timer
 
            IMapTableDescription tableDesc = GetTableDesc(mapServer, layerID);
 
            logger.LogMessage(ServerLogger.msgType.infoDetailed, "FindNearFeatures", -1, timer.Elapsed, "Finding table description elapsed this much.");
 
            IQueryResult result = mapServer.QueryData(mapServer.DefaultMapName, tableDesc, filter, resultOptions);
 
            return result.MimeData;
        }





Per abilitare la nostra SOE a supportare un servizio msd occorre, quando si registra, impostare la proprietà SupportsMSD a true cosicchè, quando pubblichiamo, possiamo vedere il servizio nella lista di quelli disponibili.

            // Proprietà per definire il support msd
            serverObjectExtensionType.Info.SetProperty("SupportsMSD""true");
            // Richiesto per abilitare l'esposizione della SOE con ArcGIS Server REST endpoint
            serverObjectExtensionType.Info.SetProperty("SupportsREST""true");

Se il nostro servizio è protetto, le regole di autenticazione sono applicate a tutte le extension del servizio, comprese le custom. Qui potete vedere come recuperare l'utente via codice. (Disponibile dalla 10 sp2)

Debug della SOE
Le SOE sono componenti COM che sono in esecuzione all'interno di ArcGIS Server e quindi occorre una specifica tecnica per l'attività di debugging.
Innanzitutto, l'assembly della SOE deve essere registrato con l'opzione /codebase, la quale crea nel registro una voce di codebase che specifica il percorso della dll per un assembly che non è specificato nella GAC (cache globale degli assembly). Il file che specifichiamo con /codebase deve essere un assembly con nome forte. In Visual Studio questo può essere facilmente impostato andando nelle pagine delle proprietà del progetto in Signing



Per eseguire un'attività di debugging sul server object container (SOC), impostare il processo ArcSOC.exe ad un minimo ed un massimo numero di istanze per servizio che ospita la SOE pari ad 1.
Questo evita che più di un processo SOC ospiti la nostra SOE; inoltre si potrebbero fermare tutti gli altri servizi per ridurre il numero di SOC e quindi si potrebbe selezionare più agevolmente il processo quando si esegue il debug.

Ricordarsi che ArcGIS server ha comunque due processi ArcSOC.exe che utilizza per le proprie attività (cancellazione file in arcgisoutput, logging arcgis server ecc.) .
Se ci sono molti processi SOC in esecuzione e non vogliamo interromperli, agganciamo il debugger a tutti i processi ArcSOC gestiti per amministrarli.

Una volta che il servizio con abilitata la SOE è in esecuzione in ArcGIS Server, agganciamo da Visual Studio il debugger al processo della SOC relativo.

I passi da seguire sono:

1) Aprire da Visual Studio la soluzione e i progetti contenenti l'implementazione della SOE.

2) Selezionare il menu Debug e selezionare Attach to Process



3) Una volta selezionato Attach to Process ci comparirà una maschera con l'elenco dei processi disponibili.
Se i processi ArcSOC.exe non sono visualizzati, spuntare le checkbox Show processes from all users e Show processes in all sessions. Occorre trovare i processi ArcSOC.exe che hanno nella colonna Type la voce "Managed, x86". Se ce n'è più di 1 determinare qual è quello associato con il servizio che ospita la SOE.



4) Una volta selezionato il processo ArcSOC.exe, occorre cliccare su Attach. Un messaggio sui possibili pericoli di processi potenzialmente pericolosi vi avvertirà. Poichè siamo in un contesto di testing  sicuro, confermiamo clicchiamo su Attach.



5) A questo punto inseriamo un breakpoint nel codice della SOE.



6) Dal cliente che utilizza la SOE eseguiamo una richiesta dove è coinvolto il nostro breakpoint aggiunto precedentemente.



7) A questo punto l'esecuzione si fermerà al nostro breakpoint consetendoci di proseguire in debug.



8)  Una volta che il debug è terminato o fermato Visual Studio si 'stacca' da ArcSOC.exe.

9) Se abbiamo la necessità di modificare il codice ed abbiamo registrato la SOE con l'opzione codebase, fermiamo il servizio arcgis server che utilizza la SOE, modifichiamo il codice, ricompiliamo il progetto e facciamo ripartire il servizio. Non c'è la necessità di rieseguire un regasm per la registrazione della dll o riaggiungerla alla GAC.

Infine, per poter eseguire un'attività di debug sul metodo Costruct, occorre inserire un ritardo di n secondi. In questo caso abbiamo per esempio 20 secondi di tempo per agganciare il processo e andare in debug sul costruct della SOE.

#region IObjectConstruct Members
        /// <summary>
        /// Metodo che parte all'inizio quando si avvia la SOE
        /// </summary>
        /// <param name="props">proprietà della SOE</param>
        public void Construct(IPropertySet props)
        {
            ////ho 20 sec per attaccare il processo e debuggare il costruttore dopo aver avviato il servizi
            System.Threading.Thread.Sleep(20000);
 
            AutoTimer timer = new AutoTimer();
            this.logger.LogMessage(ServerLogger.msgType.infoSimple, "Construct", -1, this.soeName + ": il costruttore è stato avviato.");
 
            this.configProps = props;


Property Page per una server object extension

Se creiamo una SOE che deve essere utilizzata da più di un servizio è facile che abbiamo l'esigenza di impostare parametri in funzione del servizio utilizzato. Ad esempio, desideriamo impostare un determinato layer ad essere coinvolto nelle richieste della SOE ma non altri, oppure impostare parametri che siano indipendenti dalla richiesta fatta dal client - ad esempio il numero massimo di record restituiti per la nostra elaborazione (potrebbe essere nel caso specifico un override del parametro del servizio stesso).
In queste situazioni, possiamo costruire una property page che si posizionerà nella scheda delle capabilities della maschera di dialogo delle proprietà del servizio in ArcCatalog o in ArcGIS Server Manager
Chiaramente, se la SOE dipende da proprietà del property page, non possiamo utilizzare l'Add New Service Wizard poichè per poterla utilizzare dobbiamo impostare queste proprietà. Quindi in questo caso le alternative sono due: o impostiamo dei valori di default nelle proprietà che abbbiamo creato per la SOE, oppure pubblichiamo il servizio senza SOE abilitata e poi andiamo sui servizi, fermiamo il servizio, impostiamo le proprietà  dalla property page e riavviamo il servizio.

Per sviluppare una property page creiamo una nuova Windows Form. Le proprietà della SOE possono essere impostate basandosi sugli eventi della form (per esempio cambio selezione di una combobox).

Dobbiamo implementare  ESRI.ArcGIS.Framework.IComPropertyPage e ESRI.ArcGIS.CatalogUI.IAGSSOEParameterPage  per la nostra page per inserirla in ArcCatalog.

La nostra page deve implementare per la IComPropertyPage  i seguenti membri:
  • PageSite
  • Activate
  • Show
  • Hide
I membri Height e Deactivate non sono richiesti ma se necessari possiamo implementarli mentri i rimanenti non sono utilizzati.

L'Interfaccia IAGSSOEParameterPage definisce un insieme di proprietà per la SOE che devono essere esplicitamente implementate. Queste includono:
  • Tipo di servizio  (ServerObjectType)
  • Proprietà non relazionate alla SOE (ServerObjectProperties)
  • Nome della SOE (ServerObjectExtensionType)
  • Proprietà che definiamo per la SOE (ExtensionProperties)

Ad esempio, qui possiamo vedere Come creare pagine property SOE : come usare il set della proprietà ServerObjectProperties per farsi restituire il percorso del documento di mappa ed eseguire proprie logiche per farsi restituire il layer ed i propri campi.

Quando una SOE è abilitata in ArcCatalog cliccando OK o Apply, le sue proprietà sono restituite dalla pagina della SOE property attraverso il get della proprietà ExtensionProperties. Queste proprietà restituite sono aggiunte al file di configurazione del servizio di mappa ( C:\Program Files\ArcGIS\Server10.0\server\user\cfg ). Quando vengono effettuate modifiche alle proprietà della SOE, queste si riflettono nella property page attraverso il set della proprietà ExtensionProperties.

Per semplificare il codice della property page, potremmo voler implementare le nostre interfacce richieste ( IComPropertyPage and IAGSSOEParameterPage ) in una classe astratta e poi raggruppare la logica che le implementa in una classe separata che eredita dalla classe astratta.

Negli esempi ESRI troverete la classe astratta in SOEPropertyPage.cs e la PropertyPage.cs che eredita da essa. Tutto il codice con i set e i get delle proprietà della SOE è implementato nella PropertyPage.cs.

La nostra property page deve contenere del codice che quando è registrato con il COM include la categoria del componente AGSSOEParameterPages.  Le categorie del componente sono utilizzate dall'applicazione client per determinare in modo efficiente tutte le componenti di un particolare tipo che sono installate nel sistema. Se le categorie del componente non sono utilizzate, l'applicazione avrebbe dovuto istanziare ogni componente COM ed interrogarla per vedere se supporta la funzionalità richiesta - il che non è un approccio pratico.

ArcCatalog cerca soltanto  le pagine property nella categoria del  componente AGSSOEParameterPages.  Nella classe in SOEPropertyPage.cs il seguente codice assicura che la pagina property è registrata nella categoria AGSSOEParameterPages.

Per verificarlo possiamo eseguire categories.exe in <ArcGIS Desktop install>\bin ed espandere la categoria ESRI AGS Extension Parameter Pages. Qui potremo vedere anche altre pagine sviluppate da ESRI (KML, WCS, WFS ecc) che sono SOE.

Una volta sviluppata la nostra property page, dobbiamo registrare il COM così che i tipi .NET e la windows form possano essere utilizzati in ArcCatalog

In <Program files>\Common Files\ArcGIS\bin abbiamo l'utility ESRIRegAsm.exe:

ESRIRegAsm.exe "<path to ArcCatalog property page dll>" /p:Desktop


Qui potete vedere un esempio: Print SOE Rest

Instructions:

Print SOE Rest
Author: Domenico Ciavarella
http://www.studioat.it/
February 13th 2011
Summary:
This solution (developed in c#) creates a SOE Rest in arcgis server to print template mxd from client.
For now you can print services arcgis server and format pdf.

Installation:
a) register unregister dll and soe
 - register dll in SOC machine
 1) %WinDir%\Microsoft.NET\Framework\v2.0.50727\regasm Studioat.ARCGIS.SOE.REST.dll /codebase

 - register soe in ArcGIS server (SOM)
 1) Studioat.ARCGIS.SOE.Register.exe -c Studioat.ARCGIS.SOE.REST.Print -n Print -l "Your title"

for unregister
 1) Studioat.ARCGIS.SOE.Register.exe -n Print -u
 2) %WinDir%\Microsoft.NET\Framework\v2.0.50727\regasm Studioat.ARCGIS.SOE.REST.dll /codebase /u

b) create a service map and enable in capabilities the extension. The msd used for the service that is published isn't important because it is not used.
c) in the same directory where the msd is we must create a folder called 'Applications'. Here we create as many folder as our printing applications are and in each of them we put the mxds which will be used for printing.
   ---yourmsdpublish.msd
   ---Applications (folder)
              MyAppWorld (folder)
                  templateWorld.mxd
                  other mxds
              MyApplicationsRiver (folder)
                 River1.mxd
                 TemplateRivers.mxd
              and so on...
d) from service directory yuo can see all your applications
http://localhost/ArcGIS/rest/services/%3Cyourservice%3E/MapServer/exts/Print
e) see all template of application with id=0
http://localhost/ArcGIS/rest/services/%3Cyourservice%3E/MapServer/exts/Print/applications/0
  
f) see parameter for print template with id=0 and application with id=0
http://localhost/ArcGIS/rest/services/%3Cyourservice%3E/MapServer/exts/Print/applications/0/templates/0/printtemplate

Parameters:
1) extent: Geometry Envelope (see rest api esri)
example:
{"xmin":-8582617.03619766,"ymin":4896408.449978509,"xmax":-8535608.26380234,"ymax":4915441.270021492,"spatialReference":{"wkid":102113}}
2) scale: number (optional)
3) services: array objects (name, serverConnection, transparency(optional), layers (optional))
[{"name":"World_Street_Map", "serverConnection":"http://server.arcgisonline.com/ArcGIS/services"}, { "name":"ArcPyMapping/Birds", "serverConnection":"http://ec2-50-17-53-73.compute-1.amazonaws.com/ArcGIS/services", "transparency":50}]
name: name of service
serverConnection: connection to server
transparency: effect transparency of layer
layers: (visibility layers of service: see parameter layers in export map of rest api esri)
4) properties: (optional).  Object (texts (optional), graphics(optional))
{"texts":[{"text":"texttitle","value":"Map title"},{"text":"textscala","value":""}],"graphics":[{"geometry":{"x":-8556360.916981738,"y":4907186.070966704,"spatialReference":{"wkid":102113}},"symbol":{"color":[0,255,0,64],"size":7.5,"angle":0,"xoffset":0,"yoffset":0,"type":"esriSMS","style":"esriSMSSquare","outline":{"color":[255,0,0,255],"width":0.75,"type":"esriSLS","style":"esriSLSSolid"}}},{"geometry":{"x":-8557583.909434298,"y":4904778.3045757245,"spatialReference":{"wkid":102113}},"symbol":{"color":[0,255,0,64],"size":7.5,"angle":0,"xoffset":0,"yoffset":0,"type":"esriSMS","style":"esriSMSSquare","outline":{"color":[255,0,0,255],"width":0.75,"type":"esriSLS","style":"esriSLSSolid"}}}]}
texts: array of objects (text, value)
if in template in pagelayout you have added graphic texts with name %My name% you return list from (example application=0 and template=0)
http://localhost/ArcGIS/rest/services/<my service>/MapServer/exts/Print/applications/0/templates/0?f=pjson
{
  "name" : "TemplateA4_3",
  "id" : 0,
  "texts" : [
    "textscala",
    "texttitle"
  ]
}
graphics: array of graphics (from esri api javascript: yourgraphic.toJson())
5) resolution: number (optional) range from 150 to 600. If you want to change this range you must change in code (there are two constants)
6) options: array properties for pdf (optional)
{"embedFonts": true, "imageCompression":"esriExportImageCompressionDeflate" ...}

I have added in file zip an example in api esri javascript to see  how to use.