Generate proxy code for a web service dynamically
The first step to consuming a web service is to generate a proxy class from its wsdl. One will agree that with visual studio and a magic tool called wsdl.exe, generating the proxy class is the easiest of things. What if we do away with this concept of a proxy file from a client application? Generate one dynamically using the provided URL of the web service, consume the required web methods and forget about the web service. Took me some time to put the pieces together and come up with a solution.
“System.Web.Services.Description” namespace has a couple of classes which allows you to implement this quite easily. Two most important of those: ServiceDecription class and ServiceDescriptionImporter class. Use these in conjunction with System.CodeDom and you will get what you want. I wont go into the details of the functionalities exposed by each of these classes (msdn does a very good job of that). The path which I followed to implement the solution is as follows:
1. Generate proxy code from the downloaded wsdl file using ServiceDescriptionImporter and System.CodeDom
2. Generate a dynamic assembly which contains the compiled proxy code.
3. Reflect through the assembly to find out the web methods.
4. For each web method, find out its input parameter and return type.
You can easily develop a UI application to display the required information (regarding web methods and parameters) to the end user and let him choose which web method to invoke. I won’t cover that part in this post. First let’s have a look into the code to generate a dynamic assembly. I have commented every important operation within the code and won't explain it again. Please let me know if I have missed out on something and i will provide an explanation for that as well.
public string[] GenerateProxyAssembly()
{
//create a WebRequest object and fetch the WSDL file for the web service
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(this.uri);
request.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
System.IO.Stream stream = response.GetResponseStream();
//read the downloaded WSDL file
ServiceDescription desc = ServiceDescription.Read(stream);
//find out the number of operations exposed by the web service
//store the name of the operations inside the string array
//iterating only through the first binding exposed as
//the rest of the bindings will have the same number
int i = 0;
Binding binding = desc.Bindings[0];
OperationBindingCollection opColl = binding.Operations;
foreach (OperationBinding operation in opColl)
{
listOfOperations[i++] = operation.Name;
}
//initializing a ServiceDescriptionImporter object
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
//set the protocol to SOAP 1.1
importer.ProtocolName = "Soap12";
//setting the Style to Client in order to generate client proxy code
importer.Style = ServiceDescriptionImportStyle.Client;
//adding the ServiceDescription to the Importer object
importer.AddServiceDescription(desc, null, null);
importer.CodeGenerationOptions = CodeGenerationOptions.GenerateNewAsync;
//Initialize the CODE DOM tree in which we will import the ServiceDescriptionImporter
CodeNamespace nm = new CodeNamespace();
CodeCompileUnit unit = new CodeCompileUnit();
unit.Namespaces.Add(nm);
//generating the client proxy code
ServiceDescriptionImportWarnings warnings = importer.Import(nm, unit);
if (warnings == 0)
{
//set the CodeDOMProvider to C# to generate the code in C#
System.IO.StringWriter sw = new System.IO.StringWriter();
CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
provider.GenerateCodeFromCompileUnit(unit, sw, new CodeGeneratorOptions());
//creating TempFileCollection
//the path of the temp folder is hardcoded
TempFileCollection coll = new TempFileCollection(@"C:\wmpub\tempFiles");
coll.KeepFiles = false;
//setting the CompilerParameters for the temporary assembly
string[] refAssembly = { "System.dll", "System.Data.dll", "System.Web.Services.dll", "System.Xml.dll" };
CompilerParameters param = new CompilerParameters(refAssembly);
param.GenerateInMemory = true;
param.TreatWarningsAsErrors = false;
param.OutputAssembly = "WebServiceReflector.dll";
param.TempFiles = coll;
//compile the generated code into an assembly
//CompilerResults results = provider.CompileAssemblyFromDom(param, unitArr);
CompilerResults results = provider.CompileAssemblyFromSource(param, sw.ToString());
this.assem = results.CompiledAssembly;
}
//return the list of operations exposed by the web service
return listOfOperations;
}
This method returns the list of operations exposed by the web service. I have used ServiceDescription to achieve that as I was not able to reflect only the web method names from the generated assmebly. With the operation names available, all that remains is to find out the input and return parameters for each method.
public ParameterInfo[] ReturnInputParameters(string methodName)
{
//create an instance of the web service type
//////////////to do/////////////////////////
//get the name of the web service dynamically from the wsdl
Object o = this.assem.CreateInstance("Service");
Type service = o.GetType();
ParameterInfo[] paramArr = null;
//get the list of all public methods available in the generated //assembly
MethodInfo[] infoArr = service.GetMethods();
foreach (MethodInfo info in infoArr)
{
//get the input parameter information for the
//required web method
if (methodName.Equals(info.Name))
{
paramArr = info.GetParameters();
}
}
return paramArr;
}
This method returns the input parameters in ParameterInfo[] list. To get the output parameter, just replace the call to GetParamters() on MethodInfo class with ReturnParameter property and put that inside a new method. Put these 3 methods inside a dll and add a reference to it from any client application. That's all. Just provide the URL and consume the web service, any web service. You don't have to go through the procedure of creating a proxy file every time you want to consume a new web service.
Comments
- Anonymous
January 07, 2010
Hi, My web service client is dynamically generating the proxy, but also needs to inspect XML messages. I can do this by adding a custom behavior, but I can't figure out how to add the behavior to the proxy, since I can't access the behaviors collection. Can you tell me how to do this? Thanks in advance.