May 16 2007

How to invoke a web service without a Web Reference

Category: Tips and TricksJoeGeeky @ 04:08

At some point in your career you will need or want to call a Web Service from either a class library, a server control, HTTPModule, etc... If your lucky, you will soon realize that making a "Web Reference" in these types of projects is; at the very least; problemattic.  So what if you could reflect a Web Service and then invoke it as you would with other object types, assemblies, and so forth...  Well...  You can.  Below you will find a link to a commented class that you can use to make late-bound calls to web services given there WSDL Path, Service Name, and Service Type Name (e.g. class name).  Once you are familiar with its inner workings it is pretty easy to extend to support custom SOAP Headers, Type Reflection, etc...

samplewsinvoker.htm (32.88 kb)

Once you have the invoker, making a web service call is pretty simple.

Dim wsInvoker As New WebServiceReflectedMethodInvoker(
    "http://localhost/service/service.asmx?WSDL", "service", "service")
Dim arguments As New OrderedDictionary
Dim user As Object
arguments.Add("username", Username)
arguments.Add("userIsOnline", True)
'Execute the WS 
user = wsInvoker.InvokeWebServiceMethod("GetUser", arguments)

Here is yet another side note...  There is a performance hit when you make this type of call as compared to a "Web Reference".  However, one benefit is that you do not have to keep a lot of extraneous proxy classes under source control for methods and classes you do not plan on using.  Additionally, you will not run into problems related to needed "Update Web Reference" just to keep your solution up to date.

Tags: ,

Comments

1.
Carl Kelley Carl Kelley United States says:

WebServiceReflectedMethodInvoker is the bomb!  Thank you, JoeGeeky, sir!  The only difficultly I found was that WsdlInvokerConfigurationSectionElement was undefined in an overloaded constructor.  I just commented it out.  As an expression of gratitude, here is the C# version:
========================================
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Reflection;
using System.Web.Services.Description;
using System.Xml;
using System;
using Microsoft.VisualBasic;
using System.Collections;

namespace Streamline.Utilities
{
    /// <summary>
    /// This class allows controls and modules to call on ASMX web services dynamically and not require a fixed
    /// reference to a web service (e.g. Web Reference)
    /// </summary>
    /// <remarks></remarks>
    public sealed class WebServiceReflectedMethodInvoker
    {
        /// <summary>
        /// URL path to the target WSDL File
        /// </summary>
        /// <remarks><example>http://serviceurl/target.asmx?WSDL</example></remarks>
        private string _wsdlPath;

        /// <summary>
        /// Name of the web service
        /// </summary>
        /// <remarks></remarks>
        private string _serviceName;

        /// <summary>
        /// Name of the web service class to be reflected
        /// </summary>
        /// <remarks></remarks>
        private string _typeName;

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <remarks></remarks>
        public WebServiceReflectedMethodInvoker()
            : this(" ", " ", " ")
        {
        }

        /// <summary>
        /// Overloaded Constructor
        /// </summary>
        /// <param name="wsdlPath">URL path to the target WSDL File</param>
        /// <param name="serviceName">Name of the web service</param>
        /// <param name="typeName">Name of the web service class to be reflected</param>
        /// <remarks>An example path is <example>http://serviceurl/target.asmx?WSDL</example></remarks>
        /// <exception cref="ArgumentNullException">Thrown when a required argument is null or zero length</exception>
        public WebServiceReflectedMethodInvoker(string wsdlPath, string serviceName, string typeName)
            : base()
        {
            if (string.IsNullOrEmpty(wsdlPath))
            {
                throw new ArgumentNullException("wsdlPath");
            }
            if (string.IsNullOrEmpty(serviceName))
            {
                throw new ArgumentNullException("serviceName");
            }
            if (string.IsNullOrEmpty(typeName))
            {
                throw new ArgumentNullException("typeName");
            }
            this.WSDLPath = wsdlPath;
            this.ServiceName = serviceName;
            this.ServiceName = this.ServiceName.Replace(" ", "_x0020_");
            this.TypeName = typeName;
        }

        /// <summary>
        /// Overloaded Constructor
        /// </summary>
        /// <param name="configSectionElement">Configuration Element Defining invokation parameters</param>
        /// <remarks></remarks>
        /// <exception cref="ArgumentNullException">Thrown when a required argument is null or zero length</exception>
        //public WebServiceReflectedMethodInvoker(WsdlInvokerConfigurationSectionElement configSectionElement)
        //    : base()
        //{

        //    if (configSectionElement == null)
        //    {
        //        throw new ArgumentNullException("configSectionElement");
        //    }

        //    WSDLPath = configSectionElement.WsdlUrl;
        //    ServiceName = configSectionElement.ServiceName;
        //    ServiceName = _serviceName.Replace(" ", "_x0020_");
        //    TypeName = configSectionElement.ServiceTypeName;
        //}

        /// <summary>
        /// URL path to the target WSDL File
        /// </summary>
        /// <value>URL path to the target WSDL File</value>
        /// <returns>URL path to the target WSDL File</returns>
        /// <remarks>For Example: <example>http://serviceurl/target.asmx?WSDL</example></remarks>
        public string WSDLPath
        {
            get
            {
                return _wsdlPath;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "";
                }
                _wsdlPath = value.Trim();
            }
        }

        /// <summary>
        /// Name of the web service as defined in the web service WSDL
        /// </summary>
        /// <value>Name of the web service as defined in the web service WSDL</value>
        /// <returns>Name of the web service as defined in the web service WSDL</returns>
        /// <remarks></remarks>
        public string ServiceName
        {
            get
            {
                return _serviceName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "";
                }
                _serviceName = value.Trim();
                _serviceName = _serviceName.Replace(" ", "_x0020_");
            }
        }

        /// <summary>
        /// Name of the web service class/type name to be reflected
        /// </summary>
        /// <value>Name of the web service class/type name to be reflected</value>
        /// <returns>Name of the web service class/type name to be reflected</returns>
        /// <remarks></remarks>
        public string TypeName
        {
            get
            {
                return _typeName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "";
                }
                _typeName = value.Trim();
            }
        }

        /// <summary>
        /// Downloads the target WSDL For parsing and method/object discovery
        /// </summary>
        /// <returns>An xml file reader configured against the supplied WSDL Url</returns>
        /// <remarks></remarks>
        /// <exception cref="WebException">Thrown when a connection cannot be made to the specified web
        /// service</exception>
        private XmlReader DownloadWSDL()
        {
            return XmlReader.Create(_wsdlPath);
        }

        /// <summary>
        /// Deconstructs the WSDL document
        /// </summary>
        /// <param name="wsdlDocumentReader">Reader containing the WSDL</param>
        /// <returns>Service Descriptor describing the service being called</returns>
        /// <remarks><seealso cref="DownloadWSDL">See Related Method</seealso></remarks>
        /// <exception cref="ArgumentException">Thrown when an invalid service name is provided</exception>
        private ServiceDescription DeserializeWSDL(XmlReader wsdlDocumentReader)
        {
            ServiceDescription wsdlDescription = ServiceDescription.Read(wsdlDocumentReader);
            bool found = false;
            foreach (Service serviceItem in wsdlDescription.Services)
            {
                if (serviceItem.Name.Equals(_serviceName))
                {
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                throw new ArgumentException("Invalid Service Name");
            }
            return wsdlDescription;
        }

        /// <summary>
        /// Generates a proxy class to retrieve data from the target
        /// </summary>
        /// <param name="wsdl">Target WSDL Service Description</param>
        /// <param name="compileUnit">object to contain compilation instructions</param>
        /// <returns>Enumeration containing any imports warnings</returns>
        /// <remarks></remarks>
        private static ServiceDescriptionImportWarnings GenerateCodeForProxyClass(ServiceDescription wsdl, CodeCompileUnit compileUnit)
        {
            ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
            descriptionImporter.ProtocolName = "Soap";
            descriptionImporter.AddServiceDescription(wsdl, string.Empty, string.Empty);
            CodeNamespace targetCodeNamespace = new CodeNamespace();
            compileUnit.Namespaces.Add(targetCodeNamespace);
            return descriptionImporter.Import(targetCodeNamespace, compileUnit);
        }

        /// <summary>
        /// Compiles the code for the proxy class so that it can be executed against the target web service
        /// </summary>
        /// <param name="compileUnit">object containing compilation instructions</param>
        /// <returns>An in-memory compiled assembly</returns>
        /// <remarks></remarks>
        private static Assembly CompileCodeForProxyClass(CodeCompileUnit compileUnit)
        {

            VBCodeProvider codeProvider = new VBCodeProvider();
            CompilerParameters parameters = new CompilerParameters();

            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = true;
            parameters.IncludeDebugInformation = false;
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("System.Data.dll");
            parameters.ReferencedAssemblies.Add("System.Web.Services.dll");
            parameters.ReferencedAssemblies.Add("System.XML.dll");

            CompilerResults results = codeProvider.CompileAssemblyFromDom(parameters, compileUnit);

            codeProvider.Dispose();

            return results.CompiledAssembly;
        }

        /// <summary>
        /// Instanciates the proxy class
        /// </summary>
        /// <param name="proxyAssembly">Assembly containing the type to be instanciated</param>
        /// <returns>Active instance of the in-memory assembly</returns>
        /// <remarks></remarks>
        public object CreateProxyClassInstance(Assembly proxyAssembly)
        {
            if (proxyAssembly == null)
            {
                return null;
            }
            return proxyAssembly.CreateInstance(_typeName, true);
        }

        /// <summary>
        /// Invokes the in-memory proxy class based on the associated method name
        /// </summary>
        /// <param name="methodName">method to be invoked</param>
        /// <param name="arguments">arguments to pass to the method</param>
        /// <param name="proxyClassInstance">the proxy instance to be used for invokation</param>
        /// <returns>returns the return value from the method call or null if no value can be retrieved</returns>
        /// <remarks></remarks>
        /// <exception cref="Exception">Thrown when a matching method cannot be found or the method cannot be invoked</exception>
        private object InvokeProxyClassInstance(string methodName, IOrderedDictionary arguments, object proxyClassInstance, Assembly serviceAssembly)
        {
            Type proxyType = proxyClassInstance.GetType();
            MethodInfo[] proxyMethodInfoItems = proxyType.GetMethods();
            MethodInfo targetMethodInfo = null;
            object returnValue = null;

            for (int itemIndex = 0; itemIndex < proxyMethodInfoItems.Length; itemIndex++)
            {
                if (proxyMethodInfoItems[itemIndex].Name.Equals(methodName) && proxyMethodInfoItems[itemIndex].GetParameters().Length == arguments.Count)
                {
                    targetMethodInfo = proxyMethodInfoItems[itemIndex];
                    if (AreSameArgumentNames(targetMethodInfo, arguments))
                    {
                        break;
                    }
                }

            }
            if (targetMethodInfo == null)
            {
                throw new Exception("Method not found.");
            }
            try
            {
                if (arguments != null && arguments.Count > 0)
                {
                    ValidateAndSortMethodArguments(targetMethodInfo, arguments);

                    object[] argumentValues = new object[arguments.Count];

                    arguments.Values.CopyTo(argumentValues, 0);

                    returnValue = targetMethodInfo.Invoke(proxyClassInstance, argumentValues);
                }
                else
                {
                    returnValue = targetMethodInfo.Invoke(proxyClassInstance, null);
                }
            }
            catch (TargetInvocationException ex)
            {
                throw new Exception("Unable to invoke the requested method.  Ensure the service is available or the service configuration information is correct");
            }
            return returnValue;
        }

        /// <summary>
        /// This method determines if the supplied method paramters match; by name; the argument names
        /// which are to be invoked.
        /// </summary>
        /// <param name="sourceMethodInfo">Method info for the method being invoked</param>
        /// <param name="argumentValues">arguments to pass to the method</param>
        /// <returns>True is the arguments are the same otherwise false</returns>
        /// <remarks></remarks>
        private static bool AreSameArgumentNames(MethodInfo sourceMethodInfo, IOrderedDictionary argumentValues)
        {
            bool returnValue = true;

            //Get parameter array for the method
            ParameterInfo[] parameters = sourceMethodInfo.GetParameters();
            if (argumentValues != null && argumentValues.Count > 0)
            {
                foreach (ParameterInfo parameter in parameters)
                {
                    if (!(argumentValues.Contains(parameter.Name)))
                    {
                        returnValue = false;
                        break;
                    }
                }
            }
            return returnValue;
        }

        /// <summary>
        /// Validates passed arguments and puts them in the order they are defined by the target method
        /// </summary>
        /// <remarks></remarks>
        /// <param name="argumentValues">arguments to pass to the method</param>
        /// <param name="sourceMethodInfo">Method info for the method being invoked</param>
        /// <exception cref="ArgumentException">Thrown when either a provided argument is not valid or its value cannot
        /// be converted </exception>
        public void ValidateAndSortMethodArguments(MethodInfo sourceMethodInfo, IOrderedDictionary argumentValues)
        {
            if (sourceMethodInfo == null)
            {
                return;
            }
            if (argumentValues == null || argumentValues.Count < 1)
            {
                return;
            }
            //Get parameter array for the method
            ParameterInfo[] parameters = sourceMethodInfo.GetParameters();

            //Hold parameter values in the order they are found
            OrderedDictionary valueList = new OrderedDictionary();

            for (int paramIndex = 0; paramIndex < parameters.Length; paramIndex++)
            {
                bool found = false;

                foreach (DictionaryEntry valueEntry in argumentValues)
                {
                    if (parameters[paramIndex].Name.Equals(valueEntry.Key.ToString()))
                    {
                        found = true;
                        TypeConverter converter = TypeDescriptor.GetConverter(parameters[paramIndex].ParameterType);

                        if (converter.CanConvertFrom(valueEntry.Value.GetType()))
                        {
                            valueList.Add(valueEntry.Key, converter.ConvertFrom(valueEntry.Value));
                        }
                        else if (converter.CanConvertFrom(Type.GetType("System.String")))
                        {
                            valueList.Add(valueEntry.Key, converter.ConvertFrom(valueEntry.Value.ToString()));
                        }
                        else
                        {
                            throw new ArgumentException("Cannot convert value");
                        }
                    }
                }
                if (!found)
                {
                    throw new ArgumentException("Argument Not Found");
                }
            }
            argumentValues.Clear();

            foreach (DictionaryEntry entry in valueList)
            {
                argumentValues.Add(entry.Key, entry.Value);
            }
        }

        /// <summary>
        /// Invokes a web service method
        /// </summary>
        /// <param name="methodName">Name of the method to be invoked</param>
        /// <param name="arguments">arguments to pass to the method</param>
        /// <returns>return value</returns>
        /// <remarks></remarks>
        /// <exception cref="WebException">Thrown when a connection cannot be made to the specified web service</exception>
        /// <exception cref="ArgumentException">Thrown when either a provided argument is not valid or its value cannot
        /// be converted </exception>
        public object InvokeWebServiceMethod(string methodName, OrderedDictionary arguments)
        {
            XmlReader wsdlDocument = this.DownloadWSDL();
            ServiceDescription wsdlDescription = this.DeserializeWSDL(wsdlDocument);
            CodeCompileUnit compileUnit = new CodeCompileUnit();
            ServiceDescriptionImportWarnings warnings = GenerateCodeForProxyClass(wsdlDescription, compileUnit);
            if (warnings == 0)
            {
                Assembly serviceAssembly = CompileCodeForProxyClass(compileUnit);
                object proxyInstance = this.CreateProxyClassInstance(serviceAssembly);

                return this.InvokeProxyClassInstance(methodName, arguments, proxyInstance, serviceAssembly);
            }
            return null;
        }
    }
}

=========================================
Please excuse my email being down for the next couple of days.  

2.
Carl Kelley Carl Kelley United States says:

I'm having trouble figuring out how to change the timeout property of the service.  Any suggestions?

3.
Audrey Audrey United States says:

You made fantastic nice points here. I performed a search on the issue and discovered almost all peoples will agree with your blog.

4.
Anya Anya United States says:

Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts.Any way Ill be subscribing to your feed and I hope you post again soon

Comments are closed