달력

32024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

출처 : http://www.crowsprogramming.com/archives/66


C# - Dynamically Invoke Web Service At Runtime

Web services have become an integral part of the communications between software over a network. There are a lot of ugly details behind how they work but thankfully C# hide a lot of those gory details. What you need to know is that there is a web service description (WSDL) that describes the available services and the data types they use (via XML).

You can use the WSDL to create types in C# that can communicate with the web service. There are a couple of ways that you can do this. You can generate a proxy class using the WSDL utility in the .NET SDK and add it to your project. You can also download the WSDL file at runtime, create the proxy class, compile it into an assembly, and execute the methods on it. This allows you to dynamically invoke methods from a web service that aren’t known at build time and is the approach I will explore today.

Step 1 – Obtain a ServiceDescriptionImporter

We want to create a ServiceDescriptionImporter object that we can use to generate the web service proxy class. We have to pass to it the WSDL location which is an XML document.

XmlTextReader xmlreader = new XmlTextReader(“MyWebService” + "?wsdl");
 
ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);
 
// build an importer, that assumes the SOAP protocol, client binding, and generates properties
ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
descriptionImporter.ProtocolName = "Soap";
descriptionImporter.AddServiceDescription(serviceDescription, null, null);
descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

Step 2 – Compile an Assembly from the importer

Now that we have the ServiceDescriptionImporter we need to use it to compile a proxy assembly that we can use to communicate with the web service. The assembly will exist in the users’ temporary folder.

// a namespace and compile unit are needed by importer
CodeNamespace codeNamespace = new CodeNamespace();
CodeCompileUnit codeUnit = new CodeCompileUnit();
 
codeUnit.Namespaces.Add(codeNamespace);
 
ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit);
 
if (importWarnings == 0) // no warnings
{
     // create a c# compiler
     CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
 
     // include the assembly references needed to compile
     string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
 
     CompilerParameters parameters = new CompilerParameters(references);
 
     // compile into assembly
     CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);
 
     foreach (CompilerError oops in results.Errors)
     {
          // trap these errors and make them available to exception object
          throw new Exception("Compilation Error Creating Assembly");
     }
 
     // all done....
     return results.CompiledAssembly;
}
else
{
     // warnings issued from importers, something wrong with WSDL
     throw new Exception("Invalid WSDL");
}

Step 3 – Execute Methods from the Web Service

Finally we have an assembly built from the web service description and now we can invoke methods from it just like any other ordinary assembly we might use. To accomplish this we must use reflection to discover the correct method.

object obj = this.webServiceAssembly.CreateInstance(serviceName);
 
Type type = obj.GetType();
 
return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);

And there you have it, dynamic invocation of web service methods at runtime. Using this technique you can invoke methods from any arbitrary web service at runtime.

Here is a full example class for dynamically invoking a web service method in C#.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Xml;
 
namespace DynamicWebExample
{
    class WebServiceInvoker
    {
        Dictionary<string, Type> availableTypes;
 
        /// <summary>
        /// Text description of the available services within this web service.
        /// </summary>
        public List<string> AvailableServices
        {
            get{ return this.services; }
        }
 
        /// <summary>
        /// Creates the service invoker using the specified web service.
        /// </summary>
        /// <param name="webServiceUri"></param>
        public WebServiceInvoker(Uri webServiceUri)
        {
            this.services = new List<string>(); // available services
            this.availableTypes = new Dictionary<string, Type>(); // available types
 
            // create an assembly from the web service description
            this.webServiceAssembly = BuildAssemblyFromWSDL(webServiceUri);
 
            // see what service types are available
            Type[] types = this.webServiceAssembly.GetExportedTypes();
 
            // and save them
            foreach (Type type in types)
            {
                services.Add(type.FullName);
                availableTypes.Add(type.FullName, type);
            }
        }
 
        /// <summary>
        /// Gets a list of all methods available for the specified service.
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        public List<string> EnumerateServiceMethods(string serviceName)
        {
            List<string> methods = new List<string>();
 
            if (!this.availableTypes.ContainsKey(serviceName))
                throw new Exception("Service Not Available");
            else
            {
                Type type = this.availableTypes[serviceName];
 
                // only find methods of this object type (the one we generated)
                // we don't want inherited members (this type inherited from SoapHttpClientProtocol)
                foreach (MethodInfo minfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                    methods.Add(minfo.Name);
 
                return methods;
            }
        }
 
        /// <summary>
        /// Invokes the specified method of the named service.
        /// </summary>
        /// <typeparam name="T">The expected return type.</typeparam>
        /// <param name="serviceName">The name of the service to use.</param>
        /// <param name="methodName">The name of the method to call.</param>
        /// <param name="args">The arguments to the method.</param>
        /// <returns>The return value from the web service method.</returns>
        public T InvokeMethod<T>( string serviceName, string methodName, params object[] args )
        {
            // create an instance of the specified service
            // and invoke the method
            object obj = this.webServiceAssembly.CreateInstance(serviceName);
 
            Type type = obj.GetType();
 
            return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);
        }
 
        /// <summary>
        /// Builds the web service description importer, which allows us to generate a proxy class based on the 
        /// content of the WSDL described by the XmlTextReader.
        /// </summary>
        /// <param name="xmlreader">The WSDL content, described by XML.</param>
        /// <returns>A ServiceDescriptionImporter that can be used to create a proxy class.</returns>
        private ServiceDescriptionImporter BuildServiceDescriptionImporter( XmlTextReader xmlreader )
        {
            // make sure xml describes a valid wsdl
            if (!ServiceDescription.CanRead(xmlreader))
                throw new Exception("Invalid Web Service Description");
 
            // parse wsdl
            ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);
 
            // build an importer, that assumes the SOAP protocol, client binding, and generates properties
            ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
            descriptionImporter.ProtocolName = "Soap";
            descriptionImporter.AddServiceDescription(serviceDescription, null, null);
            descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
            descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
 
            return descriptionImporter;
        }
 
        /// <summary>
        /// Compiles an assembly from the proxy class provided by the ServiceDescriptionImporter.
        /// </summary>
        /// <param name="descriptionImporter"></param>
        /// <returns>An assembly that can be used to execute the web service methods.</returns>
        private Assembly CompileAssembly(ServiceDescriptionImporter descriptionImporter)
        {
            // a namespace and compile unit are needed by importer
            CodeNamespace codeNamespace = new CodeNamespace();
            CodeCompileUnit codeUnit = new CodeCompileUnit();
 
            codeUnit.Namespaces.Add(codeNamespace);
 
            ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit);
 
            if (importWarnings == 0) // no warnings
            {
                // create a c# compiler
                CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
 
                // include the assembly references needed to compile
                string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
 
                CompilerParameters parameters = new CompilerParameters(references);
 
                // compile into assembly
                CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);
 
                foreach (CompilerError oops in results.Errors)
                {
                    // trap these errors and make them available to exception object
                    throw new Exception("Compilation Error Creating Assembly");
                }
 
                // all done....
                return results.CompiledAssembly;
            }
            else
            {
                // warnings issued from importers, something wrong with WSDL
                throw new Exception("Invalid WSDL");
            }
        }
 
        /// <summary>
        /// Builds an assembly from a web service description.
        /// The assembly can be used to execute the web service methods.
        /// </summary>
        /// <param name="webServiceUri">Location of WSDL.</param>
        /// <returns>A web service assembly.</returns>
        private Assembly BuildAssemblyFromWSDL(Uri webServiceUri)
        {
            if (String.IsNullOrEmpty(webServiceUri.ToString()))
                throw new Exception("Web Service Not Found");
 
            XmlTextReader xmlreader = new XmlTextReader(webServiceUri.ToString() + "?wsdl");
 
            ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter(xmlreader);
 
            return CompileAssembly(descriptionImporter);
        }
 
        private Assembly webServiceAssembly;
        private List<string> services;
    }
}

This C# class can be easily used to invoke web service methods:

WebServiceInvoker invoker = new WebServiceInvoker(new Uri("http://localhost/services/test.php"));
 
string service = “MyService”;
string method = “MyMethod”;
 
string[] args = new string[] { “arg1”, “arg2” };
 
string result = invoker.InvokeMethod<string>(service, method, args);

I’ve put together an example application that you can download to evaluate the class. It allows you to invoke methods from a web service entered on the GUI and see the return value. You can download it here.

I hope you find this useful and if you have any questions or comments be sure to leave them here.

April 8, 2009   Posted in: Programming

27 Responses

  1. Joel Chinchilla - April 17, 2009

    Hi, I tried to test your example with a Java Web Service, however I need to pass credentials to Web Service. How can I do it?

  2. Neena - May 7, 2009

    Can I call this code from java

  3. crow - May 10, 2009

    Not that I am aware of.

  4. Sergio - May 14, 2009

    Hi!
    finally a good example on how to call WS dynamically!
    I tryed your code, and got this problem:
    - my WS is as follows:

    public class Service1 : System.Web.Services.WebService
    {
    public Service1()
    {
    InitializeComponent();
    }

    [WebMethod]
    public double Sum(double num1, double num2)
    {
    if ( guid == “”)
    return -1;
    return num1 + num2;
    }
    }

    couldn’t be as simpler as this.
    When i run your code, it does in fact create an assemby whith the correct Service1 classname and the Sum method.
    So everything seems to be ok.
    Problem is when I invoke Sum method it sends an exception saying that “Method ‘Service1.Sum’ not found” on line :
    return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);

    Any clue?

    thanks in advance!

  5. shortcuts - May 18, 2009

    Off-topic Help - Need a secure web proxy to get around my school firewall. Any suggestions?

  6. Jeremy Foster - May 19, 2009

    I have this implemented and working but I have run into a limitation. I can not figure out how to capture multiple returned values in the SOAP response. Do you know of a way to do this?

  7. Sergio - May 19, 2009

    Jeremy,

    what do you mean with “multiple returned values in the SOAP response”. A method only returns 1 value/object. The value itself can have a collection of values/objects. Is this what you meant?
    You may try to transform the returned value to XML. Use this:

    public static string SerializeObjectToXML(T obj)
    {
    try
    {
    string xmlString = null;
    MemoryStream memoryStream = new MemoryStream();
    System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
    string s = “”;
    XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
    System.Runtime.Serialization.Formatters.Soap.SoapFormatter xsoap = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
    xsoap.Serialize(memoryStream, obj);

    xmlString = UTF8ByteArrayToString(memoryStream.ToArray());
    return xmlString;
    }
    catch(Exception ex)
    {
    ex.GetType();
    return string.Empty;
    }
    }

    public static T UnserializeObjectFromXML(string xml)
    {
    System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
    MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xml));
    XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
    return (T)xs.Deserialize(memoryStream);
    }

    The Serialize method uses the SoapFormatter class, and in fact serializes the returned object just like what the Webservice call does.

  8. Izzy - May 22, 2009

    Hi, Crows.
    i get this webpage/artical by google.

    i got one question.

    if the input parameter of the method is normal types, like string, int etc, then it is ok.

    right now, I got one webservie, and its input parameter of the function is like this

    string
    string
    int
    int
    int
    int
    int
    int
    int
    int

    there is only one input parameter : TestMessage_input.
    it is a type which is similiar with Struct.

    I’m confused about how to forming such an input parameter in GUI???
    thanks

    your reply will be appreciated and helpful.

  9. Izzy - May 22, 2009

    oh,some infos are missing.they were deleted automatically.

    would you mind telling me your email?
    since i really have some confusions about using this.
    is that ok?

  10. Shaun - May 22, 2009

    Excellent routine! I’m having a problem using this class however. When I call “return results.CompiledAssembly;” I get the following exception:

    Execution permission cannot be acquired.
    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.Security.Policy.PolicyException: Execution permission cannot be acquired.

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Stack Trace:

    [PolicyException: Execution permission cannot be acquired.]
    System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Boolean checkExecutionPermission) +10236136
    System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Int32& securitySpecialFlags, Boolean checkExecutionPermission) +97

    [FileLoadException: Could not load file or assembly 'm2wivo6v, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Failed to grant permission to execute. (Exception from HRESULT: 0x80131418)]
    System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
    System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +416
    System.Reflection.Assembly.Load(AssemblyName assemblyRef, Evidence assemblySecurity) +33
    System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly() +105…

    I’m running this code in SharePoint 2007 on Windows Server 2008.

  11. vikram - May 26, 2009

    It’s working fine but application memory increases when web service call are made 2500 times.

    Can we delete the temporary files created while compiling assembly ?

  12. marmri - May 31, 2009

    Hi,
    Thank you for this great example.
    I have the same problem as Sergio - May 14, 2009
    Did you wirte him any suggestion? Please forward it or add it in this article for the whole aucience!
    I have another question: how to do, if you have following situation:

    MyWebReference.GoodbyeWorldWSService myProxy = new MyWebReference.GoodbyeWorldWSService();
    MyWebReference.sayGoodbye request = new MyWebReference.sayGoodbye();
    request.message = “ciao”;
    myProxy.sayGoodbye(request);

    I mean, you have like an object as parameter you first initialize then invoke the service method!

    Please help, is urgent. Thanx a lot in advance

  13. Jeff - June 13, 2009

    Hi As I walk on the code.. i see that the reason why its throwing an exception on this line (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args); it is maybe because you are calling a method with complex (custom) type and not a generic type. try to have a breakpoint on that line and see the details of the “type” variable.

  14. shail - June 27, 2009

    I am using your example. But its giving me an error on InvokeMember that “The request failed with HTTP status 401: Access Denied” Is there anything special, i will have to do here?

    Thanks,

    Shail …

  15. RadoColo - June 30, 2009

    Beautiful, works like a charm!

  16. usmanbinasif - July 6, 2009

    Dear it is awesome code example of this problem domain, and it is very helpful for me.

    Thanks for sharing such a wonderful peice of code.

    Regards,
    Usman Javed

  17. newbieDotNET - July 7, 2009

    Will it be possible to invoke or set the timeout property? Any help would be greatly appreciated.

    Thanks,
    Jon

  18. David Roys - July 8, 2009

    What an awesome example. Well explained and a great download! Thank you for your time and effort. Unfortunately it doesn’t quite work for me.

    I am also having the problem with authentication that shail posted. I know that when I use my web service by generating the proxy class, I need to use the line:

    ws.UseDefaultCredentials = true in order to be able to user the service.

    I am getting the following error:

    Unable To Load Service: The remote server returned an error: (401) Unauthorized.

    when I click the Load Service button and I can only assume it is because the XMLTextReader is not negotiating my credentials. If I use Internet explorer to hit the URL it will give me the WSDL but I believe this is because it handles the challenge response correctly. Do I need to use another method of getting my WSDL - i.e. not using XMLTextReader?

    Thanks again for such a great post! I learnt a lot.

  19. Mark - July 8, 2009

    Good work, excellent explanation.

    functions that expect strings do work, but functions that expect other types (int, byte[], …) do not work.

    Any idea how to solve this?

    Thanks

    Mark

  20. David Roys - July 10, 2009

    Hi there, OK, I thought I would let other readers know that I managed to sort out my problems with authentication.

    You can read the details on a blog post I made http://www.teachmenav.com/blogs/dave/archive/2009/07/11/using-reflection-to-call-nav-2009-web-services.aspx

    Thanks again for the great post.

  21. Crows Programming » I’m Back - July 15, 2009

    [...] Hey guys, sorry I’ve been away for a bit. I’ll try and respond to your questions, especially those over on the C# dynamic web service page. [...]

  22. MaTZ - July 22, 2009

    Hi,
    I have a problem with http authentication.
    I managed to download WSDL by modifying BuildAssemblyFromWSDL like this:

    System.Net.WebClient client = new System.Net.WebClient();

    if (login.Length > 0)
    {
    client.Credentials = new NetworkCredential(login, password);
    }

    System.IO.Stream stream = client.OpenRead(webServiceUri.ToString() + “?wsdl”);

    XmlTextReader xmlreader = new XmlTextReader(stream);

    ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter(xmlreader);

    return CompileAssembly(descriptionImporter);

    Now I’m getting exception (error 401) later when I try to invoke web method. Any ideas?

    Thanks for the post.

  23. facebook proxy - July 25, 2009

    Interesting article. Were did you got all the information from… :)

  24. proxies - July 25, 2009

    You are a very smart person! :)

  25. Andrew Brown - August 4, 2009

    Very useful post. I have been using this technique for over a year.

    I am having a problem where the web service request takes longer than 110 seconds to return a response. This causes a timeout error. I have been troubleshooting the timeout issue by changing all configurable timeout settings from the application setting (web.config) and IIS.

    [Error Details]
    System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.Net.WebException: The operation has timed out.

    I setup a simple test where the web server thread sleeps for 2 mins and 30 seconds.

    Please post if anyone has solved this.

  26. Donald Drake - August 5, 2009

    ““Method ‘Service1.Sum’ not found” ”

    I was able to fix this error by using the following instead.

    MethodInfo method = type.GetMethod(methodName);
    return (T) method.Invoke(instance, args);

    I believe the issue is due to the fact that it is using BindingFlags.InvokeMethod.. I was not able to use any other bindingflag with the type.InvokeMember method.

    Hope this helps.

    Thanks,
    Don

  27. Andrew Brown - August 5, 2009

    I found a solution to the Timeout issue. I had to set the Timeout property of the proxy instance.

    [CODE]
    PropertyInfo propInfo = obj.GetType().GetProperty(”Timeout”);
    propInfo.SetValue(obj, 300000 /* set this to whatever timeout period you want */, null);

    Then invoke the method.

Posted by tornado
|