在不添加WebReference的情况下调用Webservice - 使用复杂类型

时间:2010-04-12 12:32:11

标签: c# web-services

我正在This Site使用代码动态调用Web服务。

[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;
            }
        }
    }

当我使用内置类型时这很好用,但对于我自己的类,我得到了这个:

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.

这些类引用相同的程序集和相同的版本。在构建临时组件时,是否需要将组件作为参考?如果是这样,怎么样?

感谢。


更新

似乎最好的解决方案是构建一个可以从AssemblyX.MyCustomType映射到等效GeneratedAssembly.MyCustomType的例程。

在我的示例中,MyCustomType包含更多类型(应该都是生成的程序集的一部分),因此看起来我需要一个方法来执行“深度复制”。此外,TDXDataTypes.AgencyOutput的一些属性是其他类的数组,只是为了让事情更有趣......

我为映射创建了new question

4 个答案:

答案 0 :(得分:6)

我在我的本地机器上重现了这个问题并解决了这个问题..

您需要执行以下操作才能使自定义对象正常工作

Ur当前代码是这样的

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

让我试着解释问题的最可能原因。

当你在webservice中调用一个名为“methodname”的程序集中的方法时,你试图将args []所需的参数传递给函数“CallWebService” 当您尝试传递包括字符串等基本类型的常规参数时,传递的args []将成功运行。

但是当你尝试将自定义对象作为参数传递时,这就是你可能正在做的事情

在此完成了三件事。

  1. 在CallWebService函数之外创建该类型的对象(使用反射)。当你这样做时会发生什么是在内部用临时dll名称创建的customobject的实例。
  2. 一旦设置了对象的属性,并将其作为args数组中的对象发送到CallWebService函数。
  3. 您正在尝试通过创建动态网址来创建网络服务的实例。

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

  4. 当您最终尝试使用创建的动态程序集的实例调用该方法时  您正尝试通过args属性传递在步骤1,2中创建的自定义对象。

    在调用时,CLR会尝试查看作为输入传递的customobject和正在调用的方法是否来自同一个DLL。

    这显然不是执行方式。

    以下是应该用来克服这个问题的方法 您需要使用与创建Web服务实例相同的程序集创建自定义对象程序集...

    我完全实现了这种方法,结果很好:

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

答案 1 :(得分:3)

动态编译的类将不等于您直接引用的类,因此您无法将其转换为另一个。要使两个类相等,它们必须来自同一个程序集(或者你可以自己进行反序列化)。

我会考虑使用像AutoMapper这样的东西在两个类之间进行映射。您将设置从编译类型到引用类型的映射,然后映射类。

[编辑 - 编译的代码]

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

答案 2 :(得分:2)

对于传递自定义对象,一种方法是对自定义对象进行de /序列化。另请参阅How to: Enable a Web Service to Send and Receive Large Amounts of DataC# – Dynamically Invoke Web Service At Runtime

答案 3 :(得分:0)

我已经使用此处的代码(http://www.crowsprogramming.com/archives/66)来动态调用Web服务,并且能够通过将类型序列化为XML然后再返回来转换类型。在我的例子中,我试图访问的类型(T)是由WSDL.EXE生成的类文件,指向我正在动态调用的Web服务。

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