Los Service Object Interceptors (SOI) te permiten personalizar y extender las capacidades de ArcGIS Server sin modificar la fuente de los servicios. Con ellos, puedes optimizar el rendimiento, aplicar seguridad avanzada y modificar respuestas en tiempo real.
Nota: Los SOI solo funcionan para servicios referenciados y no para los feature layer hosteados en el datastore.
Integrar SOI a la solución SIG:
- Permiten interceptar solicitudes y respuestas
- Ejecutar lógica personalizada
- Modificar los comportamientos
- Reducir el numero de servicios
Realizar personalización del lado del servidor, las aplicaciones cliente no pueden eludir los controles de seguridad:
- Filtrar la solicitud entrante
- Anular una solicitud por datos inválidos
- Optimización de app, servicio de mapas
Integrar lógica de negocio en las funciones de servicio:
- Control de seguridad
- Control de acceso de usuarios detallado
- Requisitos de auditoría que no cumple el servicio de mapas
¿Como empiezo a usarlos?
Escenario.- Vamos a suponer que somos el ente rector de una capa para todo un país y nos han solicitado un dashboard para cada división provincial. Esto quiere decir que si son 24 provincias tengo que crear 24 vistas de capas, mapas y dashboard. Si usamos los SOI's unicamente tendremos que crear una regla que satisfaga la regla de negocio y así un mismo dashboard satisface a todas las provincias.
- Instalación y configuración previa
- Instalar Microsoft Visual Studio: Para poder desarrollar a través de la SDK es importante instalar Visual Studio (ojo no es lo mismo que Visual Studio Code) y se recomienda Microsoft Visual Studio 2022 (C#, VB.NET) Community, Professional, and Enterprise Edition. Al momento de realizar la instalación se debe colocar .NET desktop development. Nota: Se puede instalar en una máquina local.
- Instalacion ArcGIS SDK para Enterprise: Ejecutar el instalador de ArcGIS SDK para Enterprise (se puede descargar de myesri) y hacerlo en el servidor donde se tiene instalado ArcGIS Server para habilitar el .Net requerido.
Para mas información sobre instalación puede consultar el siguiente link.
Para la creación del SOI se lo realiza desde Visual Studio en menu y crear un nuevo proyecto y se recomienda CSharp SOI template (ArcGIS Pro)

Se guarda el proyecto con un nombre referente y ubicación seleccionado.
Se crea un Script para restringir la visibilidad de las capas tanto feature layer cuando se cumplan ciertos criterios: cuando el usuario viewer_DMG ingrese se filtre por el campo SUBZONA sea igual a DMG; cuando el usuario viewer_guayas ingrese se filtre por el campo SUBZONA sea igual a GUAYAS y cuando el usuario viewer_eloro sea igual a EL ORO y que modifique la estructura de la plantilla
Nota: Se puede copiar la plantilla y pegarla y se debe conservar el GUID y los nombres iniciales de la plantilla.
using System;
using System.Runtime.InteropServices;
using System.Text.Json;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Server;
using ESRI.Server.SOESupport;
using ESRI.Server.SOESupport.SOI;
namespace intersectDash2
{
[ComVisible(true)]
[Guid("c4528dac-d064-4fc6-b5ec-ffd42d08ee21")]
[ClassInterface(ClassInterfaceType.None)]
[ServerObjectInterceptor("MapServer",
Description = "Intersección por filtro de usuario para dashboard",
DisplayName = "IntersectDash",
Properties = "",
SupportsSharedInstances = false)]
public class intersectDash2 : IServerObjectExtension, IRESTRequestHandler
{
private string _soiName;
private IServerObjectHelper _soHelper;
private ServerLogger _serverLog;
private RestSOIHelper _restSOIHelper;
public intersectDash2()
{
_soiName = this.GetType().Name;
}
public void Init(IServerObjectHelper pSOH)
{
_soHelper = pSOH;
_serverLog = new ServerLogger();
_restSOIHelper = new RestSOIHelper(pSOH);
_serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".Init()", 200, "Initialized " + _soiName + " SOI.");
}
public void Shutdown()
{
_serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".Shutdown()", 200, "Shutting down " + _soiName + " SOI.");
}
public string GetSchema()
{
IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate<IRESTRequestHandler>();
if (restRequestHandler == null)
return null;
return restRequestHandler.GetSchema();
}
public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName,
string operationInput, string outputFormat, string requestProperties, out string responseProperties)
{
responseProperties = null;
_serverLog.LogMessage(ServerLogger.msgType.infoStandard, _soiName + ".HandleRESTRequest()", 200, "Request received in Sample Object Interceptor for HandleRESTRequest");
try
{
// Obtener el delegado del manejador REST
IRESTRequestHandler restRequestHandler = _restSOIHelper.FindRequestHandlerDelegate<IRESTRequestHandler>();
if (restRequestHandler == null)
return null;
// Obtener el usuario actual
string currentUser = SOIBase.GetServerEnvironment()?.UserInfo?.Name ?? "UnknownUser";
_serverLog.LogMessage(ServerLogger.msgType.infoDetailed, _soiName, 200, "Request from user: " + currentUser);
// Modificar los parámetros de consulta según el usuario
string subzonaValue = "";
if (currentUser.Equals("viewer_DMG", StringComparison.OrdinalIgnoreCase))
{
subzonaValue = "DMG";
}
else if (currentUser.Equals("viewer_guayas", StringComparison.OrdinalIgnoreCase))
{
subzonaValue = "GUAYAS";
}
else if (currentUser.Equals("viewer_eloro", StringComparison.OrdinalIgnoreCase))
{
subzonaValue = "EL ORO";
}
// Si el usuario es uno de los especificados, aplicar el filtro
if (!string.IsNullOrEmpty(subzonaValue) && operationName == "query")
{
JsonDocument inputJson = JsonDocument.Parse(operationInput);
JsonElement root = inputJson.RootElement;
// Crear un diccionario para almacenar los valores modificados
var modifiedInput = new Dictionary<string, object>();
// Agregar la condición "where" con el filtro correspondiente
if (root.TryGetProperty("where", out JsonElement whereElement))
{
string whereClause = whereElement.GetString() ?? "";
if (string.IsNullOrWhiteSpace(whereClause))
{
whereClause = $"subzona = '{subzonaValue}'";
}
else
{
whereClause += $" AND subzona = '{subzonaValue}'";
}
modifiedInput["where"] = whereClause; // Agregar la cláusula "where"
}
// Copiar el resto de los parámetros de la consulta (sin modificarlos)
foreach (var property in root.EnumerateObject())
{
if (property.Name != "where") // Asegurarnos de no sobrescribir "where"
{
modifiedInput[property.Name] = property.Value;
}
}
// Serializamos la entrada modificada
operationInput = JsonSerializer.Serialize(modifiedInput);
// Log de la entrada modificada
_serverLog.LogMessage(ServerLogger.msgType.infoDetailed, _soiName, 200, "Modified operationInput: " + operationInput);
}
// Log de la operación después de las modificaciones
_serverLog.LogMessage(ServerLogger.msgType.infoDetailed, _soiName, 200, "Final operationInput: " + operationInput);
// Continuamos con la solicitud
return restRequestHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties);
}
catch (Exception ex)
{
_serverLog.LogMessage(ServerLogger.msgType.error, _soiName + ".HandleRESTRequest()", 500, "Error: " + ex.Message);
responseProperties = null;
return null;
}
}
// Aquí puedes agregar métodos adicionales para manipular la visibilidad de capas si es necesario
}
}
Se guarda y se compila para tener un nuevo elemento con la extension .soe
- Configuracion del SOI con el Servicio
Ahora ya se tiene el archivo de configuración es momento de configurarlo con el servicio para que cada vez que se consulte por el tipo de usuario se pueda filtrar la información.
- Ingresar al Server Manager para realizar la configuración.
- En la pestaña Sitio ir a extensiones y dar clic en agregar extensión e incorporar el nuevo elemento .soe creado en el paso anterior.

3. Ahora se debe configurar el servicio que se desea que se apliquen las reglas del trabajo en este caso ir a servicios

4. Ir a capacidades y seleccionar el elemento. Nota: Cabe mencionar que el servicio solo funciona con servicios referenciados con instancias dedicadas si se quiere compartidas hay que modificar el script y colocar en InstanceShared como True. Después de configurar guardar y reiniciar.

Ahora es el proceso de validacióncon el usuario viewer_eloro


Y los datos originales sin el filtro con el usuario admin

Los SOI sirven para interceptar el servicio y aplicar reglas de negocio que pueden ayudarnos a simplificar procesos y ser mas eficiente con el manejo de servicios en ArcGIS Enterprise.
Ahora es momento de ponerlo en práctica si quieren poner marca de agua, crear nuevos filtros o aplicar auditorias a través de logs.
https://developers.arcgis.com/enterprise-sdk/guide/net/quick-tour-of-a-simple-soi-net/
https://developers.arcgis.com/enterprise-sdk/guide/net/what-is-an-soi-net/
https://developers.arcgis.com/enterprise-sdk/guide/net/audit-requests-in-sois-net/
https://developers.arcgis.com/enterprise-sdk/guide/net/soi-properties-net/
https://developers.arcgis.com/enterprise-sdk/guide/net/work-with-topologies-net/
https://developers.arcgis.com/enterprise-sdk/guide/net/upgrade-extensions-net/