I'm working on doing some ColdFusion 10 integration with a custom .NET DLL that I don't have the ability to tweak. During this process, I've been able to do everything that I need to do other than create an IEnumerable
data type to pass to one of the methods of the object. Here's what I need to integration with:
It's the Set_Events
method that I'm having the trouble with. I can create the individual Events that are supposed to be part of that enum variable, but I'm unable to create what it apparently is expecting as the proper variable type. I've tried doing this:
<cfset enum = createObject(".net", "System.Collections.Generic.IEnumerable__1") />
And this gives me a valid .NET object, with a GetEnumerator()
method:
When I attempt to call that method:
<cfdump var="#enum.GetEnumerator()#">
That just gives me the following error:
The GetEnumerator method was not found.
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset eventList.Add(javacast("bigdecimal", "30.1234" )) />
That gives me the following error:
An exception occurred while instantiating a Java object. The class must not be an interface or an abstract class. If the class has a constructor that accepts an argument, you must call the constructor explicitly using the init(args) method. Error : System.Collections.Generic.List__1
And this gives me the same error again:
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset eventList.Add("foo") />
This code from Leigh works:
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset elemClass = createObject(".net", "System.String", "dotNetCoreProxy.jar") />
<cfset elemType = elemClass.getDotNetClass() />
<cfset eventList.init( elemType ) />
<cfset eventList.Add("foo") />
<cfdump var="#eventList#">
But this doesn't:
<cfobject type="dotnet" name="VideoWallEvent" class="Utilities.VideoWall.VideoWallEvent" assembly="#ExpandPath("Utilities.dll")#">
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset elemType = VideoWallEvent.getDotNetClass() />
<cfset eventList.init( elemType ) />
<cfdump var="#eventList#">
I get the following error:
Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).
This has been completely useless. I can browse to my Utilities.dll file, and add the System.Collections assembly from GAC, but the Build option in the Project menu is always disabled, and nothing shows in any of the panes once those assemblies have been added.
I've run all of the following code on one page:
<cfset UtilitiesProxy = ExpandPath("UtilitiesProxy.jar") />
<cfset DotNetCoreProxy = "dotNetCoreProxy.jar" />
<cfset CoStarUtilities = ExpandPath("CoStar.Utilities.dll") />
<h2>Try using base DLLs and DotNetCore</h2>
<cftry>
<cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
<cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
<cfcatch>
<h3><cfoutput>#cfcatch.type#</cfoutput></h3>
<p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
</cfcatch>
</cftry>
<h2>Try using the Utilities Proxy for Everything</h2>
<cftry>
<cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#UtilitiesProxy#">
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", UtilitiesProxy) /> (error line)
<cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#">
<cfcatch>
<h3><cfoutput>#cfcatch.type#</cfoutput></h3>
<p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
</cfcatch>
</cftry>
<h2>Try using Utilities Proxy for VideoWall, and DotNetCore for List</h2>
<cftry>
<cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#UtilitiesProxy#">
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
<cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
<cfcatch>
<h3><cfoutput>#cfcatch.type#</cfoutput></h3>
<p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
</cfcatch>
</cftry>
<h2>Try Initing Wall Event</h2>
<cftry>
<cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
<cfset VideoWallEvent.Init() />
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
<cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
<cfcatch>
<h3><cfoutput>#cfcatch.type#</cfoutput></h3>
<p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
</cfcatch>
</cftry>
<h2>Try Initing Wall Event</h2>
<cftry>
<cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
<cfset VideoWallEvent.Init() />
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
<cfdump var="#eventList.init(VideoWallEvent)#"> (error line)
<cfcatch>
<h3><cfoutput>#cfcatch.type#</cfoutput></h3>
<p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
</cfcatch>
</cftry>
And I get the following error output (hint, every single one of these fails).
09:22:45.045 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 9
Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).
09:22:48.048 - coldfusion.runtime.dotnet.ProxyGenerationException - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 19
09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 31
Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).
09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 43
Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).
09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 55
Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( CoStar.Utilities.VideoWall.VideoWallEvent ).
I've generated a proxy with (what I think is) all of the items that I need.
So now I'm stuck. I can instantiate and fill all of the necessary objects to make this thing work, I just can't push all of the objects together to make them work. What am I missing when trying to create the ENum object necessary to fill the Set_Events method?
As mentioned, you need to use a concrete class (not the interface). However, looking at the method signature, it must be one that implements System.Collections.Generic.IEnumerable, and not System.Collections.IEnumerable. The latter is only for non-generic collections. One example of a generic collection you could use is List<T>
, where <T>
is the type of object the list holds. (You will need to review your API to determine the object type).
Unfortunately ... there is an issue with Generic classes. Essentially the tool used by createObject
cannot generate proxies for Generics. (The blog entry says it was resolved in CF9, but in my tests I encountered the same problem with 9.0.1 - YMMV.) So you need to do it yourself using the JNBProxyGUI.exe. Frankly it is not the most intuitive tool .. but after a little wrangling I managed to get it working under CF9. Fortunately it is only a one time event.
After exporting the generated proxies to a jar file, use the example in the blog entry to create a generic List
. Just add the proxy jar to the assembly list. Say the Collection stores simple Strings:
// create a generic list of strings ie new List<String>()
path = "c:/path/yourGenericsProxy.jar,c:/path/yourApplication.dll";
list = createObject(".net", "System.Collections.Generic.List__1", path);
elemClass = createObject(".net", "System.String", path);
elemType = elemClass.getDotNetClass();
list.init( elemType );
Once the List
is initialized, you can add a few elements to it:
list.add( "foo" );
list.add( "bar" );
Then finally invoke GetEnumerator() and pass it into your method Then pass the List
into your method:
// wrong: yourObject.Set_Events( list );
yourObject.Set_Events( list );
(If you have any questions about generating the proxies, just ask.)
UPDATE:
As Dan pointed out, his method is using IEnumerable
not IEnumerator. So the List
itself should be passed into set_Event
, not Get_Enumerator()
(as in the previous example). Also, when you invoke createObject
, the assemblyList MUST include both the proxy jar AND the dll file. Otherwise the method call(s) may fail. Turns out that was the cause of the later problems.
Below is a corrected version that works with CF10.
Regenerated the proxy jar
Create new Java -> .NET Project
Project > Edit Assembly List > Add
. Locate and select "mscorlib.dll" Project > Add Classes from Assembly File
. Locate and select "mscorlib.dll"
(The gui then generates a list of classes. It might take a while).System.Collections.Generic
package and click Add
. Edit > Check All in Exposed Proxies
Project > Build
and select the path and file name for the new proxy jarCleanup:
Just to be safe, I stopped CF and removed all generated proxy jars from WEB-INF\cfclasses\dotNetProxy
except dotNetCoreProxy.jar. Then restarted CF and ran the code. It worked perfectly. String. (See complete code below).
MyClass.cs
using System;
using System.Text;
using System.Collections.Generic;
namespace MyLibrary
{
public class MyClass {
private IEnumerable<CustomClass> events;
public void set_Events(IEnumerable<CustomClass> evts)
{
this.events = evts;
}
public IEnumerable<CustomClass> get_Events()
{
return this.events;
}
}
}
CustomClass.cs
using System;
using System.Text;
using System.Collections.Generic;
namespace MyLibrary
{
public class CustomClass
{
private string title;
public CustomClass(string title)
{
this.title = title;
}
public string getTitle()
{
return this.title;
}
public override string ToString()
{
return getTitle();
}
}
}
CF Code:
<cfscript>
// MUST include both proxy jar and DLL file
path = arrayToList([ExpandPath("./MyLibrary.dll"), ExpandPath("genericListAndEnumerator.jar")]);
//initialize custom class
customObj = createObject(".net", "MyLibrary.CustomClass", path).init("Blah, blah");
elemType = customObj.getDotNetClass();
// create generic list of custom class
list = CreateObject(".net","System.Collections.Generic.List__1", path);
list.init( elemType );
list.add( customObj );
// test setter
mainObj = createObject(".net", "MyLibrary.MyClass", path);
mainObj.set_Events( list );
// test getter
enum = mainObj.get_Events().getEnumerator();
writeDump(enum);
while (enum.MoveNext()) {
WriteDump("Current="& enum.Get_Current().toString());
}
</cfscript>
The problem is that you're trying to call a method of an interface. Instead, you need to create an instance of a class that implements that interface in order to pass that to Set_Events()
. Here is a list of .NET classes which implement IEnumerable: IEnumerable Interface.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With