Click here to Skip to main content
15,897,518 members
Articles / Programming Languages / C#

[C#] Dynamic Web Service Invoker

Rate me:
Please Sign up or sign in to vote.
3.25/5 (7 votes)
30 Apr 2012CPOL1 min read 49.6K   12   9
Invoking a web service dynamically

Introduction

Invoking a Web Service dynamically is not a new topic. I have been using the following method to invoke a web service without a proxy till recently. Later below, you can see a better way of doing this.

Create your SOAP envelope and contents. You will be playing with some strings for a while.

The Conventional Method

C#
// Create and furnish the basic requestor
HttpWebRequest oHttpWebRequest = (HttpWebRequest) WebRequest.Create(URL);
oHttpWebRequest.UseDefaultCredentials = true;
oHttpWebRequest.ContentType = "text/xml";
oHttpWebRequest.Method = "POST";
oHttpWebRequest.Accept = "text/xml";
oHttpWebRequest.Headers.Add("SOAPAction:" + SoapAction);
oHttpWebRequest.UserAgent = "Mozilla/4.0+";
// Encode and post the request
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(sSoapMessage);
Stream oSendStream = oHttpWebRequest.GetRequestStream();
oSendStream.Write(bytes, 0, bytes.Length);
oSendStream.Close();
// Get the response
oHttpResponse = (HttpWebResponse) oHttpWebRequest.GetResponse();
Stream oReceiveStream = oHttpResponse.GetResponseStream();
// Manipulate the stream and extract data here
oHttpResponse.Close();
oReadStream.Close();

I have omitted many plumbing statements here as this is intended only to give a basic idea about how difficult this is.

A Different Approach

The next method also technically works the same way, because there is only one way a SOAP request can be made. The only difference is that the new approach wraps the whole string manipulation inside itself and does all the plumbing work for us. The basic idea is to generate a proxy assembly when you need it as a one time activity.

C#
Uri uri = new Uri(_mUrl); // Create the uri object
WebRequest webRequest = WebRequest.Create(uri);

// Supply credentials, if required
Authenticate(webRequest);

WebResponse webResponse = webRequest.GetResponse();
Stream requestStream = webResponse.GetResponseStream();
// Get a WSDL file describing a service
ServiceDescription serviceDescription = ServiceDescription.Read(requestStream);
// Initialize a service description importer
ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
descriptionImporter.AddServiceDescription(serviceDescription, String.Empty, String.Empty);

//The allowed values of this property are:  "Soap", 
//"Soap12", "HttpPost" ,"HttpGet" and "HttpSoap".
//The default value is "Soap", 
//which indicates the SOAP 1.1 standard. This is case sensitive.
descriptionImporter.ProtocolName = "Soap";
descriptionImporter.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties;
CodeNamespace codeNamespace = new CodeNamespace();

// Compile to assembly
compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
Assembly assembly = compilerResults.CompiledAssembly;

// We have a valid assembly now. Try to get the Type from it.
Type type = assembly.GetType(_mTypeName) ?? FindTypeByName(assembly);

// Create the object instance
_mTargetInstance = assembly.CreateInstance(_mTypeName);
// Get the method info object
_mMethodInfo = type.GetMethod(
_mMethodName,
BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.IgnoreReturn);

// Invoke the method with necessary arguments
object result = _mMethodInfo.Invoke(_mTargetInstance, parameters);

The advantage in this approach is that you need to execute this whole thing only once when you find the MethodInfo object as null (for the first time). The rest of the time, this will behave just like a normal web service invoke using the conventional method.

I know you might be thinking why should you do like this? Why can’t I invoke like the old way? Well, the answer is ‘it depends’. There are some situations where you decide to post a piece of data to a web service whose URL, method and parameters are known only at run-time, probably from a database configuration or a config file. I found this very useful for my application integration project.

Enjoy coding and let me know if you need help with this.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBasic Authentication with second approach ? Pin
Umar Sharif24-Jan-16 4:41
Umar Sharif24-Jan-16 4:41 
GeneralMy vote of 3 Pin
MFreed779-Apr-15 6:14
MFreed779-Apr-15 6:14 
QuestionCompleting the Example Pin
MFreed779-Apr-15 6:12
MFreed779-Apr-15 6:12 
GeneralMy vote of 1 Pin
Gabriel Espinoza15-Oct-14 3:22
Gabriel Espinoza15-Oct-14 3:22 
GeneralMy vote of 1 Pin
aspnet banana31-Jul-14 22:30
aspnet banana31-Jul-14 22:30 
GeneralMy vote of 1 Pin
ctvn20025-Jun-14 11:23
ctvn20025-Jun-14 11:23 
GeneralWhat a ploncker! Pin
ctvn20025-Jun-14 11:12
ctvn20025-Jun-14 11:12 
Questionhelp Pin
Glendenning22-Aug-13 20:42
Glendenning22-Aug-13 20:42 
AnswerAdditional approach Pin
roos.ict.architectures14-Aug-13 1:16
roos.ict.architectures14-Aug-13 1:16 
I extended the first solution:

/// <summary>
            /// Create a soap webrequest to [Url]
            /// </summary>
            /// <returns></returns>
            public HttpWebRequest CreateWebRequest(Uri uri)
            {
                CookieContainer myContainer = new CookieContainer();

                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
                webRequest.Credentials = GetCredential(uri);
                webRequest.CookieContainer = myContainer;
                webRequest.PreAuthenticate = true;
                //TODO add action from config
                webRequest.Headers.Add(@"SoapAction :" + <action from config file>)
                webRequest.ContentType = "text/xml;charset=\"utf-8\"";
                webRequest.Accept = "text/xml";
                webRequest.Method = "POST";
                return webRequest;
            }

            private CredentialCache GetCredential(Uri uri)
            {
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
                CredentialCache credentialCache = new CredentialCache();
                credentialCache.Add(uri, "NTLM", new NetworkCredential("<username>", @"pwd", "domain"));
                return credentialCache;
            }
            public XElement Execute(WebServiceInfo info)
            {
                HttpWebRequest request = CreateWebRequest(info.Address);
                XElement soapEnvelopeXml = info.Envelope;
                using (Stream stream = request.GetRequestStream())
                {
                    soapEnvelopeXml.Save(stream);
                }

                using (WebResponse response = request.GetResponse())
                {
                    using (StreamReader rd = new StreamReader(response.GetResponseStream()))
                    {
                        string soapResult = rd.ReadToEnd();
                        Console.WriteLine(soapResult);
                        return XElement.Parse(soapResult);
                    }
                }
             }


My info comes from an XML config file:
pre lang="xml">?xml version="1.0" encoding="utf-8" ?>
<soap>
<general>
<key1>user</key1>
<key2>pwd</key2>
<key3>domain</key3>
</general>
<webservice name="somefunctionalname""<somefunctionalname>">
<uri>http://<someserver>.<servicename>.svc</uri>
<action>http://tempuri.org/<servicename>/findAll</action>
<xslt>units.xslt</xslt>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<findAll></findAll>
</soap:Body>
</soap:Envelope>
</webservice>
</soap>


The XMLServiceInfo parser the XML:
C#
public class WebServiceInfo
        {
            private static XElement _configFile = null;
            public WebServiceInfo(XElement webServiceElement)
            {
                InitConfig();
               
                Contract.Assume(_configFile != null);
                Init(webServiceElement);
            }

            private static void InitConfig()
            {
                if (_configFile == null)
                {
                    _configFile = XElement.Load("SoapFormat.xml");
                }
            }

            private void Init(XElement webServiceElement)
            {
                InitConfig();
                Contract.Assume(webServiceElement != null);
                Contract.Assume(_configFile != null);
                
                XAttribute name = webServiceElement.Attribute("name");
                this.Name = name.Value;
                
                XElement envelope = webServiceElement.Elements().Where(elm => elm.Name.LocalName == "Envelope").SingleOrDefault();
                this.Envelope = envelope;

                XElement uriElm = webServiceElement.Element("uri");
                Contract.Assume(uriElm != null, "Webservice is missing <uri> address to service. Please add uri to configfile.");
                try
                {
                    this.Address = new Uri(uriElm.Value);
                }
                catch(Exception ex)
                {
                    Contract.Assume(false, string.Format("uri must be valid http address", ex.Message));
                }

                XElement xsltElm = webServiceElement.Element("xslt");
                Contract.Assume(xsltElm != null, "Webservice is missing <xslt> file path. Please add xslt file path to configfile.");
                try
                {
                    this.XsltPath = new FileInfo(xsltElm.Value);
                    if (this.XsltPath.Exists)
                    {
                        this.XsltTrans = new XslTransform();
                        this.XsltTrans.Load(XsltPath.FullName);

                    }
                    else
                    {
                        Contract.Assume(false, string.Format("xslt {0} must exists. Please add file, or change config xslt settings", this.XsltPath.FullName));
                    }
                }
                catch (Exception ex)
                {
                    Contract.Assume(false, string.Format("xslt must be valid file path, and must exists, and be valid xslt xml : {0}", ex.Message));
                }

                
                XElement actionElm = webServiceElement.Element("action");
                Contract.Assume(actionElm != null, "Webservice is missing <action> webservice method command. Please add <action>  to configfile.");
                try
                {
                    this.Action = actionElm.Value;
                }
                catch (Exception ex)
                {
                    Contract.Assume(false, string.Format("action cannot be loaded : {0}", ex.Message));
                }
            }
            public XslTransform XsltTrans { get; set; } 
            public string Name {get; set;}
            public Uri Address {get;set;}
            public XElement Envelope {get;set;}
            public FileInfo XsltPath {get;set;}
            public string Action { get; set; }

            public static WebServiceInfo GetWebService(string serviceName)
            {
                InitConfig();
                Contract.Assume(_configFile != null);
                XElement elm = _configFile.Descendants().Where(serviceElm => serviceElm.Name.LocalName == "webservice" && serviceElm.Attribute("name") != null && serviceElm.Attribute("name").Value == serviceName).SingleOrDefault();
                if (elm != null)
                {
                    WebServiceInfo info = new WebServiceInfo(elm);
                    return info;
                }
                return null;
            }
        }


Then my calling looks like:
C#
WebServiceInfo info = WebServiceInfo.GetWebService("somefuncionalname>");
Assert.IsNotNull(info);
Assert.IsNotNull(info.Address);
Assert.IsNotNull(info.Envelope);
Assert.IsNotNull(info.XsltPath);
Assert.IsNotNull(info.XsltTrans);
Assert.IsNotNull(info.Action);
WebServiceExecutor executor = new WebServiceExecutor();
XElement result = executor.Execute(info);
Assert.IsNotNull(result);


As you can see, the resulting message / resonse is XML, which I intend to pre-process with an configurable XSLT.

This solution can be used to use in glue-ing together enterpise systems from different versions.
E.g., ERP and CRM systems from differnet versions. They all support "findAll", in some form or other, but the repsonses can be quite different. Instead of using the standard Visual studio proxy / stub, which is a nightmare to maintain for say 4 different ERP/CRM versions, I use this configurable framework.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.