cjk
cjk

Reputation: 46425

Call Webservice without adding a WebReference - with Complex Types

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.

Upvotes: 1

Views: 9877

Answers (4)

Phani Kumar PV
Phani Kumar PV

Reputation: 892

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

Following is what you need to do to make 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 you do that way what happens is an instance of the customobject created with a temporary dll name internally.
  2. once you set the set the properties of the object and send it across to the CallWebService function as an object in the args array.
  3. U are trying to create an instance of the webservice by creating the dynamic dll.

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

When you finally try to invoke the method with the instance of the dynamic assembly created you are trying to pass your 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 following is the approach that should be used to overcome the problem U need to create the custom object assembly with the same assembly that you used to the create the webservice 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;
}

Upvotes: 6

Greg Biles
Greg Biles

Reputation: 941

I've used the code here (http://www.crowsprogramming.com/archives/66) to dynamically call a web service and was able to convert the type by serializing it to XML and then back again. In my case, the type I'm trying to get to (T) is in a class file generated by WSDL.EXE pointing at the web service that I'm calling dynamically.

    public T ConvertType<T>(object input)
    {
        XmlSerializer serializer = new XmlSerializer(input.GetType());
        XmlSerializer deserializer = new XmlSerializer(typeof(T));

        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            serializer.Serialize(sw, input);
        }

        using (StringReader sr = new StringReader(sb.ToString()))
        {
            return (T)deserializer.Deserialize(sr);
        }
    }

Upvotes: 0

Mikael Svenson
Mikael Svenson

Reputation: 39695

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));

Upvotes: 3

Kamran Khan
Kamran Khan

Reputation: 9986

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

Upvotes: 2

Related Questions