Service Object Interceptors (SOI): El superpoder oculto de ArcGIS Server

137
0
4 weeks ago
Joffre_Quinteros
Esri Contributor
1 0 137

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
  1. 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.
  2. 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

  • Creación del SOI

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) 

Joffre_Quinteros_0-1739916364046.png

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.

  1. Ingresar al Server Manager para realizar la configuración.
  2. 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.Joffre_Quinteros_0-1739924323081.png  Joffre_Quinteros_1-1739924350736.png

     

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

Joffre_Quinteros_2-1739924452272.png

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.

Joffre_Quinteros_3-1739924535337.png

Ahora es el proceso de validacióncon el usuario viewer_eloro

Joffre_Quinteros_4-1739924573875.pngJoffre_Quinteros_5-1739924586844.png

 

Y los datos originales sin el filtro con el usuario admin

Joffre_Quinteros_6-1739924606778.png

  • Conclusión

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. 

  • Recursos

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/