Yesterday I wrote a web method to return reported problems:
<WebService(Namespace:="http://maggie/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ScriptService()> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class _Raphael
Inherits System.Web.Services.WebService
<WebMethod()> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json, UseHttpGet:=True)> _
Public Function GetReportedProblems(ByVal pathname As String) As Hashtable
When I try to use the web method I get the following error:
The type System.Collections.Hashtable is not supported because it implements IDictionary.
A quick google search made me realize that I'm apparently not supposed to be able to get a HashTable
serialized by .Net without me having to do some work.
My confusion stems from the fact that I usually use HashTable
objects as a return from my web methods (mostly because it's pretty close to a JavaScript object, and pretty convenient).
An example of a web method I wrote returning a HashTable
(which is working perfectly fine):
<ScriptService()> _
Partial Class _Lugash_Gopher
Inherits ppcBasePage 'which inherits System.Web.UI.Page
<WebMethod()> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
Public Shared Function GetAgentsPaged(ByVal iDisplayStart As Integer, ByVal iDisplayLength As Integer, ByVal sEcho As Integer, ByVal TeamID As Integer) As Hashtable
I populate the HashTable
(h
) like so:
h.Add("TeamID", TeamID) 'Integer
h.Add("sEcho", sEcho) 'Integer (blame DataTables.Net for the "s" prefix)
h.Add("iTotalRecords", iTotalRecords) 'Integer
h.Add("iTotalDisplayRecords", iTotalDisplayRecords) 'Integer
h.Add("aaData", Helpers.ConvertDataTableToJSON(ds.Tables(1))) 'Method returns System.Array
which returns (example):
{
"d": {
"iTotalRecords": 2,
"iTotalDisplayRecords": 10,
"team_name": "All Teams",
"aaData": [{
"next_due_date": "01/01/2014",
"ready_feedback": 0,
"emp_name": "John Doe",
"row": 1,
"incomplete_feedback": 1,
"emp_id": 1
}, {
"next_due_date": "01/14/2014",
"ready_feedback": 0,
"emp_name": "Jane Smith",
"row": 2,
"incomplete_feedback": 1,
"emp_id": 2
}],
"sEcho": 1,
"TeamID": 1
}
}
The method GetReportedProblems
is housed in a .asmx
code-behind and allows for a GET
.
The method GetAgentsPaged
is housed in a .aspx
code-behind and requires POST
.
These are the only two differences that I can determine in the method structures.
Why am I able to use a HashTable
as a return type for a web method in a .aspx
code-behind, but not in a a .asmx
code-behind?
Best How To :
I found the answer to your problem here in this fórum. But part of that answer you already found out by your self, from what you describe.
So the next big question you ask is, so how come, that a simple ASPX can do the job that an ASMX can't? The answer for this is because ASMX is XML based mainly, all the communication with ASMX is based on SOAP. For it to work, with ASP.NET via ScriptManager (which I believe is what you are doing) Microsoft cleverly developed and Handler:
(...) To enable Web service calls from script, you must register the ScriptHandlerFactory HTTP handler in the application's Web.config file. The handler processes calls made from script to .asmx Web services. The following example shows the Web.config element for adding the handler.(...)
But for the rest of the time, the web service is still XML based, like is supposed to:
(...) For Web service calls that are not issued from ASP.NET AJAX script, the ScriptHandlerFactory handler delegates the call to the default handler, which uses SOAP instead of JSON format. The delegation is performed automatically and you do not have to take any action unless you want to disable the use of the SOAP protocol for the Web services. In that case, you must enter the following configuration setting in the Web.config file. (...)
(...) To enable an .asmx Web service to be called from client script in an ASP.NET Web page, you must add a ScriptManager control to the page. You reference the Web service by adding an asp:ServiceReference child element to the ScriptManager control and then setting the server reference path attribute to the URL of the Web service. The ServiceReference object instructs ASP.NET to generate a JavaScript proxy class for calling the specified Web service from client script. (...)
Note: this information was extracted from here.
Continuing, so why do you receive that error in ASMX Web Service? Well you receive that error because the compiler is telling you this "Hey, when someone calls me (xml web service), most of the times I will probably respond in XML and Hashtables which implement IDictionary are not convertible to XML!", if you had some way to tell the compiler "Hey, but I promise to call you (xml ws) only from an ASP.NET page via ScriptManager which talks with you via a Proxy implemented by an ASP.NET Handler" then your code would work, but ASMX WebService doesn't allow you to perform such kind of configuration!
And that's why XML WebServices (asmx) trows that error, and ASP.NET do not, because ASP.NET does not respond in XML, it responds in what you want it to respond, since the developer can arbitrarily decide whether he want to send responses JSON, HTML, XML, Text or just binary based.
If I were you, I would take a little step forward and I would try to specify that your Web Method outputs XML (in your ASP.NET Page), to see if it throws you the same error, I haven't tried this, but most certainly that is what will happen.
Update 1
Note that the tag ScriptService does not specify inheritance, it is just an annotation, probably that is how the ScriptHandler detects a call to an XML WebService that it should intercept! The nature of your class is defined by its inheritance which is based on the WebService class (specific to XML WebServices). And so the preceding annotations of this type 'ScriptMethod' are also specific to the ScriptHandler as they specify what should it do to convert the answer of the Web Service in order to send it to your ScriptManager registered in your ASP.NET page!
If you feel that Web Services should do more for you I advise you strongly to look at WCF Services aka Indigo Services (Codename), you can find more info about this here. After you try this, you will never use XML WebServices again :), it has a little learning curve in the configuration part but after that is pure quality of work! A good book for you to read about WCF is this WCF by Jonh Sharp, in my personal opinion of course. And btw, very good and pertinent question!
Update 2
Stating a similar response to mine here, it says:
(...) The service exposed to JavaScript via [ScriptMethod] uses a different serializer (the JavaScriptSerializer), which does not have this limitation.
When you call the service from JavaScript, you invoke the JSON endpoint (declared with [ScriptMethod]). When you test from the browser, however, you reach the traditional XML enpoint (declared with [WebMethod]). (...)
An workaround would be for you to use JavaScriptSerializer to serialize the Hashtable to JSON (inside the web method), then your web method would return a String, but this is kind of... bad! And you would be probably better served with WCF Services if you intent to build something both maintainable and scalable.
Update 3
To better direct my answer to your comment bellow, yes, you can't serialize Hashtables because "XmlSerializer cannot process classes implementing the IDictionary interface" (XML WebServices at some instance, end up using the XML Serializer, and that's when the exception is thrown from the bottom of the stack, until it reaches your code):
(...) Q: Why can't I serialize hashtables? A: The XmlSerializer cannot process classes implementing the IDictionary interface. This was partly due to schedule constraints and partly due to the fact that a hashtable does not have a counterpart in the XSD type system. The only solution is to implement a custom hashtable that does not implement the IDictionary interface. (...)
This excerpt was taken from this Microsoft Resource here, it's near the bottom of the page.