Thursday, August 16, 2007

InvalidOperationException: A circular reference was detected while serializing an object of type ...

You are trying to use a WebMethod or a web service ScriptMethod in Javascript and you get an InvalidOperationException saying something about a circular reference. It happened to me when trying to read a DataTable in Javascript.

Why. The Javascript serialization of DataSets, DataTables and DataRows was available once in the ASP.Net Ajax web extensions. That's why you probably found a lot of Google results with people that could either only serialize DataSets, but not DataTables, or people that made it work by magic by adding some lines in the converters section of web.config, things that can't possibly work with your setup. Then, the option was removed in the final version of ASP.Net Ajax, only to be readded in the ASP.Net Futures, which is a test playground for future features of the platform.

What. There are several options, one being to reference an older version of ASP.Net Ajax and uses the converters there. But why bother? It's unlikely you use the DataTable or some other object in Javascript with all the options of the C# object. You probably just want to itterate through rows and read properties. So build your own converter.

How. Create a new library project. Add a class named DataTableConverter that inherits from JavaScriptConverter, and implement: IEnumerable<Type> SupportedTypes, IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) and object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer).

You probably won't need to Deserialize anything, you can leave that unimplemented. The list of convertible types is easy enough, all you are left with is the Serialize code, which is actually very easy, too. Then all you need to do is add this in the web.config file:
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization >
<converters>
<add name="DataTableAjaxFix" type="AjaxTypeConverters.DataTableConverter"/>
</converters>
</jsonSerialization>


And here is the complete C# code of my DataTableConverter, but you can easily adapt it to anything:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Web.Script.Serialization;

namespace AjaxTypeConverters
{
    public class DataTableConverter : JavaScriptConverter
    {
        public override IEnumerable<Type> SupportedTypes
        {
            get { return new Type[] {typeof (DataTable)}; }
        }

        public override object Deserialize(IDictionary<string, object> dictionary, Type type,JavaScriptSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            DataTable listType = obj as DataTable;

            if (listType != null)
            {
                // Create the representation.
                Dictionary<string, object> result = new Dictionary<string, object>();
                ArrayList itemsList = new ArrayList();
                foreach (DataRow row in listType.Rows)
                {
                    //Add each entry to the dictionary.
                    Dictionary<string, object> listDict = new Dictionary<string, object>();
                    foreach (DataColumn dc in listType.Columns)
                    {
                        listDict.Add(dc.ColumnName, row[dc]);
                    }
                    itemsList.Add(listDict);
                }
                result["Rows"] = itemsList;

                return result;
            }
            return new Dictionary<string, object>();
        }
    }
}

11 comments:

Anonymous said...

Dude you rock!! Thanks for the post. This is a much better solution than using beta microsoft software.

Anonymous said...

When I use a Page method to return a datatable/dataset, will the inclusion of the web.config code above make the type conversion in my form transparent? In other words, after I add this code, do I have to do anything within my form?

Siderite said...

I don't think so, no.

Anonymous said...

How will my form know to use the DataTableAjaxFix converter then?

Does it look at the SupportedTypes property to find matching types?

What happens if I have multiple converters using the same type?

Thanks for your response

(just a newbie trying to understand :) )

Siderite said...

For each data type that the server is trying to move to the client as a Javascript object, it uses a converter. The web.config bit in the post is declaring my converter as a converter and the SupportedTypes property declares it as one that supports datatables.

Otherwise the server will try to use the general serializer which fails in the case of the DataTable object since it references itself somewhere.

I guess the process is a cycling through all the converters in a list and inquiring if the datatype of the object is supported. After it exausts the list, it changes the searched type to the base type of the object it tries to serialize, then cycles through the list again.

The code itself transforms the DataTable into a dictionary<property,value> holding at the Rows key an ArrayList of Dictionary<columnName,value> which represents the rows.

In Javascript you will be able to use table.Rows[12]["FirstName"] or table.Rows[15][2].

Jeremy said...

I know this post is old. But thank you! You saved me a lot of headaches. (The fly is cool too :))

Anonymous said...

Hi... Had some problems, would just post a note. When I get the callback, I must use:

function OptainGeneralText_CallBack(res, context, methodName)
{
var table = res["Rows"][0];
var id = table["Id"] ;
}

Im using .Net 3.5.

Good job on the article. Not only did it make it work, I learned something new ;)

Siderite said...

Well, I did test it on Net 2.0. Maybe they changed something in Net 3.5. Didn't they also add a reasonable serializer, though? I will soon start working on a Net 3.5 site (I know, I am so late) and maybe test stuff like this then.

schmakt said...

thank you for this... I was unable to implement your exact solution, but you got me thinking in a way that let me fix my problem...

Ended up converting my datasets to List|List|string|| objects which, when sent back to javascript, show up as 2d arrays.

Not pretty, but it works. Thanks!

Anonymous said...

Hi. I'm having this same issue, but the solution seems to be out of date.

using System.Web.Script.Serialization;

doesn't work as there is no Script in Web. Does anyone know what replaced System.Web.Script?

Thanks,
Bill

Anonymous said...

Hi Bill.... this is Bill.... since I figured out my issue I thought I'd post it for anyone else having a rough day.

Added reference to System.Web.Extensions;

Problem went away.

Thanks,
Bill