Reputation: 46425
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
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.
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
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
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
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