1

Creating an Web api using Asp.net core. The endpoint I'm creating will return a list of all the customers (currently I've only seeded the DB with 1 customer). When I perform a GET request from Postman I get a "Could not get any Response". When I navigate to that endpoint via Chrome I see the response but the json returned seems to be missing brackets.

[
{
    "customerId":1,
    "firstName":"John",
    "lastName":"Doe",
    "email":"johndoe@email.com",
    "address": {
        "addressId":1,
        "addressLine1":"2119 Some street",
        "addressLine2":null,
        "locality":"New York",
        "region":"New York",
        "country":"USA",
        "postCode":"10005",
        "customerId":1

I'm wondering if the poorly formatted Json response is the reason Postman isn't working. I'd like to understand whats wrong. Is there something I'm doing wrong in my simple example that could be causing me this headache.

My repository:

public class LodgingRepository : ILodgingRepository
{
    private readonly LodgingCrmContext _context;

    public LodgingRepository( LodgingCrmContext context )
    {
        _context = context;
    }

    public async Task<IEnumerable<Customer>> GetCustomersAsync( )
    {
        return await _context.Customers
            .Include( c => c.Address )
            .ToListAsync( );
    }
}

My Controller

[Route( "api/customers" )]
public class CustomerController : Controller
{
    private readonly ILodgingRepository _lodgingRepository;

    public CustomerController( ILodgingRepository lodgingRepository )
    {
        _lodgingRepository = lodgingRepository;
    }

    [HttpGet]
    public async Task<IActionResult> GetCustomersAsync( )
    {
        return Ok( await _lodgingRepository.GetCustomersAsync( ) );
    }
}

StartUp

public void ConfigureServices( IServiceCollection services )
    {
        services.AddMvc( ).AddMvcOptions( options =>
            options.OutputFormatters.Add( new XmlDataContractSerializerOutputFormatter( ) ) );

        services.AddDbContext<LodgingCrmContext>( options =>
            options.UseSqlServer( Configuration.GetConnectionString( "DefaultConnection" ) ) );

        services.AddScoped<ILodgingRepository, LodgingRepository>( );
    }

public void Configure( IApplicationBuilder app, IHostingEnvironment env, LodgingCrmContext lodgingCrmContext )
    {
        if( env.IsDevelopment( ) )
        {
            app.UseDeveloperExceptionPage( );
        }

        app.UseMvc( );

        app.Run( async ( context ) => { await context.Response.WriteAsync( "Hello World!" ); } );
    }

Address Entity

public class Address
{
    [Key]
    [DatabaseGenerated( DatabaseGeneratedOption.Identity )]
    public int AddressId { get; set; }

    [Required]
    [MaxLength( 254 )]
    public string AddressLine1 { get; set; }

    [MaxLength( 254 )]
    public string AddressLine2 { get; set; }

    [Required]
    [MaxLength( 60 )]
    public string Locality { get; set; }

    [Required]
    [MaxLength( 50 )]
    public string Region { get; set; }

    [Required]
    [MaxLength( 60 )]
    public string Country { get; set; }

    [Required]
    [MaxLength( 9 )]
    public string PostCode { get; set; }

    [ForeignKey( nameof(CustomerId) )]
    public int CustomerId { get; set; }

    public Customer Customer { get; set; }
}
WBuck
  • 2,918
  • 2
  • 17
  • 22
  • You need to enumerate the collection – Nkosi Oct 03 '17 at 14:13
  • In my controller I did a .ToList() on the returned collection but I got the same result – WBuck Oct 03 '17 at 14:14
  • `GetCustomersAsync` returns `IEnumerable` – Nkosi Oct 03 '17 at 14:15
  • Yeah, one of the things I tried was var customers = await _lodgingRepository.GetCustomersAsync( ); return Ok(customers.ToList()); – WBuck Oct 03 '17 at 14:17
  • And that still doesn't work? – Nkosi Oct 03 '17 at 14:18
  • No, I got the same result as before. – WBuck Oct 03 '17 at 14:19
  • Is the displayed JSON snippet ALL that is returned? – Nkosi Oct 03 '17 at 14:20
  • Yes, I just cut and pasted it right from the Chome browser window. I also inspected the response in Chrome and that is indeed all I'm getting back. – WBuck Oct 03 '17 at 14:22
  • Maybe this is an issue with my customers table in the DB... Not sure what else this could be – WBuck Oct 03 '17 at 14:23
  • This looks more like an issue with the response pipeline. The object would have had to have been a complete object for the json to serialize. Some how the response stream is ending before all the content is sent – Nkosi Oct 03 '17 at 14:25
  • Wait. Does address have a reference back to customer? could be a circular reference issue. Testing a hunch remove the include address and see if it returns normally. – Nkosi Oct 03 '17 at 14:29
  • Yes it does. Ill post the Address Entity above. I have a Customer navigation property in the Address entity. – WBuck Oct 03 '17 at 14:31
  • That is the problem. If you were to use a simple POCO instead of your EF types directly you will get around this problem. – Nkosi Oct 03 '17 at 14:32
  • ohhhhhhhh I am planning on using DTO's but I just wanted to test functionality of the endpoint using the entity directly first before creating the DTO – WBuck Oct 03 '17 at 14:34
  • Go straight to the DTOs. Problem is every time it tries to serialize the address it will bounce up a customer and then the cycle repeats. then overflow. If you look at the JSON you'll see that it stops just as it has to do the customer again. – Nkosi Oct 03 '17 at 14:34
  • Awesome, makes perfect sense now. That also explains why Postman was reporting that it wasn't getting a response. I just mapped the entity to a DTO and it worked. Post it as a solution and I'll accept your answer. Thanks for your help – WBuck Oct 03 '17 at 14:46

1 Answers1

1

Problem is every time the response tries to serialize an Address it will bounce up an associated Customer and then the cycle repeats. This would cause a stack overflow.

If you look at the JSON you'll see that it stops just as it has to do the customer again

Create DTOs and map your entities to them when returning from your service to avoid circular references via EF navigation properties.

[HttpGet]
public async Task<IActionResult> GetCustomersAsync() {
    var records = await _lodgingRepository.GetCustomersAsync();
    var model = await ConvertToDTO(records); //<-- perform convertions here
    return Ok(model);
}
Nkosi
  • 191,971
  • 29
  • 311
  • 378