This walkthrough is for developers who need to build and deploy a .NET application Web service incorporating geocoding and spatial query functionality using the ArcGIS Server ArcObjects API. It describes the process of building, deploying, and consuming the ArcGIS_Toxic_Site_Geocode sample, which is part of the ArcGIS developer samples.
The install location is: <install_location>\DeveloperKit\SamplesNET\Server\Web_Services
Rather than walk through this scenario, you can get the completed Web service from the samples installation location. The sample is installed as part of the ArcGIS developer samples.
Project Description
The purpose of this scenario is to create an ASP.NET Web service using Visual Studio 2005 that uses ArcObjects to locate all of the toxic waste sites within a specified distance of a specified address. The Web service returns a .NET array of application-defined toxic waste site objects.
This Web service is intended to be called by other programs, and an example of such a client program is a .NET Windows application. This scenario will also provide an example of how a client application would consume this Web service.
Concepts
A Web service is a set of related application functions that can be programmatically invoked over the Internet. The function can be one that solves a particular application problem or performs some other type of GIS function. In this example, the Web service that finds all of the toxic waste sites within a certain distance of an address. Web services can be implemented using the native Web service framework of your Web server such as ASP.NET Web service (WebMethod)
When using native frameworks, such as ASP.NET, to create and consume your application Web services, you need to use native or application-defined types as both arguments and return values from your Web methods. Clients of the Web service will not be ArcObjects applications, and as such, your Web service should not expect ArcObjects types as arguments and should not directly return ArcObjects types.
Any development language that can use standard HTTP to invoke methods can consume this Web service. The Web service consumer can get the methods and types exposed by the Web service through its Web Service Description Language (WSDL). As you walk through this scenario, you will see where special attributes need to be added to your methods and classes such that they can be expressed in WSDL and serialized as XML.
Design
Web services are, by definition, stateless applications and this example is no different because it is designed to make stateless use of the GIS server. It uses ArcObjects on the server to locate an address and query a feature class. To support this application, you need to add a pooled geocode server object to your GIS Server using ArcCatalog.
The Web service will connect to the GIS server and use an instance of the geocode server object to locate the address supplied to the Web method from the calling application. To then buffer the resultant point and use that buffered geometry to query toxic waste sites, you will use the geocode server's server context. Since the geodatabase has been designed such that the address locator is stored in the same geodatabase as the feature class containing the toxic waste sites, you can use the fine-grained objects associated with the geocode server to get a reference to that workspace.
Requirements
This scenario requires that you have ArcGIS Server and ArcGIS Desktop installed and running. The machine on which you develop this Web service must have the ArcGIS Server Web Application Developer Framework installed.
You must have a geocode server object configured and running on your GIS Server that uses the Portland.loc locator installed with the samples. In ArcCatalog, create a connection to your GIS server and use the Add Server Object command to create a new server object with the following properties:
- Name: PortlandGeocode
- Type: GeocodeServer
- Description: Geocode server object for metropolitan Portland
- Locator: <install_location>\DeveloperKit\SamplesNET\Server\data\Portland\Portland.loc
- Batch size: 10 (default)
-
Pooling: The Web service makes stateless use of the server
object. Accept the defaults for the pooling model (pooled server object with
minimum instances = 0, max instances = 2).
Accept the defaults for the remainder of the configuration properties.
The Add Server Object wizard
After creating the server object, start it. Then, right-click the server object and click Properties to verify that it is correctly configured and that the geocoding properties are displayed.
The following ArcObjects assemblies will be used in this example:
- ESRI.ArcGIS.ADF
- ESRI.ArcGIS.ADF.Connection
- ESRI.ArcGIS.ADF.Connection.AGS
- ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer
- ESRI.ArcGIS.Geodatabase
- ESRI.ArcGIS.Geometry
- ESRI.ArcGIS.Location
- ESRI.ArcGIS.Server
- ESRI.ArcGIS.System
The development environment does not require any ArcGIS licensing; however, connecting to a server and using a geocode server object does require that the GIS server is licensed. None of the assemblies used require an extension license.
The IDE used in this example is Visual Studio 2005, and all IDE specific steps will assume that is the IDE you are using.
Implementation
All code written in this example is in C#; however, you can write this Web service using VB.NET. To begin, you must create a new project in Visual Studio 2005.
Creating a new web site
- Start Visual Studio 2005.
-
Click File, click New Web site.
- In the New Web Site dialog, click ASP.NET Web Service. For the Language, choose Visual C#.
- For the Web service name, type "http://localhost/ToxicLocations".
- Click OK. This will create a blank Web service application.
- In the Solution Explorer, right-click Service.asmx and click Rename. Type "ToxicLocations.asmx" as the new name.
Adding references to ESRI assemblies to your project
In order to program using ArcGIS Server, you need to add references to the ESRI assemblies that contain proxies to the ArcObjects components that the Web service will use. These assemblies were installed when you installed the ArcGIS Server Web Application Developer Framework.
-
In the Solution Explorer, right-click References and click Add Reference.
-
In the Add Reference dialog box, double-click the following assemblies:
- ESRI.ArcGIS.ADF
- ESRI.ArcGIS.ADF.Connection
- ESRI.ArcGIS.ADF.Connection.AGS
- ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer
- ESRI.ArcGIS.Geodatabase
- ESRI.ArcGIS.Geometry
- ESRI.ArcGIS.Location
- ESRI.ArcGIS.Server
- ESRI.ArcGIS.System
The Add Reference dialog box
-
Click OK.
Add using statements to add these assemblies' namespaces to your application.
- In the Solution Explorer, right-click ToxicLocations.asmx and click View Code. The code for the implementation of this Web service will appear.
-
At the top of the code window, add the following using statements:
using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Server; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Location; using ESRI.ArcGIS.ADF.Connection.AGS; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer;
Naming the Web Service
-
Add the following code to name the class and its constructor to
"ToxicSiteLocations".
public class ToxicLocations : System.Web.Services.WebService {public ToxicLocations () { }Your code should look like the following:
using System; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Server; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Location; using ESRI.ArcGIS.ADF.Connection.AGS; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer; ///
/// Summary description for ToxicLocations /// [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class ToxicLocations : System.Web.Services.WebService { public ToxicLocations() { }
The point of this Web service is to expose a method that is accessible via HTTP-based SOAP requests and that returns all of the toxic site locations within a specified distance of a specified address. These types of methods must be declared as public and support the [WebMethod] attribute.
Creating the toxic site class
Before you create your new method, first define your toxic waste class that will be returned as a result of the method.
- In the Solution Explorer, right-click the ToxicLocations project, click Add New Item.
-
In the Add New Item dialog box, under Visual Studio installed Templates, click
Class.
The Add New Item dialog box
- For the Name, type "ToxicSite.cs".
-
Click Add.
When prompted to add the class to the App_Code folder, click Yes.
This will add a new class (ToxicSite) to your project and will open the code for the class with some autogenerated code.
The code view for the ToxicSite class should look like the following:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; ///
/// Summary description for ToxicSite /// public class ToxicSite { public ToxicSite() { // // TODO: Add constructor logic here // } }Results returned from Web services must be serializable in XML. The ToxicSite type must be marked as to be serializable as XML by including the XmlInclude attribute. Because the XmlInclude attribute is defined in the System.Xml.Serialization namespace, you need to add a using statement for that assembly.
Classes that have the XmlInclude attribute can be serialized into XML. Any custom type that is returned by a Web service must have the XmlInclude attribute.
-
Add the following using statement to your code:
using System.Xml.Serialization;
-
Add the following attribute to your class declaration:
[XmlInclude(typeof(ToxicSite))]
Your code should now look like this:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Xml.Serialization; ///
/// Summary description for ToxicSite /// [XmlInclude(typeof(ToxicSite))] public class ToxicSite { public ToxicSite() { // // TODO: Add constructor logic here // } }The Web service returns the following information: the name of the organization associated with the toxic site, and the x,y coordinates of the toxic site feature. To do this, you will add four public fields to your ToxicSite class and an overloaded constructor to set the data.
-
Add the following lines of code to your ToxicSite class:
public string Name; public string Type; public double X; public double Y;
-
To add the overloaded constructor, type the following in your class definition:
public ToxicSite(string sName, string sType, double dX, double dY) { Name = sName; Type = sType; X = dX; Y = dY; }The code for your ToxicSite class should now look like the following:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Xml.Serialization; ///
/// Summary description for ToxicSite /// [XmlInclude(typeof(ToxicSite))] public class ToxicSite { public string Name; public string Type; public double X; public double Y; } public ToxicSite(string sName, string sType, double dX, double dY) { Name = sName; Type = sType; X = dX; Y = dY; } }
Creating the Web service method
Now that you have defined your ToxicSite class, you will implement your Web service method. As described above, the point of this Web service is to expose a method—accessible via HTTP-based SOAP requests—that returns all of the toxic site locations within a specified distance of a specified address. These types of methods must both be declared as public and support the [WebMethod] attribute.
.NET methods within a class that have the [WebMethod] attribute set are called XML Web service methods and are callable from remote Web clients.
You will create a method called FindToxicLocations that takes as arguments an address, ZIP Code, and search distance and returns an array of ToxicSite objects.
This method will ultimately open a cursor on a feature class in a geodatabase.
The FindToxicLocations Web method returns an array of ToxicSite objects, rather than a generic collection, such as an ArrayList. If the method did not have a return type of ToxicSite, then the custom class wouldn't be expressed in the Web service's WSDL.
- In the Solution Explorer, right-click ToxicLocations.asmx and click View Code. The code for the implementation of this Web service will appear.
-
Add the following lines of code:
[WebMethod] public ToxicSite[] FindToxicLocations(string Address, string ZipCode, double Distance) { IServerContext serverContext = null; ToxicSite[] toxicArray = null; try { using (ComReleaser comReleaser = new ComReleaser()) { } } catch {} return toxicArray; }Your code should now look like the following:
using System; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Server; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.Location; using ESRI.ArcGIS.ADF.Connection.AGS; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer; ///
/// Summary description for ToxicLocations /// [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class ToxicLocations : System.Web.Services.WebService { public ToxicLocations () { } [WebMethod] public ToxicSite[] FindToxicLocations(string Address, string ZipCode, double Distance) { IServerContext serverContext = null; ToxicSite[] toxicArray = null; try { using (ComReleaser comReleaser = new ComReleaser()) { } } catch {} return toxicArray; } }
Validating input parameters
The first thing the Web method will do is verify the provided parameters are valid and, if not, return null.
Type the following code:
if (Address == null || ZipCode == null || Distance == 0.0) return null;
Connecting to the GIS server
This Web service makes use of objects in the GIS server, so you will add code to connect to the GIS server and get the IServerObjectManager interface. In this example, the GIS server is running on a machine called "localhost".
-
Set the following variables to store the username, password, domain, and host
name:
string username = "myusername"; string password = "mypassword"; string domain = "mydomain"; string host = "localhost";
You will need to set values for the username, password, domain, and host name for your GIS Server.
You'll use the ManageLifetime method of the ComReleaser class to add references of any COM objects to the collection of objects that the ComReleaser will explicitly release at the end of your using block.
-
Add the following lines of code:
// Create the user identity. ESRI.ArcGIS.ADF.Identity userIdentity = new ESRI.ArcGIS.ADF.Identity(username, password, domain); // Create a connection object to an ArcGIS Server (host), with user credentials. ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsConnnection = new AGSServerConnection(host, userIdentity); // Connect to the ArcGIS Server. agsConnnection.Connect(); // Get a ServerObjectManager. IServerObjectManager som = agsConnnection.ServerObjectManager; comReleaser.ManageLifetime(som);
The Web service will make use of the PortlandGeocode server object that you created with ArcCatalog. You get a server object by asking the GIS server for a server context containing the object.
-
Add the following line of code to get the server object's context:
// Create a ServerContext for the PortlandGeocode GeocoderServer object. serverContext = som.CreateServerContext("PortlandGeocode", "GeocodeServer");It's the responsibility of the developer to release the server object's context when finished using it. It's important to ensure that your method calls ReleaseContext on the server context when the method no longer needs the server object or any other objects running in the context. It's also important that you ensure the context is released in the event of an error. So, the remainder of the code will run within the try/catch block you already added. If an error occurs in the code in the try block, the code in the catch block will be executed. So, you will add the call to release the context at the end of the try block and in the catch block.
-
Add the following code to your catch block:
serverContext.ReleaseContext();
-
Add the following code to your try block:
// Release the Server Context. serverContext.ReleaseContext();
Your FindToxicLocations method should now look like the following:
[WebMethod] public ToxicSite[] FindToxicLocations(string Address, string ZipCode, double Distance) { if (Address == null || ZipCode == null || Distance == 0.0) return null; string username = "myuser"; string password = "mypassword"; string domain = "mydomain"; string host = "localhost"; IServerContext serverContext = null; ToxicSite[] toxicArray = null; try { using (ComReleaser comReleaser = new ComReleaser()) { // Create the user identity. ESRI.ArcGIS.ADF.Identity userIdentity = new ESRI.ArcGIS.ADF.Identity(username, password, domain); // Create a connection object to an ArcGIS Server (host), with user credentials. ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsConnnection = new AGSServerConnection(host, userIdentity); // Connect to the ArcGIS Server. agsConnnection.Connect(); // Get a ServerObjectManager. IServerObjectManager som = agsConnnection.ServerObjectManager; comReleaser.ManageLifetime(som); // Create a ServerContext for the PortlandGeocode GeocoderServer object. serverContext = som.CreateServerContext("PortlandGeocode", "GeocodeServer"); // Release the Server Context. serverContext.ReleaseContext(); } } catch { serverContext.ReleaseContext(); } return toxicArray; } }
Geocoding the input address
Now that you have connected to the GIS server and have a context containing the geocode server object, you will add the code to use the server object to geocode the input address and store the resulting point as gcPoint.
Add the following lines of code to your try block:
// Get the ServerObject.
IServerObject so = serverContext.ServerObject;
comReleaser.ManageLifetime(so);
// Cast it to a GeocodeServer.
IGeocodeServer geocodeServer = (IGeocodeServer)so;
comReleaser.ManageLifetime(geocodeServer);
// Create a PropertySet and set Address and ZipCode properties of the address
// we are going to geocode.
IPropertySet addressProperties = (IPropertySet)serverContext.CreateObject("esriSystem.PropertySet");
comReleaser.ManageLifetime(addressProperties);
addressProperties.SetProperty("street", Address);
addressProperties.SetProperty("Zone", ZipCode);
// Geocode our address.
IPropertySet geocodeResults = geocodeServer.GeocodeAddress(addressProperties, null);
comReleaser.ManageLifetime(geocodeResults);
// Use the results from the geocode to get the coordinates of the address.
IPoint addressPoint = (IPoint)geocodeResults.GetProperty("Shape");
comReleaser.ManageLifetime(addressPoint);
A PropertySet is a generic class that is used to hold a set of any properties. A PropertySet's properties are stored as name/value pairs. Examples for the use of a property set are to hold the properties required for opening an SDE workspace or geocoding an address. To learn more about PropertySet objects, see the online developer documentation.
Buffering the result and querying the toxic sites
You will buffer this point and use the resulting geometry to query the toxic sites from the ToxicSites feature class. Since the ToxicSites feature class is in the same workspace as the geocode server object's locator's reference data, you do not have to create a new connection to the geodatabase but can use the geocode server object's connection.
-
Add the following code to your try block to buffer the point, open the feature
class, and query it:
// Create a buffer around the address. // Create an ISegmentCollection object. ISegmentCollection buffer = (ISegmentCollection)serverContext.CreateObject("esriGeometry.Polygon"); comReleaser.ManageLifetime(buffer); // Set a circle around our address with a radius of a specified distance. buffer.SetCircle(addressPoint, Distance); // We need to get the FeatureClass for the ToxicSites. // First cast our geocodeServer to IGeocodeServerObjects. IGeocodeServerObjects geocodeServerObjects = (IGeocodeServerObjects)geocodeServer; comReleaser.ManageLifetime(geocodeServerObjects); // Get the reference data tables by casting the AddressLocator. IReferenceDataTables refTables = (IReferenceDataTables)geocodeServerObjects.AddressLocator; comReleaser.ManageLifetime(refTables); // Get an IEnumReferenceDataTable. IEnumReferenceDataTable enumRefTable = refTables.Tables; comReleaser.ManageLifetime(enumRefTable); // Reset the IEnumReferenceDataTable. enumRefTable.Reset(); // Get the next table. IReferenceDataTable refTable = enumRefTable.Next(); comReleaser.ManageLifetime(refTable); // Get an IDatasetName. IDatasetName dsName = (IDatasetName)refTable.Name; comReleaser.ManageLifetime(dsName); // Get an IName by casting the IDatasetName. IName wsName = (IName)dsName.WorkspaceName; comReleaser.ManageLifetime(wsName); // Open the IName and get an IFeatureWorkspace. IFeatureWorkspace featureWorkSpace = (IFeatureWorkspace)wsName.Open(); comReleaser.ManageLifetime(featureWorkSpace); // Open the FeatureClass. IFeatureClass featureClass = featureWorkSpace.OpenFeatureClass("ToxicSites"); comReleaser.ManageLifetime(featureClass); // Create a SpatialFilter object. ISpatialFilter spatialFilter = (ISpatialFilter)serverContext.CreateObject("esriGeoDatabase.SpatialFilter"); comReleaser.ManageLifetime(spatialFilter); // Set the Geometry to the buffer we created earlier. spatialFilter.Geometry = buffer as IGeometry; // Set the GeometryField to the ShapeFieldName of the FeatureClass. spatialFilter.GeometryField = featureClass.ShapeFieldName; // Specify an Intersection spatial relationship. spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects; // Get a FeatureCursor to enumerate the features filtered by our SpatialFilter. IFeatureCursor fCursor = featureClass.Search(spatialFilter, true); comReleaser.ManageLifetime(fCursor);Since you can get the workspace from the geocode server object, the connection is pooled by the geocode server. The code as written still has to open the ToxicSites feature class. This application could be further optimized by using a pooled map server object that has a single layer whose source data is the ToxicSites feature class. The application would then get the feature class from the map server object, effectively using the map server to pool the feature class.
If the ToxicSites feature class was not in the same workspace as the locator, this method would be the recommended approach for pooling both the workspace connection and the feature class.
You will now add code to loop through the features returned by the query and use the Name and SiteType field values and the X and Y properties of the feature's geometry as the arguments for the ToxicSite class constructor to create a new ToxicSite object for each feature. Because the number of features returned by the query is unknown until the cursor has been exhausted, you can't declare your ToxicSite array, as its size is unknown. First, you will store these ToxicSite objects in an ArrayList collection, then copy the contents of that ArrayList to an array of ToxicSite objects that will be returned by the method.
-
Add the following code to your try block:
// Get the indices of the Name and Type fields. int nameFieldIndex = featureClass.Fields.FindField("NAME"); int typeFieldIndex = featureClass.Fields.FindField("SITETYPE"); // Enumerate through the features. // For each feature, create a new ToxicSite and add it to an ArrayList. ArrayList toxicList = new ArrayList(); IFeature feature = null; while ((feature = fCursor.NextFeature()) != null) { // Get the location of the feature. IPoint location = (IPoint)feature.Shape; // Create a new ToxicSite for the feature. ToxicSite toxicSite = new ToxicSite((string)feature.get_Value(nameFieldIndex), (string)feature.get_Value(typeFieldIndex), location.X, location.Y); // Add it to our collection. toxicList.Add(toxicSite); // Managelifetime for COM objects comReleaser.ManageLifetime(feature); comReleaser.ManageLifetime(location); } // We are returning an array so we need to copy the // array list collection to the ToxicSite array. toxicArray = new ToxicSite[toxicList.Count]; toxicList.CopyTo(toxicArray);Your FindToxicLocations method should now look like the following:
[WebMethod] public ToxicSite[] FindToxicLocations(string Address, string ZipCode, double Distance) { if (Address == null || ZipCode == null || Distance == 0.0) return null; string username = "myuser"; string password = "mypassword"; string domain = "mydomain"; string host = "localhost"; IServerContext serverContext = null; ToxicSite[] toxicArray = null; try { using (ComReleaser comReleaser = new ComReleaser()) { // Create the user identity. ESRI.ArcGIS.ADF.Identity userIdentity = new ESRI.ArcGIS.ADF.Identity(username, password, domain); // Create a connection object to an ArcGIS Server (host), with user credentials. ESRI.ArcGIS.ADF.Connection.AGS.AGSServerConnection agsConnnection = new AGSServerConnection(host, userIdentity); // Connect to the ArcGIS Server. agsConnnection.Connect(); // Get a ServerObjectManager. IServerObjectManager som = agsConnnection.ServerObjectManager; comReleaser.ManageLifetime(som); // Create a ServerContext for the PortlandGeocode GeocoderServer object. serverContext = som.CreateServerContext("PortlandGeocode", "GeocodeServer"); // Get the ServerObject. IServerObject so = serverContext.ServerObject; comReleaser.ManageLifetime(so); // Cast it to a GeocodeServer. IGeocodeServer geocodeServer = (IGeocodeServer)so; comReleaser.ManageLifetime(geocodeServer); // Create a PropertySet and set Address and ZipCode properties of the address // we are going to geocode. IPropertySet addressProperties = (IPropertySet)serverContext.CreateObject("esriSystem.PropertySet"); comReleaser.ManageLifetime(addressProperties); addressProperties.SetProperty("street", Address); addressProperties.SetProperty("Zone", ZipCode); // Geocode our address. IPropertySet geocodeResults = geocodeServer.GeocodeAddress(addressProperties, null); comReleaser.ManageLifetime(geocodeResults); // Use the results from the geocode to get the coordinates of the address. IPoint addressPoint = (IPoint)geocodeResults.GetProperty("Shape"); comReleaser.ManageLifetime(addressPoint); // Create a buffer around the address. // Create an ISegmentCollection object. ISegmentCollection buffer = (ISegmentCollection)serverContext.CreateObject("esriGeometry.Polygon"); comReleaser.ManageLifetime(buffer); // Set a circle around our address with a radius of a specified distance. buffer.SetCircle(addressPoint, Distance); // We need to get the FeatureClass for the ToxicSites. // First cast our geocodeServer to IGeocodeServerObjects. IGeocodeServerObjects geocodeServerObjects = (IGeocodeServerObjects)geocodeServer; comReleaser.ManageLifetime(geocodeServerObjects); // Get the reference data tables by casting the AddressLocator. IReferenceDataTables refTables = (IReferenceDataTables)geocodeServerObjects.AddressLocator; comReleaser.ManageLifetime(refTables); // Get an IEnumReferenceDataTable. IEnumReferenceDataTable enumRefTable = refTables.Tables; comReleaser.ManageLifetime(enumRefTable); // Reset the IEnumReferenceDataTable. enumRefTable.Reset(); // Get the next table. IReferenceDataTable refTable = enumRefTable.Next(); comReleaser.ManageLifetime(refTable); // Get an IDatasetName. IDatasetName dsName = (IDatasetName)refTable.Name; comReleaser.ManageLifetime(dsName); // Get an IName by casting the IDatasetName. IName wsName = (IName)dsName.WorkspaceName; comReleaser.ManageLifetime(wsName); // Open the IName and get an IFeatureWorkspace. IFeatureWorkspace featureWorkSpace = (IFeatureWorkspace)wsName.Open(); comReleaser.ManageLifetime(featureWorkSpace); // Open the FeatureClass. IFeatureClass featureClass = featureWorkSpace.OpenFeatureClass("ToxicSites"); comReleaser.ManageLifetime(featureClass); // Create a SpatialFilter object. ISpatialFilter spatialFilter = (ISpatialFilter)serverContext.CreateObject("esriGeoDatabase.SpatialFilter"); comReleaser.ManageLifetime(spatialFilter); // Set the Geometry to the buffer we created earlier. spatialFilter.Geometry = buffer as IGeometry; // Set the GeometryField to the ShapeFieldName of the FeatureClass. spatialFilter.GeometryField = featureClass.ShapeFieldName; // Specify an Intersection spatial relationship. spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects; // Get a FeatureCursor to enumerate the features filtered by our SpatialFilter. IFeatureCursor fCursor = featureClass.Search(spatialFilter, true); comReleaser.ManageLifetime(fCursor); // Get the indices of the Name and Type fields. int nameFieldIndex = featureClass.Fields.FindField("NAME"); int typeFieldIndex = featureClass.Fields.FindField("SITETYPE"); // Enumerate through the features. // For each feature, create a new ToxicSite and add it to an ArrayList. ArrayList toxicList = new ArrayList(); IFeature feature = null; while ((feature = fCursor.NextFeature()) != null) { // Get the location of the feature. IPoint location = (IPoint)feature.Shape; // Create a new ToxicSite for the feature. ToxicSite toxicSite = new ToxicSite((string)feature.get_Value(nameFieldIndex), (string)feature.get_Value(typeFieldIndex), location.X, location.Y); // Add it to our collection. toxicList.Add(toxicSite); // Managelifetime for COM objects comReleaser.ManageLifetime(feature); comReleaser.ManageLifetime(location); } // We are returning an array so we need to copy the // array list collection to the ToxicSite array. toxicArray = new ToxicSite[toxicList.Count]; toxicList.CopyTo(toxicArray); // Release the Server Context. serverContext.ReleaseContext(); } } catch { serverContext.ReleaseContext(); } return toxicArray; } }Your Web service is now ready to be tested. Compile the project (Build/Build Solution) and fix any errors.
Testing the Web service
If you run the Web service from within Visual Studio, it will open a browser and list the FindToxicLocations method, which you can invoke from within the browser.
-
Click Debug, then click Start.
- On the browser that opens, click the FindToxicLocations link.
-
Type the following values for the Web service parameters:
- Address: 2111 Division St
- ZipCode: 97202
- Distance: 10000
-
Click Invoke. A new browser will open; confirm the following results from the
Web service:
<?xml version="1.0" encoding="utf-8" ?> - <ArrayOfAnyType xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/"> - <anyType xsi:type="ToxicSite"> <Name>TRI MET CENTER STREET GARAGE</Name> <Type>Hazardous waste generator</Type> <X>7651285.4405499762</X> <Y>672416.22979136126</Y> </anyType> - <anyType xsi:type="ToxicSite"> <Name>EAST SIDE PLATING INC PLANT 4</Name> <Type>Hazardous waste generator</Type> <X>7647860.94568513</X> <Y>679162.18254093162</Y> </anyType> - <anyType xsi:type="ToxicSite"> <Name>Portland Office of Transportation</Name> <Type>Brownfield Pilot</Type> <X>7646057.6159455236</X> <Y>684318.6635023664</Y> </anyType> - <anyType xsi:type="ToxicSite"> <Name>Portland Office of Transportation</Name> <Type>Brownfield Pilot</Type> <X>7646057.6159455236</X> <Y>684318.6635023664</Y> </anyType> </ArrayOfAnyType>
When you run your Web service within Visual Studio, it will open a browser listing the Web service's methods. You can evoke the methods by clicking the links on the browser and typing the method's inputs . You can also see the WSDL for the Web service by clicking the ServiceDescription link.
This indicates that four toxic sites were found within 10,000 feet of the given address.
When you evoke the Web method, a new browser will open, displaying the results returned from the method in XML.
Creating a client application
Since your Web service exposes a language-neutral interface that can be called using HTTP, your Web service can be called from any language that understands HTTP and WSDL. An elaborate client application, which itself could be a Web application, a desktop application, or even another Web service, is not demonstrated here, but the following is an example of how such an application would call the Web service method.
The example uses C#. Rather than describe such an application in detail, assume that this is a Windows desktop application that contains a button called btnCallWS whose Click event calls into your Web service. The code for this might look like the following, assuming you have added your Web service as a Web reference called ToxicLocation:
private void btnCallWS_Click(object sender, System.EventArgs e)
{
ToxicLocation.ToxicSiteLocator toxloc = new ToxicLocation.ToxicSiteLocator();
object[] objs = toxloc.FindToxicLocations("2111 Division St","97202",10000);
for (int i = 0; i < objs.Length; i++)
{
ToxicLocation.ToxicSite toxsite = objs[i] as ToxicLocation.ToxicSite;
// Do something with the toxic site object.
}
}
Deployment
Presumably you developed this Web service using your development Web server. To deploy this Web service on your production Web server, you can use the built-in Visual Studio 2005 tools to copy the Web Site.
- In the Solution Explorer, click ToxicLocations.
-
Click Website, then click Copy Website.
- Use the Copy Website dialog box to specify a connection to the Web server that you want the Web site copied to.
Additional Resources
This scenario includes functionality and programming techniques covering a number of different aspects of ArcObjects, the ArcGIS Server ArcObjects API, and Web controls.
You are encouraged to use the Developer Help to get a better understanding of core ArcGIS Server programming concepts and programming guidelines for working with server contexts and ArcObjects running within those contexts.
This scenario makes use of the ArcGIS Server Web ADF to provide the GIS server connection object for this Web service. 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 Web service is no exception. It includes the use of ArcObjects to work with the components of a GeocodeServer to locate an address, manipulate geometries, and perform spatial queries against feature classes in a geodatabase. To learn more about these aspects of ArcObjects, refer to the online developer documentation on the Location, GeoDatabase, and Geometry object libraries.