Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScriptSerializer circular reference when using ScriptIgnore

I have my Entity Framework Entities split out into a separate class library from my web project and data access layer. In my controller I make a call to my repository to get an IEnumerable<RobotDog.Entities.Movie> and then try to serialize into json using JavaScriptSerializer but I get a circular reference even though I'm using the [ScriptIgnore] attribute.

IMPORTANT: Originally I had my entities, data access and web all under one project and I was able to successfully serialize my entites without a circular reference. When I created separate layers that's when I started having problems. I did not change any of the entities.

An example of one of my entities in the RobotDog.Entities namespace:

namespace RobotDog.Entities {
    public class Character {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [MaxLength(200)]
        public string Name { get; set; }

        public virtual Person Person { get; set; }

        [ScriptIgnore]
        public virtual Movie Movie { get; set; }
    }
}

My controller:

namespace RobotDog.Web.Controllers {
    public class MoviesController : Controller {
        private UnitOfWork _unitOfWork = new UnitOfWork();

        [HttpGet]
        public ActionResult Index() {
            var user = Membership.GetUser(User.Identity.Name);
            if(user != null) {
                var movies = _unitOfWork.UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey).Select(m => m.Movie);
                var serializer = new JavaScriptSerializer();
                var json = serializer.Serialize(movies);
                return View(json);
            }
            return View();
        }

    }
}

My Repository:

namespace RobotDog.DataAccess.Movies {
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class {
        internal MovieContext Context;
        internal DbSet<TEntity> DbSet;

        public Repository(MovieContext context) {
            if (context == null)
                throw new ArgumentNullException("context");

            Context = context;
            DbSet = Context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null ) {
            IQueryable<TEntity> query = DbSet;

            if (predicate != null)
                query = query.Where(predicate);

            return orderBy != null ? orderBy(query).ToList() : query.ToList();
        }
    }
}
like image 735
bflemi3 Avatar asked Jan 28 '13 19:01

bflemi3


People also ask

What is the use of JavaScriptSerializer in C #?

The JavaScriptSerializer class is used internally by the asynchronous communication layer to serialize and deserialize the data that is passed between the browser and the Web server.

What is circular reference C#?

Circular reference occurs when two or more interdependent resources cause lock condition. This makes the resource unusable. To handle the problem of circular references in C#, you should use garbage collection. It detects and collects circular references.


2 Answers

Maybe kinda late response, but I had similar problem with POCO Classes for Entity Framework Code-Firts. The problem was that may properties were declared as virtual. In this case EF creates proxy class which overrides the virtual property. It seems that ScriptIgnore attribute is not by default applied on overriden properties, unless you use it like this:

[ScriptIgnore(ApplyToOverrides=true)]
like image 162
Đonny Avatar answered Oct 10 '22 07:10

Đonny


Circular object graphs cannot be JSON serialized. And when you give it a second thought it actually makes sense. The correct way to handle this is to use view models. You should never pass your domain entities directly to your views. Always define a view model containing only the necessary properties that you want to be exposed.

I am sure that the client consuming this JSON doesn't care about having this circular object graph. So simply define a view model breaking this circular dependency and including only the properties you need.

Then all you have to do is map your domain model to the view model and pass this view model to a JsonResult (yeah that's another issue in your code - you are manually JSON serializing and writing plumbing code in your controller action instead of delegating this to the framework).

So:

[HttpGet]
public ActionResult Index() 
{
    var user = Membership.GetUser(User.Identity.Name);
    if(user != null) 
    {
        IEnumerable<Movie> movies = _unitOfWork
            .UserMovieRepository.Get(u => u.UserId == (Guid) user.ProviderUserKey)
            .Select(m => m.Movie);
        IEnumerable<MovieViewModel> moviesVm = ... map the domain model to your view model
        return Json(moviesVm, JsonRequestBehavior.AllowGet);
    }

    // return an empty movies array
    var empty = Enumerable.Empty<MovieViewModel>();
    return Json(empty, JsonRequestBehavior.AllowGet);
}

The important thing you should be focusing right now on is defining the MovieViewModel class which will contain only the information that you want to expose to the client as JSON. Break all circular references. Feel free to have additional view models that this main view model is referencing in order to map other entities.

And most importantly : never pass your domain models to the view. Always define view models. This way your application is completely independent of the underlying data access technology you are using. You could modify your DAL layer as much as you like without impacting the UI part because this UI is represented by view models.

like image 40
Darin Dimitrov Avatar answered Oct 10 '22 06:10

Darin Dimitrov