5

Imagine a set of Entity Framework entities:

public class Country {
    public string CountryCode { get; set; }
    public string Name { get; set; }
    public string Flag { get; set; }
}

public class Market {
    public string CountryCode { get; set; }
    public virtual Country Country { get; set; }
    public int ProductID { get; set; }      
    public virtual Product Product { get; set; }
}

public class Product {
    public int ProductID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Market> Markets{ get; set; }
}

Imagine as well a DOTNET 5 api GET

// GET api/product
[HttpGet]
public async Task<IActionResult> GetProduct([FromRoute] int id)
{
    return Ok(await _context.Products
        .Include(p => p.Markets)
        .SingleAsync(m => m.ProductID == id));
}

If there are no markets attached to the entity the data returns without issue, but as soon as I have a few linked items attached I get an error:

HTTP Error 502.3 - Bad Gateway
The specified CGI application encountered an error and the server terminated the process.

I vaguely recall a previous application where every complex EF object had a "primitives only" type object to send and receive this object to and from the client, but I wonder if there is a way to communicate without intermediary objects?

eg:

public class ProductViewModel {
    public int ProductID { get; set; }
    public string Name { get; set; }
    public List<MarketViewModel> Markets{ get; set; }
}

public class MarketViewModel {
    public int ProductID { get; set; }
    public Country Country { get; set; }
}

My concern is the coding overhead of translating every complex object back and forth from the client (which, I'll admit, I'm not sure is even a bad thing, maybe it has to be done anyways).

Since the scaffolded APIs seem to take and return entities directly I find myself wondering if there is a way to handle the complex part of the object directly

Edit #1:

Per Noel's comment below, if I change the code which is causing the error to

    [HttpGet("{id}", Name = "GetProduct")]
    public async Task<IActionResult> GetProduct([FromRoute] int id)
    {

        Product product = await _context.Products
            .Include(t => t.Markets)
            .SingleAsync(m => m.ProductID == id);

        throw new System.Exception("error sample");
        return Ok(product);
    }

the stack trace is Correctly thrown. If I remove the exception, the 500 gateway error appears. I agree that it looks like it's probably a serialization error, but it's tough to say.

EDIT 2 - per a comment from Oleg below:

The solution to a bad gateway is to first explicitly update a more recent version of NewtonSoft.Json in the dependencies in the project.json file:

"dependencies": {
  "Newtonsoft.Json": "8.0.1-beta3",

next you must alter the Startup.cs file

    public void ConfigureServices(IServiceCollection services)
    {

         services.AddMvc()
            .AddJsonOptions(options => {
                options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            });

with those two settings in place, the bad gateway no longer occurs and an api call successfully returns the complex object as expected.

Alex C
  • 16,624
  • 18
  • 66
  • 98
  • What is the actual exception your application is throwing? Your issue is probably a serialization issue that you could solve without using viewmodels, but a 503 doesn't tell us enough to suggest how to fix it. – Noel Dec 15 '15 at 14:43
  • I'll dig that up this evening. I was struggling to get proper stack traces out of .net5 – Alex C Dec 15 '15 at 18:04
  • hey Noel, I've experimented with your suggestion and updated the post above. It seems that isn't the key. The stack trace does not appear to get thrown even when you enable errors. – Alex C Dec 15 '15 at 22:50
  • 2
    If you suspect serialization error, then you can try to add `Newtonsoft.Json` in the latest version [8.0.1-beta3](https://www.nuget.org/packages/Newtonsoft.Json/8.0.1-beta3) explicitly. You can examine `package.lock.json` to see which version will be loaded by automatic resolution of dependencies. See [the issue](https://github.com/aspnet/EntityFramework/issues/3818) which confirm HTTP Error 502.3 - Bad Gateway during serialization. You can try to set `Newtonsoft.Json.ReferenceLoopHandling.Ignore;` in the the configuration of `SerializerSettings.ReferenceLoopHandling` (see the issue). – Oleg Dec 15 '15 at 23:12
  • @Oleg you are literally amazing! that worked! Wow. Thank you so much :) – Alex C Dec 16 '15 at 01:32
  • @AlexC: You are welcome! I'm glad that I could help you. I appended **UPDATED** part to my answer. I think that other people could have the same problem. – Oleg Dec 16 '15 at 05:52

2 Answers2

4

It seems to me that you just missed await in the call of SingleAsync. Try to use

[HttpGet]
public async Task<IActionResult> GetProduct([FromRoute] int id)
{
    return Ok(await _context.Products
        .Include(p => p.Markets)
        .SingleAsync(m => m.ProductID == id));
}

UPDATED: I found the issue. I would recommend you to examine You can examine package.lock.json to see, which version will be loaded by automatic resolution of dependencies. Then I would you recommend to explicitly add Newtonsoft.Json in the latest version 8.0.1-beta3 to the dependencies of your project. Additionally you should add the setting of to Newtonsoft.Json.ReferenceLoopHandling.Ignore in the the configuration of SerializerSettings.ReferenceLoopHandling. See the issue for more details.

Oleg
  • 220,925
  • 34
  • 403
  • 798
  • Apologies - I think that is an error in the anonymized/generalized code that I re-wrote to be generic enough for a stackoverflow question. I'll have to check this evening when I can get back to the code. – Alex C Dec 15 '15 at 18:13
  • @AlexC: Sorry, I'm not sure what you mean. The code, which you posed have prototype `public async Task GetProduct([FromRoute] int id)` and you calls `SingleAsync` inside. You should use `await` in the call to force the thread wait till the asynchronous call be finished and only then returns the results. See [here](https://msdn.microsoft.com/en-us/library/hh191443.aspx). Do you tried to add `await` key like I posted? – Oleg Dec 15 '15 at 18:24
  • @AlexC: Look at [the post](http://www.elanderson.net/2015/08/basic-web-api-with-asp-net-5/) or [the answer](http://stackoverflow.com/a/29168586/315935) or [the post](http://wildermuth.com/2015/09/27/A_Look_at_ASP_NET_5_Part_5_-_The_API) or many other. Be careful that if one add `async` then one have to add `await` to the corresponding async method, which one use, *before* returning the data from the method. – Oleg Dec 15 '15 at 19:11
  • @AlexC: I can reproduce the error "HTTP Error 502.3 - Bad Gateway" which you describes if I just set breakpoint on the return statement of *working* code and wait long enough. I will try to examine problem more deep and find out why MVC6 produce the error on timeout. It's important that web browser get the error only after I allow to continue to run my MVC code after the long timeout. I mean that the error 502.3 - Bad Gateway really report the server code (the code of MVC). – Oleg Dec 15 '15 at 21:47
  • @Alexc and Oleg Thanks a ton both of you for the post and for the loophandling solution, worked for me! – Jaya May 23 '16 at 20:31
  • @JS_GodBlessAll: You are welcome! I'm like that I could help you. – Oleg May 23 '16 at 20:35
  • @JS_GodBlessAll - it's a love fest! Thanks so much for the comment – Alex C May 24 '16 at 14:37
0

You can return anonymous object or use ExpandoObject / JsonObject:

public HttpResponseMessage Get()
{
    return this.Request.CreateResponse(
        HttpStatusCode.OK,
        new { Message = "Hello", Value = 123 });
}

//JsonObject

dynamic json = new JsonObject();
json.Message = "Hello";
json.Value = 123;

return new HttpResponseMessage<JsonObject>(json);

//ExpandoObject

 dynamic expando = new ExpandoObject();
    expando.message = "Hello";
    expando.message2 = "World";
    return expando;
Thiago Custodio
  • 17,332
  • 6
  • 45
  • 90