Вызовите веб-сервис без добавления веб-ссылки - со сложными типами - PullRequest
1 голос
/ 12 апреля 2010

Я использую код Этот сайт для динамического вызова веб-службы.

[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 являются массивами других классов, просто чтобы сделать вещи более увлекательными ...

Я создал новый вопрос для сопоставления.

Ответы [ 4 ]

6 голосов
/ 14 апреля 2010

Я воспроизвел проблему на своем локальном компьютере и исправил эту проблему.

Вот что вам нужно сделать, чтобы пользовательский объект работал

Ваш текущий код выглядит так

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

Позвольте мне попытаться объяснить наиболее вероятную причину возникновения проблемы.

когда вы вызываете метод в сборке, который называется "methodname" в веб-сервисе, вы пытаетесь передать параметры, необходимые для этого как args [], в функцию "CallWebService" Этот аргумент [] при передаче будет успешно работать, когда вы попытаетесь передать нормальные параметры, такие как примитивные типы, включая строку.

Но это то, что вы можете делать, когда пытаетесь передать пользовательский объект в качестве параметра

Три вещи, которые сделаны в этом.

  1. создать объект этого типа вне функции CallWebService (используя отражение). когда вы делаете так, то происходит экземпляр пользовательского объекта, созданного с временным именем dll внутри.
  2. как только вы установите набор свойств объекта и отправите его в функцию CallWebService как объект в массиве args.
  3. Вы пытаетесь создать экземпляр веб-сервиса, создавая динамическую DLL.

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

Когда вы наконец попытаетесь вызвать метод с экземпляром динамической сборки, созданной вы пытаетесь передать свой пользовательский объект, созданный на шаге 1,2, через свойство args.

Во время вызова CLR пытается определить, принадлежат ли пользовательский объект, который передается в качестве входных данных, и вызываемый метод из одной и той же DLL.

Что явно не от способа реализации.

Итак, следующий подход должен быть использован для преодоления проблемы. Вам нужно создать пользовательскую сборку объекта с той же сборкой, которую вы использовали для создания экземпляра веб-сервиса ...

Я полностью реализовал этот подход, и он отлично работал:

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;
}
3 голосов
/ 12 апреля 2010

Класс, который вы динамически компилируете, не будет равен тому, на который вы ссылаетесь напрямую, и, следовательно, вы не можете приводить один к другому. Чтобы два класса были равны, они должны быть из одной сборки (или вы можете свернуть свою собственную десериализацию).

Я бы хотел использовать что-то вроде 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 голосов
/ 12 апреля 2010

Для передачи пользовательских объектов одним из способов может быть де / сериализация вашего пользовательского объекта. См. Также Как: разрешить веб-службе отправлять и получать большие объемы данных и C # - динамически вызывать веб-службу во время выполнения

0 голосов
/ 10 ноября 2011

Я использовал здесь код (http://www.crowsprogramming.com/archives/66) для динамического вызова веб-службы и смог преобразовать тип, сериализовав его в XML, а затем снова обратно. В моем случае тип, который я пытаюсьчтобы добраться до (T) находится в файле класса, сгенерированном WSDL.EXE, указывающим на веб-сервис, который я вызываю динамически.

    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);
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...