При сериализации объекта типа при выполнении вызова ajax обнаружена циклическая ссылка - PullRequest
0 голосов
/ 13 сентября 2018

На мой взгляд, я использую Viewmodel, и у меня есть форма, которая имеет только одно текстовое поле, которое принимает даты (не является частью viewmodel) и 3 таблицы. По умолчанию при загрузке страницы ... таблицы заполняются данными, основанными на сегодняшней дате (вы можете увидеть это в приведенном ниже коде контроллера), но если пользователь выбирает дату и нажимает кнопку поиска, то я хочу, чтобы данные таблиц были изменены без обновления страницы на основе выбранной даты.

@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>
}

То, что я пытаюсь сделать, - это если пользователь выбирает дату и нажимает кнопку поиска. Я хотел бы, чтобы страница не обновлялась, а данные таблиц были изменены на основе даты. На данный момент я получаю:

Обнаружена циклическая ссылка при сериализации объекта типа '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);
        }
    });
});

Контроллер:

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);
    }

}

Итак, если параметры actionresult detailsDate не равны NULL, то он входит в оператор else, который возвращает объект JSON. При отладке это происходит, и когда возвращается представление, я получаю сообщение об ошибке, которое я разместил выше.

Есть ли способ заменить модель в представлении на то, что я возвращаю из вызова ajax, чтобы таблицы могли основываться на правильной дате без обновления страницы?

Любая помощь очень ценится.

UPDATE

Основываясь на ответе ниже, я отредактировал оператор else в методе моего контроллера:

Контроллер

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");
}

Мой jQuery / Ajax все тот же:

$("#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);
        }
    });
});

Но теперь, когда дата выбрана, меня возвращают на страницу, на которой Json отображается в виде обычного текстового файла, а HTML и CSS теряются как обычный вид.

Вот что мне возвращается, когда выбрана дата и нажата кнопка.

enter image description here

Кроме того, когда я проверяю консоль при выборе даты и нажимаю кнопку для отправки этой даты на контроллер, я вижу это:

enter image description here

ОБНОВЛЕНИЕ 2

Вот одна из моих таблиц .. Остальные же настройки:

<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>

Ответы [ 7 ]

0 голосов
/ 24 сентября 2018

Я бы предложил следующие шаги

  1. Одно частичное представление для фильтров, которые включают DateTime и кнопку Submit (FilterPartial)
  2. Одно частичное представление для отображаемых таблиц (ResultsPartial)

Когда тело загружено, загрузите первое частичное представление (давайте назовем FilterPartial), которое установит значения в представлении ResultsPartial.

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("");
    }

Логика ViewModel может быть сохранена как есть, вы должны разделить представления. Дайте мне знать, если это поможет ...

0 голосов
/ 24 сентября 2018

Вам необходимо создать два метода, первый для возврата представления с типом возврата ActionResult:

public ActionResult Details(string id, string detailsDate)
{
....
return View(person);
}

И второй метод, который будет вызываться через Ajax для возврата данных Json с типом JsonResult

public JsonResult GetData(string id, string detailsDate)
{
....
return Json(person, JsonRequestBehavior.AllowGet);
   }

Чтобы предотвратить повторение одной и той же логики получения данных дважды, вы можете переместить всю логику данных в метод Json и сохранить возврат метода View без данных, а затем просто вызвать вызов Ajax, когда загрузка страницы завершена, чтобы получить исходные данные из метода Json.

0 голосов
/ 24 сентября 2018

Насколько я вижу из вашей проблемы, чтобы исправить ее, вы можете сделать следующие шаги:

1- Просмотр

Добавить частичное представление(_Detail.cshtml)

Вам необходим partial view как _Detail, который включает в себя ваш table, например:

@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- Контроллер

Вернуть частичное представление

Вы должны заполнить модель в контроллере и передать ее в частичное представление.

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);

}

По мере того как выКак видно из приведенного выше кода, вы должны прокомментировать две строки в вашем коде:

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

3- Ajax call

Получить частичное представление изаполните форму

Вы не вносите никаких изменений в вызов ajax, и вы можете сделать это следующим образом:

$("#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 таким образом, это HTML, который приходитот частичного представления, и у него есть все классы, как вы его разработали.

0 голосов
/ 22 сентября 2018

У вас есть 2 различных метода возврата, и вы устанавливаете содержимое тела для ответа на запрос, который, если выполняется оператор else, будет JSON, а не html.

Я бы предложил всегда возвращать JSON или всегда возвращать 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);
}
0 голосов
/ 17 сентября 2018

Настройка ReferenceLoopHandling = ReferenceLoopHandling.Ignore решит вашу проблему, связанную с исключением циклической ссылки.

Теперь по поводу проблем, которые вы указали в своих обновлениях, я думаю, у вас может быть некоторое недопонимание о том, как работает запрос Ajax. Я думаю, это потому, что когда вы делаете ajax-запрос к серверу, он будет отвечать данными JSON, которые будут представлять вашу модель представления, а не будет зависеть от кода вашего представления (cshtml) , поэтому при вызове $("body").html(response); вы заменяете содержимое своей страницы строковым представлением модели представления JSON. Примите во внимание то, что когда вы делаете ajax-запрос, будет выполняться только внутренний код, а ваш код представления (cshtml) не будет выполняться, точка.

Чтобы решить эту проблему, вам придется заменить содержимое самой таблицы, а не тело страницы, поэтому ваш обратный вызов Ajax должен выглядеть примерно так:

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>

Надеюсь, это поможет вам!

0 голосов
/ 14 сентября 2018

При сериализации объекта типа обнаружена циклическая ссылка, поскольку сериализатор JSON не поддерживает циклические ссылки внутри иерархии объектов (т. Е. Передача PersonnelDetailsVm, которая содержит ссылки на модели данных). Чтобы решить эту проблему, используйте JSON.NET JsonConvert.SerializeObject() с настройками по умолчанию, установленными следующим образом:

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

После этого вы можете вернуть JsonResult из viewmodel:

string jsonStr = JsonConvert.SerializeObject(person);

return Json(jsonStr);

Если вы используете IE и сталкиваетесь с диалоговым окном сохранения из-за дружественной конфигурации ошибок JSON , вам может потребоваться добавить text/html или text/plain при возврате данных JSON:

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

Или скрыть метод Json() внутри класса контроллера следующим образом:

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

Кроме того, вместо return View(person); вы можете рассмотреть return PartialView("Details", person);, потому что вызов AJAX намеревался остаться на той же странице.

0 голосов
/ 13 сентября 2018

Это сообщение об ошибке означает, что одно из ваших дочерних свойств ссылается на родительский объект, а сериализация JSON вызывает циклический цикл.

Чтобы исправить, замените это:

return Json(person, JsonRequestBehavior.AllowGet);

следующим:

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

Вам нужно будет установить NewtonSoft.Json:

using Newtonsoft.Json;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...