views:

407

answers:

3

I'm using the code at This Site to call a webservice dynamically.

[SecurityPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
    public static object CallWebService(string webServiceAsmxUrl, string serviceName, string methodName, object[] args)
    {
        System.Net.WebClient client = new System.Net.WebClient();
        //-Connect To the web service
        using (System.IO.Stream stream = client.OpenRead(webServiceAsmxUrl + "?wsdl"))
        {
            //--Now read the WSDL file describing a service.
            ServiceDescription description = ServiceDescription.Read(stream);
            ///// LOAD THE DOM /////////
            //--Initialize a service description importer.
            ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
            importer.ProtocolName = "Soap12"; // Use SOAP 1.2.
            importer.AddServiceDescription(description, null, null);
            //--Generate a proxy client. importer.Style = ServiceDescriptionImportStyle.Client;
            //--Generate properties to represent primitive values.
            importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
            //--Initialize a Code-DOM tree into which we will import the service.
            CodeNamespace nmspace = new CodeNamespace();
            CodeCompileUnit unit1 = new CodeCompileUnit();
            unit1.Namespaces.Add(nmspace);
            //--Import the service into the Code-DOM tree. This creates proxy code
            //--that uses the service.
            ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit1);
            if (warning == 0) //--If zero then we are good to go
            {
                //--Generate the proxy code 
                CodeDomProvider provider1 = CodeDomProvider.CreateProvider("CSharp");
                //--Compile the assembly proxy with the appropriate references
                string[] assemblyReferences = new string[5] { "System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll" };
                CompilerParameters parms = new CompilerParameters(assemblyReferences);
                CompilerResults results = provider1.CompileAssemblyFromDom(parms, unit1);
                //-Check For Errors
                if (results.Errors.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (CompilerError oops in results.Errors)
                    {
                        sb.AppendLine("========Compiler error============");
                        sb.AppendLine(oops.ErrorText);
                    }
                    throw new System.ApplicationException("Compile Error Occured calling webservice. " + sb.ToString());
                }
                //--Finally, Invoke the web service method 
                Type foundType = null;
                Type[] types = results.CompiledAssembly.GetTypes();
                foreach (Type type in types)
                {
                    if (type.BaseType == typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
                    {
                        Console.WriteLine(type.ToString());
                        foundType = type;
                    }
                }

                object wsvcClass = results.CompiledAssembly.CreateInstance(foundType.ToString());
                MethodInfo mi = wsvcClass.GetType().GetMethod(methodName);
                return mi.Invoke(wsvcClass, args);
            }
            else
            {
                return null;
            }
        }
    }

This works fine when I use built in types, but for my own classes, I get this:

Event Type: Error
Event Source:   TDX Queue Service
Event Category: None
Event ID:   0
Date:       12/04/2010
Time:       12:12:38
User:       N/A
Computer:   TDXRMISDEV01
Description:
System.ArgumentException: Object of type 'TDXDataTypes.AgencyOutput' cannot be converted to type 'AgencyOutput'.

Server stack trace: 
   at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at TDXQueueEngine.GenericWebserviceProxy.CallWebService(String webServiceAsmxUrl, String serviceName, String methodName, Object[] args) in C:\CkAdmDev\TDXQueueEngine\TDXQueueEngine\TDXQueueEngine\GenericWebserviceProxy.cs:line 76
   at TDXQueueEngine.TDXQueueWebserviceItem.Run() in C:\CkAdmDev\TDXQueueEngine\TDXQueueEngine\TDXQueueEngine\TDXQueueWebserviceItem.cs:line 99
   at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
   at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
   at System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.EndInvokeHelper(Message reqMsg, Boolean bProxyCase)
   at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(Object NotUsed, MessageData& msgData)
   at TDXQueueEngine.TDXQueue.RunProcess.EndInvoke(IAsyncResult result)
   at TDXQueueEngine.TDXQueue.processComplete(IAsyncResult ar) in C:\CkAdmDev\TDXQueueEngine\TDXQueueEngine\TDXQueueEngine\TDXQueue.cs:line 130

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

The classes reference the same assembly and the same version. Do I need to include my assembly as a reference when building the temporary assembly? If so, how?

Thanks.


Update

It appears that the best solution will be to build a routine that can map from AssemblyX.MyCustomType to an equivalent GeneratedAssembly.MyCustomType.

In my example, MyCustomType contains more types (which should all be part of the generated assembly) so it appears I need a method to do this "deep copy". Also, some of the properties of the TDXDataTypes.AgencyOutput are arrays of other classes, just to make things more fun...

I've created a new question for the mapping.

+2  A: 

For passing custom objects, one way could be to de/serialize your custom object. Also see How to: Enable a Web Service to Send and Receive Large Amounts of Data and C# – Dynamically Invoke Web Service At Runtime

KMan
I did think about adding a custom serialization step, but that would require changing every webservice - this looks like it might be slightly more manageable. Thanks.
ck
+2  A: 

The class you compile dynamically will not be equal to the one you reference directly, and thus you cannot cast one to the other. For two classes to be equal, they have to come from the same assembly (or you can roll your own deserialization).

I would look into using something like AutoMapper to map between the two classes. You will set up a map from the compiled type to your references type, and then map the classes.

[Edit - code which compiles]

Example using AutoMapper:

object ret = DynWebservice.CallWebService(...);
Mapper.CreateMap(ret.GetType(), typeof(TDXDataTypes.AgencyOutput));
TDXDataTypes.AgencyOutput ao = (TDXDataTypes.AgencyOutput)Mapper.Map(ret, ret.GetType(), typeof(TDXDataTypes.AgencyOutput));
Mikael Svenson
-1: your code won't compile.
John Saunders
@John, sorry about that. That's what I get from taking code samples instead of coding in VS. This example will now compile and map the classes.
Mikael Svenson
@Mikael - this is a good concept, but my types aren't known at design time, and there are many levels of nesting of lots of complex types. Can this be expanded to do this?
ck
@ck, It can, but you would have to return the other types as well and Map them to the referring classes. Since the classes have the same names, you could map types based on the class names. If you return out all types from "Type[] types = results.CompiledAssembly.GetTypes()", you should be well on your way. Hope this helps and that it's not too complex. Also read up on the AutoMapper docs to get a better understanding on how it works.
Mikael Svenson
+3  A: 

I reproduced the issue on my local machine and fixed that issue..

Following is what u need to do to mke the custom object work

Ur current code is like this

   object wsvcClass = results.CompiledAssembly.CreateInstance(foundType.ToString());   
   MethodInfo mi = wsvcClass.GetType().GetMethod(methodName);   
   return mi.Invoke(wsvcClass, args);   

Let me try to explain the most probable reason for the problem to come.

when u r invoking a method in the assembly called as "methodname" in the webservice u r trying to pass the parameters required for that as args[] to the function "CallWebService" This args[] when passed will be successfully working when u try to pass a normal parameters like primitive types including string.

But this is what u might be doing when u try to pass a custom object as a parameter

Three things that are done in this.

  1. create an object of that type outside the CallWebService function (using reflection). when u do that way what happens is an instance of the customobject created with a temporary dll name internally.
  2. once u set the set the properties of the object and send it across to the CallWebService fuction as an object in the args array.
  3. U are trying to create an instance fo the webservice by creating the dynamic dll.

    object wsvcClass = results.CompiledAssembly.CreateInstance(foundType.ToString());

When u finally try to invoke the method with the instance of the dynamic assembly created u r trying to pass ur customobject that is created at step 1,2 via args property.

at the time of invocation the CLR tries to see if the customobject that is passed as input and the method that is being invoked are from the same DLL.

which is obviously not from the way the implementation is done.

So follwing is the appraoch that should be used to overcome the problem U need to create the cusotm object assembly with the same assembly that u used to the create the webserivce instance..

I implemented this approach completely and it worked out fine

MethodInfo m = type.GetMethod(methodName);
ParameterInfo[] pm = m.GetParameters();
object ob;
object[] y = new object[1];
foreach (ParameterInfo paraminfo in pm)
{
    ob = this.webServiceAssembly.CreateInstance(paraminfo.ParameterType.Name);

    foreach (PropertyInfo propera in ob.GetType().GetProperties())
    {
        if (propera.Name == "AppGroupid")
        {
            propera.SetValue(ob, "SQL2005Tools", null);
        }
        if (propera.Name == "Appid")
        {
            propera.SetValue(ob, "%", null);
        }
    }
    y[0] = ob;
}
Phani Kumar PV
Thanks - this looks good. I'll give it a whirl...
ck
This looks like you're populating an object with 2 hardcoded strings - my AgencyOutput contains a number of other classes, arrays etc., which in turn could contain anything, so I need a dynamic deep-copy routine to handle this...
ck
I've updated the question to this effect. +1 for getting me on the right track.
ck
the hard coding is just done from my sample values as inputs. u can write ur own custom peice of code to set the values for the properties inside the object. my primary intention to make a point out of that is whatever we need to get the custom objects as parameters, that needs to be done in a fashion similar to that.Hope i solved ur problem ..:)
Phani Kumar PV
@Phanii - yes, you've certainly steered me on the right course, but the mapping is going to be a pain. The Mapper project specified by another answerer looks good, but I don't know if it can be that dynamic.
ck