Monday, September 24, 2018

A circular reference was detected while serializing an object of type when performing ajax call

Leave a Comment

On my view, I am using a Viewmodel, and I have a form that has only one textbox that accepts dates (not a part of the viewmodel) and 3 tables. By default on page load.. the tables are populated with data based on today's date (you can see that in the controller code below), but if a user selects a date and clicks the search button then I want the tables data to be changed without a page refresh based on the date they selected.

@using (Html.BeginForm()) {     <div class="form-group mb-3 mt-3" style="margin-right: -1.3%;">         <div class="input-group col-md-3 offset-md-9">             @Html.TextBox("detailsDate", null, new { id = "Details-Date", @class = "form-control datetimepicker" })             <div class="input-group-append">                 <button id="Details-Date-Btn" type="submit" class="btn btn-outline-primary"><span class="fa fa-search"></span></button>             </div>         </div>     </div> } 

What I am trying to do is if a user selects and date and hits the search button.. I would like the page to not refresh and the tables data have been changed based on the date. As of right now I am getting:

A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.tbl_WeighAssc_8AA7AB5F9DAB261D5142F1D5F5BA6705A588A5AAD2D369FBD4B4BC1BBE0487D4'.

Viewmodel

public class PersonnelDetailsVm {     private static ConnectionString db = new ConnectionString();     public PersonnelDetailsVm()     {         CurrentWeekDates = new List<DateTime>();         WeighAssociations = new List<tbl_WeighAssc>();         ArrestAssociations = new List<tbl_TEUArrestAssc>();         InspectionAssociations = new List<tblTEUInspectionAssc>();     }     public string IBM { get; set; }      [Display(Name = "Name")]     public string UserName { get; set; }      public bool Active { get; set; }      public List<DateTime> CurrentWeekDates { get; set; }     public List<tbl_WeighAssc> WeighAssociations { get; set; }     public List<tbl_TEUArrestAssc> ArrestAssociations { get; set; }     public List<tblTEUInspectionAssc> InspectionAssociations { get; set; }     public List<code_WeighLocation> WeighLocations => db.code_WeighLocation.ToList();     public List<code_ArrestType> ArrestTypes => db.code_ArrestType.ToList();     public List<code_InspectionLevel> InspectionLevels => db.code_InspectionLevel.ToList(); } 

Ajax:

// Submission //var redirectUrl = '@Url.Action("Index", "Personnels")'; var settings = {}; settings.baseUri = '@Request.ApplicationPath'; var infoGetUrl = ""; if (settings.baseUri === "/AppName") {     infoGetUrl = settings.baseUri + "/Personnels/Details/"; } else {     infoGetUrl = settings.baseUri + "Personnels/Details/"; }  $("#Details-Date-Btn").click(function() {     $.ajax({         url: infoGetUrl,         method: "POST",         data: $("form").serialize(),         success: function(response) {             console.log("success");             $("body").html(response);         },         error: function(jqXHR, textStatus, errorThrown) {             console.log(jqXHR);         }     }); }); 

Controller:

public ActionResult Details(string id, string detailsDate) {     if (id == null)     {         return new HttpStatusCodeResult(HttpStatusCode.BadRequest);     }      tblPersonnel tblPersonnel = db.tblPersonnels.Find(id);      if (tblPersonnel == null)     {         return HttpNotFound();     }      Mapper.Initialize(config => config.CreateMap<tblPersonnel, PersonnelDetailsVm>());     PersonnelDetailsVm person = Mapper.Map<tblPersonnel, PersonnelDetailsVm>(tblPersonnel);      var employeeData = EmployeeData.GetEmployee(person.IBM);      person.UserName =         $"{ConvertRankAbbr.Conversion(employeeData.Rank_Position)} {employeeData.FirstName} {employeeData.LastName}";      if (string.IsNullOrWhiteSpace(detailsDate))     {         var startOfWeek = DateTime.Today.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                                  (int)DateTime.Today.DayOfWeek);         person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();         var teuFormIds = db.tbl_TEUForm             .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();          person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.InspectionAssociations =             db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();           return View(person);      }     else     {         var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);          var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                                  (int)paramDate.DayOfWeek);         person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();         var teuFormIds = db.tbl_TEUForm             .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();          person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.InspectionAssociations =             db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();          return Json(person, JsonRequestBehavior.AllowGet);     }  } 

So, if the actionresult's paramets detailsDate is not null, then it goes into the else statement which returns a JSON object. When debugging this goes through and when the view is returned I am receiving the error I posted above.

Is there a way to replace the model in the view with what I'm returning from the ajax call so the tables can be based on the right date without a page refresh?

Any help is greatly appreciated.

UPDATE

Based on answer's below I have edited the else statement in my controller method to:

Controller

else {     var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);      var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                              (int)paramDate.DayOfWeek);     person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();     var teuFormIds = db.tbl_TEUForm         .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();      person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();     person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();     person.InspectionAssociations =         db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();      JsonConvert.DefaultSettings = () => new JsonSerializerSettings()     {         PreserveReferencesHandling = PreserveReferencesHandling.All,         ReferenceLoopHandling = ReferenceLoopHandling.Ignore     };      var jsonStr = JsonConvert.SerializeObject(person);      return Json(jsonStr, "text/plain"); } 

My jQuery/Ajax is still the same:

$("#Details-Date-Btn").click(function() {     $.ajax({         url: infoGetUrl,         data: $("form").serialize(),         success: function(response) {             console.log("success");             console.log(response);             $("body").html(response);         },         error: function(jqXHR, textStatus, errorThrown) {             console.log(jqXHR);         }     }); }); 

But now, when the date is selected I am being returned to a page that shows the Json like a plain text file and losing the HTML and CSS like a normal view.

Here is what I am being returned when a date is selected and the button is clicked.

enter image description here

Also, when I check the console when I select a date and click the button for that date to be sent to the controller I am seeing this:

enter image description here

UPDATE 2

Here is one of my tables.. the other ones are the same setup:

<table class="table table-bordered">     <thead>         <tr>             <th></th>             @foreach (var date in Model.CurrentWeekDates)             {                 <th class="text-center">@date.ToString("ddd") <br /> @date.ToShortDateString()</th>             }                 <th class="text-center table-success">Total For Week</th>         </tr>     </thead>     <tbody>         @foreach (var weighLocation in Model.WeighLocations)         {             <tr class="text-center">                 <td class="table-dark">@weighLocation.Weigh_Location</td>                 @foreach (var date in Model.CurrentWeekDates)                 {                     if (Model.WeighAssociations.Any(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID))                     {                         <td>@Model.WeighAssociations.Single(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID).OccurenceCount</td>                     }                     else                     {                         <td>0</td>                     }                  }                 <td class="table-success font-weight-bold">@Model.WeighAssociations.Where(x => x.WeighLocationId == weighLocation.ID).Sum(x => x.OccurenceCount)</td>             </tr>         }     </tbody> </table> 

6 Answers

Answers 1

As far as I can see from your problem, to fix it you can do following steps:

1- View

Add a partial view (_Detail.cshtml)

You need a partial view like _Detail that includes your table like this:

@model PersonnelDetailsVm    <table class="table table-bordered">     <thead>         <tr>             <th></th>             @foreach (var date in Model.CurrentWeekDates)             {                 <th class="text-center">@date.ToString("ddd") <br /> @date.ToShortDateString()</th>             }                 <th class="text-center table-success">Total For Week</th>         </tr>     </thead>     <tbody>         @foreach (var weighLocation in Model.WeighLocations)         {             <tr class="text-center">                 <td class="table-dark">@weighLocation.Weigh_Location</td>                 @foreach (var date in Model.CurrentWeekDates)                 {                     if (Model.WeighAssociations.Any(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID))                     {                         <td>@Model.WeighAssociations.Single(x => x.tbl_TEUForm.EventDate == date && x.WeighLocationId == weighLocation.ID).OccurenceCount</td>                     }                     else                     {                         <td>0</td>                     }                  }                 <td class="table-success font-weight-bold">@Model.WeighAssociations.Where(x => x.WeighLocationId == weighLocation.ID).Sum(x => x.OccurenceCount)</td>             </tr>         }     </tbody> </table> 

2- Controller

Return the partial view

You should fill the model in your controller and pass it to the partial view.

public ActionResult Details(string id, string detailsDate) {     if (id == null)     {         return new HttpStatusCodeResult(HttpStatusCode.BadRequest);     }      tblPersonnel tblPersonnel = db.tblPersonnels.Find(id);      if (tblPersonnel == null)     {         return HttpNotFound();     }      Mapper.Initialize(config => config.CreateMap<tblPersonnel, PersonnelDetailsVm>());     PersonnelDetailsVm person = Mapper.Map<tblPersonnel, PersonnelDetailsVm>(tblPersonnel);      var employeeData = EmployeeData.GetEmployee(person.IBM);      person.UserName =         $"{ConvertRankAbbr.Conversion(employeeData.Rank_Position)} {employeeData.FirstName} {employeeData.LastName}";      if (string.IsNullOrWhiteSpace(detailsDate))     {         var startOfWeek = DateTime.Today.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                                  (int)DateTime.Today.DayOfWeek);         person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();         var teuFormIds = db.tbl_TEUForm             .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();          person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.InspectionAssociations =             db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();           // return View(person);         }     else     {         var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);          var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                                  (int)paramDate.DayOfWeek);         person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList();         var teuFormIds = db.tbl_TEUForm             .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();          person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList();         person.InspectionAssociations =             db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();          // return Json(person, JsonRequestBehavior.AllowGet);     }      // return PartialView with the person model     return PartialView("_Detail", person);  } 

As you can see in the above code, you should comment the two lines in your code:

// return View(person);  // return Json(person, JsonRequestBehavior.AllowGet); 

3- Ajax call

Get the partial view and fill form by it

You don't any changes in ajax call and you can do it like this:

$("#Details-Date-Btn").click(function() {     $.ajax({         url: infoGetUrl,         method: "POST",         data: $("form").serialize(),         success: function(response) {             console.log("success");             $("body").html(response);         },         error: function(jqXHR, textStatus, errorThrown) {             console.log(jqXHR);         }     }); }); 

response in this way is a html that comes from partial view and it has all classes as you designed it.

Answers 2

That error message means that one of your child properties refers back to the parent and JSON serialization causes a circular loop.

To fix, replace this:

return Json(person, JsonRequestBehavior.AllowGet); 

with this:

return Content(JsonConvert.SerializeObject(person,                 new JsonSerializerSettings                 {                     ReferenceLoopHandling = ReferenceLoopHandling.Ignore                 }), "application/json"); 

You will have to install NewtonSoft.Json:

using Newtonsoft.Json; 

Answers 3

A circular reference was detected while serializing an object of type occurred because JSON serializer doesn't support circular references inside your object hierarchy (i.e. passing PersonnelDetailsVm that contains references to data models). To resolve this issue, use JSON.NET's JsonConvert.SerializeObject() with default settings set like this:

JsonConvert.DefaultSettings = () => new JsonSerializerSettings {     PreserveReferencesHandling = PreserveReferencesHandling.All,     ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; 

Afterwards, you can return JsonResult from viewmodel:

string jsonStr = JsonConvert.SerializeObject(person);  return Json(jsonStr); 

If you're using IE and encountering save dialog because of friendly JSON errors configuration, you may need to add text/html or text/plain when returning JSON data:

return Json(jsonStr, "text/html"); 

Or hide the Json() method inside controller class like this:

protected new JsonResult Json(object data) {     if (!Request.AcceptTypes.Contains("application/json"))         return base.Json(data, "text/plain");     else         return base.Json(data); } 

Additionally, instead of return View(person); you may consider return PartialView("Details", person); because AJAX call intended to stay on same page.

Answers 4

Setting ReferenceLoopHandling = ReferenceLoopHandling.Ignore will handle your issue related to circular reference exception.

Now to about issue(s) you presented in your updates, I think you might have some misunderstanding about how Ajax request work. I think this because when you make an ajax request to server it 'll respond with JSON data which will be representation of your view model and will be agnostic to your view (cshtml) code, so when you call $("body").html(response); you are replacing content of your page with stringified representation of your JSON view model. Take away here is that when you make an ajax request only back-end code will get executed and your view code(cshtml) will not be executed, period.

To solve your you will have to replace content of the table itself and not the body of the page, so you Ajax success callback should be something like:

var tempData = {  "IBM": "IBM",  "UserName": "UserName",  "Active": false,  "CurrentWeekDates": [],   "WeighAssociations": [],  "ArrestAssociations": [],  "InspectionAssociations": [],  "WeighLocations": [],  "ArrestTypes": [],  "InspectionLevels": []  };    function onSuccess(response){  	var table = $("#tblData");    table.html("");        // code to create your head same code as your cshtml  	table.append("<thead><th>New Column</th></thead>");    table.append("<tr><td>Column 1</td></tr>");        $("#btnLoad").text("Loaded");  }    $("#btnLoad").click(function(){    $("#btnLoad").attr("disabled", "");    $("#btnLoad").text("Loading...");    // call onSuccess function after 5 second to replicate server call    setTimeout(function(){ onSuccess(tempData) }, 5000);  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>    <button id="btnLoad">Load Data</button>  <table id="tblData" class="table table-bordered">      <thead>          <tr>              <th></th>              <th class="text-center">Mon<br /> 01-01-1990</th>              <th class="text-center table-success">Total For Week</th>          </tr>      </thead>      <tbody>        <tr class="text-center">          <td class="table-dark">Column 1</td>          <td>Columns 2</td>          <td>0</td>          <td class="table-success font-weight-bold">0</td>        </tr>      </tbody>  </table>

I hope this helps you!

Answers 5

You have 2 different return methods and you're setting the body content to the response of the request, which if the else statement is run will be JSON and not html.

I would suggest to either always return JSON or always return a View/Partial.

else { var paramDate = DateTime.ParseExact(detailsDate, "MM/dd/yyyy", CultureInfo.CurrentCulture);  var startOfWeek = paramDate.AddDays((int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek -                                          (int)paramDate.DayOfWeek); person.CurrentWeekDates = Enumerable.Range(0, 7).Select(i => startOfWeek.AddDays(i)).ToList(); var teuFormIds = db.tbl_TEUForm     .Where(x => person.CurrentWeekDates.Contains(x.EventDate) && x.PersonnelIBM == person.IBM).Select(t => t.Id).ToList();  person.WeighAssociations = db.tbl_WeighAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList(); person.ArrestAssociations = db.tbl_TEUArrestAssc.Where(x => teuFormIds.Contains(x.TEUId)).ToList(); person.InspectionAssociations =     db.tblTEUInspectionAsscs.Where(x => teuFormIds.Contains(x.TEUId)).ToList();  JsonConvert.DefaultSettings = () => new JsonSerializerSettings() {     PreserveReferencesHandling = PreserveReferencesHandling.All,     ReferenceLoopHandling = ReferenceLoopHandling.Ignore };  var jsonStr = JsonConvert.SerializeObject(person);  //return Json(jsonStr, "text/plain"); return Partial(person); } 

Answers 6

I would suggest the below steps

  1. One partial view for filters which include DateTime and Submit button(FilterPartial)
  2. One Partial view for tables to be rendered(ResultsPartial)

When the body is loaded then load the first partial view(let's call FilterPartial) which will set the values in the ResultsPartial view.

function GetData(params) { $.ajax({     type: "POST",     url: '/controller/Action',     data: {         //Parameters     },     dataType: 'html',     success: function (data) {         //You can either load the html directly or render the control here      } }); 

}

  public PartialViewResult Action()     {        return PartialView("");     } 

The ViewModel logic can be kept as it is, you have to segregate the views. Let me know if this helps...

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment