In this topic
- Creating the SOE solution and interfaces project
- Creating the interfaces class
- Implementing the SOE interfaces class
- Setting assembly properties for SpatialQuerySOE.Interfaces_CSharp project
- Creating the SOE project
- Creating the Results class
- Implementing the Results class
- Creating the SOE class
- Implementing the SOE class
- Setting assembly properties for the SpatialQuerySOE_CSharp project
- Building the SOE projects
Creating the SOE solution and interfaces project
In this section of the implementation, do the following steps to create a project that contains interfaces implemented by SOE classes and utilized by clients:
- Start Visual Studio.
- Click the File menu, click New, then click Project. The New Project dialog box appears.
- Under the Project types pane, click to expand the Other Project Types node, then click Visual Studio solutions. Click Blank solution under the Visual Studio installed templates.
- Type ArcGIS_Spatial_Query_SOE_CSharp2008 in the Name text box.
- Type the path in the Location text box or click Browse for the location.
- Click OK.
- In the Solution Explorer, right-click the solution and select Add, New Solution Folder, then name the folder Implementation (where you will create the projects storing the SOE's implementation). See the following screen shot:
- Right-click the Implementation folder and select Add, then New Project. The Add New Project dialog box appears.
- Under the Project types pane, click to expand the Visual C# project type. Under the Visual Studio installed templates, select Class Library.
- Type the path or click Browse for the location.
- Type SpatialQuerySOE.Interfaces_CSharp in the Name text box for the project name.
- Click OK. In the Solution Explorer, right-click Class1.cs and select Delete.
- Right-click the project in the Solution Explorer and select Properties.
- On the Application tab, specify an assembly name and default namespace of SpatialQuerySOE.Interfaces.
Creating the interfaces class
You will create a C# (Interfaces) class that contains two interfaces to implement within the SOE classes in another project. Interfaces for the SOE are stored in a separate assembly because client applications only need the interfaces registered as Component Object Model (COM) types to interact with the SOE. The business logic for the SOE only needs to reside on the GIS server and does not need to be provided to the client. As a result, the assembly provided to a client application only contains interfaces (no business logic). Similar to working with ArcObjects remotely via ArcGIS Server, a client uses a custom SOE interface to work with SOE classes (objects) remotely via ArcGIS Server.
Do the following steps to add the new class to the SpatialQuerySOE.Interfaces_CSharp project:
- In the Solution Explorer, right-click the SpatialQuerySOE.Interfaces_CSharp project, click Add, then click New Item. The Add New Item dialog box appears.
- Under the Templates pane, click Class.
- Type Interfaces in the Name text box.
- Click Add. A new class is added to the project and code for the class appears with automatically generated code.
Implementing the SOE interfaces class
You will need to add references to assemblies that contain the required ArcObjects for the interface definitions in this class file. Because the class contains interfaces that will be exposed as COM types, it needs to reference additional assemblies to manage interoperability.
Do the following steps to implement the SOE interfaces class:
- Add references to the following assemblies:
- ESRI.ArcGIS.Carto
- ESRI.ArcGIS.Display
- ESRI.ArcGIS.Geodatabase
- ESRI.ArcGIS.Geometry
- ESRI.ArcGIS.Server
- ESRI.ArcGIS.System
- System.EnterpriseServices
- Add the SpatialQuerySOE.Interfaces namespace. Within the namespace definition, add the IExtension and IResults interfaces.
The IExtension interface defines the basic framework to implement by the SpatialQuerySOE.Extension class, namely a method to accept a user provided ArcObjects object of type IPoint and a double value to define the distance around the point to generate a buffer. An ArcGIS Server client can work with a reference to the SpatialQuerySOE.Extension object running within the server object container (SOC) via the QueryPoint method on the IExtension interface. This pattern follows standard programming techniques for working with ArcObjects (COM objects) remotely via ArcGIS Server. The QueryPoint method creates a SpatialQuerySOE.Results object in the server container process, updates its properties, and returns to the client a reference to the remote SpatialQuerySOE.Results COM object via the IResults interface.
The IResults interface defines the basic framework to implement by the SpatialQuerySOE.Results class, namely two properties to store a reference to an array of graphic elements and a set of records.
Since both interfaces are registered (added to the registry) with COM, they should define a globally unique identifier (GUID) using the COM attribute, GuidAttribute. The GUID is used to uniquely identify an application, component, class, interface, and so on within the Windows registry. There are many options to generate a unique GUID. You can create a unique GUID using the .NET System.Guid struct. The call to System.Guid.NewGuid().ToString() returns a usable GUID string. Additionally, various Web sites and services provide GUID generating capabilities. The following code example shows code for the interface:
namespace SpatialQuerySOE.Interfaces
{
[GuidAttribute("9D47D51C-F2B8-4381-8FA7-A6E1E1E9792F")]
public interface IExtension
{
IResults QueryPoint(ESRI.ArcGIS.Geometry.IPoint point, double distance);
}
[GuidAttribute("19A98B11-1EDE-4470-88FD-007F4B7BD05F")]
public interface IResults
{
ESRI.ArcGIS.Carto.IGraphicElements ResultsGraphics
{
get;
set;
}
ESRI.ArcGIS.Geodatabase.IRecordSet SummaryStatistics
{
get;
set;
}
}
}
[VB.NET]
Namespace SpatialQuerySOE.Interfaces_VBNet
<Guid("20433F77-8AB9-45b0-B858-C3144EC50347")> _
Public Interface IExtension
Function QueryPoint(ByVal point As ESRI.ArcGIS.Geometry.IPoint, ByVal distance As Double) As IResults
End Interface
<Guid("D4086DEF-564B-4442-94EE-C4DB0C90402A")> _
Public Interface IResults
Property ResultsGraphics() As ESRI.ArcGIS.Carto.IGraphicElements
Property SummaryStatistics() As ESRI.ArcGIS.Geodatabase.IRecordSet
End Interface
End Namespace
Setting assembly properties for SpatialQuerySOE.Interfaces_CSharp project
Do the following steps to set the assembly properties:
- In the SpatialQuerySOE.Interfaces_CSharp project, open the AssemblyInfo.cs file and change it to reflect the settings in the following code example.
- Set the ComVisibleAttribute to true and specify a unique GUID for the GuidAttribute. Since the assembly is used to generate a type library, these attributes enable the interfaces to be registered with COM and defines a unique identifier.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SpatialQuerySOE.Interfaces")][assembly:
AssemblyDescription("")][assembly: AssemblyConfiguration("")][assembly:
AssemblyCompany("ESRI")][assembly: AssemblyProduct("SpatialQuerySOE.Interfaces")
][assembly: AssemblyCopyright("Copyright © ESRI 2010")][assembly:
AssemblyTrademark("")][assembly: AssemblyCulture("")][assembly: AssemblyVersion(
"1.0.0.0")][assembly: AssemblyFileVersion("1.0.0.0")][assembly:
ComVisibleAttribute(true)][assembly: GuidAttribute(
"8B929DF3-7D8A-4119-BEC8-FA8986CA10E3")]
[VB.NET]
Imports Microsoft.VisualBasic
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
<Assembly
AssemblyTitle("SpatialQuerySOE.Interfaces_VBNet")>
<Assembly
AssemblyDescription("")>
<Assembly
AssemblyConfiguration("")>
<Assembly
AssemblyCompany("ESRI")>
<Assembly
AssemblyProduct("SpatialQuerySOE.Interfaces_VBNet")>
<Assembly
AssemblyCopyright("Copyright © ESRI 2010")>
<Assembly
AssemblyTrademark("")>
<Assembly
AssemblyCulture("")>
<Assembly
AssemblyVersion("1.0.0.0")>
<Assembly
AssemblyFileVersion("1.0.0.0")>
<Assembly
ComVisibleAttribute(True)>
<Assembly
Guid("07A3ECE1-FE0D-4aa3-A84F-72DE58F7CAEB")>
Creating the SOE project
Do the following steps to create the SOE project:
- In the same Visual Studio session used to create the SpatialQuerySOE.Interfaces_CSharp project, right-click the Implementation folder, click Add, then click New Project. The Add New Project dialog box appears.
- Under the Project types pane, click the Visual C# project type. Under the Templates pane, click Class Library.
- In the Location text box, type the path or click Browse for the location.
- Type SpatialQuerySOE_CSharp in the Name text box.
- Click OK. In the Solution Explorer, right-click Class1.cs and select Delete.
- Right-click the project in the Solution Explorer and select Properties.
- On the Application tab of the Properties page, specify an assembly name and a default namespace of SpatialQuerySOE.
Creating the Results class
You will create a C# class—named Extension—that represents the SOE, which provides a method to perform the spatial query, and returns an object of type Results that contains an array of graphic elements and a recordset. Before creating the SOE, create the Results (spatial query results) class. This class is used as a complex type to store the results of the SOE in a single object for use by the consuming client (for example, Web application).
Do the following steps to add the new class to the SpatialQuerySOE_CSharp project:
- In the Solution Explorer, right-click the SpatialQuerySOE_CSharp project, click Add, then click New Item. The Add New Item dialog box appears.
- Under the Templates pane, click Class.
- Type Results.cs in the Name text box.
- Click Add. A new class is added to your project and the code appears for the class with automatically generated code. Delete the automatically generated code.
Implementing the Results class
You need to add references to assemblies that contain the required ArcObjects for this class. Because this is a COM object to use in the GIS server, add additional references to support calling a managed .NET component from a COM client. The .NET component is the custom SOE assembly and the COM client is the container process, ArcSOC.exe. The COM client uses a COM callable wrapper to work with the .NET component. COM interop is the technology that permits .NET components and COM clients to work together.
Do the following steps to implement the Results class:
- Add references to the following assemblies:
- ESRI.ArcGIS.Carto
- ESRI.ArcGIS.Display
- ESRI.ArcGIS.Geodatabase
- ESRI.ArcGIS.Geometry
- ESRI.ArcGIS.Server
- ESRI.ArcGIS.System
- System.EnterpriseServices
- Add a reference to the SpatialQuerySOE.Interfaces_CSharp project in the same solution. If the SpatialQuerySOE.Interfaces_CSharp project is not in the same solution, add a reference to the SpatialQuerySOE.Interfaces assembly.
- Define the public COM class to contain implementation of the Results object. Since this class is exposed to COM, some COM attributes need to be added to the class definition (specifically, AutomationProxy, ClassInterface, and GuidAttribute). AutomationProxy determines if an object should be marshaled using the automation marshaller (true) or custom marshaled (false). In this case, use the automation marshaller.
ClassInterface defines how interfaces to the COM class are generated in the type library and exposed to a COM client. In this case, you want to support COM versioning and explicitly define the default interface by which a COM client accesses the class. Setting the ClassInterface attribute to ClassInterfaceType.None requires that you define an explicit interface (IResults) to work with your class (Results). Reference the GuidAttribute with a unique GUID to identify your class.
Derive your class from the ServicedComponent class to be hosted by COM clients. Implement the IResults interface as a default interface to interact with your COM object. Define two properties to get and set the graphic elements and recordset—ResultsGraphics and SummaryStatistics, respectively. See the following code example:
namespace SpatialQuerySOE
{
[AutomationProxy(true), ClassInterface(ClassInterfaceType.None), GuidAttribute(
"C9FB0536-D1D6-455d-897D-8AD26849C79A")]
public class Results: ServicedComponent, SpatialQuerySOE.Interfaces.IResults
{
private ESRI.ArcGIS.Carto.IGraphicElements m_resultsGraphics;
private ESRI.ArcGIS.Geodatabase.IRecordSet m_summaryStats;
public ESRI.ArcGIS.Carto.IGraphicElements ResultsGraphics
{
get
{
return m_resultsGraphics;
}
set
{
m_resultsGraphics = (ESRI.ArcGIS.Carto.IGraphicElements)value;
}
}
public ESRI.ArcGIS.Geodatabase.IRecordSet SummaryStatistics
{
get
{
return m_summaryStats;
}
set
{
m_summaryStats = (ESRI.ArcGIS.Geodatabase.IRecordSet)value;
}
}
}
}
[VB.NET]
Namespace SpatialQuerySOE_VBNet
<AutomationProxy(True), ClassInterface(ClassInterfaceType.None), Guid("EC1526DE-97E9-4228-8972-DE50C4A71696")> _
Public Class Results
Inherits ServicedComponent
Implements SpatialQuerySOE.Interfaces_VBNet.IResults
Private m_resultsGraphics As ESRI.ArcGIS.Carto.IGraphicElements
Private m_summaryStats As ESRI.ArcGIS.Geodatabase.IRecordSet
Public Property ResultsGraphics() As ESRI.ArcGIS.Carto.IGraphicElements Implements SpatialQuerySOE.Interfaces_VBNet.IResults.ResultsGraphics
Get
Return m_resultsGraphics
End Get
Set(ByVal Value As ESRI.ArcGIS.Carto.IGraphicElements)
m_resultsGraphics = CType(Value, ESRI.ArcGIS.Carto.IGraphicElements)
End Set
End Property
Public Property SummaryStatistics() As ESRI.ArcGIS.Geodatabase.IRecordSet Implements SpatialQuerySOE.Interfaces_VBNet.IResults.SummaryStatistics
Get
Return m_summaryStats
End Get
Set(ByVal Value As ESRI.ArcGIS.Geodatabase.IRecordSet)
m_summaryStats = CType(Value, ESRI.ArcGIS.Geodatabase.IRecordSet)
End Set
End Property
End Class
End Namespace
Creating the SOE class
Do the following steps to add the new class to the project:
- In the Solution Explorer, right-click the SpatialQuerySOE_CSharp project, click Add, then click New Item. The Add New Item dialog box appears.
- Under the Templates pane, click Class.
- Type Extension.cs in the Name text box.
- Click Add. A new class is added to the project and the code for the class appears with automatically generated code. Delete the automatically generated code.
Implementing the SOE class
Now that you have implemented the Results class, you can implement the SOE class. SOEs extend a server object with additional interfaces to provide more specialized functionality. In this case, the SOE implements the IExtension custom interface.
- Define the public COM class to contain the implementation of the Extension object. Since this class is exposed to COM, COM attributes need to be added to the class definition (specifically, AutomationProxy, ClassInterface, and GuidAttribute).
AutomationProxy determines if an object should be marshaled using the automation marshaller (true) or custom marshaled (false). You will use the automation marshaller. ClassInterface defines how interfaces to the COM class are generated in the type library and exposed to a COM client. In this case, you want to support COM versioning and explicitly define the default interface by which a COM client accesses the class. Setting the ClassInterface attribute to ClassInterfaceType.None requires that you define an explicit interface (IExtension) to work with your class (Extension). Reference the GuidAttribute—a unique GUID—to identify your class.
Create a set of member variables to store a reference to an IServerObjectHelper (IServerObjectExtension implementation), an ILayer, two strings to account for layer name and field name (IExtension implementation), and an ILog (ILogSupport implementation). Populating these variables will be discussed in the next section. See the following code example:
namespace SpatialQuerySOE
{
[AutomationProxy(true), ClassInterface(ClassInterfaceType.None), GuidAttribute(
"1932805D-7266-41a2-9428-0421A5617436")]
public class Extension: ServicedComponent, SpatialQuerySOE.Interfaces.IExtension,
ESRI.ArcGIS.Server.IServerObjectExtension,
ESRI.ArcGIS.esriSystem.IObjectConstruct, ESRI.ArcGIS.esriSystem.ILogSupport,
ESRI.ArcGIS.esriSystem.IObjectActivate
{
private ESRI.ArcGIS.Server.IServerObjectHelper m_ServerObjectHelper;
private ESRI.ArcGIS.Carto.IFeatureLayer m_featureLayer;
private string m_layerName;
private string m_fieldName;
private ESRI.ArcGIS.esriSystem.ILog m_log;
[VB.NET]
Namespace SpatialQuerySOE_VBNet
<AutomationProxy(True), ClassInterface(ClassInterfaceType.None), Guid("3896A0F5-1028-489c-81CD-F7757D117C9E")> _
Public Class Extension
Inherits ServicedComponent
Implements SpatialQuerySOE.Interfaces_VBNet.IExtension, ESRI.ArcGIS.Server.IServerObjectExtension, ESRI.ArcGIS.esriSystem.IObjectConstruct, ESRI.ArcGIS.esriSystem.ILogSupport, ESRI.ArcGIS.esriSystem.IObjectActivate
Private m_ServerObjectHelper As ESRI.ArcGIS.Server.IServerObjectHelper
Private m_featureLayer As ESRI.ArcGIS.Carto.IFeatureLayer
Private m_layerName As String
Private m_fieldName As String
Private m_log As ESRI.ArcGIS.esriSystem.ILog
- Implement the following class and interfaces:
- ServicedComponent (class)
- ILogSupport
- IServerObjectExtension
- IObjectConstruct
- IObjectActivate
- IExtension
- ServicedComponent—Derive the Extension class from the ServicedComponent class hosted by COM clients (for example, ArcSOC.exe). Additional code is not required.
- ILogSupport—If you want your SOE to log messages to the GIS server's log file, your SOE should implement ILogSupport. ILogSupport is an optional interface for SOEs that has a single InitLogging method. InitLogging is called when the SOE is created and returns a reference to the GIS server's log object via the log argument. Once you have a reference to the server log, you will often call a single method—AddMessage()—to add information to the log. The AddMessage() method has the following parameters:
- Level—Message's level of detail in relation to other messages. Levels are classified from 1 to 5 as follows:
- 1 = Error
- 2 = Warning
- 3 = Normal
- 4 = Detailed
- 5 = Debug
- Code—Result code associated with the message. The code is an arbitrary integer value to uniquely define the source of the message. Codes 0 through 5999 are utilized by the service object manager (SOM). Codes 6000 and above can be generated by any serviced component (for example, MapServer, GeocodeServer, custom component, and so on).
-
Message—Custom string inserted into the GIS server log file. See the following code example:
The following illustration shows the order in which the methods of interfaces implemented by an SOE are called during initialization and used:
See the following discussion and appropriate implementation code:
ArcGIS Server log file settings determine the messages that are included in the server log.
public void InitLogging(ESRI.ArcGIS.esriSystem.ILog log)
{
m_log = log;
}
[VB.NET]
Public Sub InitLogging(ByVal Log As ESRI.ArcGIS.esriSystem.ILog) Implements ESRI.ArcGIS.esriSystem.ILogSupport.InitLogging
m_log = Log
End Sub
-
IServerObjectExtension—A mandatory interface that must be supported by all SOEs and includes the Init and Shutdown methods. This interface is used by the server object to manage the SOE's life span. The server object cocreates the SOE and calls the Init method, handing it a reference to the server object via the server object helper argument. The server object helper implements a weak reference on the server object. The extension can keep a strong reference on the server object helper (for example, in a member variable) but should not keep a strong reference on the server object.
Extensions should get the server object from the server object helper to make any method calls on the server object and release the reference after making the method calls. Init is called once, when the instance of the SOE is created. The Shutdown method is called once and informs the SOE that the server object's context is shutting down. In response, the SOE releases its reference on the server object helper. The log entries are informative and optional. See the following code example:
public void Init(ESRI.ArcGIS.Server.IServerObjectHelper pSOH)
{
m_ServerObjectHelper = pSOH;
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Init called");
}
public void Shutdown()
{
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Shutdown called");
m_ServerObjectHelper = null;
m_featureLayer = null;
m_log = null;
}
[VB.NET]
Public Sub Init(ByVal pSOH As ESRI.ArcGIS.Server.IServerObjectHelper) Implements ESRI.ArcGIS.Server.IServerObjectExtension.Init
m_ServerObjectHelper = pSOH
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Init called")
End Sub
Public Sub Shutdown() Implements ESRI.ArcGIS.Server.IServerObjectExtension.Shutdown
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Shutdown called")
m_ServerObjectHelper = Nothing
m_featureLayer = Nothing
m_log = Nothing
End Sub
-
IObjectConstruct—If your SOE includes configuration properties or requires additional initialization logic, implement IObjectConstruct. IObjectConstruct is an optional interface for SOEs. The interface includes a single method called Construct. Construct is called only once—when the SOE is created—after IServerObjectExtension.Init is called. Include any expensive initialization logic within your implementation of Construct.
Construct returns the configuration properties for the SOE as a property set. The configuration properties are stored in the server object configuration file. Configuration files are named <service name>.<server object type>.cfg and stored on the SOM machine in the <ArcGIS Install>\server\user\cfg directory. For example, a map service named Yellowstone has a configuration file named Yellowstone.MapServer.cfg. SOE properties configured to use with a server object are also stored in the .cfg file. In this case, a set of properties are read from the service's .cfg file. The property values, a feature layer name and field name, are validated to confirm they exist in the default map frame associated with the map server object. If an error occurs, the appropriate log entries are added. See the following code example:
public void Construct(ESRI.ArcGIS.esriSystem.IPropertySet props)
{
try
{
m_layerName = props.GetProperty("LayerName")as string;
m_fieldName = props.GetProperty("FieldName")as string;
}
catch (Exception ex)
{
m_log.AddMessage(1, 8000,
"SpatialQuerySOE custom error. Error reading properties: " + ex.Message
+ " " + props.Count.ToString());
return ;
}
try
{
// Get the map underlying the map service and the IGeoFeatureLayers contained
// in the map.
ESRI.ArcGIS.Carto.IMapServer mapServer = (ESRI.ArcGIS.Carto.IMapServer)
m_ServerObjectHelper.ServerObject;
ESRI.ArcGIS.Carto.IMapServerObjects mapServerObjects =
(ESRI.ArcGIS.Carto.IMapServerObjects)mapServer;
ESRI.ArcGIS.Carto.IMap map = mapServerObjects.get_Map
(mapServer.DefaultMapName);
ESRI.ArcGIS.esriSystem.UID layerTypeID = new ESRI.ArcGIS.esriSystem.UIDClass
();
layerTypeID.Value = "{E156D7E5-22AF-11D3-9F99-00C04F6BC78E}";
ESRI.ArcGIS.Carto.IEnumLayer enumLayer = map.get_Layers(layerTypeID, true);
enumLayer.Reset();
// Get the layer specified as the SOE layer.
while ((m_featureLayer = enumLayer.Next()as ESRI.ArcGIS.Carto.IFeatureLayer)
!= null)
{
if (m_featureLayer.Name == m_layerName)
break;
}
if (m_featureLayer == null)
{
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: Layer " +
m_layerName + " not found.");
return ;
}
// Make sure the layer contains the field specified by the SOE's configuration.
if (m_featureLayer.FeatureClass.FindField(m_fieldName) == - 1)
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: Field " +
m_fieldName + " not found in layer " + m_layerName);
else
m_log.AddMessage(3, 8000, "SpatialQuerySOE successfully initialized.");
}
catch (Exception ex)
{
m_log.AddMessage(1, 8000,
"SpatialQuerySOE custom error: Failed to initialize extension: " +
ex.Message + "::" + ex.StackTrace.Length.ToString());
}
}
[VB.NET]
Public Overloads Sub Construct(ByVal props As ESRI.ArcGIS.esriSystem.IPropertySet) Implements ESRI.ArcGIS.esriSystem.IObjectConstruct.Construct
Try
m_layerName = TryCast(props.GetProperty("LayerName"), String)
m_fieldName = TryCast(props.GetProperty("FieldName"), String)
Catch ex As Exception
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error. Error reading properties: " & ex.Message & " " & props.Count.ToString())
Return
End Try
Try
' Get the map underlying the map service and the IGeoFeatureLayers contained in the map.
Dim mapServer As ESRI.ArcGIS.Carto.IMapServer = CType(m_ServerObjectHelper.ServerObject, ESRI.ArcGIS.Carto.IMapServer)
Dim mapServerObjects As ESRI.ArcGIS.Carto.IMapServerObjects = CType(mapServer, ESRI.ArcGIS.Carto.IMapServerObjects)
Dim map As ESRI.ArcGIS.Carto.IMap = mapServerObjects.Map(mapServer.DefaultMapName)
Dim layerTypeID As ESRI.ArcGIS.esriSystem.UID = New ESRI.ArcGIS.esriSystem.UIDClass()
layerTypeID.Value = "{E156D7E5-22AF-11D3-9F99-00C04F6BC78E}"
Dim enumLayer As ESRI.ArcGIS.Carto.IEnumLayer = map.Layers(layerTypeID, True)
enumLayer.Reset()
' Get the layer specified as the SOE layer.
m_featureLayer = TryCast(enumLayer.Next(), ESRI.ArcGIS.Carto.IFeatureLayer)
Do While m_featureLayer IsNot Nothing
If m_featureLayer.Name = m_layerName Then
Exit Do
End If
m_featureLayer = TryCast(enumLayer.Next(), ESRI.ArcGIS.Carto.IFeatureLayer)
Loop
If m_featureLayer Is Nothing Then
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: Layer " & m_layerName & " not found.")
Return
End If
' Make sure the layer contains the field specified by the SOE's configuration.
If m_featureLayer.FeatureClass.FindField(m_fieldName) = -1 Then
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: Field " & m_fieldName & " not found in layer " & m_layerName)
Else
m_log.AddMessage(3, 8000, "SpatialQuerySOE successfully initialized.")
End If
Catch ex As Exception
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: Failed to initialize extension: " & ex.Message & "::" & ex.StackTrace.Length.ToString())
End Try
End Sub
- IObjectActivate—While IServerObjectExtension.Init and IObjectConstruct.Construct are called once when the instance of the SOE is created, if your SOE requires logic to run each time its server context is acquired and released (each time a client calls CreateServerContext and ReleaseContext), implement IObjectActivate. IObjectActivate is an optional interface for SOEs that includes the following methods:
- Activate—Called each time a client calls CreateServerContext on the SOE's server object's context.
- Deactivate—Called each time a client releases the context (via ReleaseContext).
Because Activate and Deactivate are called each time a client gets and releases the server object's context, any logic implemented in these methods should not be expensive. In this case, add instructive information to the log file to indicate the method was called. See the following code example:
void ESRI.ArcGIS.esriSystem.IObjectActivate.Activate()
{
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Activate called");
}
void ESRI.ArcGIS.esriSystem.IObjectActivate.Deactivate()
{
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Deactivate called");
}
[VB.NET]
Private Sub IObjectActivate_Activate() Implements ESRI.ArcGIS.esriSystem.IObjectActivate.Activate
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Activate called")
End Sub
Private Sub Deactivate() Implements ESRI.ArcGIS.esriSystem.IObjectActivate.Deactivate
m_log.AddMessage(3, 8000, "SpatialQuerySOE custom message. Deactivate called")
End Sub
-
IExtension—A custom interface implemented by the custom SOE to expose a method called by a client application. Implementing the QueryPoint method defined by the IExtension interface is the last step to complete the SOE. The QueryPoint method takes as arguments a point and a distance. In the method, add code that buffers the point to the specified distance, then query the layer's feature class for all polygons that intersect that buffer. For each polygon, the method clips it to the buffer, creates a graphic of the clipped polygon, and adds its area to a Dictionary object based on the value of the specified field. See the following code example:
public SpatialQuerySOE.Interfaces.IResults QueryPoint(ESRI.ArcGIS.Geometry.IPoint
point, double distance)
{
if (m_featureLayer == null)
{
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: layer not found");
return null;
}
ESRI.ArcGIS.Geodatabase.IFeatureClass featureClass = m_featureLayer.FeatureClass;
// Buffer the point.
ESRI.ArcGIS.Geometry.ITopologicalOperator topologicalOperator =
(ESRI.ArcGIS.Geometry.ITopologicalOperator)point;
ESRI.ArcGIS.Geometry.IGeometry queryGeometry = topologicalOperator.Buffer
(distance);
// Query the feature class.
ESRI.ArcGIS.Geodatabase.ISpatialFilter spatialFilter = new
ESRI.ArcGIS.Geodatabase.SpatialFilter();
spatialFilter.Geometry = queryGeometry;
spatialFilter.SpatialRel =
ESRI.ArcGIS.Geodatabase.esriSpatialRelEnum.esriSpatialRelIntersects;
spatialFilter.GeometryField = featureClass.ShapeFieldName;
ESRI.ArcGIS.Geodatabase.IFeatureCursor resultsFeatureCursor =
featureClass.Search(spatialFilter, true);
[VB.NET]
Public Function QueryPoint(ByVal point As ESRI.ArcGIS.Geometry.IPoint, ByVal distance As Double) As SpatialQuerySOE.Interfaces_VBNet.IResults Implements SpatialQuerySOE.Interfaces_VBNet.IExtension.QueryPoint
If m_featureLayer Is Nothing Then
m_log.AddMessage(1, 8000, "SpatialQuerySOE custom error: layer not found")
Return Nothing
End If
Dim featureClass As ESRI.ArcGIS.Geodatabase.IFeatureClass = m_featureLayer.FeatureClass
' Buffer the point.
Dim topologicalOperator As ESRI.ArcGIS.Geometry.ITopologicalOperator = CType(point, ESRI.ArcGIS.Geometry.ITopologicalOperator)
Dim queryGeometry As ESRI.ArcGIS.Geometry.IGeometry = topologicalOperator.Buffer(distance)
' Query the feature class.
Dim spatialFilter As ESRI.ArcGIS.Geodatabase.ISpatialFilter = New ESRI.ArcGIS.Geodatabase.SpatialFilter()
spatialFilter.Geometry = queryGeometry
spatialFilter.SpatialRel = ESRI.ArcGIS.Geodatabase.esriSpatialRelEnum.esriSpatialRelIntersects
spatialFilter.GeometryField = featureClass.ShapeFieldName
Dim resultsFeatureCursor As ESRI.ArcGIS.Geodatabase.IFeatureCursor = featureClass.Search(spatialFilter, True)
Before looping through the features, create a GraphicElements collection to hold the graphics, a simple fill symbol to apply to each graphic element, a dictionary object that you use to categorize the different feature types, and other necessary variables. The fill symbol is created using a helper method called createFillSymbol, which will be defined later. See the following code example:
[C#]
topologicalOperator = (ESRI.ArcGIS.Geometry.ITopologicalOperator)queryGeometry;
int classFieldIndex = featureClass.FindField(m_fieldName);
System.Collections.Specialized.ListDictionary summaryStatsDictionary = new
System.Collections.Specialized.ListDictionary();
// Create the symbol and graphic elements collection for the graphics.
ESRI.ArcGIS.Display.ISimpleFillSymbol simpleFillSymbol = createFillSymbol();
ESRI.ArcGIS.Carto.IGraphicElements resultsGraphics = new
ESRI.ArcGIS.Carto.GraphicElements();
[VB.NET]
topologicalOperator = CType(queryGeometry, ESRI.ArcGIS.Geometry.ITopologicalOperator)
Dim classFieldIndex As Integer = featureClass.FindField(m_fieldName)
Dim summaryStatsDictionary As New System.Collections.Specialized.ListDictionary()
' Create the symbol and graphic elements collection for the graphics.
Dim simpleFillSymbol As ESRI.ArcGIS.Display.ISimpleFillSymbol = createFillSymbol()
Dim resultsGraphics As ESRI.ArcGIS.Carto.IGraphicElements = New ESRI.ArcGIS.Carto.GraphicElements()
Loop through the features in the feature class that intersect the buffer geometry and clip each feature's polygon to the buffer. The resulting clipped geometry is then used to create a graphic that is added to the graphic collection. The area of the clipped geometry is added to the total area of the feature's type (as defined by the field specified in the SOE properties) in the Dictionary object. See the following code example:
[C#]
ESRI.ArcGIS.Geodatabase.IFeature resultsFeature;
while ((resultsFeature = resultsFeatureCursor.NextFeature()) != null)
{
// Create the graphic.
ESRI.ArcGIS.Carto.IFillShapeElement fillShapeElement = new
ESRI.ArcGIS.Carto.PolygonElement()as ESRI.ArcGIS.Carto.IFillShapeElement;
ESRI.ArcGIS.Carto.IElement element = fillShapeElement as
ESRI.ArcGIS.Carto.IElement;
// Clip the geometry.
ESRI.ArcGIS.Geometry.IGeometry clippedResultsGeometry =
topologicalOperator.Intersect(resultsFeature.Shape,
ESRI.ArcGIS.Geometry.esriGeometryDimension.esriGeometry2Dimension);
element.Geometry = clippedResultsGeometry;
fillShapeElement.Symbol = simpleFillSymbol;
ESRI.ArcGIS.Carto.IGraphicElement resultsGraphicElement = fillShapeElement as
ESRI.ArcGIS.Carto.IGraphicElement;
resultsGraphics.Add(resultsGraphicElement);
// Get statistics and add to dictionary.
ESRI.ArcGIS.Geometry.IArea area = clippedResultsGeometry as
ESRI.ArcGIS.Geometry.IArea;
string resultsClass = resultsFeature.get_Value(classFieldIndex)as string;
// If the class is already in the dictionary, add the current feature's area to the
// existing entry.
if (summaryStatsDictionary.Contains(resultsClass))
summaryStatsDictionary[resultsClass] = (double)
summaryStatsDictionary[resultsClass] + area.Area;
else
summaryStatsDictionary[resultsClass] = area.Area;
}
[VB.NET]
Dim resultsFeature As ESRI.ArcGIS.Geodatabase.IFeature
resultsFeature = resultsFeatureCursor.NextFeature()
Do While resultsFeature IsNot Nothing
' Create the graphic.
Dim fillShapeElement As ESRI.ArcGIS.Carto.IFillShapeElement = TryCast(New ESRI.ArcGIS.Carto.PolygonElement, ESRI.ArcGIS.Carto.IFillShapeElement)
Dim element As ESRI.ArcGIS.Carto.IElement = TryCast(fillShapeElement, ESRI.ArcGIS.Carto.IElement)
' Clip the geometry.
Dim clippedResultsGeometry As ESRI.ArcGIS.Geometry.IGeometry = topologicalOperator.Intersect(resultsFeature.Shape, ESRI.ArcGIS.Geometry.esriGeometryDimension.esriGeometry2Dimension)
element.Geometry = clippedResultsGeometry
fillShapeElement.Symbol = simpleFillSymbol
Dim resultsGraphicElement As ESRI.ArcGIS.Carto.IGraphicElement = TryCast(fillShapeElement, ESRI.ArcGIS.Carto.IGraphicElement)
resultsGraphics.Add(resultsGraphicElement)
' Get statistics and add to dictionary.
Dim area As ESRI.ArcGIS.Geometry.IArea = TryCast(clippedResultsGeometry, ESRI.ArcGIS.Geometry.IArea)
Dim resultsClass As String = TryCast(resultsFeature.Value(classFieldIndex), String)
' If the class is already in the dictionary, add the current feature's area to the existing entry.
If summaryStatsDictionary.Contains(resultsClass) Then
summaryStatsDictionary(resultsClass) = CDbl(summaryStatsDictionary(resultsClass)) + area.Area
Else
summaryStatsDictionary(resultsClass) = area.Area
End If
resultsFeature = resultsFeatureCursor.NextFeature()
Loop
The summary statistics Dictionary object will have a key for each unique value of the specified field and a value that is the total area within the buffer of features that share the unique value. Create a recordset object and copy the keys and values from the dictionary into rows and fields in the recordset. This is accomplished using the createSummaryRecordSet helper method, which will be implemented later. See the following code example:
[C#]
ESRI.ArcGIS.Geodatabase.IRecordSet summaryStatsRecordSet = createSummaryRecordSet
(summaryStatsDictionary);
[VB.NET]
Dim summaryStatsRecordSet As ESRI.ArcGIS.Geodatabase.IRecordSet = createSummaryRecordSet(summaryStatsDictionary)
Finally, since the QueryPoint function returns a SpatialQuerySOE.Results object, the last part of the function creates a Results object, sets the graphic collection and summary recordset in the object, and returns the object to the caller. The new Results object is referenced using the SpatialQuerySOE.Interfaces.IResults interface. The client application works with the interface to process the results. See the following code example:
[C#]
SpatialQuerySOE.Interfaces.IResults results = new Results();
results.ResultsGraphics = resultsGraphics;
results.SummaryStatistics = summaryStatsRecordSet;
return results;
[VB.NET]
Dim results As SpatialQuerySOE.Interfaces_VBNet.IResults = New Results()
results.ResultsGraphics = resultsGraphics
results.SummaryStatistics = summaryStatsRecordSet
Return results
- As previously described, the QueryPoint method uses two helper methods to create a fill symbol (createFillSymbol) and to copy the contents of a Dictionary object to a recordset (createSummaryRecordSet). Implement these helper methods in your SOE class. The createSummaryRecordSet method takes a Dictionary object as an argument and returns a recordset. The function creates a recordset with a field for the type of feature (key) and a field for the total area (value). It then loops through the keys and values in the dictionary and creates a row in the recordset for each key and value pair. See the following code example:
private ESRI.ArcGIS.Geodatabase.IRecordSet createSummaryRecordSet
(System.Collections.Specialized.ListDictionary summaryStatsDictionary)
{
// Initialize the summary statistics recordset.
ESRI.ArcGIS.Geodatabase.IRecordSet summaryStatsRecordSet = new
ESRI.ArcGIS.Geodatabase.RecordSet();
ESRI.ArcGIS.Geodatabase.IRecordSetInit recordSetInit = summaryStatsRecordSet as
ESRI.ArcGIS.Geodatabase.IRecordSetInit;
ESRI.ArcGIS.Geodatabase.IFields summaryFields = new
ESRI.ArcGIS.Geodatabase.Fields();
ESRI.ArcGIS.Geodatabase.IFieldsEdit summaryFieldsEdit = summaryFields as
ESRI.ArcGIS.Geodatabase.IFieldsEdit;
summaryFieldsEdit.FieldCount_2 = 2;
ESRI.ArcGIS.Geodatabase.IField field = new ESRI.ArcGIS.Geodatabase.Field();
ESRI.ArcGIS.Geodatabase.IFieldEdit fieldEdit = field as
ESRI.ArcGIS.Geodatabase.IFieldEdit;
fieldEdit.Name_2 = "Type";
fieldEdit.Type_2 = ESRI.ArcGIS.Geodatabase.esriFieldType.esriFieldTypeString;
fieldEdit.Length_2 = 50;
summaryFieldsEdit.set_Field(0, field);
field = new ESRI.ArcGIS.Geodatabase.Field();
fieldEdit = field as ESRI.ArcGIS.Geodatabase.IFieldEdit;
fieldEdit.Name_2 = "Area";
fieldEdit.Type_2 = ESRI.ArcGIS.Geodatabase.esriFieldType.esriFieldTypeDouble;
summaryFieldsEdit.set_Field(1, field);
recordSetInit.CreateTable(summaryFields);
ESRI.ArcGIS.Geodatabase.ICursor cursor = recordSetInit.Insert();
ESRI.ArcGIS.Geodatabase.IRowBuffer rowBuffer = recordSetInit.CreateRowBuffer();
// Copy the summary stats to the recordset.
System.Collections.IDictionaryEnumerator summaryStatsEnumerator =
summaryStatsDictionary.GetEnumerator();
while (summaryStatsEnumerator.MoveNext())
{
rowBuffer.set_Value(0, summaryStatsEnumerator.Key);
rowBuffer.set_Value(1, summaryStatsEnumerator.Value);
cursor.InsertRow(rowBuffer);
}
return summaryStatsRecordSet;
}
[VB.NET]
Private Function createSummaryRecordSet(ByVal summaryStatsDictionary As System.Collections.Specialized.ListDictionary) As ESRI.ArcGIS.Geodatabase.IRecordSet
' Initialize the summary statistics recordset.
Dim summaryStatsRecordSet As ESRI.ArcGIS.Geodatabase.IRecordSet = New ESRI.ArcGIS.Geodatabase.RecordSet()
Dim recordSetInit As ESRI.ArcGIS.Geodatabase.IRecordSetInit = TryCast(summaryStatsRecordSet, ESRI.ArcGIS.Geodatabase.IRecordSetInit)
Dim summaryFields As ESRI.ArcGIS.Geodatabase.IFields = New ESRI.ArcGIS.Geodatabase.Fields()
Dim summaryFieldsEdit As ESRI.ArcGIS.Geodatabase.IFieldsEdit = TryCast(summaryFields, ESRI.ArcGIS.Geodatabase.IFieldsEdit)
summaryFieldsEdit.FieldCount_2 = 2
Dim field As ESRI.ArcGIS.Geodatabase.IField = New ESRI.ArcGIS.Geodatabase.Field()
Dim fieldEdit As ESRI.ArcGIS.Geodatabase.IFieldEdit = TryCast(field, ESRI.ArcGIS.Geodatabase.IFieldEdit)
fieldEdit.Name_2 = "Type"
fieldEdit.Type_2 = ESRI.ArcGIS.Geodatabase.esriFieldType.esriFieldTypeString
fieldEdit.Length_2 = 50
summaryFieldsEdit.Field_2(0) = field
field = New ESRI.ArcGIS.Geodatabase.Field()
fieldEdit = TryCast(field, ESRI.ArcGIS.Geodatabase.IFieldEdit)
fieldEdit.Name_2 = "Area"
fieldEdit.Type_2 = ESRI.ArcGIS.Geodatabase.esriFieldType.esriFieldTypeDouble
summaryFieldsEdit.Field_2(1) = field
recordSetInit.CreateTable(summaryFields)
Dim cursor As ESRI.ArcGIS.Geodatabase.ICursor = recordSetInit.Insert()
Dim rowBuffer As ESRI.ArcGIS.Geodatabase.IRowBuffer = recordSetInit.CreateRowBuffer()
' Copy the summary stats to the recordset.
Dim summaryStatsEnumerator As System.Collections.IDictionaryEnumerator = summaryStatsDictionary.GetEnumerator()
Do While summaryStatsEnumerator.MoveNext()
rowBuffer.Value(0) = summaryStatsEnumerator.Key
rowBuffer.Value(1) = summaryStatsEnumerator.Value
cursor.InsertRow(rowBuffer)
Loop
Return summaryStatsRecordSet
End Function
The createFillSymbol method creates and returns a SimpleFillSymbol object, which is a hollow fill symbol with a green outline. The client application can choose to use this rendering scheme when working with SOE results. See the following code example:
[C#]
private ESRI.ArcGIS.Display.ISimpleFillSymbol createFillSymbol()
{
ESRI.ArcGIS.Display.ISimpleLineSymbol simpleLineSymbol = new
ESRI.ArcGIS.Display.SimpleLineSymbol();
ESRI.ArcGIS.Display.IRgbColor rgbColor = new ESRI.ArcGIS.Display.RgbColor();
rgbColor.Red = 0;
rgbColor.Green = 255;
rgbColor.Blue = 0;
simpleLineSymbol.Color = rgbColor;
simpleLineSymbol.Style = ESRI.ArcGIS.Display.esriSimpleLineStyle.esriSLSSolid;
simpleLineSymbol.Width = 2;
ESRI.ArcGIS.Display.ISimpleFillSymbol simpleFillSymbol = new
ESRI.ArcGIS.Display.SimpleFillSymbol();
simpleFillSymbol.Outline = simpleLineSymbol;
simpleFillSymbol.Style = ESRI.ArcGIS.Display.esriSimpleFillStyle.esriSFSHollow;
return simpleFillSymbol;
}
[VB.NET]
Private Function createFillSymbol() As ESRI.ArcGIS.Display.ISimpleFillSymbol
Dim simpleLineSymbol As ESRI.ArcGIS.Display.ISimpleLineSymbol = New ESRI.ArcGIS.Display.SimpleLineSymbol()
Dim rgbColor As ESRI.ArcGIS.Display.IRgbColor = New ESRI.ArcGIS.Display.RgbColor()
rgbColor.Red = 0
rgbColor.Green = 255
rgbColor.Blue = 0
simpleLineSymbol.Color = rgbColor
simpleLineSymbol.Style = ESRI.ArcGIS.Display.esriSimpleLineStyle.esriSLSSolid
simpleLineSymbol.Width = 2
Dim simpleFillSymbol As ESRI.ArcGIS.Display.ISimpleFillSymbol = New ESRI.ArcGIS.Display.SimpleFillSymbol()
simpleFillSymbol.Outline = simpleLineSymbol
simpleFillSymbol.Style = ESRI.ArcGIS.Display.esriSimpleFillStyle.esriSFSHollow
Return simpleFillSymbol
End Function
Setting assembly properties for the SpatialQuerySOE_CSharp project
Do the following steps to set assembly properties:
- In the SpatialQuerySOE_CSharp project, open the AssemblyInfo.cs file.
- Modify the AssemblyInfo.cs file to reflect the settings in the following code example. Set ComVisibleAttribute to true (or remove the value since the default is true). Setting this on the assembly makes all public types visible to COM clients. The ComVisibleAttribute can also be applied to individual types in the assembly.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SpatialQuerySOE")][assembly: AssemblyDescription("")
][assembly: AssemblyConfiguration("")][assembly: AssemblyCompany("ESRI")
][assembly: AssemblyProduct("SpatialQuerySOE")][assembly: AssemblyCopyright(
"Copyright © ESRI 2010")][assembly: AssemblyTrademark("")][assembly:
AssemblyCulture("")][assembly: AssemblyVersion("1.0.0.0")][assembly:
AssemblyFileVersion("1.0.0.0")][assembly: ComVisibleAttribute(true)][assembly:
GuidAttribute("C1E4CA92-0380-4ed1-ABF4-9F45C9914D6D")]
[VB.NET]
Imports Microsoft.VisualBasic
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
<Assembly
AssemblyTitle("SpatialQuerySOE_VBNet")>
<Assembly
AssemblyDescription("")>
<Assembly
AssemblyConfiguration("")>
<Assembly
AssemblyCompany("ESRI")>
<Assembly
AssemblyProduct("SpatialQuerySOE_VBNet")>
<Assembly
AssemblyCopyright("Copyright © ESRI 2010")>
<Assembly
AssemblyTrademark("")>
<Assembly
AssemblyCulture("")>
<Assembly
AssemblyVersion("1.0.0.0")>
<Assembly
AssemblyFileVersion("1.0.0.0")>
<Assembly
ComVisibleAttribute(True)>
<Assembly
Guid("AE0C359B-55AE-4bb4-AD9F-A78EF0D0660B")>
Building the SOE projects
Build the SpatialQuerySOE.Interfaces_CSharp and SpatialQuerySOE_CSharp projects. Each project creates an assembly, SpatialQuerySOE.Interfaces.dll and SpatialQuerySOE.dll.
See Also:
Walkthrough: Creating a server object extensionHow to create SOE property pages
How to register the SOE
How to create an SOE client
Sample: Server spatial query server object extension