From d77295e3e1311a9e97672eaa444598314c573b0f Mon Sep 17 00:00:00 2001 From: arcgisprosdk Date: Fri, 19 May 2017 11:16:26 -1000 Subject: [PATCH] Bug fix --- .../ServerApplyWatermarkImageServiceSOI.cs | 700 ++++---- .../NetEditFeaturesRESTSOE.cs | 1463 +++++++++-------- 2 files changed, 1082 insertions(+), 1081 deletions(-) diff --git a/Net/Server/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI.cs b/Net/Server/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI.cs index 33428440..f2b5619b 100644 --- a/Net/Server/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI.cs +++ b/Net/Server/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI/ServerApplyWatermarkImageServiceSOI.cs @@ -1,350 +1,350 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using System.Collections.Specialized; - -using System.Runtime.InteropServices; - -using ESRI.ArcGIS.esriSystem; -using ESRI.ArcGIS.Server; -using ESRI.ArcGIS.Geometry; -using ESRI.ArcGIS.Geodatabase; -using ESRI.ArcGIS.Carto; -using ESRI.ArcGIS.SOESupport; -using ESRI.ArcGIS.SOESupport.SOI; -using System.Drawing; - - -//TODO: sign the project (project properties > signing tab > sign the assembly) -// this is strongly suggested if the dll will be registered using regasm.exe .dll /codebase - - -namespace ServerApplyWatermarkImageServiceSOI -{ - [ComVisible(true)] - [Guid("ccf64e95-f8e9-4bdd-b3cf-9349a4361f25")] - [ClassInterface(ClassInterfaceType.None)] - [ServerObjectInterceptor("MapServer", - Description = "SOI example that applies watermark to exported images", - DisplayName = "DotNet Apply Watermark Sample SOI for Image Service", - Properties = "")] - public class ServerApplyWatermarkImageServiceSOI : IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler - { - private string _soiName; - private IServerObjectHelper _soHelper; - private ServerLogger _serverLog; - private string _outputDirectory = string.Empty; - private RestSOIHelper _restSOIHelper; - - - public ServerApplyWatermarkImageServiceSOI() - { - _soiName = this.GetType().Name; - } - - public void Init ( IServerObjectHelper pSOH ) - { - try - { - _soHelper = pSOH; - _serverLog = new ServerLogger(); - - _restSOIHelper = new RestSOIHelper(pSOH); - - try - { - //interop problem? - var se4 = _restSOIHelper.ServerEnvironment as IServerEnvironmentEx; - var dirInfos = se4.GetServerDirectoryInfos(); - dirInfos.Reset(); - object dirInfo = dirInfos.Next(); - while (dirInfo != null) - { - var dinfo2 = dirInfo as IServerDirectoryInfo2; - if (null != dinfo2 && dinfo2.Type == esriServerDirectoryType.esriSDTypeOutput) - { - _outputDirectory = dinfo2.Path; - break; - } - dirInfo = dirInfos.Next(); - } - } - catch (Exception ignore) - { - _outputDirectory = string.Empty; - } - - _outputDirectory = _outputDirectory.Trim(); - if (string.IsNullOrEmpty(_outputDirectory)) - { - _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".init()", 500, "OutputDirectory is empty or missing. Reset to default."); - _outputDirectory = "C:\\arcgisserver\\directories\\arcgisoutput"; - } - - _serverLog.LogMessage(ServerLogger.msgType.infoDetailed, _soiName + ".init()", 500, "OutputDirectory is " + _outputDirectory); - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Initialized " + _soiName + " SOI."); - } - catch (Exception e) - { - _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); - throw; - } - } - - public void Shutdown () - { - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Shutting down " + _soiName + " SOI."); - } - - #region REST interceptors - - public string GetSchema () - { - try - { - IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (restRequestHandler == null) - throw new RestErrorException("Service handler not found"); - - return restRequestHandler.GetSchema(); - } - catch (Exception e) - { - _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); - throw; - } - } - - public byte[] HandleRESTRequest ( string Capabilities, string resourceName, string operationName, - string operationInput, string outputFormat, string requestProperties, out string responseProperties ) - { - try - { - responseProperties = null; - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleRESTRequest()", - 200, "Request received in Sample Object Interceptor for handleRESTRequest"); - - /* - * Add code to manipulate REST requests here - */ - - // Find the correct delegate to forward the request too - IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (restRequestHandler == null) - { - throw new RestErrorException("Service handler not found"); - } - - var response = restRequestHandler.HandleRESTRequest( - Capabilities, resourceName, operationName, operationInput, - outputFormat, requestProperties, out responseProperties); - - /* - * Manipulate the response. - * - * Add watermark - */ - - if (operationName.Equals("exportImage", StringComparison.CurrentCultureIgnoreCase)) - { - Image sourceImage = null; - if (outputFormat.Equals("image", StringComparison.CurrentCultureIgnoreCase)) - { - sourceImage = Image.FromStream(new System.IO.MemoryStream(response)); - - var watermarker = new ApplyWatermark.ApplyWatermark(); - - var watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); - var newResponse = new System.IO.MemoryStream(); - watermarkedImage.Save(newResponse, sourceImage.RawFormat); - - return newResponse.GetBuffer(); - } - else if (outputFormat.Equals("json", StringComparison.CurrentCultureIgnoreCase)) - { - - var responseString = System.Text.Encoding.UTF8.GetString(response); - var jo = new JsonObject(responseString); - string hrefString = null; - if (!jo.TryGetString("href", out hrefString)) - throw new RestErrorException("Export operation returned invalid response"); - - if (string.IsNullOrEmpty(hrefString)) - throw new RestErrorException("Export operation returned invalid response"); - - // Generate output file location - var outputImageFileLocation = GetOutputImageFileLocation(hrefString); - - // debug logging - //_serverLog.LogMessage(ServerLogger.msgType.error, "debug", 0, "output is " + outputImageFileLocation); - - var watermarker = new ApplyWatermark.ApplyWatermark(); - Image watermarkedImage; - - System.Drawing.Imaging.ImageFormat sourceImageFormat; - using( sourceImage = Image.FromFile(outputImageFileLocation)) - { - sourceImageFormat = sourceImage.RawFormat; - watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); - } - // make sure we dispose sourceImage handles before saving to it - - watermarkedImage.Save(outputImageFileLocation, sourceImageFormat); - watermarkedImage.Dispose(); - - // return response as is because we have modified the file its pointing to - return response; - } - else if (outputFormat.Equals("kmz", StringComparison.CurrentCultureIgnoreCase)) - { - // Note: Watermark can be added for the kmz format too. In this example we didn't - // implement it. - throw new RestErrorException("Kmz format is not supported"); - } - else - { - throw new RestErrorException("Invalid operation parameters"); - } - }//if operationName==export - return response; - } - catch (RestErrorException restException) - { - responseProperties = "{\"Content-Type\":\"text/plain;charset=utf-8\"}"; - return System.Text.Encoding.UTF8.GetBytes(restException.Message); - } - catch (Exception e) - { - _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); - throw; - } - } - - #endregion - - #region SOAP interceptors - - public byte[] HandleStringWebRequest ( esriHttpMethod httpMethod, string requestURL, - string queryString, string Capabilities, string requestData, - out string responseContentType, out esriWebResponseDataType respDataType ) - { - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringWebRequest()", - 200, "Request received in Sample Object Interceptor for HandleStringWebRequest"); - - /* - * Add code to manipulate requests here - */ - - IWebRequestHandler webRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (webRequestHandler != null) - { - return webRequestHandler.HandleStringWebRequest( - httpMethod, requestURL, queryString, Capabilities, requestData, out responseContentType, out respDataType); - } - - responseContentType = null; - respDataType = esriWebResponseDataType.esriWRDTPayload; - //Insert error response here. - return null; - } - - public byte[] HandleBinaryRequest ( ref byte[] request ) - { - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest()", - 200, "Request received in Sample Object Interceptor for HandleBinaryRequest"); - - /* - * Add code to manipulate requests here - */ - - IRequestHandler requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (requestHandler != null) - { - return requestHandler.HandleBinaryRequest(request); - } - - //Insert error response here. - return null; - } - - public byte[] HandleBinaryRequest2 ( string Capabilities, ref byte[] request ) - { - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest2()", - 200, "Request received in Sample Object Interceptor for HandleBinaryRequest2"); - - /* - * Add code to manipulate requests here - */ - - IRequestHandler2 requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (requestHandler != null) - { - return requestHandler.HandleBinaryRequest2(Capabilities, request); - } - - //Insert error response here. - return null; - } - - public string HandleStringRequest ( string Capabilities, string request ) - { - _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringRequest()", - 200, "Request received in Sample Object Interceptor for HandleStringRequest"); - - /* - * Add code to manipulate requests here - */ - - IRequestHandler requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); - if (requestHandler != null) - { - return requestHandler.HandleStringRequest(Capabilities, request); - } - - //Insert error response here. - return null; - } - - #endregion - - #region Utility code - - - /** - * Generate physical file path from virtual path - * - * @param virtualPath Path returned by the MapServer SO - * @return - */ - private String GetOutputImageFileLocation ( String virtualPath ) - { - /* - * Sample output returned by MapServer SO - * - * example : /rest/directories/arcgisoutput/SampleWorldCities_MapServer/ - * _ags_map26c62f8c2c0c4965b53e87e300e1912f.png - */ - var virtualPathParts = virtualPath.Split('/'); - String imageFileLocation = _outputDirectory; - - // build the physical path to the image file - bool buildPath = false; - foreach (String virtualPathPart in virtualPathParts) - { - if (buildPath) - { - imageFileLocation += "\\" + virtualPathPart; - } - if (virtualPathPart.Equals("arcgisoutput", StringComparison.CurrentCultureIgnoreCase)) - { - buildPath = true; - } - } - - return imageFileLocation; - } - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Collections.Specialized; + +using System.Runtime.InteropServices; + +using ESRI.ArcGIS.esriSystem; +using ESRI.ArcGIS.Server; +using ESRI.ArcGIS.Geometry; +using ESRI.ArcGIS.Geodatabase; +using ESRI.ArcGIS.Carto; +using ESRI.ArcGIS.SOESupport; +using ESRI.ArcGIS.SOESupport.SOI; +using System.Drawing; + + +//TODO: sign the project (project properties > signing tab > sign the assembly) +// this is strongly suggested if the dll will be registered using regasm.exe .dll /codebase + + +namespace ServerApplyWatermarkImageServiceSOI +{ + [ComVisible(true)] + [Guid("ccf64e95-f8e9-4bdd-b3cf-9349a4361f25")] + [ClassInterface(ClassInterfaceType.None)] + [ServerObjectInterceptor("ImageServer", + Description = "SOI example that applies watermark to exported images", + DisplayName = "DotNet Apply Watermark Sample SOI for Image Service", + Properties = "")] + public class ServerApplyWatermarkImageServiceSOI : IServerObjectExtension, IRESTRequestHandler, IWebRequestHandler, IRequestHandler2, IRequestHandler + { + private string _soiName; + private IServerObjectHelper _soHelper; + private ServerLogger _serverLog; + private string _outputDirectory = string.Empty; + private RestSOIHelper _restSOIHelper; + + + public ServerApplyWatermarkImageServiceSOI() + { + _soiName = this.GetType().Name; + } + + public void Init ( IServerObjectHelper pSOH ) + { + try + { + _soHelper = pSOH; + _serverLog = new ServerLogger(); + + _restSOIHelper = new RestSOIHelper(pSOH); + + try + { + //interop problem? + var se4 = _restSOIHelper.ServerEnvironment as IServerEnvironmentEx; + var dirInfos = se4.GetServerDirectoryInfos(); + dirInfos.Reset(); + object dirInfo = dirInfos.Next(); + while (dirInfo != null) + { + var dinfo2 = dirInfo as IServerDirectoryInfo2; + if (null != dinfo2 && dinfo2.Type == esriServerDirectoryType.esriSDTypeOutput) + { + _outputDirectory = dinfo2.Path; + break; + } + dirInfo = dirInfos.Next(); + } + } + catch (Exception ignore) + { + _outputDirectory = string.Empty; + } + + _outputDirectory = _outputDirectory.Trim(); + if (string.IsNullOrEmpty(_outputDirectory)) + { + _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".init()", 500, "OutputDirectory is empty or missing. Reset to default."); + _outputDirectory = "C:\\arcgisserver\\directories\\arcgisoutput"; + } + + _serverLog.LogMessage(ServerLogger.msgType.infoDetailed, _soiName + ".init()", 500, "OutputDirectory is " + _outputDirectory); + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Initialized " + _soiName + " SOI."); + } + catch (Exception e) + { + _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); + throw; + } + } + + public void Shutdown () + { + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".init()", 200, "Shutting down " + _soiName + " SOI."); + } + + #region REST interceptors + + public string GetSchema () + { + try + { + IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (restRequestHandler == null) + throw new RestErrorException("Service handler not found"); + + return restRequestHandler.GetSchema(); + } + catch (Exception e) + { + _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); + throw; + } + } + + public byte[] HandleRESTRequest ( string Capabilities, string resourceName, string operationName, + string operationInput, string outputFormat, string requestProperties, out string responseProperties ) + { + try + { + responseProperties = null; + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleRESTRequest()", + 200, "Request received in Sample Object Interceptor for handleRESTRequest"); + + /* + * Add code to manipulate REST requests here + */ + + // Find the correct delegate to forward the request too + IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (restRequestHandler == null) + { + throw new RestErrorException("Service handler not found"); + } + + var response = restRequestHandler.HandleRESTRequest( + Capabilities, resourceName, operationName, operationInput, + outputFormat, requestProperties, out responseProperties); + + /* + * Manipulate the response. + * + * Add watermark + */ + + if (operationName.Equals("exportImage", StringComparison.CurrentCultureIgnoreCase)) + { + Image sourceImage = null; + if (outputFormat.Equals("image", StringComparison.CurrentCultureIgnoreCase)) + { + sourceImage = Image.FromStream(new System.IO.MemoryStream(response)); + + var watermarker = new ApplyWatermark.ApplyWatermark(); + + var watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); + var newResponse = new System.IO.MemoryStream(); + watermarkedImage.Save(newResponse, sourceImage.RawFormat); + + return newResponse.GetBuffer(); + } + else if (outputFormat.Equals("json", StringComparison.CurrentCultureIgnoreCase)) + { + + var responseString = System.Text.Encoding.UTF8.GetString(response); + var jo = new JsonObject(responseString); + string hrefString = null; + if (!jo.TryGetString("href", out hrefString)) + throw new RestErrorException("Export operation returned invalid response"); + + if (string.IsNullOrEmpty(hrefString)) + throw new RestErrorException("Export operation returned invalid response"); + + // Generate output file location + var outputImageFileLocation = GetOutputImageFileLocation(hrefString); + + // debug logging + //_serverLog.LogMessage(ServerLogger.msgType.error, "debug", 0, "output is " + outputImageFileLocation); + + var watermarker = new ApplyWatermark.ApplyWatermark(); + Image watermarkedImage; + + System.Drawing.Imaging.ImageFormat sourceImageFormat; + using( sourceImage = Image.FromFile(outputImageFileLocation)) + { + sourceImageFormat = sourceImage.RawFormat; + watermarkedImage = watermarker.Mark(sourceImage, "(c) ESRI Inc."); + } + // make sure we dispose sourceImage handles before saving to it + + watermarkedImage.Save(outputImageFileLocation, sourceImageFormat); + watermarkedImage.Dispose(); + + // return response as is because we have modified the file its pointing to + return response; + } + else if (outputFormat.Equals("kmz", StringComparison.CurrentCultureIgnoreCase)) + { + // Note: Watermark can be added for the kmz format too. In this example we didn't + // implement it. + throw new RestErrorException("Kmz format is not supported"); + } + else + { + throw new RestErrorException("Invalid operation parameters"); + } + }//if operationName==export + return response; + } + catch (RestErrorException restException) + { + responseProperties = "{\"Content-Type\":\"text/plain;charset=utf-8\"}"; + return System.Text.Encoding.UTF8.GetBytes(restException.Message); + } + catch (Exception e) + { + _serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Exception " + e.GetType().Name + " " + e.Message + " " + e.StackTrace); + throw; + } + } + + #endregion + + #region SOAP interceptors + + public byte[] HandleStringWebRequest ( esriHttpMethod httpMethod, string requestURL, + string queryString, string Capabilities, string requestData, + out string responseContentType, out esriWebResponseDataType respDataType ) + { + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringWebRequest()", + 200, "Request received in Sample Object Interceptor for HandleStringWebRequest"); + + /* + * Add code to manipulate requests here + */ + + IWebRequestHandler webRequestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (webRequestHandler != null) + { + return webRequestHandler.HandleStringWebRequest( + httpMethod, requestURL, queryString, Capabilities, requestData, out responseContentType, out respDataType); + } + + responseContentType = null; + respDataType = esriWebResponseDataType.esriWRDTPayload; + //Insert error response here. + return null; + } + + public byte[] HandleBinaryRequest ( ref byte[] request ) + { + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest()", + 200, "Request received in Sample Object Interceptor for HandleBinaryRequest"); + + /* + * Add code to manipulate requests here + */ + + IRequestHandler requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (requestHandler != null) + { + return requestHandler.HandleBinaryRequest(request); + } + + //Insert error response here. + return null; + } + + public byte[] HandleBinaryRequest2 ( string Capabilities, ref byte[] request ) + { + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleBinaryRequest2()", + 200, "Request received in Sample Object Interceptor for HandleBinaryRequest2"); + + /* + * Add code to manipulate requests here + */ + + IRequestHandler2 requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (requestHandler != null) + { + return requestHandler.HandleBinaryRequest2(Capabilities, request); + } + + //Insert error response here. + return null; + } + + public string HandleStringRequest ( string Capabilities, string request ) + { + _serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleStringRequest()", + 200, "Request received in Sample Object Interceptor for HandleStringRequest"); + + /* + * Add code to manipulate requests here + */ + + IRequestHandler requestHandler = _restSOIHelper.FindRequestHandlerDelegate(); + if (requestHandler != null) + { + return requestHandler.HandleStringRequest(Capabilities, request); + } + + //Insert error response here. + return null; + } + + #endregion + + #region Utility code + + + /** + * Generate physical file path from virtual path + * + * @param virtualPath Path returned by the MapServer SO + * @return + */ + private String GetOutputImageFileLocation ( String virtualPath ) + { + /* + * Sample output returned by MapServer SO + * + * example : /rest/directories/arcgisoutput/SampleWorldCities_MapServer/ + * _ags_map26c62f8c2c0c4965b53e87e300e1912f.png + */ + var virtualPathParts = virtualPath.Split('/'); + String imageFileLocation = _outputDirectory; + + // build the physical path to the image file + bool buildPath = false; + foreach (String virtualPathPart in virtualPathParts) + { + if (buildPath) + { + imageFileLocation += "\\" + virtualPathPart; + } + if (virtualPathPart.Equals("arcgisoutput", StringComparison.CurrentCultureIgnoreCase)) + { + buildPath = true; + } + } + + return imageFileLocation; + } + #endregion + } +} diff --git a/Net/Server/ServerEditFeaturesRESTSOE/CSharp/EditFeaturesRESTSOE/NetEditFeaturesRESTSOE.cs b/Net/Server/ServerEditFeaturesRESTSOE/CSharp/EditFeaturesRESTSOE/NetEditFeaturesRESTSOE.cs index 0ddf330f..224598c0 100644 --- a/Net/Server/ServerEditFeaturesRESTSOE/CSharp/EditFeaturesRESTSOE/NetEditFeaturesRESTSOE.cs +++ b/Net/Server/ServerEditFeaturesRESTSOE/CSharp/EditFeaturesRESTSOE/NetEditFeaturesRESTSOE.cs @@ -1,731 +1,732 @@ -// Copyright 2015 ESRI -// -// All rights reserved under the copyright laws of the United States -// and applicable international laws, treaties, and conventions. -// -// You may freely redistribute and use this sample code, with or -// without modification, provided you include the original copyright -// notice and use restrictions. -// -// See the use restrictions at /userestrictions.txt. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using System.Collections.Specialized; - -using System.Runtime.InteropServices; - -using ESRI.ArcGIS.esriSystem; -using ESRI.ArcGIS.Server; -using ESRI.ArcGIS.Geometry; -using ESRI.ArcGIS.Geodatabase; -using ESRI.ArcGIS.Carto; -using ESRI.ArcGIS.SOESupport; - -using ESRI.ArcGIS.DataSourcesRaster; - -//TODO: sign the project (project properties > signing tab > sign the assembly) -// this is strongly suggested if the dll will be registered using regasm.exe .dll /codebase - - -namespace NetEditFeaturesRESTSOE -{ - [ComVisible(true)] - [Guid("6d79a67b-27d2-4a7f-85a3-6bf6fab061df")] - [ClassInterface(ClassInterfaceType.None)] - [ServerObjectExtension("MapServer", - AllCapabilities = "", - DefaultCapabilities = "", - Description = ".Net Edit features SOE - allows feature validation and editing.", - DisplayName = ".Net Edit Features REST SOE", - Properties = "layerId=0", - SupportsREST = true, - SupportsSOAP = false)] - public class NetEditFeaturesRESTSOE : IServerObjectExtension, IObjectConstruct, IRESTRequestHandler - { - private string soe_name; - - private IPropertySet configProps; - private IServerObjectHelper serverObjectHelper; - private ServerLogger logger; - private IRESTRequestHandler reqHandler; - - private IMapServerInfo mapServerInfo = null; - private IMapServerDataAccess mapServerDataAccess = null; - private IMapLayerInfos layerInfos = null; - private IMapLayerInfo editLayerInfo = null; - private int layerId = -1; - private IFeatureClass fc = null; - - public NetEditFeaturesRESTSOE() - { - soe_name = this.GetType().Name; - logger = new ServerLogger(); - reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler; - } - - #region IServerObjectExtension Members - - public void Init(IServerObjectHelper pSOH) - { - serverObjectHelper = pSOH; - - mapServerDataAccess = (IMapServerDataAccess)pSOH.ServerObject; - IMapServer3 ms = (IMapServer3)pSOH.ServerObject; - this.mapServerInfo = ms.GetServerInfo(ms.DefaultMapName); - this.layerInfos = mapServerInfo.MapLayerInfos; - - if (layerId < 0) - layerId = 0; - } - - public void Shutdown() - { - } - - #endregion - - #region IObjectConstruct Members - - public void Construct(IPropertySet props) - { - configProps = props; - string lid = (string)props.GetProperty("layerId"); - this.layerId = Convert.ToInt32(lid); - - this.fc = (IFeatureClass) this.mapServerDataAccess.GetDataSource(this.mapServerInfo.Name, this.layerId); - this.editLayerInfo = this.layerInfos.get_Element(this.layerId); - } - - #endregion - - #region IRESTRequestHandler Members - - public string GetSchema() - { - return reqHandler.GetSchema(); - } - - public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties) - { - return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties); - } - - #endregion - - private RestResource CreateRestSchema() - { - RestResource rootRes = new RestResource(soe_name, false, RootResHandler); - - RestResource layerResource = new RestResource("layers", false, LayersResHandler); - rootRes.resources.Add(layerResource); - - RestOperation addNewFeatureOper = new RestOperation("addNewFeature", - new string[] { "featureJSON" }, - new string[] { "json" }, - addNewFeatureOperHandler); - rootRes.operations.Add(addNewFeatureOper); - - RestOperation editFeatureOper = new RestOperation("editFeature", - new string[] { "featureId", "featureJSON" }, - new string[] { "json" }, - editFeatureOperHandler); - rootRes.operations.Add(editFeatureOper); - - return rootRes; - } - - private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties) - { - responseProperties = null; - - JsonObject infoJSON = new JsonObject(); - infoJSON.AddString("name", ".Net Edit Features REST SOE"); - infoJSON.AddString("description", "This SOE adds and edits features to a selected layer in the host map service. " - + "Note that this SOE is not designed to work with map services that have features stored in SDC data format." - + " The \"layers\" subresource returns all layers in the map service." - + " The \"editFeature\" operation allows editing an existing feature in the layer indicated by this SOE's layerId property.\n" - + " The \"addFeatures\" operation allows addition of a new feature to the layer indicated by this SOE's layerId property.\n" - + " The acceptableSchema JSON below indicates the correct schema that could be used to add/edit features. This schema belongs to the layer " - + "selected for editing by the ArcGIS Server administrator via the SOE's layerId property. This property's value can be " - + "modified using ArcGIS Manager."); - - // validation - ensure user has provided right layer id property value. - if (this.layerId > this.layerInfos.Count - 1) - { - return createErrorObject(406, "Layer Id " + this.layerId + " is invalid.", new String[] { - "Acceptable layer ids are between 0 and " - + (layerInfos.Count - 1) + ".", - "Also ensure that the id points to a feature layer." }); - } - - // inform the user that edits can be done only on feature layers, if no - // feature layer corresponds to user-provided layerId - if (this.editLayerInfo == null) - { - this.editLayerInfo = this.layerInfos.get_Element(this.layerId); - if (!this.editLayerInfo.IsFeatureLayer) - { - return createErrorObject( - 403, - "The layerId property of this SOE currently points to a layer (id: " - + this.layerId - + ") that is not a feature layer.", - new String[] { - "Only feature layers can be edited by this SOE.", - "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); - } - } - - // Grab the fc powering the layer if its null, which means it did not get initialized in construct(), thereby - // suggesting that the layerId property value is incorrect. - if (this.fc == null) - { - // The down side of grabbing fc here is - // that a new instance of fc is created once for every request. - // Can't create fc in init(), since layerId property value for a - // particular service is not necessarily available always when init() is invoked. - this.fc = (IFeatureClass) this.mapServerDataAccess.GetDataSource(this.mapServerInfo.Name, this.layerId); - if (this.fc == null) - { - // if its still null, return error - return createErrorObject( - 406, - "Incorrect layer id provided.", - new String[] { "Please provide layer id of a feature layer." }); - } - } - - infoJSON.AddString("Layer selected for editing", editLayerInfo.Name.ToString() + " (" + layerId + ")"); - JsonObject schemaJSON = getSchemaJSON(); - infoJSON.AddObject("acceptableSchema", schemaJSON); - - return Encoding.UTF8.GetBytes(infoJSON.ToJson()); - } - - private byte[] createErrorObject(int codeNumber, String errorMessageSummary, String[] errorMessageDetails) - { - if (errorMessageSummary.Length == 0 || errorMessageSummary == null) - { - throw new Exception("Invalid error message specified."); - } - - JSONObject errorJSON = new JSONObject(); - errorJSON.AddLong("code", codeNumber); - errorJSON.AddString("message", errorMessageSummary); - - if (errorMessageDetails == null) - { - errorJSON.AddString("details", "No error details specified."); - } - else - { - String errorMessages = ""; - for (int i = 0; i < errorMessageDetails.Length; i++) - { - errorMessages = errorMessages + errorMessageDetails[i] + "\n"; - } - - errorJSON.AddString("details", errorMessages); - } - - JSONObject error = new JSONObject(); - error.AddJSONObject("error", errorJSON); - - return Encoding.UTF8.GetBytes(error.ToJSONString(null)); - } - - private byte[] LayersResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties) - { - responseProperties = null; - - CustomLayerInfo[] layerInfos = GetLayerInfos(); - - JsonObject[] jos = new JsonObject[layerInfos.Length]; - - for (int i = 0; i < layerInfos.Length; i++) - jos[i] = layerInfos[i].ToJsonObject(); - - JsonObject result = new JsonObject(); - result.AddArray("layersInfo", jos); - - string json = result.ToJson(); - - return Encoding.UTF8.GetBytes(json); - } - - private CustomLayerInfo[] GetLayerInfos() - { - int c = this.layerInfos.Count; - - CustomLayerInfo[] customLayerInfos = new CustomLayerInfo[c]; - - for (int i = 0; i < c; i++) - { - IMapLayerInfo layerInfo = layerInfos.get_Element(i); - customLayerInfos[i] = new CustomLayerInfo(layerInfo); - } - - return customLayerInfos; - } - - private byte[] addNewFeatureOperHandler(NameValueCollection boundVariables, - JsonObject operationInput, - string outputFormat, - string requestProperties, - out string responseProperties) - { - - responseProperties = null; - - // get the feature JSON - JsonObject newFeatureJSON = null; - operationInput.TryGetJsonObject("featureJSON", out newFeatureJSON); - - // add the new feature - IFeature newFeature; - var bytes = addFeature(newFeatureJSON, out newFeature); - - if (null == newFeature) - return bytes; //return error - - // send response back to client app - var response = new JsonObject(); - response.AddString("status", "success"); - response.AddString("message", "Feature " + newFeature.OID + " added."); - - return Encoding.UTF8.GetBytes(response.ToJson()); - } - - private byte[] editFeatureOperHandler(NameValueCollection boundVariables, - JsonObject operationInput, - string outputFormat, - string requestProperties, - out string responseProperties) - { - responseProperties = null; - - // get the id of the feature to be edited - object featureIdObj; - operationInput.TryGetObject("featureId", out featureIdObj); - int updateFeatureId = Convert.ToInt32(featureIdObj.ToString()); - - object featureJSONObj; - operationInput.TryGetObject("featureJSON", out featureJSONObj); - JsonObject updateFeatureJSON = (JsonObject)featureJSONObj; - - // set a filter for the specific feature - QueryFilter queryFilter = new QueryFilter(); - if (this.fc == null) - { - return createErrorObject( - 406, - "Incorrect layer id provided.", - new String[] { "Please provide layer id of a feature layer." }); - } - - IClass myClass = (IClass) this.fc; - queryFilter.WhereClause = myClass.OIDFieldName + "=" + updateFeatureId; - - IFeatureCursor featureCursor = this.fc.Search(queryFilter, false); - - // attempt retrieval of the feature and check if it does exist - IFeatureCursor myFeatureCursor = (IFeatureCursor) featureCursor; - IFeature updateFeature = myFeatureCursor.NextFeature(); - if (updateFeature == null) { - return createErrorObject( - 406, - "Incorrect feature id provided.", - new String[] { "No feature exists for feature id " - + updateFeatureId + "." }); - } - - JsonObject response = new JsonObject(); - - // edit feature - string result = System.Text.Encoding.GetEncoding("utf-8").GetString(performEdits(updateFeature, updateFeatureJSON)); - featureCursor.Flush(); - if (result.Equals(System.Boolean.TrueString)) - { - response.AddString("status", "success"); - response.AddString("message", "Feature " + updateFeatureId + " updated"); - } - else - { - response.AddString("status", "failure"); - response.AddString("message", result); - } - - // send response back to client app - return Encoding.UTF8.GetBytes(response.ToJson()); - } - - /** - * Performs edits to the geodatabase powering the map service that this SOE - * extends - * - * @param feature - * @param featureJSON - * @throws Exception - */ - private byte[] performEdits(IFeature feature, JsonObject featureJSON) - { - IDataset fsDataset = (IDataset) this.fc; - IWorkspace ws = fsDataset.Workspace; - IWorkspaceEdit wsEdit = (IWorkspaceEdit) ws; - try - { - // start an edit transaction to add a new feature to feature class - wsEdit.StartEditing(false); - wsEdit.StartEditOperation(); - - // set attributes - if (this.editLayerInfo == null) - { - this.editLayerInfo = this.layerInfos.get_Element(this.layerId); - if (!this.editLayerInfo.IsFeatureLayer) - { - return createErrorObject( - 403, - "The layerId property of this SOE currently points to a layer (id: " - + this.layerId - + ") that is not a feature layer.", - new String[] { - "Only feature layers can be edited by this SOE.", - "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); - } - } - - IFields fields = this.editLayerInfo.Fields; - - JsonObject attributesJSON = null; - featureJSON.TryGetJsonObject("attributes", out attributesJSON); - - System.Collections.IEnumerator itKeys = attributesJSON.GetEnumerator(); - while (itKeys.MoveNext()) - { - KeyValuePair kv = (KeyValuePair) itKeys.Current; - String key = kv.Key; - int fieldId = fields.FindField(key); - IField field = fields.get_Field(fieldId); - - object fieldValue = null; - if(field.Editable) - { - //not using specific types based on field type, since can't assign value of any type to C# object - attributesJSON.TryGetObject(key, out fieldValue); - - // set attribute field value - feature.set_Value(fieldId, fieldValue); - } - } - - // retrieve geometry as json and convert it to ArcObject geometry - JsonObject geometryJSON = null; - featureJSON.TryGetJsonObject("geometry", out geometryJSON); - - IJSONConverterGeometry iConverter = new JSONConverterGeometryClass(); - IJSONObject obj = new JSONObjectClass(); - obj.ParseString(geometryJSON.ToJson()); - - IGeometry geometry = null; - switch (this.fc.ShapeType) - { - case esriGeometryType.esriGeometryPoint: - geometry = iConverter.ToPoint(obj); - break; - - case esriGeometryType.esriGeometryMultipoint: - geometry = iConverter.ToMultipoint(obj, false, false); - break; - - case esriGeometryType.esriGeometryPolyline: - geometry = iConverter.ToPolyline(obj, false, false); - break; - - case esriGeometryType.esriGeometryPolygon: - geometry = iConverter.ToPolygon(obj, false, false); - break; - } - - // set geometry - feature.Shape = geometry; - - // store feature in feature class - feature.Store(); - - // end edit transaction - wsEdit.StopEditOperation(); - wsEdit.StopEditing(true); - } catch (Exception e) { - if (wsEdit != null && wsEdit.IsBeingEdited()) - { - wsEdit.StopEditing(false); - } - return createErrorObject(500, - "Error occured while editing layer " + this.layerId + ".", - new String[] { "Error details:", e.Message}); - } - - return Encoding.UTF8.GetBytes(System.Boolean.TrueString); - } - - /** - * Performs edits to the geodatabase powering the map service that this SOE - * extends - * - * @param feature - * @param featureJSON - * @throws Exception - */ - private byte[] addFeature(JsonObject featureJSON, out IFeature feature) - { - feature = null; - IDataset fsDataset = (IDataset)this.fc; - IWorkspace ws = fsDataset.Workspace; - IWorkspaceEdit wsEdit = (IWorkspaceEdit)ws; - try - { - // start an edit transaction to add a new feature to feature class - wsEdit.StartEditing(false); - wsEdit.StartEditOperation(); - - feature = fc.CreateFeature(); - - // set attributes - if (this.editLayerInfo == null) - { - this.editLayerInfo = this.layerInfos.get_Element(this.layerId); - if (!this.editLayerInfo.IsFeatureLayer) - { - return createErrorObject( - 403, - "The layerId property of this SOE currently points to a layer (id: " - + this.layerId - + ") that is not a feature layer.", - new String[] { - "Only feature layers can be edited by this SOE.", - "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); - } - } - - IFields fields = this.editLayerInfo.Fields; - - JsonObject attributesJSON = null; - featureJSON.TryGetJsonObject("attributes", out attributesJSON); - - System.Collections.IEnumerator itKeys = attributesJSON.GetEnumerator(); - while (itKeys.MoveNext()) - { - KeyValuePair kv = (KeyValuePair)itKeys.Current; - String key = kv.Key; - int fieldId = fields.FindField(key); - IField field = fields.get_Field(fieldId); - - object fieldValue = null; - if (field.Editable) - { - //not using specific types based on field type, since can't assign value of any type to C# object - attributesJSON.TryGetObject(key, out fieldValue); - - // set attribute field value - feature.set_Value(fieldId, fieldValue); - } - } - - // retrieve geometry as json and convert it to ArcObject geometry - JsonObject geometryJSON = null; - featureJSON.TryGetJsonObject("geometry", out geometryJSON); - - IJSONConverterGeometry iConverter = new JSONConverterGeometryClass(); - IJSONObject obj = new JSONObjectClass(); - obj.ParseString(geometryJSON.ToJson()); - - IGeometry geometry = null; - switch (this.fc.ShapeType) - { - case esriGeometryType.esriGeometryPoint: - geometry = iConverter.ToPoint(obj); - break; - - case esriGeometryType.esriGeometryMultipoint: - geometry = iConverter.ToMultipoint(obj, false, false); - break; - - case esriGeometryType.esriGeometryPolyline: - geometry = iConverter.ToPolyline(obj, false, false); - break; - - case esriGeometryType.esriGeometryPolygon: - geometry = iConverter.ToPolygon(obj, false, false); - break; - } - - // set geometry - feature.Shape = geometry; - - // store feature in feature class - feature.Store(); - - // end edit transaction - wsEdit.StopEditOperation(); - wsEdit.StopEditing(true); - } - catch (Exception e) - { - if (wsEdit != null && wsEdit.IsBeingEdited()) - { - wsEdit.StopEditing(false); - } - return createErrorObject(500, - "Error occured while editing layer " + this.layerId + ".", - new String[] { "Error details:", e.Message }); - } - - return Encoding.UTF8.GetBytes(System.Boolean.TrueString); - } - - // Validates schema of JSON provided by user for editing - private bool isSchemaValid(JsonObject userFeatureJson) - { - Fields fields = (Fields) editLayerInfo.Fields; - string[] fieldsSet = new string[fields.FieldCount]; - for (int i = 0; i < fields.FieldCount; i++) - { - fieldsSet[i] = fields.get_Field(i).Name; - } - - JsonObject attributesJson; - userFeatureJson.TryGetJsonObject("attributes", out attributesJson); - - System.Collections.IEnumerator itKeys = attributesJson.GetEnumerator(); - - while (itKeys.MoveNext()) - { - String key = (String) itKeys.Current; - IField field = fields.get_Field(fields.FindField(key)); - - // as long as user supplied schema contains all editable fields and - // is a subset of feature class schema, we are ok. - if (field.Editable && !fieldsSet.Contains(key)) - { - return false; - } - } - - return true; - } - - //Retrieves feature schema for selected layer that could be used to provide data for editing. - private JsonObject getSchemaJSON() - { - Fields fields = (Fields) editLayerInfo.Fields; - int fieldCount = fields.FieldCount; - - JsonObject attributeJsonObject = new JsonObject(); - for (int i = 0; i < fieldCount; i++) - { - Field field = (Field)fields.get_Field(i); - String typeStr = null; - - switch (field.Type) - { - case esriFieldType.esriFieldTypeBlob: - typeStr = "Blob"; - break; - - case esriFieldType.esriFieldTypeDate: - typeStr = "Date"; - break; - - case esriFieldType.esriFieldTypeDouble: - typeStr = "Double"; - break; - - case esriFieldType.esriFieldTypeInteger: - typeStr = "Integer"; - break; - - case esriFieldType.esriFieldTypeRaster: - typeStr = "Raster"; - break; - - case esriFieldType.esriFieldTypeSmallInteger: - typeStr = "Integer"; - break; - - case esriFieldType.esriFieldTypeString: - typeStr = "String"; - break; - - case esriFieldType.esriFieldTypeXML: - typeStr = "XML"; - break; - - default: - break; - } - - if (typeStr != null && typeStr.Length > 0 && field.Editable) - { - attributeJsonObject.AddString(field.Name, typeStr); - } - } - - JsonObject featuresJsonObject = new JsonObject(); - featuresJsonObject.AddJsonObject("attributes", attributeJsonObject); - - JsonObject geometryJson = new JsonObject(); - - switch (((IFeatureClass)fc).ShapeType) - { - case esriGeometryType.esriGeometryPoint: - geometryJson.AddString("x", "x"); - geometryJson.AddString("y", "y"); - geometryJson.AddString("z", "z"); - break; - - case esriGeometryType.esriGeometryMultipoint: - geometryJson.AddString("hasM", "true | false"); - geometryJson.AddString("hasZ", "true | false"); - geometryJson.AddString("points", - "[ [ x1, y1, z1, m1 ] , [ x2, y2, z2, m2 ], ... ]"); - break; - - case esriGeometryType.esriGeometryPolyline: - geometryJson.AddString("hasM", "true | false"); - geometryJson.AddString("hasZ", "true | false"); - geometryJson.AddString("paths", "[" - + "[ [x11, y11, z11, m11], [x12, y12, z12, m12] ]," - + "[ [x21, y21, z21, m21], [x22, y22, z22, m22] ]]"); - break; - - case esriGeometryType.esriGeometryPolygon: - geometryJson.AddString("hasM", "true | false"); - geometryJson.AddString("hasZ", "true | false"); - geometryJson.AddString - ("rings", - "[" - + "[ [x11, y11, z11, m11], [x12, y12, z12, m12], ..., [x11, y11, z11, m11] ]," - + "[ [x21, y21, z21, m21], [x22, y22, z22, m22], ..., [x21, y21, z21, m21] ]]"); - break; - - default: break; - } - - JsonObject srJson = new JsonObject(); - srJson.AddString("wkid", "wkid"); - - geometryJson.AddJsonObject("spatialReference", srJson); - - featuresJsonObject.AddJsonObject("geometry", geometryJson); - - return featuresJsonObject; - } - - } -} +// Copyright 2015 ESRI +// +// All rights reserved under the copyright laws of the United States +// and applicable international laws, treaties, and conventions. +// +// You may freely redistribute and use this sample code, with or +// without modification, provided you include the original copyright +// notice and use restrictions. +// +// See the use restrictions at /userestrictions.txt. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Collections.Specialized; + +using System.Runtime.InteropServices; + +using ESRI.ArcGIS.esriSystem; +using ESRI.ArcGIS.Server; +using ESRI.ArcGIS.Geometry; +using ESRI.ArcGIS.Geodatabase; +using ESRI.ArcGIS.Carto; +using ESRI.ArcGIS.SOESupport; + +using ESRI.ArcGIS.DataSourcesRaster; + +//TODO: sign the project (project properties > signing tab > sign the assembly) +// this is strongly suggested if the dll will be registered using regasm.exe .dll /codebase + + +namespace NetEditFeaturesRESTSOE +{ + [ComVisible(true)] + [Guid("6d79a67b-27d2-4a7f-85a3-6bf6fab061df")] + [ClassInterface(ClassInterfaceType.None)] + [ServerObjectExtension("MapServer", + AllCapabilities = "", + DefaultCapabilities = "", + Description = ".Net Edit features SOE - allows feature validation and editing.", + DisplayName = ".Net Edit Features REST SOE", + Properties = "layerId=0", + SupportsREST = true, + SupportsSOAP = false)] + public class NetEditFeaturesRESTSOE : IServerObjectExtension, IObjectConstruct, IRESTRequestHandler + { + private string soe_name; + + private IPropertySet configProps; + private IServerObjectHelper serverObjectHelper; + private ServerLogger logger; + private IRESTRequestHandler reqHandler; + + private IMapServerInfo mapServerInfo = null; + private IMapServerDataAccess mapServerDataAccess = null; + private IMapLayerInfos layerInfos = null; + private IMapLayerInfo editLayerInfo = null; + private int layerId = -1; + private IFeatureClass fc = null; + + public NetEditFeaturesRESTSOE() + { + soe_name = this.GetType().Name; + logger = new ServerLogger(); + reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler; + } + + #region IServerObjectExtension Members + + public void Init(IServerObjectHelper pSOH) + { + serverObjectHelper = pSOH; + + mapServerDataAccess = (IMapServerDataAccess)pSOH.ServerObject; + IMapServer3 ms = (IMapServer3)pSOH.ServerObject; + this.mapServerInfo = ms.GetServerInfo(ms.DefaultMapName); + this.layerInfos = mapServerInfo.MapLayerInfos; + + if (layerId < 0) + layerId = 0; + } + + public void Shutdown() + { + } + + #endregion + + #region IObjectConstruct Members + + public void Construct(IPropertySet props) + { + configProps = props; + string lid = (string)props.GetProperty("layerId"); + this.layerId = Convert.ToInt32(lid); + + this.fc = (IFeatureClass) this.mapServerDataAccess.GetDataSource(this.mapServerInfo.Name, this.layerId); + this.editLayerInfo = this.layerInfos.get_Element(this.layerId); + } + + #endregion + + #region IRESTRequestHandler Members + + public string GetSchema() + { + return reqHandler.GetSchema(); + } + + public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties) + { + return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties); + } + + #endregion + + private RestResource CreateRestSchema() + { + RestResource rootRes = new RestResource(soe_name, false, RootResHandler); + + RestResource layerResource = new RestResource("layers", false, LayersResHandler); + rootRes.resources.Add(layerResource); + + RestOperation addNewFeatureOper = new RestOperation("addNewFeature", + new string[] { "featureJSON" }, + new string[] { "json" }, + addNewFeatureOperHandler); + rootRes.operations.Add(addNewFeatureOper); + + RestOperation editFeatureOper = new RestOperation("editFeature", + new string[] { "featureId", "featureJSON" }, + new string[] { "json" }, + editFeatureOperHandler); + rootRes.operations.Add(editFeatureOper); + + return rootRes; + } + + private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties) + { + responseProperties = null; + + JsonObject infoJSON = new JsonObject(); + infoJSON.AddString("name", ".Net Edit Features REST SOE"); + infoJSON.AddString("description", "This SOE adds and edits features to a selected layer in the host map service. " + + "Note that this SOE is not designed to work with map services that have features stored in SDC data format." + + " The \"layers\" subresource returns all layers in the map service." + + " The \"editFeature\" operation allows editing an existing feature in the layer indicated by this SOE's layerId property.\n" + + " The \"addFeatures\" operation allows addition of a new feature to the layer indicated by this SOE's layerId property.\n" + + " The acceptableSchema JSON below indicates the correct schema that could be used to add/edit features. This schema belongs to the layer " + + "selected for editing by the ArcGIS Server administrator via the SOE's layerId property. This property's value can be " + + "modified using ArcGIS Manager."); + + // validation - ensure user has provided right layer id property value. + if (this.layerId > this.layerInfos.Count - 1) + { + return createErrorObject(406, "Layer Id " + this.layerId + " is invalid.", new String[] { + "Acceptable layer ids are between 0 and " + + (layerInfos.Count - 1) + ".", + "Also ensure that the id points to a feature layer." }); + } + + // inform the user that edits can be done only on feature layers, if no + // feature layer corresponds to user-provided layerId + if (this.editLayerInfo == null) + { + this.editLayerInfo = this.layerInfos.get_Element(this.layerId); + if (!this.editLayerInfo.IsFeatureLayer) + { + return createErrorObject( + 403, + "The layerId property of this SOE currently points to a layer (id: " + + this.layerId + + ") that is not a feature layer.", + new String[] { + "Only feature layers can be edited by this SOE.", + "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); + } + } + + // Grab the fc powering the layer if its null, which means it did not get initialized in construct(), thereby + // suggesting that the layerId property value is incorrect. + if (this.fc == null) + { + // The down side of grabbing fc here is + // that a new instance of fc is created once for every request. + // Can't create fc in init(), since layerId property value for a + // particular service is not necessarily available always when init() is invoked. + this.fc = (IFeatureClass) this.mapServerDataAccess.GetDataSource(this.mapServerInfo.Name, this.layerId); + if (this.fc == null) + { + // if its still null, return error + return createErrorObject( + 406, + "Incorrect layer id provided.", + new String[] { "Please provide layer id of a feature layer." }); + } + } + + infoJSON.AddString("Layer selected for editing", editLayerInfo.Name.ToString() + " (" + layerId + ")"); + JsonObject schemaJSON = getSchemaJSON(); + infoJSON.AddObject("acceptableSchema", schemaJSON); + + return Encoding.UTF8.GetBytes(infoJSON.ToJson()); + } + + private byte[] createErrorObject(int codeNumber, String errorMessageSummary, String[] errorMessageDetails) + { + if (errorMessageSummary.Length == 0 || errorMessageSummary == null) + { + throw new Exception("Invalid error message specified."); + } + + JSONObject errorJSON = new JSONObject(); + errorJSON.AddLong("code", codeNumber); + errorJSON.AddString("message", errorMessageSummary); + + if (errorMessageDetails == null) + { + errorJSON.AddString("details", "No error details specified."); + } + else + { + String errorMessages = ""; + for (int i = 0; i < errorMessageDetails.Length; i++) + { + errorMessages = errorMessages + errorMessageDetails[i] + "\n"; + } + + errorJSON.AddString("details", errorMessages); + } + + JSONObject error = new JSONObject(); + error.AddJSONObject("error", errorJSON); + + return Encoding.UTF8.GetBytes(error.ToJSONString(null)); + } + + private byte[] LayersResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties) + { + responseProperties = null; + + CustomLayerInfo[] layerInfos = GetLayerInfos(); + + JsonObject[] jos = new JsonObject[layerInfos.Length]; + + for (int i = 0; i < layerInfos.Length; i++) + jos[i] = layerInfos[i].ToJsonObject(); + + JsonObject result = new JsonObject(); + result.AddArray("layersInfo", jos); + + string json = result.ToJson(); + + return Encoding.UTF8.GetBytes(json); + } + + private CustomLayerInfo[] GetLayerInfos() + { + int c = this.layerInfos.Count; + + CustomLayerInfo[] customLayerInfos = new CustomLayerInfo[c]; + + for (int i = 0; i < c; i++) + { + IMapLayerInfo layerInfo = layerInfos.get_Element(i); + customLayerInfos[i] = new CustomLayerInfo(layerInfo); + } + + return customLayerInfos; + } + + private byte[] addNewFeatureOperHandler(NameValueCollection boundVariables, + JsonObject operationInput, + string outputFormat, + string requestProperties, + out string responseProperties) + { + + responseProperties = null; + + // get the feature JSON + JsonObject newFeatureJSON = null; + operationInput.TryGetJsonObject("featureJSON", out newFeatureJSON); + + // add the new feature + IFeature newFeature; + var bytes = addFeature(newFeatureJSON, out newFeature); + + if (null == newFeature) + return bytes; //return error + + // send response back to client app + var response = new JsonObject(); + response.AddString("status", "success"); + response.AddString("message", "Feature " + newFeature.OID + " added."); + + return Encoding.UTF8.GetBytes(response.ToJson()); + } + + private byte[] editFeatureOperHandler(NameValueCollection boundVariables, + JsonObject operationInput, + string outputFormat, + string requestProperties, + out string responseProperties) + { + responseProperties = null; + + // get the id of the feature to be edited + object featureIdObj; + operationInput.TryGetObject("featureId", out featureIdObj); + int updateFeatureId = Convert.ToInt32(featureIdObj.ToString()); + + object featureJSONObj; + operationInput.TryGetObject("featureJSON", out featureJSONObj); + JsonObject updateFeatureJSON = (JsonObject)featureJSONObj; + + // set a filter for the specific feature + QueryFilter queryFilter = new QueryFilter(); + if (this.fc == null) + { + return createErrorObject( + 406, + "Incorrect layer id provided.", + new String[] { "Please provide layer id of a feature layer." }); + } + + IClass myClass = (IClass) this.fc; + queryFilter.WhereClause = myClass.OIDFieldName + "=" + updateFeatureId; + + IFeatureCursor featureCursor = this.fc.Search(queryFilter, false); + + // attempt retrieval of the feature and check if it does exist + IFeatureCursor myFeatureCursor = (IFeatureCursor) featureCursor; + IFeature updateFeature = myFeatureCursor.NextFeature(); + if (updateFeature == null) { + return createErrorObject( + 406, + "Incorrect feature id provided.", + new String[] { "No feature exists for feature id " + + updateFeatureId + "." }); + } + + JsonObject response = new JsonObject(); + + // edit feature + string result = System.Text.Encoding.GetEncoding("utf-8").GetString(performEdits(updateFeature, updateFeatureJSON)); + featureCursor.Flush(); + if (result.Equals(System.Boolean.TrueString)) + { + response.AddString("status", "success"); + response.AddString("message", "Feature " + updateFeatureId + " updated"); + } + else + { + response.AddString("status", "failure"); + response.AddString("message", result); + } + + // send response back to client app + return Encoding.UTF8.GetBytes(response.ToJson()); + } + + /** + * Performs edits to the geodatabase powering the map service that this SOE + * extends + * + * @param feature + * @param featureJSON + * @throws Exception + */ + private byte[] performEdits(IFeature feature, JsonObject featureJSON) + { + IDataset fsDataset = (IDataset) this.fc; + IWorkspace ws = fsDataset.Workspace; + IWorkspaceEdit wsEdit = (IWorkspaceEdit) ws; + try + { + // start an edit transaction to add a new feature to feature class + wsEdit.StartEditing(false); + wsEdit.StartEditOperation(); + + // set attributes + if (this.editLayerInfo == null) + { + this.editLayerInfo = this.layerInfos.get_Element(this.layerId); + if (!this.editLayerInfo.IsFeatureLayer) + { + return createErrorObject( + 403, + "The layerId property of this SOE currently points to a layer (id: " + + this.layerId + + ") that is not a feature layer.", + new String[] { + "Only feature layers can be edited by this SOE.", + "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); + } + } + + IFields fields = this.editLayerInfo.Fields; + + JsonObject attributesJSON = null; + featureJSON.TryGetJsonObject("attributes", out attributesJSON); + + System.Collections.IEnumerator itKeys = attributesJSON.GetEnumerator(); + while (itKeys.MoveNext()) + { + KeyValuePair kv = (KeyValuePair) itKeys.Current; + String key = kv.Key; + int fieldId = fields.FindField(key); + IField field = fields.get_Field(fieldId); + + object fieldValue = null; + if(field.Editable) + { + //not using specific types based on field type, since can't assign value of any type to C# object + attributesJSON.TryGetObject(key, out fieldValue); + + // set attribute field value + feature.set_Value(fieldId, fieldValue); + } + } + + // retrieve geometry as json and convert it to ArcObject geometry + JsonObject geometryJSON = null; + featureJSON.TryGetJsonObject("geometry", out geometryJSON); + + IJSONConverterGeometry iConverter = new JSONConverterGeometryClass(); + IJSONObject obj = new JSONObjectClass(); + obj.ParseString(geometryJSON.ToJson()); + + IGeometry geometry = null; + switch (this.fc.ShapeType) + { + case esriGeometryType.esriGeometryPoint: + geometry = iConverter.ToPoint(obj); + break; + + case esriGeometryType.esriGeometryMultipoint: + geometry = iConverter.ToMultipoint(obj, false, false); + break; + + case esriGeometryType.esriGeometryPolyline: + geometry = iConverter.ToPolyline(obj, false, false); + break; + + case esriGeometryType.esriGeometryPolygon: + geometry = iConverter.ToPolygon(obj, false, false); + break; + } + + // set geometry + feature.Shape = geometry; + + // store feature in feature class + feature.Store(); + + // end edit transaction + wsEdit.StopEditOperation(); + wsEdit.StopEditing(true); + } catch (Exception e) { + if (wsEdit != null && wsEdit.IsBeingEdited()) + { + wsEdit.StopEditing(false); + } + return createErrorObject(500, + "Error occured while editing layer " + this.layerId + ".", + new String[] { "Error details:", e.Message}); + } + + return Encoding.UTF8.GetBytes(System.Boolean.TrueString); + } + + /** + * Performs edits to the geodatabase powering the map service that this SOE + * extends + * + * @param feature + * @param featureJSON + * @throws Exception + */ + private byte[] addFeature(JsonObject featureJSON, out IFeature feature) + { + feature = null; + IDataset fsDataset = (IDataset)this.fc; + IWorkspace ws = fsDataset.Workspace; + IWorkspaceEdit wsEdit = (IWorkspaceEdit)ws; + try + { + // start an edit transaction to add a new feature to feature class + wsEdit.StartEditing(false); + wsEdit.StartEditOperation(); + + feature = fc.CreateFeature(); + + // set attributes + if (this.editLayerInfo == null) + { + this.editLayerInfo = this.layerInfos.get_Element(this.layerId); + if (!this.editLayerInfo.IsFeatureLayer) + { + return createErrorObject( + 403, + "The layerId property of this SOE currently points to a layer (id: " + + this.layerId + + ") that is not a feature layer.", + new String[] { + "Only feature layers can be edited by this SOE.", + "Modify SOE's layerId property using ArcGIS Manager or ArcGIS Desktop's Service Editor." }); + } + } + + IFields fields = this.editLayerInfo.Fields; + + JsonObject attributesJSON = null; + featureJSON.TryGetJsonObject("attributes", out attributesJSON); + + System.Collections.IEnumerator itKeys = attributesJSON.GetEnumerator(); + while (itKeys.MoveNext()) + { + KeyValuePair kv = (KeyValuePair)itKeys.Current; + String key = kv.Key; + int fieldId = fields.FindField(key); + IField field = fields.get_Field(fieldId); + + object fieldValue = null; + if (field.Editable) + { + //not using specific types based on field type, since can't assign value of any type to C# object + attributesJSON.TryGetObject(key, out fieldValue); + + // set attribute field value + feature.set_Value(fieldId, fieldValue); + } + } + + // retrieve geometry as json and convert it to ArcObject geometry + JsonObject geometryJSON = null; + featureJSON.TryGetJsonObject("geometry", out geometryJSON); + + IJSONConverterGeometry iConverter = new JSONConverterGeometryClass(); + IJSONObject obj = new JSONObjectClass(); + obj.ParseString(geometryJSON.ToJson()); + + IGeometry geometry = null; + switch (this.fc.ShapeType) + { + case esriGeometryType.esriGeometryPoint: + geometry = iConverter.ToPoint(obj); + break; + + case esriGeometryType.esriGeometryMultipoint: + geometry = iConverter.ToMultipoint(obj, false, false); + break; + + case esriGeometryType.esriGeometryPolyline: + geometry = iConverter.ToPolyline(obj, false, false); + break; + + case esriGeometryType.esriGeometryPolygon: + geometry = iConverter.ToPolygon(obj, false, false); + break; + } + + // set geometry + feature.Shape = geometry; + + // store feature in feature class + feature.Store(); + + // end edit transaction + wsEdit.StopEditOperation(); + wsEdit.StopEditing(true); + } + catch (Exception e) + { + if (wsEdit != null && wsEdit.IsBeingEdited()) + { + wsEdit.StopEditing(false); + } + feature = null; + return createErrorObject(500, + "Error occured while editing layer " + this.layerId + ".", + new String[] { "Error details:", e.Message }); + } + + return Encoding.UTF8.GetBytes(System.Boolean.TrueString); + } + + // Validates schema of JSON provided by user for editing + private bool isSchemaValid(JsonObject userFeatureJson) + { + Fields fields = (Fields) editLayerInfo.Fields; + string[] fieldsSet = new string[fields.FieldCount]; + for (int i = 0; i < fields.FieldCount; i++) + { + fieldsSet[i] = fields.get_Field(i).Name; + } + + JsonObject attributesJson; + userFeatureJson.TryGetJsonObject("attributes", out attributesJson); + + System.Collections.IEnumerator itKeys = attributesJson.GetEnumerator(); + + while (itKeys.MoveNext()) + { + String key = (String) itKeys.Current; + IField field = fields.get_Field(fields.FindField(key)); + + // as long as user supplied schema contains all editable fields and + // is a subset of feature class schema, we are ok. + if (field.Editable && !fieldsSet.Contains(key)) + { + return false; + } + } + + return true; + } + + //Retrieves feature schema for selected layer that could be used to provide data for editing. + private JsonObject getSchemaJSON() + { + Fields fields = (Fields) editLayerInfo.Fields; + int fieldCount = fields.FieldCount; + + JsonObject attributeJsonObject = new JsonObject(); + for (int i = 0; i < fieldCount; i++) + { + Field field = (Field)fields.get_Field(i); + String typeStr = null; + + switch (field.Type) + { + case esriFieldType.esriFieldTypeBlob: + typeStr = "Blob"; + break; + + case esriFieldType.esriFieldTypeDate: + typeStr = "Date"; + break; + + case esriFieldType.esriFieldTypeDouble: + typeStr = "Double"; + break; + + case esriFieldType.esriFieldTypeInteger: + typeStr = "Integer"; + break; + + case esriFieldType.esriFieldTypeRaster: + typeStr = "Raster"; + break; + + case esriFieldType.esriFieldTypeSmallInteger: + typeStr = "Integer"; + break; + + case esriFieldType.esriFieldTypeString: + typeStr = "String"; + break; + + case esriFieldType.esriFieldTypeXML: + typeStr = "XML"; + break; + + default: + break; + } + + if (typeStr != null && typeStr.Length > 0 && field.Editable) + { + attributeJsonObject.AddString(field.Name, typeStr); + } + } + + JsonObject featuresJsonObject = new JsonObject(); + featuresJsonObject.AddJsonObject("attributes", attributeJsonObject); + + JsonObject geometryJson = new JsonObject(); + + switch (((IFeatureClass)fc).ShapeType) + { + case esriGeometryType.esriGeometryPoint: + geometryJson.AddString("x", "x"); + geometryJson.AddString("y", "y"); + geometryJson.AddString("z", "z"); + break; + + case esriGeometryType.esriGeometryMultipoint: + geometryJson.AddString("hasM", "true | false"); + geometryJson.AddString("hasZ", "true | false"); + geometryJson.AddString("points", + "[ [ x1, y1, z1, m1 ] , [ x2, y2, z2, m2 ], ... ]"); + break; + + case esriGeometryType.esriGeometryPolyline: + geometryJson.AddString("hasM", "true | false"); + geometryJson.AddString("hasZ", "true | false"); + geometryJson.AddString("paths", "[" + + "[ [x11, y11, z11, m11], [x12, y12, z12, m12] ]," + + "[ [x21, y21, z21, m21], [x22, y22, z22, m22] ]]"); + break; + + case esriGeometryType.esriGeometryPolygon: + geometryJson.AddString("hasM", "true | false"); + geometryJson.AddString("hasZ", "true | false"); + geometryJson.AddString + ("rings", + "[" + + "[ [x11, y11, z11, m11], [x12, y12, z12, m12], ..., [x11, y11, z11, m11] ]," + + "[ [x21, y21, z21, m21], [x22, y22, z22, m22], ..., [x21, y21, z21, m21] ]]"); + break; + + default: break; + } + + JsonObject srJson = new JsonObject(); + srJson.AddString("wkid", "wkid"); + + geometryJson.AddJsonObject("spatialReference", srJson); + + featuresJsonObject.AddJsonObject("geometry", geometryJson); + + return featuresJsonObject; + } + + } +}