출처 : 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
'.NET > C#' 카테고리의 다른 글
[펌] System.Xml. XmlSerializer Examples (0) | 2009.08.31 |
---|---|
ADHelper - An Active Directory Class (0) | 2009.08.17 |
Change the group of a user in AD through C# (0) | 2009.06.16 |
.NET Framework 3.5의 디렉터리 보안 주체 관리 (0) | 2009.04.01 |
Debugging windows service Onstart (0) | 2009.03.27 |
27 Responses
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?
Can I call this code from java
Not that I am aware of.
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!
Off-topic Help - Need a secure web proxy to get around my school firewall. Any suggestions?
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?
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.
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.
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?
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.
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 ?
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
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.
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 …
Beautiful, works like a charm!
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
Will it be possible to invoke or set the timeout property? Any help would be greatly appreciated.
Thanks,
Jon
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.
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
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.
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. [...]
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.
Interesting article. Were did you got all the information from…
You are a very smart person!
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.
““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
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.