Return complex object from .Net core API

2019-09-21 06:40发布

问题:

I'm using a .Net core backend with Entity Framework to retrieve objects to my angular2 frontend. As long as I return flat objects from the backend I have not problems. But when I use an Linq Include I get an net::ERR_CONNECTION_RESET error. The error seems to occur as soon as the call returns from backend, but it never enters the result handler.

How can I return a complex object with lists inside the objects?

DocumentsController.cs:

[Produces("application/json")]
[Route("api/[controller]")]
public class DocumentsController : Controller
{
    private readonly DatabaseContext _context;

    public DocumentsController(DatabaseContext context)
    {
        _context = context;
    }

    // GET: api/Documents
    [HttpGet]
    public IEnumerable<Document> GetDocuments()
    {
        var documents = _context.Documents
            .Include(x => x.Pages);

        return documents;
    }
}

Document.cs:

public class Document : IEntity
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string AlternativeTitle { get; set; }
    public ICollection<Page> Pages { get; set; }
}

Page.cs:

public class Page : IEntity
{
    public Document Document { get; set; }
    public int DocumentId { get; set; }
    public int Id { get; set; }
    public string OriginalText { get; set; }
    public int Sorting { get; set; }
}

documents.components.ts:

import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';

@Component({
    selector: 'documents',
    templateUrl: './documents.component.html'
})
export class DocumentsComponent {
    public documents: Document[];

    constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
        http.get(baseUrl + 'api/Documents').subscribe(result => {
            this.documents = result.json() as Document[];
        }, error => console.error(error));
    }
}

interface Document {
    title: string;
    alternativeTitle: string;
    pageCount: number;
    pages: Array<Page>;
}
interface Page {
    originalText: string;
    sorting: number;
}

回答1:

The connection reset error is due to your Action method throwing an unhandled exception, returning a 500 to the client. This happens because of the infinite loop Document => Page => Document when the serializer kicks in.
An easy way to solve this problem without breaking the relationship (you might need them even when using DTOs) is to change the default serialization process:

public IActionResult GetDocuments()
{
    var documents = ...;

    var options = new JsonSerializerSettings 
    {
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
    };

    return Json(documents, options);
}

With that said, you should always return either IActionResult or async Task<IActionResult> instead of the type directly (like IEnumerable<Document>).
Also, if you find yourself having to use the above code continuously, you might want to change the default options at the application level in Startup



回答2:

After a while a thought struck me; it could be a problem with a never ending loop of Documents with Pages with nested Document with Pages and so on. I created a DTO (data transfer object) that doesn't include the back references.

public class DocumentDto
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string AlternativeTitle { get; set; }
    public ICollection<PageDto> Pages { get; set; }
    public string OriginalLanguage { get; set; }
    public string TranslationLanguage { get; set; }
}
public class PageDto
{
    public int Id { get; set; }
    public string OriginalText { get; set; }
    public int Sorting { get; set; }
    public string Translation { get; set; }
}

Worked like a charm.