Creating Server solutions using .NET—Scenarios  

Creating a Server Object Extension

 


This walkthrough is for developers who need to build and deploy a server object extension for use in server applications. It describes the process of extending the MapServer to provide methods for performing a specific type of spatial analysis on the features in one layer of the map.  The scenario consists of four parts:


This scenario is also included as a developer sample, ArcGIS_Spatial_Query_Server_Object_Extension.  

The sample code is located in:<install location>\DeveloperKit\SamplesNET\Server\Web_Applications

The purpose of this scenario is to create a server object extension using C# to extend a MapServer server object. The server object extension exposes methods to clip the geometries of polygons in one of the layers in the map to a buffer around a user-defined point. Summary statisitcs are then provided using the area of the clipped polygons based on the unique values of a specified field. Both the layer to perform the analysis on, and the field to summarize statistics on will be a property of the server object extension.

As part of this scenario, you'll create a custom administration property page for ArcCatalog that will allow you to set the layer and field properties of the server object extension, based on the MapServer object's map document. Finally, you'll create a client Web application that consumes your server object extension. This application will utilize the Web ADF controls and capabilities.

Concepts

Both coarse-grained calls to remote ArcObjects, such as the methods on the MapServer and GeocodeServer, as well as fine-grained calls to remote ArcObjects, such as looping through all the vertices of a polygon, are exposed through the ArcGIS Server ArcObjects API and can be used in your application. However, it’s important to note that when making a call against an object running in the server from your Web application, you are making that call across processes. The Web application is running in one process, while the object is running in another process. 

Calls to objects across processes are significantly slower than calls to objects in the same process. It’s also likely that your Web application is running on a Web server that is actually a different machine from the one the object is running on, so the calls are not only cross process but also cross machine.

If your application requires making a large number of fine-grained ArcObjects calls, there are two strategies you can employ to extend the GIS server with your own object that expose coarse-grained interfaces: create utility COM objects or create server object extensions.

Extending a server object has the following advantages over creating a generic COM object:

However, unlike utility COM objects, server object extensions are reigstered and configured with speficic server objects and are not for add-hoc use or use with an empty server context. See the sample ArcGIS_Spatial_Query COM Utility for an example of extending the GIS server with utility COM object.

The server object extension satisfies application requirements for functionality that requires making a large number of fine-grained ArcObjects calls within the ArcGIS Server container process (ArcSOC.exe).  It this scenario these calls include looping through features, getting their geometry, clipping the geometry, summarizing the areas based on an attribute, creating a graphic for each feature, and so on.  Since the caller of the server object extension is free to specify a buffer distance that may include a large number of features, the number of features that would be analyzed is indeterminate, which could easily result in thousands of fine-grained ArcObjects calls.

Design

The server object extension in this scenario extends the MapServer with specialized functionality exposed as a stateless method on a custom interface.  As a result, you should configure the server object extension with a pooled MapServer. 
The server object extension is also designed to store two custom properties with the MapServer configuration for which the extension is enabled.  These properties will be settable through a custom ArcCatalog property page for the server object extension and used during execution:

The Web application is designed to make stateless use of the GIS server.  It uses events on the Web ADF Map control to get a point from the user, gains access to server context using the MapResource for an ArcGIS Server Local data source, then uses the point as input to the server object extension to perform the analysis. To support this application, you need to add a pooled map server object with the server object extension enabled to your ArcGIS Server using ArcCatalog.

The Web application will use the Web ADF framework to manage the connection to the GIS server, and the Web ADF controls will provide the basic mapping functionality required for this application. You will add a new tool to the Toolbar control that allows the user to click the map as input to the analysis.  The results are displayed on the map as a set of graphics and summarized in a table on the Web page.

Requirements

The requirements for working through this scenario are that you have ArcGIS Server and ArcGIS Desktop installed and running.  The machine on which you develop the Web application must, at a minimum have the Web ADF, .NET SDK, and IIS installed.

The following ArcObjects .NET assemblies will be used to build the server object extension and property page:


The following .NET framework assembly will also be required:


The following ArcObjects and Web ADF .NET assemblies will be used to build the Web application:

The development environment does not require any ArcGIS licensing; however, connecting to a server and using a map server object does require that the GIS server is licensed to run ArcObjects in the server.  None of the assemblies or object libraries used require an extension license.

The IDE used in this example is Visual Studio .NET 2005, and all IDE specific steps will assume that it is the IDE you are using.

Implementation

The code for this scenario is divided into four parts; all are written in C#.  The first is the implementation of the server object extension, which will run within the GIS server, and exposes the methods that extend the MapServer.  The second is the server object extension's property page that will be registered with the ArcCatalog desktop application.

The third is a utility application that will register the server object extension with the GIS Server.  The final part illustrates how to create a Web application that makes use of the server object extension and the Web ADF controls and APIs.

The instructions below will be easiest to follow as a narrative description of the sample code for this scenario. You may wish to open the sample in Visual Studio and read the rest of this section as a walk through of the sample.

Part 1: Developing the server object extension

Create the server object extension interfaces project

The first step is to create a new project that will contain interfaces implemented by server object extension classes and utilized by clients.

  1. Start Visual Studio.
  2. Click File, click New, then click Project.
  3. In the New Project dialog box, under Project Types, click the Visual C# Projects category.  Under Templates, click Class Library.
  4. Type a path to where you want to put this project.
  5. For the project name, type "VegSOEInterfacesCSharp".
  6. Click OK. Remove the Class1.cs file which was autogenerated when the project was created.

You will create a C# class named VegSOEInterfacesCSharp that contains the two interfaces to implement within the server object extension classes in another project. Why are the interfaces for the server object extension stored in a separate assembly? Client applications only need the interfaces, registered as COM types, to interact with the server object extension. The business logic for the server object extension 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. Much like working with ArcObjects remotely via ArcGIS Server, a client will use a custom server object extension interface to work with server object extension classes (objects) remotely via ArcGIS Server.

Create the interfaces class

Add the new class to the VegSOEInterfacesCSharp project.

  1. In the Solution Explorer, right-click the VegSOEInterfacesCSharpproject, click Add, then click Add New Item.
  2. In the Add New Item dialog box, under Templates, click Class.
  3. For the name, type "VegSOEInterfacesCSharp.cs".
  4. Click Open. This will add a new class to your project and will open the code for the class with some autogenerated code. Remove the autogenerated code.

Implementing the SOE Interfaces class

You'll need to add references to assemblies that contain the ArcObjects required for the interface definitions in this class file. Because the class contains interfaces that will be exposed as COM types, it will need to reference some additional assemblies to manage interoperability.

  1. Add references to the following assemblies:
    • ESRI.ArcGIS.Carto
    • ESRI.ArcGIS.Display
    • ESRI.ArcGIS.Geodatabase
    • ESRI.ArcGIS.Geometry
    • ESRI.ArcGIS.Server
    • ESRI.ArcGIS.esriSystem
    • System.EnterpriseServices
  2. At the top of the code window, add the following using statements.
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    using ESRI.ArcGIS.Geometry;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.Geodatabase;
    
  3. Add the namespace VegSOEInterfacesCSharp. Within the namespace definition, add two interfaces: IVegUtilsSOE and IVegResultsSOE.

    The IVegUtilsSOE interface will define the basic framework to be implemented by the VegUtilsSOE class, namely a method to accepts 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 work with a reference to the VegUtilsSOE object running within the server object container via the sumVegetationType method and the IVegUtilsSOE interface. The pattern will follow standard programming techniques for working with ArcObjects (COM objects) remotely via ArcGIS Server. The sumVegetationType method will create a VegResultsSOE object in the server container process, update it's properties and return to the client a reference to the remote VegResultsSOE COM object via the IVegResultsSOE interface.

    The IVegResultsSOE interface will define the basic framework to be implemented by the VegResultsSOE class, namely two properties to store a reference to an array of graphic elements and a recordset.

    Since both interfaces will be registered (added to the registry) with COM, they should define a unique GUID using the COM attribute GuidAttribute. The GUID will be used to uniquely identify an application, component, class, interface, etc. within the Windows registry. There are many options for generating a unique GUID. You can create a unique GUID using the .NET System.Guid struct. The call to System.Guid.NewGuid().ToString() will return a usable GUID string. In addition, a number of Web sites and services provide GUID generating capabilities.

    namespace VegSOEInterfacesCSharp
    {
        [GuidAttribute("70a7fbc8-ab8e-4e3c-810f-4e0f46f62e49")]
        public interface IVegUtilsSOE
        {
            IVegResultsSOE sumVegetationType(IPoint pPoint, double dDistance);
        }
    
        [GuidAttribute("3c09de7a-c0ce-4c6f-b1b6-8100e712c1b2")]
        public interface IVegResultsSOE
        {
            IGraphicElements ResGraphics
            {
                get;
                set;
            }
            IRecordSet Stats
            {
                get;
                set;
            }
        }
    }
    

Set assembly properties for the VegSOEInterfacesCSharp project

In the VegSOEInterfacesCSharp project, open the AssemblyInfo.cs file and change it to reflect the following settings. It is important to set the ComVisibleAttribute to true and specify a unique GUID for the GuidAttribute.  Since the assembly will be used to generate a type library, these attributes will enable the interfaces to be registered with COM and will define a unique identifier.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("VegSOEInterfacesCSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ESRI")]
[assembly: AssemblyProduct("VegSOEInterfacesCSharp")]
[assembly: AssemblyCopyright("Copyright 2006")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: ComVisibleAttribute(true)]
[assembly: GuidAttribute("3AAEB4BE-263F-380D-89FD-D9717DE4BFF9")]

Create the server object extension project

  1. In the same Visual Studio session used to create the VegSOEInterfacesCSharp project, right-click the solution, click New, then click Project.
  2. In the New Project dialog box, under Project Types, click the Visual C# Projects category.  Under Templates, click Class Library.
  3. Type a path to where you want to put this project.
  4. For the project name, type "VegSOECSharp".
  5. Click OK. Remove the Class1.cs file which was autogenerated when the project was created.

You will create a C# class named VegSOECSharp that represents the server object extension. This server object extension will provide a method that performs the spatial query and returns an object of type VegResultsSOE that contains an array of graphic elements and a recordset. Before creating the server object extension itself, you'll create the VegResultsSOE (vegetation results) class. This class is merely used as a complex type to store the results of the server object extension in a single object for use by the consuming client (e.g. Web application).

Create the Results class

Add the new class to the VegSOECSharp project.

  1. In the Solution Explorer, right-click the VegSOECSharpproject, click Add, then click Add New Item.
  2. In the Add New Item dialog box, under Templates, click Class.
  3. For the name, type "VegResultsSOE.cs".
  4. Click Open. This will add a new class to your project and will open the code for the class with some autogenerated code.

Implementing the Results class

You'll need to add references to assemblies that contain the ArcObjects required for this class. In addition, because this is a COM object for use in the GIS server, you'll need to add some additional references to support calling a managed .NET component from a COM client.  The .NET component is the custom server object extension 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.  The technology that permits .NET components and COM clients to work together is known as COM Interop. 

  1. Add references to the following assemblies:
    • ESRI.ArcGIS.Carto
    • ESRI.ArcGIS.Catalog
    • ESRI.ArcGIS.CatalogUI
    • ESRI.ArcGIS.Display
    • ESRI.ArcGIS.Framework
    • ESRI.ArcGIS.Geodatabase
    • ESRI.ArcGIS.Geometry
    • ESRI.ArcGIS.Server
    • ESRI.ArcGIS.esriSystem
    • System.EnterpriseServices
  2. Add a reference to the VegSOEInterfacesCSharp project in the same solution. If the VegSOEInterfacesCSharp project is not in the same solution, add a reference to the VegSOEInterfacesCSharp assembly.
  3. At the top of the code window, add the following using statements.
    using System;
    using System.Runtime.InteropServices;
    using System.EnterpriseServices;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.Geodatabase;
    
  4. Define the public COM class to contain implementation of the VegResultsSOE object.  Since this class will be exposed to COM, a couple of 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 marshaler (true) or custom marshaled (false).  We'll use the automation marshaler.  ClassInterface defines how interfaces to the COM class are generated in the type library and exposed to a COM client.  In this case, we want to support COM versioning and explictly define the default interface by which a COM client will access the class.  Setting the ClassInterface attribute to ClassInterfaceType.None requires that we define an explict interface (IVegResultsSOE) to work with our class (VegResultsSOE).  The GuidAttribute should reference a unique GUID to identify our class.  

    Our class should derive from the ServiceComponent class to be hosted by COM clients.  In addition, we will implement the IVegResultsSOE interface as a default interface to interact with our COM object.  Two properties will be defined to get\set the graphics elements and recordset, ResGraphics and Stats, respectively.      

       

    namespace VegSOECSharp
    {
    	[AutomationProxy(true), ClassInterface(ClassInterfaceType.None),GuidAttribute("05922ca8-1a80-4502-8bbf-b3c0637b80ac")]
    	public class VegResultsSOE: ServicedComponent, VegSOEInterfacesCSharp.IVegResultsSOE
    	{
    		private IGraphicElements m_resGraphics;
    		private IRecordSet m_resStats;
    
    		public IGraphicElements ResGraphics
    		{
    			get{ return m_resGraphics;	}
    			set{ m_resGraphics = (IGraphicElements) value; }
    		}
    
    		public IRecordSet Stats
    		{
    			get{ return m_resStats; }
    			set{ m_resStats = (IRecordSet) value; }
    		}
    	}
    }
    

    Create the Server Object Extension class

    The first step is to add the new class to the project.

    1. In the Solution Explorer, right-click the VegSOECSharpproject, click Add, then click Add New Item.
    2. In the Add New Item dialog box, under Templates, click Class.
    3. For the name, type "VegUtilsSOE.cs".
    4. Click Open. This will add a new class to your project and will open the code for the class with some autogenerated code.

    Implementing the Server Object Extension class

    Now that you have implemented the results class, you can implement your server object extension class. A server object extension extends a server object with additional interfaces to provide more specialized functionality. In this example, the server object extension implements the custom interface IVegUtilsSOE.

    1. References to the appropriate assemblies were added during the creation of the results class. At the top of the code window for the VegUtilsSOE class, add the following using statements.
      using System;
      using System.Runtime.InteropServices;
      using System.EnterpriseServices;
      using ESRI.ArcGIS.Carto;
      using ESRI.ArcGIS.Display;
      using ESRI.ArcGIS.esriSystem;
      using ESRI.ArcGIS.Geodatabase;
      using ESRI.ArcGIS.Geometry;
      using ESRI.ArcGIS.Server;
      
      
    2. Define the public COM class to contain the implementation of the VegUtilsSOE object. Since this class will be exposed to COM, a couple of 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 marshaler (true) or custom marshaled (false). We'll use the automation marshaler. ClassInterface defines how interfaces to the COM class are generated in the type library and exposed to a COM client. In this case, we want to support COM versioning and explictly define the default interface by which a COM client will access the class. Setting the ClassInterface attribute to ClassInterfaceType.None requires that we define an explict interface (IVegResultsSOE) to work with our class (VegResultsSOE). The GuidAttribute should reference a unique GUID to identify our class.

      A set of member variables should be created to store a reference an IServerObjectHelper (IServerObjectExtension implmementation), an ILayer and two strings to account for layer name and field name (IVegUtilSOE implmementation) and an ILog (ILogSupport implementation). Polulating these variables will be discussed in the next section.

      namespace VegSOECSharp
      {
          [AutomationProxy(true), ClassInterface(ClassInterfaceType.None), GuidAttribute("87176523-1ede-4fe6-abe0-66481ac7d04b")]
          public class VegUtilsSOE : ServicedComponent, VegSOEInterfacesCSharp.IVegUtilsSOE, IServerObjectExtension, IObjectConstruct, ILogSupport, IObjectActivate
          {
              private IServerObjectHelper m_SOH;
              private ILayer m_layer;
              private string m_layerName;
              private string m_fieldName;
              private ILog m_log;
      
      
    3. The following class and interfaces need to be implemented:

      • ServicedComponent (class) 
      • ILogSupport
      • IServerObjectExtension
      • IObjectConstruct
      • IObjectActivate
      • IVegUtilsSOE

      The following diagram provides the order in which the methods of interfaces implementation by a server object extension are called during initialization and use:

      A discussion and appropriate implementation code is provided below:

      • ServicedComponent

        The VegUtilsSOE class should derive from the ServicedComponent class to be hosted by COM clients (e.g. ArcSOC.exe). No additional code is necessary.

      • ILogSupport

        If you want your server object extension to log messages to the GIS server's log file, your server object extension should implement ILogSupport. ILogSupport is an optional interface for server object extensions that has a single InitLogging method. InitLogging is called when the server object extension is created and hands back 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 three parameters: level, code, and message.

        The level is the level of detail of the message in relation to other messages. Levels are classified from 1 to 5 and termed, in order, Error, Warning, Normal, Detailed, and Debug. ArcGIS Server log file settings determine which messages are included in the server log.

        The code is the result code associated with the message. The code is an arbitrary integer value to uniquely define the source of the message. Codes 0 - 5999 are utilized by the SOM. Codes 6000 and above can be generated by any serviced component (e.g. MapServer, GeocodeServer, custom component, etc.).

        The message is the custom string inserted into the GIS server log file.

                public void InitLogging(ILog log)
                {
                    m_log = log;
                }
        
      • IServerObjectExtension

        A mandatory interface that must be supported by all server object extensions, and includes two methods: Init and Shutdown. This interface is used by the server object to manage the lifetime of the server object extension. The server object cocreates the server object extension and calls the Init method handing it a back 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 in order 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 server object extension is created. The Shutdown method is called once and informs the server object extension that the server object's context is being shut down and is about to go away. In response the server object extension should release its reference on the server object helper. The log entries are merely informative and completely optional.

                public void Init(IServerObjectHelper pSOH)
                {
                    m_SOH = pSOH;
                    m_log.AddMessage(3,8000,"VegUtilsSOE custom message. Init called");
                }
        
                public void Shutdown()
                {
                    m_log.AddMessage(3,8000,"VegUtilsSOE custom message. Shutdown called");
                    m_SOH = null;
                    m_layer = null;
                    m_log = null;
                }
        
      • IObjectConstruct

        If your server object extension includes configuration properties or requires any additional intitialization logic, you need to implement IObjectConstruct. IObjectConstruct is an optional interface for server object extensions. The interface includes a single method called Construct. Construct is called only once, when the server object extension is created, after IServerObjectExtension::Init is called. You should include any expensive initialization logic within your implementation of Construct.

        Construct hands back the configuration properties for the server object extension 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. Properties of server object extensions configured for use with a server object are also stored in the cfg file. In this scenario, a set of properties are being read from the service 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.

                public void Construct(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, "VegUtilsSOE custom error. Error reading properties: " + ex.Message + " " + props.Count.ToString());
                        return;
                    }
        
                    try
                    {
                        IMapServer ms = (IMapServer) m_SOH.ServerObject;
                        IMapServerObjects mso = (IMapServerObjects) ms;
                        IMap map = mso.get_Map(ms.DefaultMapName);
                        UID ltype = new UIDClass();
                        ltype.Value = "{E156D7E5-22AF-11D3-9F99-00C04F6BC78E}";
                        IEnumLayer el = map.get_Layers(ltype, true);
                        el.Reset();
        
                        ILayer l = null;
                        while ((l = el.Next()) != null)
                        {
                            if (l.Name == m_layerName)
                            {
                                m_layer = l;
                                break;
                            }
                        }
        
                        if (m_layer == null)
                        {
                            m_log.AddMessage(1, 8000, "VegUtilsSOE custom error: Layer " + m_layerName + " not found.");
                            return;
                        }
        
                        IFeatureLayer fl = (IFeatureLayer)m_layer;
                        IFeatureClass fc = fl.FeatureClass;
        
                        if (fc.FindField(m_fieldName) == -1)
                            m_log.AddMessage(1, 8000, "VegUtilsSOE custom error: Field " + m_fieldName + " not found in layer " + m_layerName);
                        else
                            m_log.AddMessage(3, 8000, "VegUtilsSOE sucessfully initialized.");
        
                    }
                    catch (Exception ex)
                    {
                        m_log.AddMessage(1, 8000, "VegUtilsSOE custom error: Failed to initialize extension: " + ex.Message + "::" + ex.StackTrace.Length.ToString());
                    }
                }
        
      • IObjectActivate

        While IServerObjectExtension::Init and IObjectConstruct::Construct are called once when the instance of the server object extension is created, if your server object extension requires logic to run each time its server context is acquired and released (each time a client calls CreateServerContext and ReleaseContext), you need to implement IObjectActivate. IObjectActivate is an optional interface for server object extensions that includes two methods: Activate and Deactivate. Activate is called each time a client calls CreateServerContext on the server object extension's server object's context, and Deactivate is 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 you implement in these methods should not be expensive. In this scenario, we merely add some instructive information to the log file to indicate the method was called.

                public void Deactivate()
                {
                    m_log.AddMessage(3,8000,"VegUtilsSOE custom message. Deactivate called");
                }
        
                public void Activate()
                {
                    m_log.AddMessage(3,8000,"VegUtilsSOE custom message. Activate called");
                }
        
      • IVegUtilsSOE

        IVegUtilsSOE is a custom interface implemented by the custom SOE to expose a method called by a client application. Implementing the sumVegetationType method defined by the IVegUtilsSOE interface is the last step to complete the server object extension. The sumVegetationType method takes as arguments a point and a distance. In the method, you'll add code that buffers the point to the specified distance, then queries the layer's feature class for all the polygons that intersect that buffer. For each polygon, it 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.

                public VegSOEInterfacesCSharp.IVegResultsSOE sumVegetationType(IPoint pPoint, double dDistance)
                {
                    if (m_layer == null)
                    {
                        m_log.AddMessage(1, 8000, "VegUtilsSOE custom error: layer not found");
                        return null;
                    }
        
                    IFeatureLayer fl = (IFeatureLayer) m_layer;
                    IFeatureClass pVegClass = fl.FeatureClass;
        
                    ITopologicalOperator pTopoOp = (ITopologicalOperator) pPoint;
                    IGeometry pGeom = pTopoOp.Buffer(dDistance);
        
                    ISpatialFilter pSFilter = new SpatialFilter();
                    pSFilter.Geometry = pGeom;
                    pSFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                    pSFilter.GeometryField = pVegClass.ShapeFieldName;
        
                    IFeatureCursor pFCursor = pVegClass.Search(pSFilter, true);
        

        Before looping through the features, you need to create a GraphicElements collection to hold the graphics, a simple fill symbol to apply to each graphic element, a dictionary object that you will use to categorize the different vegetation types, and some other needed variables. Note that the fill symbol is created using a helper method called newFillS . You will create this method later.

                    pTopoOp = (ITopologicalOperator) pGeom;
                    int lPrim = pVegClass.FindField(m_fieldName);
        
                    System.Collections.Specialized.ListDictionary dict = new System.Collections.Specialized.ListDictionary();
        
                    ISimpleFillSymbol pSFS = newFillS();
                    IGraphicElements pGraphics = new GraphicElements();
        

        The next step is to loop through the features in the pVegClass feature class that intersect the buffer geometry and clip each vegetation polygon to the buffer. The resulting clipped geometry is then used to create a graphic that is added to the graphics 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 server object extension properties) in the dictionary object

                    IFeature pFeature;
                    while ((pFeature = pFCursor.NextFeature()) != null)
                    {
                        IFillShapeElement pFE = (IFillShapeElement) new PolygonElement();
                        IElement pElement = pFE as IElement;
        
                        IGeometry pNewGeom = pTopoOp.Intersect(pFeature.Shape, esriGeometryDimension.esriGeometry2Dimension);
                        pElement.Geometry = pNewGeom;
                        pFE.Symbol = pSFS;
                        IGraphicElement ge = (IGraphicElement) pFE;
                        pGraphics.Add(ge);
        
                        IArea pArea = pNewGeom as IArea;
                        string sType = pFeature.get_Value(lPrim) as string;
                        if (dict.Contains(sType))
                            dict[sType] = (double)dict[sType] + pArea.Area;
                        else
                            dict[sType] = pArea.Area;
                    }
        
        

        At this point, the dictionary object will have a key for each unique value of the field whose item is the total area for that unique value within the buffer. The next step is to create a record set object and copy the keys and items from the dictionary into rows and fields in the record set. This is accomplished using the sumRS helper method, which will be implemented later.

                    IRecordSet psumRS = sumRS(dict);
        

        Finally, since the sumVegetationType function returns a VegResultsSOE object, the last part of the function creates a new VegResultsSOE object, sets the graphics collection and summary record set in the object, and returns the object to the caller. Note that the new VegResultsSOE object is referenced using the VegSOEInterfacesCSharp.IVegResultsSOE interface. The client application will work with the interface to process the results.

                    VegSOEInterfacesCSharp.IVegResultsSOE pRes = new VegResultsSOE();
                    pRes.ResGraphics = pGraphics;
                    pRes.Stats = psumRS;
        
                    return pRes;
                }
        
        
    4. As described in the previous step, the sumVegetationType method makes use of two helper methods to create a fill symbol (newFillS) and to copy the contents of a dictionary object to a record set (sumRS). You will now implement these helper methods in your server object extension class.

      The sumRS method takes a dictionary object as an argument, and returns a record set. The function creates a new record set with a field for the type of vegetation (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 record set for each key/value pair.

              private IRecordSet sumRS(System.Collections.Specialized.ListDictionary dict)
              {
                  IRecordSet pNewRs = new RecordSet();
                  IRecordSetInit prsInit = pNewRs as IRecordSetInit;
      
                  IFields pFields = new Fields();
                  IFieldsEdit pFieldsEdit = pFields as IFieldsEdit;
                  pFieldsEdit.FieldCount_2 = 2;
      
                  IField pField = new Field();
                  IFieldEdit pFieldEdit = pField as IFieldEdit;
                  pFieldEdit.Name_2 = "Type";
                  pFieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
                  pFieldEdit.Length_2 = 50;
                  pFieldsEdit.set_Field(0, pField);
      
                  pField = new Field();
                  pFieldEdit = pField as IFieldEdit;
      
                  pFieldEdit.Name_2 = "Area";
                  pFieldEdit.Type_2 = esriFieldType.esriFieldTypeDouble;
                  pFieldsEdit.set_Field(1, pField);
      
                  prsInit.CreateTable(pFields);
      
                  ICursor pIC = prsInit.Insert();
                  IRowBuffer pRowBuf = prsInit.CreateRowBuffer();
      
                  System.Collections.IDictionaryEnumerator myEnumerator = dict.GetEnumerator();
                  while (myEnumerator.MoveNext())
                  {
                      pRowBuf.set_Value(0, myEnumerator.Key);
                      pRowBuf.set_Value(1, myEnumerator.Value);
                      pIC.InsertRow(pRowBuf);
                  }
      
                  return pNewRs;
              }
              

      The newFillS method creates and returns a new SimpleFillSymbol object. This fill symbol is a hollow fill symbol with a green outline. The client application can choose to use this rendering scheme when working with the server object extension results.

              private ISimpleFillSymbol newFillS()
              {
                  ISimpleLineSymbol pSLS = new SimpleLineSymbol();
                  IRgbColor pcolor = new RgbColor();
                  pcolor.Red = 0;
                  pcolor.Green = 255;
                  pcolor.Blue = 0;
                  pSLS.Color = pcolor;
                  pSLS.Style = esriSimpleLineStyle.esriSLSSolid;
                  pSLS.Width = 2;
      
                  ISimpleFillSymbol pSFS = new SimpleFillSymbol();
                  pSFS.Outline = pSLS;
                  pSFS.Style = esriSimpleFillStyle.esriSFSHollow;
      
                  return pSFS;
              }
          }
      }
      

Set assembly properties for the VegSOECSharp project

In the VegSOECSharp project, open the AssemblyInfo.cs file and change it to reflect the following settings. It is important that the ComVisibleAttribute be set to true (or removed since the default is true).  Setting this on the assembly will make 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("VegSOECSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ESRI")]
[assembly: AssemblyProduct("VegSOECSharp")]
[assembly: AssemblyCopyright("Copyright 2006")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: ComVisibleAttribute(true)]

Build the server object extension projects

Build the VegSOEInterfacesCSharp and VegSOECSharp projects. Each project will create an assembly, VegSOEInterfacesCSharp.dll and VegSOECSharp.dll. The deployment section below will discuss the distribution and registration of these assemblies.

Part 2: Creating the server object extension's property page

In this part of the implementation, you'll create the necessary objects to implement a property page in ArcCatalog that allows you to configure your server object extension's properties. The properties for this server object extension are:

Both of these properties will be settable using this property page. The property page will display all of the feature layers in the default map, and the fields for the selected feature layer. The property page will only be used by the GIS server administrator to specify the properties for the custom server object extension when the extension is enabled on a map service (MapServer).

Create the server object extension property page project

The first step is to create a new project that will contain the implementation of an ArcCatalog property page.

  1. In the solution explorer, right-click the solution, click Add, then click New Project...
  2. In the Add New Project dialog, under Project Types, click Visual C# Projects.
  3. Under Templates, click Class Library. For the project name, specify VegSOEPropsCSharp.
  4. Click OK. Remove the Class1.cs file which was autogenerated when the project was created.

This project will contain a class file and a Windows form. The Windows form will provide the visual interface of the property page in ArcCatalog. The class file will provide the logic for registering the property page, showing\hiding the property page, and retrieving and setting server object extension properties in the server object configuration file.

Create the property page class and form

  1. In the Solution Explorer, right-click the VegSOEPropsCSharpproject, click Add, then click Add New Item.
  2. In the Add New Item dialog box, under Templates, click Class.
  3. For the name, type "VegSOEProps.cs".
  4. Click Open. This will add a new class to your project and will open the code for the class with some autogenerated code. Remove the autogenerated code.
  5. Again, in the Solution Explorer, right-click the VegSOEPropsCSharpproject, click Add, then click Add New Item.
  6. In the Add New Item dialog box, under Templates, click Windows Form.
  7. For the name, type "FormVegProps.cs". This will add a new Windows form to the project.

Implementing the Property page form

You'll need to add references to assemblies that contain the ArcObjects required for implementation code in both the property page class and form.

  1. Add references to the following assemblies:
    • ESRI.ArcGIS.Carto
    • ESRI.ArcGIS.Catalog
    • ESRI.ArcGIS.CatalogUI
    • ESRI.ArcGIS.esriSystem
    • ESRI.ArcGIS.Server
    • ESRI.ArcGIS.Framework
  2. Add some controls to the form in design view. These controls will work with the Map document to return feature layer names and fields.
    1. In the Solution Explorer, double-click the FormVegProps.cs to open the form in design mode.
    2. In the properties for the form, click the FormBorderStyle dropdown and click None.
    3. Open the Visual Studio toolbox, click the Windows Forms tab, click Label and drag a label control on your form.
    4. In the properties dialog for the label, type the following string for the Text property:
      Select the layer and summary field for the extension:
    5. Drag two more label controls onto the form and set their text properties to be "Layer" and "Field".
    6. Drag a ComboBox control onto the form, and in its properties, type ComboLayers for the (Name).
    7. Drag another combo box onto the form and type ComboFields for the (Name).

      The form should appear as follows:

  3. Add code to the form.
    1. In Solution Explorer, right-click the form and click View Code to open the code window for the form.
    2. At the top of the code window, add the following using statements:
      using ESRI.ArcGIS.Carto;
      using ESRI.ArcGIS.esriSystem;
      using ESRI.ArcGIS.Geodatabase;
      using ESRI.ArcGIS.Geometry;
      
    3. The code already contains a constructor and the class is defined within the VegSOEPropsCSharp namespace.
      namespace VegSOEPropsCSharp
      {
          public partial class FormVegProps : Form
          {
              public FormVegProps()
              {
                  InitializeComponent();
              }
      
    4. Add a method to return the HWnd of the form. This is needed for the property page form to display its contents in ArcCatalog.
              public int getHWnd()
              {
                  return this.Handle.ToInt32();
              }
      
    5. The form needs to expose a method to set the map document the server object extension will use and define properties to get and set the layer and field names the user chooses in the form. Add the following member variables to the form to hold these properties:
              private IMapDocument m_map;
              private string m_layer;
              private string m_field;
      
    6. Define the method to set the map document the server object extension will use. The server object extension will use the same map document associated with the MapServer on which the custom server object extension is enabled. In the method, you'll add code to open the map document, get all of all the polygon feature layers and add their names to the ComboLayers combo box. The code will also set the selected layer to the first layer in the list.
              public void setMap(string sName)
              {
                  ComboLayers.Items.Clear();
                  ComboFields.Items.Clear();
      
                  m_map = new MapDocumentClass();
                  m_map.Open(sName, null);
      
                  IMap map = m_map.get_Map(0);
      
                  UID id = new UIDClass();
                  id.Value = "{E156D7E5-22AF-11D3-9F99-00C04F6BC78E}";
                  IEnumLayer el = map.get_Layers(id, true);
      
                  ILayer l = null;
                  while ((l = el.Next()) != null)
                  {
                      if (l is IFeatureLayer)
                      {
                          IFeatureLayer fl = (IFeatureLayer) l;
                          IFeatureClass fc = fl.FeatureClass;
      
                          if (fc.ShapeType == esriGeometryType.esriGeometryPolygon && fc.FeatureType == esriFeatureType.esriFTSimple)
                              ComboLayers.Items.Add(l.Name);
                      }
                  }
      
                  if (m_layer == null)
                      ComboLayers.SelectedIndex = 0;
                  else
                  {
                      IEnumerator itemenum = ComboLayers.Items.GetEnumerator();
                      while (itemenum.MoveNext())
                      {
                          string lname = itemenum.Current.ToString();
                          if (lname == m_layer)
                              ComboLayers.SelectedIndex = ComboLayers.Items.IndexOf(itemenum.Current);
                      }
                  }
              }
      
    7. Add the properties to get and set the selected layer and field name from the form. Add the following code to your form. The get will be used when the ArcCatalog is used to create a new server object, and when changes are made to a server object via its property page. The set is needed to display the properties for an existing server object when the page is displayed in the server object's property page.
              public string theLayer
              {
                  get { return m_layer; }
                  set { ComboLayers.Text = value;
                      m_layer = ComboLayers.Text; }
              }
      
              public string theField
              {
                  get { return m_field; }
                  set { ComboFields.Text = value;
                      m_field = ComboFields.Text; }
              }
      
    8. Add code to the ComboLayers click event to set the m_layer member variable to the name of the selected layer, and to get the field names for the selected layer and display those field names in the ComboFields combo box.
      1. Open the form in design mode.
      2. Select the ComboLayers combo box.
      3. In the properties, click the Events button.
      4. Double click the SelectedIndexChanged event. This will add an event and open the code window to the implementation of the event.

        When the user clicks the ComboLayers drop down to choose a layer, the code in the SelectedIndexChanged event will be executed. Add code to this event to get the layer from the map, and add the names of all its fields to the ComboFields combo box, and set the m_layer member variable to the name of the selected layer:
              private void ComboLayers_SelectedIndexChanged(object sender, EventArgs e)
              {
                  ComboFields.Items.Clear();
      
                  IMap map = m_map.get_Map(0);
      
                  UID id = new UIDClass();
                  id.Value = "{E156D7E5-22AF-11D3-9F99-00C04F6BC78E}";
                  IEnumLayer el = map.get_Layers(id, true);
      
                  ILayer l = null;
                  while ((l = el.Next()) != null)
                  {
                      if (l is IFeatureLayer && l.Name == ComboLayers.Text)
                      {
                          IFeatureLayer fl = (IFeatureLayer) l;
                          IFeatureClass fc = fl.FeatureClass;
                          IFields flds = fc.Fields;
      
                          for (int i = 0; i < flds.FieldCount; i++)
                          {
                              IField fld = flds.get_Field(i);
                              ComboFields.Items.Add(fld.Name);
                          }
                      }
                  }
      
                  if (m_field == null)
                      ComboFields.SelectedIndex = 0;
                  else
                  {
                      IEnumerator itemenum = ComboFields.Items.GetEnumerator();
                      while (itemenum.MoveNext())
                      {
                          string fname = itemenum.Current.ToString();
                          if (fname == m_field)
                              ComboFields.SelectedIndex = ComboFields.Items.IndexOf(itemenum.Current);
                      }
                  }
      
                  m_layer = ComboLayers.SelectedItem.ToString();
      
              }
      
    9. Add code to the ComboFields combo box to set the m_field member variable to the name of the selected field.
      1. Open the form in design mode.
      2. Select the ComboFields combo box.
      3. In the properties, click the Events button.
      4. Double click the SelectedIndexChanged event. This will add an event and open the code window to the implementation of the event.

        When the user clicks the ComboFields drop down to choose a field, the code in the SelectedIndexChanged event will be executed. Add code to this event to set the m_field member variable to the name of the selected field:
              private void ComboFields_SelectedIndexChanged(object sender, EventArgs e)
              {
                  m_field = ComboFields.Text;
              }
      
    10. In order to close the map document when the form closes, you'll need to add code the the form's Close event.
      1. Open the form in design mode.
      2. Select the form.
      3. In the properties, click the Events button.
      4. Double click the Closed event. This will add an event and open the code window to the implementation of the event.

        Add code to this event to close the map document and set the member variables to null:
              private void FormVegProps_FormClosed(object sender, FormClosedEventArgs e)
              {
                  m_map.Close();
                  m_map = null;
                  m_layer = null;
                  m_field = null;
              }
          }
      

Implementing the Property page class

References to the assemblies needed by the Property page class were added when implementing the Property page form.

Add code to the property page class:
  1. In Solution Explorer, open the VegSOEProps.cs class file.
  2. At the top of the code window, add the following using statements:
    using System;
    using System.Runtime.InteropServices;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.Catalog;
    using ESRI.ArcGIS.CatalogUI;
    using ESRI.ArcGIS.esriSystem;
    using ESRI.ArcGIS.Server;
    using ESRI.ArcGIS.Framework;
    
  3. Since this class will be consumed by a COM client (ArcCatalog) is will need to be registered as a COM object type. The GuidAttribute attribute will define a unique GUID to identify the property page class via COM. A set of member variables should be created to store a reference to: a set of server object properties (m_soprops), a set of server object extension properties (m_props), the unique name of the server object extension (m_exttype), the name of the server object type it is associated with (m_sotype), and a reference to the property page form (propertyPage).
    namespace VegSOEPropsCSharp
    {
       [GuidAttribute("EB085B89-D01F-448c-98F0-448F0BECB0BF")]
    	public class VegSOEProps: IComPropertyPage, IAGSSOEParameterPage
    	{
    		private IPropertySet m_props;
    		private IPropertySet m_soprops;
    		private string m_exttype;
    		private string m_sotype;
    
    		private FormVegProps propertyPage;
    
  4. The constructor of the Property page class will be called by ArcCatalog when the property page needs to be created and displayed. The VegSOEProps constructor will populate a few member variables. A new instance of the FormVegProps Windows form will be created and assigned to the propertyPage variable. The server object extension will be registered to be used with server objects of type "MapServer", stored using the m_sotype variable. The name of the extension, "VegUtilitiesSOE_CSharp", will to stored using the m_exttype variable. This value must match the name of the server object extension when it is registered with ArcGIS Server (discussed in the next section).

    The destructor is explicitly defined to dispose of the property page.

    		public VegSOEProps()
    		{
    			propertyPage = new FormVegProps();
    			m_sotype = "MapServer";
    			m_exttype = "VegUtilitiesSOE_CSharp";
    		}
    
    		~VegSOEProps()
    		{
    			propertyPage.Dispose();
    			propertyPage = null;
    		}
    
  5. Two interfaces need to be implemented: IComPropertyPage and IAGSSOEParameterPage.

    • The IComPropertyPage interfaces defines the framework for ArcCatalog to work with property pages in general, such as show and hide. The following code defines the minimum implementation requirements for this scenario.

      		public bool IsPageDirty
      		{
      			get { return false; }
      		}
      
      		public void Cancel()
      		{}
      
      		public int get_HelpContextID(int controlID)
      		{ return 0; }
      
      		public string Title
      		{
      			get { return null; }
      			set { }
      		}
      
      		public void SetObjects(ISet objects)
      		{}
      
      		public int Width
      		{
      			get { return 0;}
      		}
      
      		public int Priority
      		{
      			get { return 0; }
      			set {}
      		}
      
      		public void Apply()
      		{}
      
      		public IComPropertyPageSite PageSite
      		{
      			set { }
      		}
      
      		public void Deactivate()
      		{ }
      
      		public int Height
      		{
      			get { return 0; }
      		}
      
      		public void Show()
      		{
      			propertyPage.Show();
      		}
      
      		public string HelpFile
      		{
      			get { return null; }
      		}
      
      		public int Activate()
      		{
      			return propertyPage.getHWnd();
      		}
      
      		public bool Applies(ISet objects)
      		{
      			return false;
      		}
      
      		public void Hide()
      		{
      			propertyPage.Hide();
      		}
      		
    • The IAGSSOEParameterPage interface defines a set of properties that need to be explicitly implemented by a custom ArcGIS Server server object extension. This interface works with server object and extension properties defined in this class and the map server object's configuration file to update both the property page and the configuration file. The configuration properties for a server object 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. Properties of server object extensions configured for use with a server object are also stored in the cfg file. In this scenario, a set of properties associated with our custom server object extension are being written to the configuration file ("LayerName" and "FieldName"). In addition, three properties are being read from the configuration file, two from our custom server object extension ("LayerName" and "FieldName") and one from the server object itself ("FilePath").

      The ServerObjectProperties property stores the properties of the server object only, not the extensions. If the server object extension has already been configured, the LayerName and FieldName properties will be available. If not, only the path to the server object's map document will be used. The setter portion of the ServerObjectProperties property will be called when the property page form opens.

      The ExtensionProperties property manages access to the properties of the server object extension in the server object configuration file. In this scenario, if the server object extension is enabled, the following entries are added to the server object configuration file:
      <Extension>
          <TypeName>VegUtilitiesSOE_CSharp</TypeName>
          <Enabled>true</Enabled>
          <Properties>
      	    <LayerName>Vegetation</LayerName>
      	    <FieldName>PRIMARY_</FieldName>
          </Properties>
          <Info>
      	    <WebEnabled>true</WebEnabled>
      	    <WebCapabilities></WebCapabilities>
          </Info>
      </Extension>
      	
      The LayerName and FieldName properties will differ depending on changes made via the property page. When the custom server object extension is checked in ArcCatalog, these properties are added to the map server object configuration file. The properties are added to the configuration file in the getter portion of the ExtensionProperties property definition. To store a custom property in the configuration file, add it in the getter. If the properties have already been added to the configuration file, the setter portion of the ExtensionProperties property will return a propertyset with current properties and values.

      The ServerObjectExtensionType and ServerObjectType properties are set when the property page class is instantiated.
      		public IPropertySet ServerObjectProperties
      		{
      			get { return m_soprops; }
      			set {
      				m_soprops = value;
      
      				try
      				{
      					propertyPage.theLayer = m_props.GetProperty("LayerName").ToString();
      					propertyPage.theField= m_props.GetProperty("FieldName").ToString();
      					propertyPage.setMap(m_soprops.GetProperty("FilePath").ToString());
      				}
      				catch
      				{
      					propertyPage.setMap(m_soprops.GetProperty("FilePath").ToString());
      				}
      			}
      		}
      
      		public IPropertySet ExtensionProperties
      		{
      			get
      			{
      				m_props.SetProperty("LayerName",propertyPage.theLayer);
      				m_props.SetProperty("FieldName",propertyPage.theField);
      				return m_props;
      			}
      			set
      			{
      				m_props = value;
      			}
      		}
      
      		public string ServerObjectExtensionType
      		{
      			get
      			{
      				return m_exttype;
      			}
      		}
      
      		public string ServerObjectType
      		{
      			get
      			{
      				return m_sotype;
      			}
      		}
      
  6. For the property page to be accessible from ArcCatalog as an extension to ArcGIS Server server object capabilities, it must be registered as an "AGS Extension Parameter Pages" component category. This registration takes place when the property page class is registered with COM. A set of COM specific methods to register this property page in the appropriate category are provided below. The string parameter passed to both methods is the Guid defined for our custom property page (EB085B89-D01F-448c-98F0-448F0BECB0BF). When registering our property page, the Guid associated with AGS Extention Parameter Page component category (A585A585-B58B-4560-80E3-87A411859379) is added to the "Implemented Categories" key in the registry. ArcCatalog will now recognize that our page is associated with ArcGIS Server extension parameter pages. When our property page is unregistered, the custom property page Guid is removed from the registry. 

    		[ComRegisterFunction()]
    		static void RegisterFunction(String regKey)
    		{
    			Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(regKey.Substring(18) + "\\Implemented Categories\\" + "{A585A585-B58B-4560-80E3-87A411859379}");
    		}
    
    		[ComUnregisterFunction()]
    		static void UnregisterFunction(String regKey)
    		{
    			Microsoft.Win32.Registry.ClassesRoot.DeleteSubKeyTree(regKey.Substring(18));
    		}
    	}
    }
    
    
  7. To view property pages registered with ArcGIS component categories, run the categories.exe in <ArcGIS Install>\bin directory and select the category you are interested in.


Set assembly properties for the VegSOEPropsCSharp project

In the VegSOEPropsCSharp project, open the AssemblyInfo.cs file and change it to reflect the following settings.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("VegSOEPropsCSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ESRI")]
[assembly: AssemblyProduct("VegSOEPropsCSharp")]
[assembly: AssemblyCopyright("Copyright 2006")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Build the server object extension property page project

Build the VegSOEPropsCSharp project and create the assembly VegSOEPropsCSharp.dll. The deployment section below will discuss the distribution and registration of this assembly.


Part 3: Registering the custom server object extension

Components of a custom server object extension must be registered in two ways.  One, the assemblies must be registered with .NET\COM on the machine they will be used.  And two, the server object extension must be registered with ArcGIS Server.

Thusfar, three .NET assemblies have been created:

VegSOECSharp.dll

The VegSOECSharp assembly must be registered on all Server Object Container machines (ArcSOC.exe) where a server object may enable and use the server object extension. As a COM client, the ArcSOC.exe process can work with other COM objects and types. To expose the .NET types in the VegSOECSharp assembly to COM, the .NET Assembly Registration tool (regasm.exe) will be used. To register the VegSOECSharp.dll, do the following:

  1. Open a Visual Studio 2005 command prompt and navigate to the location of the the VegSOECSharp.dll. Use the following command:
    regasm VegSOECSharp.dll /codebase
    

    The regasm tool reads the metadata within an assembly and adds the necessary entries to the registry. The /codebase option registers the explicit location of the assembly. The codebase option will return a warning indicating that the assembly should be signed. If you choose to sign the assembly, you can also place it in the Global Assembly Cache (GAC) and remove the "/codebase" option when using regasm. To sign the assembly and add it to the GAC, do the following:

    1. Create a strongly named key:
      sn -k MyKeyPair.snk
      
    2. Add or uncomment the following entries to the AssemblyInfo.cs file in the VegCOMCSharp project. Change the path for the AssemblyKeyFile attribute to the key created in the previous step.
      [assembly: AssemblyDelaySign(false)]
      [assembly: AssemblyKeyFile("C:/temp/MyKeyPair.snk")]
      
    3. Rebuild the VegSOECSharp project.
    4. Run the following command:
      regasm VegSOECSharp.dll
      
    5. Add the assembly to the GAC:
      gacutil -i VegSOECSharp.dll
      						
  2. Important:  Make sure that the ArcGIS Server Object Container user account has read/execute access on the directory that contains the registered assembly.
  3. To confirm that the property page registered with ArcGIS component categories correctly, run the categories.exe in <ArcGIS Install>\bin directory and open the AGS Extention Parameter Pages folder.  The VegSOEPropsCSharp.VegSOEPropsCSharp property page should be listed.
  4. To unregister the assembly and type library, use the following command:
    regasm /unregister VegSOECSharp.dll


VegSOEInterfacesCSharp.dll

The VegSOEInterfacesCSharp assembly stores interface definitions implemented by the server object extension, therefore it must be registered on all Server Object Container machines (ArcSOC.exe) where a server object may enable and use the server object extension.  As a COM client, the ArcSOC.exe process can work with the server object extension classes and interfaces as COM objects and types. 

In addition, the VegSOEInterfacesCSharp.dll must be registered on any application client machine that will consume the server object extension.  As a result, the Microsoft .NET Framework used to build the assembly must be installed on the client machine.  The VegSOEInterfacesCSharp.dll contains the interfaces a client application will use to access server object extension objects remotely (just like working with ArcObjects remotely via ArcGIS Server).  No business logic is distributed with the VegSOEInterfacesCSharp.dll. 

To expose the .NET interface types in the VegSOEInterfacesCSharp assembly to COM, the .NET Assembly Registration tool (regasm.exe) will be used. To register the VegSOEInterfacesCSharp.dll, do the following:

  1. Open a Visual Studio 2005 command prompt and navigate to the location of the the VegSOECSharp.dll. Use the following command:
    regasm VegSOEInterfacesCSharp.dll /tlb:VegSOEInterfacesCSharp.tlb
    

    The regasm tool reads the metadata within an assembly.  Since the assembly only contains interface types, a type library must be generated using the /tlb option.  The location of the tlb is stored in the registry.          

  2. Important:  Make sure that the ArcGIS Server Object Container user account has read/execute access on the registered type library.  On the client application machine, make sure the user account that application is running as has read/execute access to the registered type library.   To work with the types, add a reference to the VegSOEInterfacesCSharp.dll in the client application (discussed in part 4 of this scenario).     
  3. To unregister the assembly and type library, use the following command:
    regasm /unregister VegSOEInterfacesCSharp.dll /tlb:VegSOEInterfacesCSharp.tlb


VegSOEPropsCSharp.dll

The VegSOEPropsCSharp assembly must be registered on all machines where ArcCatalog will be used to administer GIS Servers where the server object extension can be enabled.    Since the assembly is a .NET component, the Microsoft .NET Framework used to build the assembly must also be installed.  As a COM client, ArcCatalog can work with other COM objects and types.  To expose the .NET types and Windows form in the VegSOEPropsCSharp assembly to COM so they can be used by ArcCatalog, the .NET Assembly Registration tool (regasm.exe) will be used. To register the VegSOEPropsCSharp.dll, do the following:

  1. Open a Visual Studio 2005 command prompt and navigate to the location of the the VegSOEPropsCSharp.dll. Use the following command:
    regasm VegSOEPropsCSharp.dll /codebase
    

    The regasm tool reads the metadata within an assembly and adds the necessary entries to the registry. The /codebase option registers the explicit location of the assembly. The codebase option will return a warning indicating that the assembly should be signed. If you choose to sign the assembly, you can also place it in the Global Assembly Cache (GAC) and remove the "/codebase" option when using regasm. To sign the assembly and add it to the GAC, do the following:

    1. Create a strongly named key:
      sn -k MyKeyPair.snk
      
    2. Add or uncomment the following entries to the AssemblyInfo.cs file in the VegCOMCSharp project. Change the path for the AssemblyKeyFile attribute to the key created in the previous step.
      [assembly: AssemblyDelaySign(false)]
      [assembly: AssemblyKeyFile("C:/temp/MyKeyPair.snk")]
      
    3. Rebuild the VegSOECSharp project.
    4. Run the following command:
      regasm VegSOEPropsCSharp.dll
      
    5. Add the assembly to the GAC:
      gacutil -i VegSOEPropsCSharp.dll
      						
  2. Important:  Make sure that the user account running ArcCatalog has read/execute access on the directory that contains the registered assembly.
  3. To unregister the assembly and type library, use the following command:
    regasm /unregister VegSOEPropsCSharp.dll


Register the server object extension with ArcGIS Server

The server object extension needs to be registered with ArcGIS Server - more specifically, it needs to be registered with the Server Object Manager (SOM).  Registration will involve using ArcObjects in the ESRI.ArcGIS.Server component to add information about the availability and properties of the server object extension to ArcGIS Server configuration files.   Using .NET the easiest way to accomplish this is to create a console application in Visual Studio and add the necessary code.  The following steps will cover creating the console application project, adding the code, and running the application.  
  1. Create the server object extension registration project.
    1. Start Visual Studio.
    2. Click File, click New, then click Project.
    3. In the New Project dialog box, under Project Types, click the Visual C# Projects category.  Under Templates, click Console Application.
    4. Type a path to where you want to put this project.
    5. For the project name, type "RegisterSOE" and click OK. 
  2. Add a reference to the following assemblies:
    • ESRI.ArcGIS.ADF.Connection
    • ESRI.ArcGIS.Server
  3. Open the Program.cs file and add the following using statements:
    using ESRI.ArcGIS.ADF.Connection.AGS;
    using ESRI.ArcGIS.Server;
  4. Add the following code in the Main method.  The Main method has already been created for you.

    Create a new instance of AGSServerConnection to initiate a connection to the SOM.  Specify the correct name or ip address of the machine on which the SOM is running.  Cast to IServerObjectAdmin2 to create a new extension type. 

    	AGSServerConnection gisconnection = new AGSServerConnection();
    	gisconnection.Host = "localhost";
    gisconnection.Connect(); IServerObjectAdmin2 soa = (IServerObjectAdmin2) gisconnection.ServerObjectAdmin; IServerObjectExtensionType soet = soa.CreateExtensionType();


    Set properties on the new extension type.   All properties listed are defined using a string.   

    The CLSID property is really the ProgID.  This is the fully qualified name of the class that implements IServerObjectExtension.  In our scenario, the CLSID value is "VegSOECSharp.VegUtilsSOE". 

    The Description can be populated with basic information on the purpose of the server object extension.  It will be displayed in ArcCatalog and accessible programmatically. 

    The Name property should be a intuitive title of the server object extension and unique among all other extensions.  The Name is used by ArcCatalog to display the server object extension on the capabilities tab.  It is also used to uniquely identify the server object extension and display the correct property page. 

    Important: The Name property must match the ServerObjectExtensionType property defined in the property page class to display the correct property page when the server object extension is selected in ArcCatalog.  In this scenario, the Name property must match the m_exttype variable value in the VegSOEProps.cs class file.     

    	soet.CLSID = "VegSOECSharp.VegUtilsSOE";
    	soet.Description = "Veg Utilities Server Object Extension";
    	soet.Name = "VegUtilitiesSOE_CSharp";
    


    Once the server object extension properties are set, it can be added to a server object type using the AddExtensionType method off the IServerObjectAdmin2 interface.  To add the extension to multiple server object types, call this method for each type.  To remove the extension, call DeleteExtensionType with the same parameters.   

    	soa.AddExtensionType("MapServer", soet);
    	Console.WriteLine("Registered SOE with ArcGIS Server");
    	Console.ReadLine();
    
  5. Build the RegisterSOE project and run it.  The application must be run with a user account that has administrative privledges on the ArcGIS Server - thus the user must be part of the agsadmin group on the SOM machine.  If registration was successful, the line "Registered SOE with ArcGIS Server" will appear in the console window. 
  6. On the SOM machine, the following entry will be added to the ServerTypesExt.dat file in the <ArcGIS Install>\server\system directory:
    <types>
    	<ServerObjectType>
    		<Name>MapServer</Name>
    		<ExtensionTypes>
    			<ExtensionType>
    				<Name>VegUtilitiesSOE_CSharp</Name>
    				<DisplayName></DisplayName>
    				<CLSID>VegSOECSharp.VegUtilsSOE</CLSID>
    				<Description>Veg Utilities Server Object Extension</Description>
    			</ExtensionType>
    		</ExtensionTypes>
    	</ServerObjectType>
    </types>
    				

Enable the server object extension with a map service

The easiest way to enable and configure a server object extension is to use ArcCatalog. A set of sample data is provided for this scenario in <ArcGIS Install\DeveloperKit\SamplesNET\Server\data\Yellowstone.  The Yellowstone.mxd map document contains two layers: a polygon feature class and a grid.  The polygon feature class is stored in the personal geodatabase veg.mdb.  It contains polygons referencing the geographic areas of different vegetation types in Yellowstone National Park.  The grid is an DEM in Arc\Info GRID format and merely provides a topographic reference for the area.   In this section, we will add the Yellowstone map document as an ArcGIS Server map service and enable the VegUtilitiesSOE_CSharp extension using a field in the vegetation layer.  

  1. Open ArcCatalog and create a new administrative connection to ArcGIS Server.
  2. Add a new map service using the Yellowstone.mxd included with the sample data.  While configuring the new map service, select the Capabilities tab and check the box next to the "VegUtilitiesSOE_CSharp" extension. 
  3. In the Properties section of the Capabilities tab, set the Layer property to "Vegetation" and Field property to "PRIMARY_".   The "PRIMARY_" field stores the vegetation type name and will be used by the VegUtilitiesSOE_CSharp extension to aggregate the area totals for the selected region.

  4. Finish configuring the Yellowstone map service and accept the defaults.  Publish the service.
The next section will cover the creation of a Web ADF application to utilize the VegUtilitiesSOE_CSharp extension via the Yellowstone map service. 
  

Part 4: Creating the Web application client

Now that you have developed your server object extension and deployed it with an ArcGIS Server map service, you can build your Web application to make use of it.  In this example, we will create a Web application from scratch using the Web ADF components.  The Web ADF and ASP.NET will provide the basic framework for mapping, capturing user events, and displaying results.  We will create a custom tool to interact with the server object extension.

Register the COM types in the VegSOEInterfacesCSharp assembly

As mentioned in part 3, the VegSOEInterfacesCSharp.dll must be registered on any client machine that will consume the server object extension.  The assembly contains COM interface types that will be used by the Web application to work with custom server object extension COM objects remotely.  The VegSOEInterfacesCSharp.dll was built at the conclusion of part 1 of this scenario.

  1. Open a Visual Studio 2005 command prompt and navigate to the location of the the VegSOECSharp.dll.
  2. Use the following command:
    regasm VegSOEInterfacesCSharp.dll /tlb:VegSOEInterfacesCSharp.tlb
    

    The regasm tool reads the metadata within an assembly and adds the necessary entries to the registry.  Since the assembly only contains interface types, a type library must be generated using the /tlb option.  The location of the tlb is stored in the registry. 


Create the Web application project
  1. Start Visual Studio.
  2. Click File, click New, then click WebSite.
  3. In the New Web Site dialog box, under Templates, click ASP.NET Web Site.
  4. For the Web application name and url enter "http://localhost/ArcGIS_Spatial_Query_SOE_CSharp/VegSOEWebAppCSharp"

Add and configure Web ADF and ASP.NET controls

  1. Add the following controls in the locations specified in the screenshot below.  The following steps will configure each control: MapResourceManager, Map, Toolbar, Toc, Label, Textbox, Checkbox, and div

  2. Configure the MapResourceManager control
    1. Set the ResourceItems property on the MapResourceManager control.  Click the "Edit Map Resources" smart tag on the control in the design view -or- in the MapResourceManager property page click the ellipsis next to the ResourceItems property to display the MapResourceInfo Collection Editor dialog.
        
    2. In the MapResourceItem Collection Editor dialog, add a new MapResourceItem (click the Add button).  Set its Definition property by clicking the ellipsis in the property page to display the Map Resource Definition Editor.  Select an "ArcGIS Server Local" data source type and specify the name of the SOM machine on which the Yellowstone map service is running with the custom server object extension enabled.
    3. Once the data source is defined, the Resource property opens the ArcGIS Resource Definition Editor dialog which provides a list of available map services and data frames.   Select the Yellowstone map service and the default data frame.      



                                                         
                       
    4. Note that the Identity property is grayed out and cannot be set from the Map Resource Definition Editor dialog.  At design-time, the identity of the user running Visual Studio is used to connect to an ArcGIS Server local data source.  At runtime, that identity is established by the Web application.  Only one identity can be used to define access to all ArcGIS Server local data sources in a single Web application .  You can define this identity by right-clicking the Web project in Solution Explorer and selecting the Add ArcGIS Identity option.  Enter the identity credentials which will be used to access ArcGIS Server local resources at runtime.  This information will be added to the web.config file within a standard ASP.NET identity tag.  If the "Encrypt identity in web.config" checkbox is checked, the identity tag will be encrypted; otherwise, the username and password will be stored as clear text.    



  3. Configure the Map control by setting the MapResourceManager property.  Select the Map control in design view or in the Properties window, then click the dropdown list for the MapResourceManager property.  Select the name of the MapResourceManager configured in the previous step (by default "MapResourceManager1").
  4. Configure the Toolbar control by setting its BuddyControls and ToolbarItems properties.

    Toolbar when first added to page
                                         down arrow
    Toolbar with properties set
    1. Specifying buddy controls Toolbar control properties

      You specify which map or page layout controls a toolbar will work with through the toolbar control’s BuddyControls property. Set the BuddyControlType property to "Map" and click the ellipsis button in the BuddyControls property value to display the BuddyControls Collection Editor. Add an item associated with the map control (Map1). 
    2. To add an item to your toolbar, follow the steps below:

      1. Click the Toolbar control, then, in the Properties window, click the … button next to the ToolbarItems property. You will see a form with a list of available toolbar items and current toolbar contents.
      2. In the Toolbar Items column, select and add the Zoom In, Zoom Out, and Pan tools individually.  The custom tool will be added in a later step.
  5. Configure the Toc control by setting the BuddyControl property.  Select the Toc control in design view or in the Properties window, then click the dropdown list for the BuddyControl property.   Select the name of the Map control configured in the previous step (by default "Map1").
  6. Configure the Label, Textbox, and Checkbox control using the screenshot above.  Confirm that the id for each control is Label1, Textbox1, and Checkbox1, respectively.  Add the appropriate text for the Label and Checkbox control as shown in the screenshot in the first step.  Set the default value in Textbox1 to 10000. 
  7. Configure the div and GridView controls.  Select the HTML div control and set its Id value to "griddiv" (either in the property page or HTML source).  Insert (drag and drop) the GridView control into the div.   Resize the GridView control to a height and width to approximately 200 by 250. 


Add content to capture user input in the browser

The custom server object extension will be utilized via a custom tool.   The Web application user interface contains a textbox and checkbox to store information that the custom tool will use to calculate buffer distance and update a GridView.  In this section, Web content will be added to capture textbox and checkbox values.  

  1. Add JavaScript content to the Default.aspx page to capture user provided content in the textbox and checkbox.  In the HEAD section at the top of the aspx page, add the following script tag and JavaScript content:

    <script language="javascript" type="text/javascript">
    
             function SetCheckBox(id)
             {
                if (id == 'CheckBox1'){
                    document.getElementById('CheckBox1').value = "";
                    document.getElementById('CheckBox1').value = document.getElementById('CheckBox1').checked;
                }
             }
    
             function CustomLoad(){
                var customform = document.forms[0];
                var original_value = customform.elements["ESRIWebADFHiddenFields"].value;
                var new_value = original_value + ",TextBox1,CheckBox1";
                customform.elements["ESRIWebADFHiddenFields"].value = new_value;
             }
    
      </script>
    					
    The SetCheckBox function will be called when CheckBox1 is checked.  It will set the value of CheckBox1 to "true" or "false", checked or unchecked respectively. 

    The CustomLoad function will modify a hidden input element ("ESRIWebADFHiddenFields") used by the Web ADF to store values in the browser.  All callbacks and postbacks generated by the Web ADF will include values assocated with the fields in the ESRIWebADFHiddenFields element value.   In this case, a callback will be generated when our custom tool interacts with the Web ADF Map control.  

    The ESRIWebADFHiddenFields input element is written to a Web page at runtime.  It's value is a comman-delimited array of element ids in the page.  Here is an example:

    <input type="hidden" name="ESRIWebADFHiddenFields" id="ESRIWebADFHiddenFields"
    value="maxx,maxy,minx,miny,coords,Map1_mode,control,Toolbar1_Group_current_tool," />
    					
    When the Web ADF generates a postback (callback or full page postback) the createClientPostBackQueryString function in display_dotnetadf.js iterates through the comma-delimited array and adds an argument and value for each field.  To take advantage of this capability which is already included in the Web ADF, we will append the textbox and checkbox ids onto the list.  The values will be parsed in the custom tool implementation code.
       
  2. The CustomLoad function should run upon page load in the browser.  To enable this, add an onload event to the body tag in the Default.aspx page.  Set the onload event to "CustomLoad()":
    <body onload="CustomLoad()">
    					

Add a custom tool to work with the server object extension
 

  1. In Solution Explorer, right-click the Web project and select "Add New Item...".  In the Add New Item dialog, under the Visual Studio Installed Templates section, select the "Class" item.  Set the class file name to "VegTool.cs" and make sure the language is "Visual C#".  Visual Studio will prompt you to create an "App_Code" folder and place the new class file inside.  Click Yes.  The VegTool.cs file should open for you to start adding content.  This file will contain the executable code associated with the custom tool.

  2. The server object extension exposes two interfaces: IVegUtilsSOE and IVegResultsSOE.  As a client, the Web application needs a reference to the type definition for both interfaces to remotely utilize the COM objects they represent.  The COM interface types are provided in the VegSOEInterfacesCSharp.dll. 

    In Solution Explorer, right-click the Web project and select "Add Reference...".  In the dialog, browse to the location of the VegSOEInterfacesCSharp.dll and add it to the project.

  3. Addition components are also required to work with ArcGIS Server and the Web ADF.  In Solution Explorer, right-click the Web project and select "Add ArcGIS Reference...".  In the dialog, select the following components, click Add, then Finish:

    ESRI.ArcGIS.ADF.ArcGISServer
    ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer
    ESRI.ArcGIS.Server
    ESRI.ArcGIS.Carto
    ESRI.ArcGIS.Geometry
    ESRI.ArcGIS.System
    ESRI.ArcGIS.Display

  4. At the top of the VegTool.cs file, add the following using statements:

    [C#]

    using ESRI.ArcGIS.ADF.Web.UI.WebControls;
    using ESRI.ArcGIS.ADF.Web.UI.WebControls.Tools;
    using ESRI.ArcGIS.ADF.ArcGISServer;
    using ESRI.ArcGIS.ADF.Web.DataSources;
    using ESRI.ArcGIS.Carto;
    using ESRI.ArcGIS.Geodatabase;
    using ESRI.ArcGIS.Server;
    using ESRI.ArcGIS.Geometry;
    using VegSOEInterfacesCSharp;
  5. Remove the VegTool constructor and implement the IMapServerToolAction interface on the VegTool class.  The code for the class should begin as follows: 
    public class VegTool : IMapServerToolAction
    {
        public void ServerAction(ToolEventArgs args)
        {
    
  6. In the next section of code, a reference to the Map control is returned for use throughout the tool implementation code.  In this part, a reference to the Page is returned to get user provided values from the client.  In this case, the value in TextBox1 is the buffer distance and CheckBox1 determines if a table of data will be returned to the browser.  If the tool initiates a callback, the __CALLBACKPARAM argument in the HTTP request will contain an argument/value pair cooresponding to the value of both TextBox1 and CheckBox1.  If a initiating a full page postback, the argument/value pairs will be included independently in the HTTP request.     
            ESRI.ArcGIS.ADF.Web.UI.WebControls.Map mapctrl = (ESRI.ArcGIS.ADF.Web.UI.WebControls.Map) args.Control;
    
            System.Web.UI.Page page = mapctrl.Page;
    
            string cbxvalue = String.Empty;
            string tbxvalue = String.Empty;
            string callbackArgs = String.Empty;
            System.Collections.Specialized.NameValueCollection keyValColl = null;
    
            if (page.IsCallback)
            {
                callbackArgs = page.Request.Params["__CALLBACKPARAM"];
                keyValColl = CallbackUtility.ParseStringIntoNameValueCollection(callbackArgs);
            }
            else
            {
                keyValColl = page.Request.Params;
            }
    
            tbxvalue = keyValColl["TextBox1"];
            cbxvalue = keyValColl["CheckBox1"];
    
    
  7. The ArcGIS Server implemenation of the Web ADF Common API will be used to get the map resource for an ArcGIS Server local data source (MapResourceLocal).  Local data sources provide access to the ArcObjects API for a server object.  So in this case,  we have access to server context and the stateless interface for map services, IMapServer.  The user click in the browser will provide the center point for a buffer to select and aggregate features.  The point is provided in screen units and must be converted to map units.  The Web ADF provides a convienience method, ToMapPoint, to assist in this conversion.  Server context is used to create an ArcObjects Point for use with the custom server object extension.  Buffer distance is set with the value provided by the user in TextBox1.
     
            ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality mapfunc = (ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality) mapctrl.GetFunctionality(mapctrl.MapResourceManagerInstance.ResourceItems.Count - 1);
            ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal mapres = (ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceLocal) mapfunc.MapResource;
            IServerContext sc = mapres.ServerContextInfo.ServerContext;
            IMapServer mapserver = mapres.MapServer;
    
            PointEventArgs pargs = (PointEventArgs) args;
            ESRI.ArcGIS.ADF.Web.Geometry.Point inpt = ESRI.ArcGIS.ADF.Web.Geometry.Point.ToMapPoint(pargs.ScreenPoint, mapctrl.Extent, mapfunc.DisplaySettings.ImageDescriptor.Width, mapfunc.DisplaySettings.ImageDescriptor.Height);
            IPoint pt = (IPoint)sc.CreateObject("esriGeometry.Point");
            pt.X = inpt.X;
            pt.Y = inpt.Y;
    
            double distance = 0;
    
            if (!Double.TryParse(tbxvalue, out distance))
            {
                distance = 10000;
            }
    
    
  8. Every server object type implements the ArcObjects IServerObjectExtensionManager interface to support the discovery of server object extensions.  If a server object extension is enabled on the server object, the FindExtensionByTypeName method can be used to return a reference to the server object extension with its registered name.  In this scenario, the custom server object extension VegUtilitiesSOE_CSharp class VegUtilsSOE implements the IVegUtilsSOE interface which exposes a single method, sumVegetationType.  The method requires two parameters: an ArcObjects Point and a double value to store distance.  The method uses the point and distance to construct a buffer.   When the server object extension was enabled (in ArcCatalog) a feature layer and field was selected.  The buffer will select features in the feature layer and the results will be summarized using the field.  In this case, the feature layer contains polygons defining vegetation regions and the field is the proper name of the vegetation type classification.   The sumVegetationType method returns a reference to a VegResultsSOE object via the IVegResultsSOE interface.  The next few sections will cover working this the properties of this results object.
        
            IServerObjectExtensionManager soext_manager = (IServerObjectExtensionManager) mapserver;
            IServerObjectExtension soext = soext_manager.FindExtensionByTypeName("VegUtilitiesSOE_CSharp");
            IVegUtilsSOE vegutils = (IVegUtilsSOE) soext;
            IVegResultsSOE vegresults = vegutils.sumVegetationType(pt, distance);
    
  9. The IVegResultsSOE.ResGraphics property references a set of graphics elements via the IGraphicElements interface.  To render these graphics in the map requested by the Web ADF and generated by ArcGIS Server the graphics elements need to be converted from a COM object to a value object.  Value objects are used by the ArcGIS Server SOAP API to store values.  The Web ADF uses the ArcGIS Server SOAP API to work with ArcGIS Server data sources, both Internet and Local.  When the Web ADF requests a map image from an ArcGIS Server map service, it uses the MapFunctionality MapDescription value object.  By default, changes to the MapDescription are maintained for the duration of the user session.  In this section of code, the properties of the graphics representing the summarized features are explicitly defined. The color of the solid outline is dark red.  Since we're working with the ArcGIS Server SOAP API, value objects are used to set and store these properties.  The symbol for each element in the graphic element array is set, then the graphic element array is associated with the MapDescription via the CustomGraphics property.    The next time time a map is requested from this map resource, the custom graphics will be rendered in the map.   
    
            IGraphicElements comGraphics = vegresults.ResGraphics;
    
            GraphicElement[] proxyGraphics = (GraphicElement[])
                ESRI.ArcGIS.ADF.ArcGISServer.Converter.ComObjectToValueObject(comGraphics, sc, typeof(GraphicElement[]));
    
            RgbColor rgb = new RgbColor();
            rgb.Red = 155;
            rgb.Green = 0;
            rgb.Blue = 0;
            rgb.AlphaValue = 255;
    
            SimpleLineSymbol sls = new SimpleLineSymbol();
            sls.Style = ESRI.ArcGIS.ADF.ArcGISServer.esriSimpleLineStyle.esriSLSSolid;
            sls.Color = rgb;
            sls.Width = 0.2;
    
            foreach (ESRI.ArcGIS.ADF.ArcGISServer.PolygonElement pe in proxyGraphics)
            {
                SimpleFillSymbol sfs = (SimpleFillSymbol) pe.Symbol;
                sfs.Outline = sls;
            }
    
            mapfunc.MapDescription.CustomGraphics = proxyGraphics;
    
    
  10. The Web page contains a div tag to store the location of GridView content.  The Web application has a reference to a GridView control that will be used to display summarized statistics returned from the server object extension.  If the GridView should be displayed, the IRecordSet is returned from the IVegResultsSOE.Stats property.  The IRecordSet is converted into a ArcGIS Server SOAP API RecordSet which can easily be converted into an ADO.NET DataTable using a convenient Converter method ToDataTable.  The GridView is rendered as HTML, which is returned to the browser and inserted into the div "griddiv".   Since our custom tool triggers the Map to generate the initial callback, the Map will also recieve the callback response.  We will create a custom CallbackResult to contain the raw HTML GridView content and append it to the Map's callback result collection.      
            GridView gdview = (GridView) mapctrl.Page.FindControl("GridView1");
            string showtable = "'hidden'";
            Boolean displaydiv = false;
    
            if (!Boolean.TryParse(cbxvalue, out displaydiv))
            {
                displaydiv = false;
            }
    
            if (displaydiv)
            {
                IRecordSet rs = vegresults.Stats;
                ESRI.ArcGIS.ADF.ArcGISServer.RecordSet value_rs = (ESRI.ArcGIS.ADF.ArcGISServer.RecordSet)ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.Converter.ComObjectToValueObject(rs, sc, typeof(ESRI.ArcGIS.ADF.ArcGISServer.RecordSet));
                System.Data.DataTable datatable = ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.Converter.ToDataTable(value_rs);
    
                gdview.DataSource = datatable;
                gdview.DataBind();
    
                string returnstring = null;
    
                using (System.IO.StringWriter sw = new System.IO.StringWriter())
                {
                    HtmlTextWriter htw = new HtmlTextWriter(sw);
                    gdview.RenderControl(htw);
                    htw.Flush();
                    returnstring = sw.ToString();
                }
    
                CallbackResult cr = new CallbackResult("div", "griddiv", "innercontent", returnstring);
                mapctrl.CallbackResults.Add(cr);
    
                if (datatable.Rows.Count > 1)
                    showtable = "'visible'";
    
            }
    
    
  11. If the DataTable is not empty, the content of the griddiv tag in the browser should be visible.  The following custom CallbackResult includes a set of JavaScript to change the visibility of the griddiv, and thus the GridView content, in the browser.
     
            object[] oa = new object[1];
            string sa = "var griddiv = document.getElementById('griddiv');";
            sa += "griddiv.style.visibility = " + showtable + ";";
            oa[0] = sa;
    
            CallbackResult cr1 = new CallbackResult(null, null, "javascript", oa);
            mapctrl.CallbackResults.Add(cr1);
    
    
  12. The final step in the custom tool is to refresh the ArcGIS Server resource that contains the custom graphics aded earlier.  Depending on the blending scheme for the map, the entire map or just the ArcGIS Server resource should be refreshed. 
            if (mapctrl.ImageBlendingMode == ImageBlendingMode.WebTier)
            {
                mapctrl.Refresh();
            }
            else if (mapctrl.ImageBlendingMode == ImageBlendingMode.Browser)
            {
                mapctrl.RefreshResource(mapres.Name);
            }
        }
    }
    
  13. Now that the implementation code for our custom tool is finished, we need to add a tool item to the Toolbar control to trigger the action that executes the custom tool.  Select the Toolbar control in design view or in the Properties window, then click the ellipsis for the ToolbarItems property.  In the ToolbarItems Collection Editor dialog, add a new "Tool" item.  In the Toolbar Items section, select the Tool item and click the Add button.  The new Tool should appear under the Current Toolbar Items section.
  14. Select the new Tool item and click the Show Properties button.  A properties dialog for the new Tool should be displayed.   

     

    Set the following properties:

    Property Value Description
     Text

     Vegetation Summary Proximity Search Tool

     Label for the tool in the Toolbar
     ClientAction

     Point

     Client event passed to the server
     Name  VegetationSummary  Object name of the tool, if used in code
     ServerActionAssembly

     App_Code

     Class libraries associated with a Web site are compiled into an assembly named App_Code
     ServerActionClass

     VegTool

     The name of the custom class which implements IMapServerToolAction and will be executed when this tool is used in the map

Using the Web application at run-time


  1. Open a browser and navigate to the Web application URL (for example, http://localhost/ArcGIS_Spatial_Query_SOE_CSharp/VegWebAppCSharp) -or- load the Web application solution within Visual Studio 2005.
  2. Enter a distance in meters to search vegetation regions within the Vegetation layer. The default is 10000.
  3. To display the summary statistics for the selected regions, check the box next to "Show Summary Results in a Table".
  4. In the toolbar, click on the tool "Vegetation Summary Proximity Search Tool".
  5. Click on the map. 
  6. When the application is finished processing, a circular region in the map should be highlighted along the boundary of the features in the Vegetation layer. The summary statistics should be rendered in a table below the Toc.


Additional Resources

This scenario includes functionality and programming techniques covering a number of different aspects of ArcObjects, the ArcGIS Server ArcObjects API, ArcGIS Server SOAP API, Web ADF Common API and Web controls.

You are encouraged to read The ArcGIS Server ArcObjects API , to get a better understanding of core ArcGIS Server programming concepts such as stateful versus stateless server application development. This section also covers concepts and programming guidelines for working with server contexts and ArcObjects running within those contexts, as well as discussion on extending server objects as demonstrated in this scenario.

This scenario makes use of the Web ADF to provide the majority of the user interface for this Web application. To learn more about the Web ADF read Developing Web Application using the Web ADF - which includes detailed descriptions and examples of using the Web controls, including the Map and Toolbar Web controls that you made use of while programming this Web application. If you are unfamiliar with ASP.NET Web development, it's also recommended that you refer to your .NET developer documentation to become more familiar with Web application development.

ArcGIS Server applications exploit the rich GIS functionality of ArcObjects. This application is no exception. It includes the use of ArcObjects to work with the components of a MapServer, buffer and clip geometries, query a geodatabase, and create graphics. To learn more about these aspects of ArcObjects, refer to the online developer documentation on the Carto, Display, GeoDatabase, and Geometry object libraries.