public void Tune(HttpContext httpContext, object serviceInstance, SoapCore.OperationDescription operation)
        {
            IsCalled = true;
            if ((serviceInstance != null) && (serviceInstance is TestService) &&
                operation.Name.Equals("PingWithServiceOperationTuning"))
            {
                TestService service = serviceInstance as TestService;
                string      result  = string.Empty;

                StringValues pingValue;
                if (httpContext.Request.Headers.TryGetValue("ping_value", out pingValue))
                {
                    result = pingValue[0];
                }

                service.SetPingResult(result);
                IsSetPingValue = true;
            }
        }
Example #2
0
 public void Tune(HttpContext httpContext, object serviceInstance, SoapCore.OperationDescription operation)
 {
 }
Example #3
0
        private object[] GetRequestArguments(Message requestMessage, OperationDescription operation, ref Dictionary <string, object> outArgs)
        {
            var parameters = operation.DispatchMethod.GetParameters().Where(x => !x.IsOut && !x.ParameterType.IsByRef).ToArray();
            var arguments  = new List <object>();

            var requestMessageCopy = CopyMessage(ref requestMessage);

            // Deserialize request wrapper and object
            using (var xmlReader = requestMessage.GetReaderAtBodyContents())
            {
                var xmlReaderCopy = requestMessageCopy.GetReaderAtBodyContents();
                // Find the element for the operation's data
                xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
                xmlReaderCopy.ReadStartElement(operation.Name, operation.Contract.Namespace);
                //DuplicateReaders(xmlReader, out XmlReader xmlReaderClone1, out XmlReader xmlReaderClone2);
                //StringBuilder sb = new StringBuilder();

                //while (xmlReaderClone1.Read())
                //	sb.AppendLine(xmlReader.ReadOuterXml());
                //var str = sb.ToString();

                for (int i = 0; i < parameters.Length; i++)
                {
                    var elementAttribute = parameters[i].GetCustomAttribute <XmlElementAttribute>();
                    var parameterName    = !string.IsNullOrEmpty(elementAttribute?.ElementName)
                                                                    ? elementAttribute.ElementName
                                                                    : parameters[i].GetCustomAttribute <MessageParameterAttribute>()?.Name ?? parameters[i].Name;
                    var parameterNs = elementAttribute?.Namespace ?? operation.Contract.Namespace;

                    if (xmlReader.IsStartElement(parameterName, parameterNs))
                    {
                        xmlReader.MoveToStartElement(parameterName, parameterNs);
                        xmlReaderCopy.MoveToStartElement(parameterName, parameterNs);
                        if (xmlReader.IsStartElement(parameterName, parameterNs))
                        {
                            var elementType = parameters[i].ParameterType.GetElementType();
                            if (elementType == null || parameters[i].ParameterType.IsArray)
                            {
                                elementType = parameters[i].ParameterType;
                            }
                            //Logging(xmlReaderCopy);
                            switch (_serializer)
                            {
                            case SoapSerializer.XmlSerializer:
                            {
                                // see https://referencesource.microsoft.com/System.Xml/System/Xml/Serialization/XmlSerializer.cs.html#c97688a6c07294d5
                                var serializer = new XmlSerializer(elementType, null, new Type[0], new XmlRootAttribute(parameterName), parameterNs);
                                arguments.Add(serializer.Deserialize(xmlReader));
                            }
                            break;

                            case SoapSerializer.DataContractSerializer:
                            {
                                var serializer = new DataContractSerializer(elementType);                                                //, parameterName, parameterNs);
                                //arguments.Add(serializer.ReadObject(xmlReader, verifyObjectName: true));
                                arguments.Add(serializer.ReadObject(xmlReader, false));
                            }
                            break;

                            default: throw new NotImplementedException();
                            }
                        }
                        //xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
                        //xmlReader.MoveToStartElement(parameterName, parameterNs);
                        //StringBuilder sb = new StringBuilder();
                        //while (xmlReader.Read())
                        //	sb.AppendLine(xmlReader.ReadOuterXml());
                        //var str = sb.ToString();
                    }
                    else
                    {
                        arguments.Add(null);
                    }
                }
            }

            var outParams = operation.DispatchMethod.GetParameters().Where(x => x.IsOut || x.ParameterType.IsByRef).ToArray();

            foreach (var parameterInfo in outParams)
            {
                if (parameterInfo.ParameterType.Name == "Guid&")
                {
                    outArgs[parameterInfo.Name] = Guid.Empty;
                }
                else if (parameterInfo.ParameterType.Name == "String&" || parameterInfo.ParameterType.GetElementType().IsArray)
                {
                    outArgs[parameterInfo.Name] = null;
                }
                else
                {
                    var type = parameterInfo.ParameterType.GetElementType();
                    outArgs[parameterInfo.Name] = Activator.CreateInstance(type);
                }
            }
            return(arguments.ToArray());
        }
Example #4
0
        private object[] GetRequestArguments(Message requestMessage, System.Xml.XmlDictionaryReader xmlReader, OperationDescription operation, ref Dictionary <string, object> outArgs)
        {
            var parameters = operation.NormalParameters;
            // avoid reallocation
            var arguments = new List <object>(parameters.Length + operation.OutParameters.Length);

            // Find the element for the operation's data
            xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);

            for (int i = 0; i < parameters.Length; i++)
            {
                var parameterName = parameters[i].Name;
                var parameterNs   = parameters[i].Namespace;

                if (xmlReader.IsStartElement(parameterName, parameterNs))
                {
                    xmlReader.MoveToStartElement(parameterName, parameterNs);

                    if (xmlReader.IsStartElement(parameterName, parameterNs))
                    {
                        var elementType = parameters[i].Parameter.ParameterType.GetElementType();
                        if (elementType == null || parameters[i].Parameter.ParameterType.IsArray)
                        {
                            elementType = parameters[i].Parameter.ParameterType;
                        }

                        switch (_serializer)
                        {
                        case SoapSerializer.XmlSerializer:
                        {
                            // see https://referencesource.microsoft.com/System.Xml/System/Xml/Serialization/XmlSerializer.cs.html#c97688a6c07294d5
                            var serializer = CachedXmlSerializer.GetXmlSerializer(elementType, parameterName, parameterNs);
                            lock (serializer)
                                arguments.Add(serializer.Deserialize(xmlReader));
                        }
                        break;

                        case SoapSerializer.DataContractSerializer:
                        {
                            var serializer = new DataContractSerializer(elementType, parameterName, parameterNs);
                            arguments.Add(serializer.ReadObject(xmlReader, verifyObjectName: true));
                        }
                        break;

                        default: throw new NotImplementedException();
                        }
                    }
                }
                else
                {
                    arguments.Add(null);
                }
            }

            foreach (var parameterInfo in operation.OutParameters)
            {
                if (parameterInfo.Parameter.ParameterType.Name == "Guid&")
                {
                    outArgs[parameterInfo.Name] = Guid.Empty;
                }
                else if (parameterInfo.Parameter.ParameterType.Name == "String&" || parameterInfo.Parameter.ParameterType.GetElementType().IsArray)
                {
                    outArgs[parameterInfo.Name] = null;
                }
                else
                {
                    var type = parameterInfo.Parameter.ParameterType.GetElementType();
                    outArgs[parameterInfo.Name] = Activator.CreateInstance(type);
                }
            }
            return(arguments.ToArray());
        }
        private object[] GetRequestArguments(Message requestMessage, System.Xml.XmlDictionaryReader xmlReader, OperationDescription operation, HttpContext httpContext)
        {
            var arguments = new object[operation.AllParameters.Length];

            // Find the element for the operation's data
            if (!operation.IsMessageContractRequest)
            {
                xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
            }

            // if any ordering issues, possible to rewrite like:

            /*while (!xmlReader.EOF)
             * {
             *      var parameterInfo = operation.InParameters.FirstOrDefault(p => p.Name == xmlReader.LocalName && p.Namespace == xmlReader.NamespaceURI);
             *      if (parameterInfo == null)
             *      {
             *              xmlReader.Skip();
             *              continue;
             *      }
             *      var parameterName = parameterInfo.Name;
             *      var parameterNs = parameterInfo.Namespace;
             *      ...
             * }*/

            foreach (var parameterInfo in operation.InParameters)
            {
                var parameterName = parameterInfo.Name;

                var parameterNs = parameterInfo.Namespace ?? operation.Contract.Namespace;

                if (xmlReader.IsStartElement(parameterName, parameterNs))
                {
                    xmlReader.MoveToStartElement(parameterName, parameterNs);

                    if (xmlReader.IsStartElement(parameterName, parameterNs))
                    {
                        switch (_serializer)
                        {
                        case SoapSerializer.XmlSerializer:
                        {
                            // case [XmlElement("parameter")] int parameter
                            // case int[] parameter
                            // case [XmlArray("parameter")] int[] parameter
                            if (!parameterInfo.Parameter.ParameterType.IsArray || (parameterInfo.ArrayName != null && parameterInfo.ArrayItemName == null))
                            {
                                // see https://referencesource.microsoft.com/System.Xml/System/Xml/Serialization/XmlSerializer.cs.html#c97688a6c07294d5
                                var elementType = parameterInfo.Parameter.ParameterType.GetElementType();
                                if (elementType == null || parameterInfo.Parameter.ParameterType.IsArray)
                                {
                                    elementType = parameterInfo.Parameter.ParameterType;
                                }

                                var serializer = CachedXmlSerializer.GetXmlSerializer(elementType, parameterName, parameterNs);
                                lock (serializer)
                                {
                                    arguments[parameterInfo.Index] = serializer.Deserialize(xmlReader);
                                }
                            }

                            // case [XmlElement("parameter")] int[] parameter
                            // case [XmlArray("parameter"), XmlArrayItem(ElementName = "item")] int[] parameter
                            else
                            {
                                //if (parameterInfo.ArrayItemName != null)
                                {
                                    xmlReader.ReadStartElement(parameterName, parameterNs);
                                }

                                var elementType = parameterInfo.Parameter.ParameterType.GetElementType();

                                var localName = parameterInfo.ArrayItemName ?? elementType.Name;
                                if (parameterInfo.ArrayItemName == null && elementType.Namespace.StartsWith("System"))
                                {
                                    localName = localName.ToLower();
                                }

                                //localName = "ComplexModelInput";
                                var deserializeMethod = typeof(XmlSerializerExtensions)
                                                        .GetGenericMethod(nameof(XmlSerializerExtensions.DeserializeArray), new[] { elementType });
                                var serializer = CachedXmlSerializer.GetXmlSerializer(elementType, localName, parameterNs);
                                lock (serializer)
                                {
                                    arguments[parameterInfo.Index] = deserializeMethod.Invoke(null, new object[] { serializer, localName, parameterNs, xmlReader });
                                }

                                //if (parameterInfo.ArrayItemName != null)
                                {
                                    xmlReader.ReadEndElement();
                                }
                            }
                        }

                        break;

                        case SoapSerializer.DataContractSerializer:
                        {
                            var elementType = parameterInfo.Parameter.ParameterType.GetElementType();
                            if (elementType == null || parameterInfo.Parameter.ParameterType.IsArray)
                            {
                                elementType = parameterInfo.Parameter.ParameterType;
                            }

                            var serializer = new DataContractSerializer(elementType, parameterName, parameterNs);
                            arguments[parameterInfo.Index] = serializer.ReadObject(xmlReader, verifyObjectName: true);
                        }

                        break;

                        default: throw new NotImplementedException();
                        }
                    }
                }
                else if (parameterInfo.Parameter.ParameterType == typeof(HttpContext))
                {
                    arguments[parameterInfo.Index] = httpContext;
                }
                else
                {
                    arguments[parameterInfo.Index] = null;
                }
            }

            foreach (var parameterInfo in operation.OutParameters)
            {
                if (arguments[parameterInfo.Index] != null)
                {
                    // do not overwrite input ref parameters
                    continue;
                }

                if (parameterInfo.Parameter.ParameterType.Name == "Guid&")
                {
                    arguments[parameterInfo.Index] = Guid.Empty;
                }
                else if (parameterInfo.Parameter.ParameterType.Name == "String&" || parameterInfo.Parameter.ParameterType.GetElementType().IsArray)
                {
                    arguments[parameterInfo.Index] = null;
                }
                else
                {
                    var type = parameterInfo.Parameter.ParameterType.GetElementType();
                    arguments[parameterInfo.Index] = Activator.CreateInstance(type);
                }
            }

            return(arguments);
        }
Example #6
0
        private object[] GetRequestArguments(Message requestMessage, System.Xml.XmlDictionaryReader xmlReader, OperationDescription operation)
        {
            var arguments = new object[operation.AllParameters.Length];

            // Find the element for the operation's data
            if (!operation.IsMessageContractRequest)
            {
                xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
            }

            // if any ordering issues, possible to rewrite like:

            /*while (!xmlReader.EOF)
             * {
             *      var parameterInfo = operation.InParameters.FirstOrDefault(p => p.Name == xmlReader.LocalName && p.Namespace == xmlReader.NamespaceURI);
             *      if (parameterInfo == null)
             *      {
             *              xmlReader.Skip();
             *              continue;
             *      }
             *      var parameterName = parameterInfo.Name;
             *      var parameterNs = parameterInfo.Namespace;
             *      ...
             * }*/

            foreach (var parameterInfo in operation.InParameters)
            {
                var parameterName = parameterInfo.Name;
                var parameterNs   = parameterInfo.Namespace;

                if (xmlReader.IsStartElement(parameterName, parameterNs))
                {
                    xmlReader.MoveToStartElement(parameterName, parameterNs);

                    if (xmlReader.IsStartElement(parameterName, parameterNs))
                    {
                        var elementType = parameterInfo.Parameter.ParameterType.GetElementType();
                        if (elementType == null || parameterInfo.Parameter.ParameterType.IsArray)
                        {
                            elementType = parameterInfo.Parameter.ParameterType;
                        }

                        switch (_serializer)
                        {
                        case SoapSerializer.XmlSerializer:
                        {
                            // see https://referencesource.microsoft.com/System.Xml/System/Xml/Serialization/XmlSerializer.cs.html#c97688a6c07294d5
                            var serializer = CachedXmlSerializer.GetXmlSerializer(elementType, parameterName, parameterNs);
                            lock (serializer)
                            {
                                arguments[parameterInfo.Index] = serializer.Deserialize(xmlReader);
                            }
                        }

                        break;

                        case SoapSerializer.DataContractSerializer:
                        {
                            var serializer = new DataContractSerializer(elementType, parameterName, parameterNs);
                            arguments[parameterInfo.Index] = serializer.ReadObject(xmlReader, verifyObjectName: true);
                        }

                        break;

                        default: throw new NotImplementedException();
                        }
                    }
                }
                else
                {
                    arguments[parameterInfo.Index] = null;
                }
            }

            foreach (var parameterInfo in operation.OutParameters)
            {
                if (arguments[parameterInfo.Index] != null)
                {
                    // do not overwrite input ref parameters
                    continue;
                }

                if (parameterInfo.Parameter.ParameterType.Name == "Guid&")
                {
                    arguments[parameterInfo.Index] = Guid.Empty;
                }
                else if (parameterInfo.Parameter.ParameterType.Name == "String&" || parameterInfo.Parameter.ParameterType.GetElementType().IsArray)
                {
                    arguments[parameterInfo.Index] = null;
                }
                else
                {
                    var type = parameterInfo.Parameter.ParameterType.GetElementType();
                    arguments[parameterInfo.Index] = Activator.CreateInstance(type);
                }
            }

            return(arguments);
        }
        private object[] GetRequestArguments(Message requestMessage, XmlDictionaryReader xmlReader, OperationDescription operation, HttpContext httpContext)
        {
            var arguments = new object[operation.AllParameters.Length];

            IEnumerable <Type> serviceKnownTypes = operation
                                                   .GetServiceKnownTypesHierarchy()
                                                   .Select(x => x.Type);

            if (!operation.IsMessageContractRequest)
            {
                if (xmlReader != null)
                {
                    xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
                    while (!xmlReader.EOF)
                    {
                        var parameterInfo = operation.InParameters.FirstOrDefault(p => p.Name == xmlReader.LocalName);
                        if (parameterInfo == null)
                        {
                            xmlReader.Skip();
                            continue;
                        }

                        var parameterType = parameterInfo.Parameter.ParameterType;

                        var argumentValue = _serializerHelper.DeserializeInputParameter(
                            xmlReader,
                            parameterType,
                            parameterInfo.Name,
                            operation.Contract.Namespace,
                            parameterInfo.Parameter.Member,
                            serviceKnownTypes);

                        //fix https://github.com/DigDes/SoapCore/issues/379 (hack, need research)
                        if (argumentValue == null)
                        {
                            argumentValue = _serializerHelper.DeserializeInputParameter(
                                xmlReader,
                                parameterType,
                                parameterInfo.Name,
                                parameterInfo.Namespace,
                                parameterInfo.Parameter.Member,
                                serviceKnownTypes);
                        }

                        arguments[parameterInfo.Index] = argumentValue;
                    }

                    var httpContextParameter = operation.InParameters.FirstOrDefault(x => x.Parameter.ParameterType == typeof(HttpContext));
                    if (httpContextParameter != default)
                    {
                        arguments[httpContextParameter.Index] = httpContext;
                    }
                }
                else
                {
                    arguments = Array.Empty <object>();
                }
            }
            else
            {
                // MessageContracts are constrained to having one "InParameter". We can do special logic on
                // for this
                Debug.Assert(operation.InParameters.Length == 1, "MessageContracts are constrained to having one 'InParameter'");

                var parameterInfo = operation.InParameters[0];
                var parameterType = parameterInfo.Parameter.ParameterType;

                var messageContractAttribute = parameterType.GetCustomAttribute <MessageContractAttribute>();

                Debug.Assert(messageContractAttribute != null, "operation.IsMessageContractRequest should be false if this is null");

                var @namespace = parameterInfo.Namespace ?? operation.Contract.Namespace;

                if (messageContractAttribute.IsWrapped && !parameterType.GetMembersWithAttribute <MessageHeaderAttribute>().Any())
                {
                    //https://github.com/DigDes/SoapCore/issues/385
                    if (operation.DispatchMethod.GetCustomAttribute <XmlSerializerFormatAttribute>()?.Style == OperationFormatStyle.Rpc)
                    {
                        var importer = new SoapReflectionImporter(@namespace);
                        var map      = new XmlReflectionMember
                        {
                            IsReturnValue = false,
                            MemberName    = parameterInfo.Name,
                            MemberType    = parameterType
                        };
                        var mapping    = importer.ImportMembersMapping(parameterInfo.Name, @namespace, new[] { map }, false, true);
                        var serializer = XmlSerializer.FromMappings(new[] { mapping })[0];
                        var value      = serializer.Deserialize(xmlReader);
                        if (value is object[] o && o.Length > 0)
                        {
                            arguments[parameterInfo.Index] = o[0];
                        }
                    }
                    else
                    {
                        // It's wrapped so we treat it like normal!
                        arguments[parameterInfo.Index] = _serializerHelper.DeserializeInputParameter(
                            xmlReader,
                            parameterInfo.Parameter.ParameterType,
                            parameterInfo.Name,
                            @namespace,
                            parameterInfo.Parameter.Member,
                            serviceKnownTypes);
                    }
                }
                else
                {
                    var messageHeadersMembers = parameterType.GetPropertyOrFieldMembers()
                                                .Where(x => x.GetCustomAttribute <MessageHeaderAttribute>() != null)
                                                .Select(mi => new
                    {
                        MemberInfo = mi,
                        MessageHeaderMemberAttribute = mi.GetCustomAttribute <MessageHeaderAttribute>()
                    }).ToArray();

                    var wrapperObject = Activator.CreateInstance(parameterInfo.Parameter.ParameterType);

                    for (var i = 0; i < requestMessage.Headers.Count; i++)
                    {
                        var header = requestMessage.Headers[i];
                        var member = messageHeadersMembers.FirstOrDefault(x => x.MessageHeaderMemberAttribute.Name == header.Name || x.MemberInfo.Name == header.Name);

                        if (member != null)
                        {
                            var reader = requestMessage.Headers.GetReaderAtHeader(i);

                            var value = _serializerHelper.DeserializeInputParameter(
                                reader,
                                member.MemberInfo.GetPropertyOrFieldType(),
                                member.MessageHeaderMemberAttribute.Name ?? member.MemberInfo.Name,
                                member.MessageHeaderMemberAttribute.Namespace ?? @namespace,
                                member.MemberInfo,
                                serviceKnownTypes);

                            member.MemberInfo.SetValueToPropertyOrField(wrapperObject, value);
                        }
                    }

                    // This object isn't a wrapper element, so we will hunt for the nested message body
                    // member inside of it
                    var messageBodyMembers = parameterType.GetPropertyOrFieldMembers().Where(x => x.GetCustomAttribute <MessageBodyMemberAttribute>() != null).Select(mi => new
                    {
                        Member = mi,
                        MessageBodyMemberAttribute = mi.GetCustomAttribute <MessageBodyMemberAttribute>()
                    }).OrderBy(x => x.MessageBodyMemberAttribute.Order);

                    if (messageContractAttribute.IsWrapped)
                    {
                        xmlReader.Read();
                    }

                    foreach (var messageBodyMember in messageBodyMembers)
                    {
                        var messageBodyMemberAttribute = messageBodyMember.MessageBodyMemberAttribute;
                        var messageBodyMemberInfo      = messageBodyMember.Member;

                        var innerParameterName = messageBodyMemberAttribute.Name ?? messageBodyMemberInfo.Name;
                        var innerParameterNs   = messageBodyMemberAttribute.Namespace ?? @namespace;
                        var innerParameterType = messageBodyMemberInfo.GetPropertyOrFieldType();

                        //xmlReader.MoveToStartElement(innerParameterName, innerParameterNs);
                        var innerParameter = _serializerHelper.DeserializeInputParameter(
                            xmlReader,
                            innerParameterType,
                            innerParameterName,
                            innerParameterNs,
                            parameterInfo.Parameter.Member,
                            serviceKnownTypes);

                        messageBodyMemberInfo.SetValueToPropertyOrField(wrapperObject, innerParameter);
                    }

                    arguments[parameterInfo.Index] = wrapperObject;
                }
            }

            foreach (var parameterInfo in operation.OutParameters)
            {
                if (arguments[parameterInfo.Index] != null)
                {
                    // do not overwrite input ref parameters
                    continue;
                }

                if (parameterInfo.Parameter.ParameterType.Name == "Guid&")
                {
                    arguments[parameterInfo.Index] = Guid.Empty;
                }
                else if (parameterInfo.Parameter.ParameterType.Name == "String&" || parameterInfo.Parameter.ParameterType.GetElementType().IsArray)
                {
                    arguments[parameterInfo.Index] = null;
                }
                else
                {
                    var type = parameterInfo.Parameter.ParameterType.GetElementType();
                    arguments[parameterInfo.Index] = Activator.CreateInstance(type);
                }
            }

            return(arguments);
        }
        private void ExecuteFiltersAndTune(HttpContext httpContext, IServiceProvider serviceProvider, OperationDescription operation, object[] arguments, object serviceInstance)
        {
            // Execute model binding filters
            object modelBindingOutput = null;

            foreach (var modelBindingFilter in serviceProvider.GetServices <IModelBindingFilter>())
            {
                foreach (var modelType in modelBindingFilter.ModelTypes)
                {
                    foreach (var parameterInfo in operation.InParameters)
                    {
                        var arg = arguments[parameterInfo.Index];
                        if (arg != null && arg.GetType() == modelType)
                        {
                            modelBindingFilter.OnModelBound(arg, serviceProvider, out modelBindingOutput);
                        }
                    }
                }
            }

            // Execute Mvc ActionFilters
            foreach (var actionFilterAttr in operation.DispatchMethod.CustomAttributes.Where(a => a.AttributeType.Name == "ServiceFilterAttribute"))
            {
                var actionFilter = serviceProvider.GetService(actionFilterAttr.ConstructorArguments[0].Value as Type);
                actionFilter.GetType().GetMethod("OnSoapActionExecuting")?.Invoke(actionFilter, new[] { operation.Name, arguments, httpContext, modelBindingOutput });
            }

            // Invoke OnModelBound
            _soapModelBounder?.OnModelBound(operation.DispatchMethod, arguments);

            // Tune service instance for operation call
            var serviceOperationTuners = serviceProvider.GetServices <IServiceOperationTuner>();

            foreach (var operationTuner in serviceOperationTuners)
            {
                operationTuner.Tune(httpContext, serviceInstance, operation);
            }
        }