执行ajax调用时,在序列化类型的对象时检测到循环引用

时间:2018-09-13 15:37:03

标签: json ajax asp.net-mvc

在我看来,我正在使用一个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'的对象时,检测到循环引用。

视图模型

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调用返回的内容替换视图中的模型,以便表可以基于正确的日期而无需页面刷新?

非常感谢您的帮助。

更新

基于下面的答案,我在控制器方法中将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 :(得分:3)

据您所知,要解决此问题,您可以执行以下步骤:

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

获取部分视图并填写表单

您没有对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,它具有您设计时的所有类。

答案 1 :(得分:1)

该错误消息表示您的子属性之一返回到父属性,而JSON序列化导致循环循环。

要修复,请替换为:

return Json(person, JsonRequestBehavior.AllowGet);

与此:

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

您将必须安装NewtonSoft.Json:

using Newtonsoft.Json;

答案 2 :(得分:1)

序列化对象类型时检测到循环引用,因为JSON序列化程序不支持对象层次结构内的循环引用(即,传递包含数据模型引用的PersonnelDetailsVm) 。要解决此问题,请使用JSON.NET的JsonConvert.SerializeObject(),并将默认设置设置如下:

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

然后,您可以从viewmodel返回JsonResult

string jsonStr = JsonConvert.SerializeObject(person);

return Json(jsonStr);

如果您使用IE并由于friendly JSON errors configuration而遇到保存对话框,则在返回JSON数据时可能需要添加text/htmltext/plain

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调用打算停留在同一页面上。

答案 3 :(得分:1)

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

希望对您有所帮助!

答案 4 :(得分:0)

您有2种不同的返回方法,并且您将主体内容设置为请求的响应,如果运行else语句,它将为JSON,而不是html。

我建议始终返回JSON或始终返回视图/部分。

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

答案 5 :(得分:0)

我建议采取以下步骤

  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逻辑可以保持原样,您必须分离视图。 让我知道这是否有帮助...

答案 6 :(得分:0)

您需要创建两个方法,第一个方法以ActionResult返回类型返回视图:

mounted

还有第二种方法,该方法将通过Ajax调用以返回JsonResult类型的Json数据

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

为防止重复两次相同的获取数据逻辑,可以将所有数据逻辑移至Json方法,并使View方法不返回任何数据,然后仅在页面加载完成后触发Ajax调用,以从Json方法获取初始数据。 / p>