zijianhuang / webapiclientgen

Strongly Typed Client API Generators generate strongly typed client APIs in C# .NET and in TypeScript for jQuery and Angular 2+ from ASP.NET Web API and .NET Core Web API
MIT License
168 stars 38 forks source link

Cannot implicitly convert type 'System.Net.Http.HttpResponseMessage' to 'Newtonsoft.Json.Linq.JObject' #92

Closed ChristianWeyer closed 4 years ago

ChristianWeyer commented 4 years ago

Hi - thanks for this nice project :-)

I have an API controller with an action like this:

[HttpGet("distribution")]
public async Task<dynamic> GetDistribution()

When running client code gen the result is this - and this will not compile:

/// <summary>
/// GET api/Statistics/distribution
/// </summary>
public async Task<Newtonsoft.Json.Linq.JObject> GetDistributionAsync()
{
    var requestUri = new Uri(this.baseUri, "api/Statistics/distribution");
    var responseMessage = await client.GetAsync(requestUri);
    try
    {
        responseMessage.EnsureSuccessStatusCode();
        return responseMessage;
    }
    finally
    {
        responseMessage.Dispose();
    }
}

Thanks.

zijianhuang commented 4 years ago

The generated codes should be like:

        /// <summary>
        /// Anonymous Dynamic of C#
        /// GET api/SpecialTypes/AnonymousDynamic
        /// </summary>
        /// <returns>dyanmic things</returns>
        public async Task<Newtonsoft.Json.Linq.JObject> GetAnonymousDynamicAsync()
        {
            var requestUri = "api/SpecialTypes/AnonymousDynamic";
            var responseMessage = await client.GetAsync(requestUri);
            try
            {
                responseMessage.EnsureSuccessStatusCode();
                var stream = await responseMessage.Content.ReadAsStreamAsync();
                using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
                {
                var serializer = new JsonSerializer();
                return serializer.Deserialize<Newtonsoft.Json.Linq.JObject>(jsonReader);
                }
            }
            finally
            {
                responseMessage.Dispose();
            }
        }

If you check DemoCoreWeb, you will find:

namespace DemoCoreWeb.Controllers
{
    [Produces("application/json")] //.net core difference: .net core 2.0 does not support these.let's see 2.1 upcoming. issue #40
    [Route("api/SpecialTypes")]
    public class SpecialTypesController : Controller
    {
        /// <summary>
        /// Anonymous Dynamic of C#
        /// </summary>
        /// <returns>dyanmic things</returns>
        [HttpGet]
        [Route("AnonymousDynamic")]
        public dynamic GetAnonymousDynamic()
        {
            return new
            {
                Id = 12345,
                Name = "Something",
            };
        }

WebApiClientGenCore was originally developed for Core 2.0 which apparently did not support dynamic well. I am not entirely sure if Core 2.1 had resolved this problem, however, at least recent releases of WebApiClientGenCore had no problem with dynamic. So it should be better to move to Core 3.x, when .NET 5 will be release in Nov 2020. And there are plenty tutorials online telling how to migrate from 2.x to 3.x, and the most significant changes occur in Startup.cs generally.

ChristianWeyer commented 4 years ago

Hi - what are you trying to tell me? :-) This is a .NET Core 3.1 project I am using.

Thanks.

zijianhuang commented 4 years ago

I had just found the problem, when the service function is decorated with async Task, the generated codes is incorrect.

zijianhuang commented 4 years ago

WebApiClientGen is working fine for

[HttpGet("distribution")] public async Task<dynamic> GetDistribution()

WebApiClientGenCore is having problem.

It seems to be a bug in .NET Core 3.1 and previous versions that give wrong data type in ApiExplorer.

More investigation needed before I submit a bug report to .NET team.

Meanwhile, the workaround is to have public dynamic GetDistribution() on the service side, though not ideal for high traffic and thread management on the service side.

When WebApiClientGenCore or .NET Core is fixed, you may change back to async and this won't be a breaking change of your Web API.

ChristianWeyer commented 4 years ago

Thanks for the update - highly appreciated.

zijianhuang commented 4 years ago

WebApiClientGenCore 3.8 released.

.NET Core has some special treatment toward async Task, different from the behavior of .NET Framework. Not really hard to resolve such difference.

ChristianWeyer commented 4 years ago

Sorry, it seems there is some issue going on here. I get this exception with 3.8 now:

image

The related generated code is this:

public async Task<Newtonsoft.Json.Linq.JObject> GetDistributionAsync()
{
    var requestUri = new Uri(this.baseUri, "api/Statistics/distribution");
    var responseMessage = await client.GetAsync(requestUri);
    try
    {
        responseMessage.EnsureSuccessStatusCode();
        var stream = await responseMessage.Content.ReadAsStreamAsync();
        using (JsonReader jsonReader = new JsonTextReader(new System.IO.StreamReader(stream)))
        {
        var serializer = new JsonSerializer();
        return serializer.Deserialize<Newtonsoft.Json.Linq.JObject>(jsonReader);
        }
    }
    finally
    {
        responseMessage.Dispose();
    }
}

The calling code is like this:

protected override async Task OnInitializedAsync()
{
    var result = await _conferencesService.Statistics.GetDistributionAsync();
    _data = JsonSerializer.Serialize(result);
}

Thanks.

zijianhuang commented 4 years ago

That's strange. I have a few complex business Web APIs, all updated with 3.8, as well as https://github.com/zijianhuang/DemoCoreWeb, the codegen works fine on all of them, including a MVC project. My guess in this stage is: your WebApi has some sophisticated data models of edge cases that the codegen may not properly cover, and the latest changes in 3.8 had fixed the issue of the OP, however, could be breaking with some of your data models. This is on presumption that you did not change anything on your part.

The log of exception does not give much info since it somehow showed BlazorconfigTool.client.pages. So I presume yours is a MVC project.

As you can see in DemoCoreWeb of the WebapiClientGen repository, the SpecialTypesControllers.cs contains a few API similar to yours in the OP, so I would suspect there could be something else rather than what the log told.

My suggestion:

  1. You check out WebApiClientGen and make a Debug build, and use the debug build of WebApiClientGenCore, so the exception may tell more call stacks.

  2. Or, you give me the codes of the whole controller that contains public async Task<dynamic> GetDistribution(), so we could inspect if some special combination of the API functions in the controller may cause the problem of some suspected edge cases.

ChristianWeyer commented 4 years ago

The sample application can be found on Github.

This is the API controller: https://github.com/thinktecture/basta-spring-2020-blazor/blob/client-codegen/BlazorConfTool/BlazorConfTool/Server/Controllers/StatisticsController.cs#L25

The Blazor WASM client is here: https://github.com/thinktecture/basta-spring-2020-blazor/blob/client-codegen/BlazorConfTool/BlazorConfTool/Client/Pages/Statistics.razor#L42

Greetings from the OP ;-)

zijianhuang commented 4 years ago

I had just tested your controller at https://github.com/zijianhuang/DemoCoreWeb and https://github.com/zijianhuang/DemoCoreWeb/tree/SwaggerToWACG which also contains Swashbuckle.AspNetCore, all works fine, I mean, the client code generation is fine.

So is the exception raised when the client is calling the service function GetDistribution()? or during code generation?

If it is during code generation, can you show the JSON payload posted to the CodeGenController?

zijianhuang commented 4 years ago

And another possible cause could be with what mentioned in Readme.md:

.NET Core 2.x had dependency on Newtonsoft.JSON, while .NET Core 3.0 had been decoupled from Neewtonsoft.JSON and the default serializer is working well in most scenarios except for Tuple, 2D array and anonymous object etc. If you would support these data types or would keep 100% compitability with the serialization of NewtonSoft.JSON, you should explicitly include package Microsoft.AspNetCore.Mvc.NewtonsoftJson and add add AddNewtonsoftJson() in Startup.cs.

Can you try AddNewtonsoftJson()?

ChristianWeyer commented 4 years ago

The exception happens at runtime.

ChristianWeyer commented 4 years ago

Microsoft.AspNetCore.Mvc.NewtonsoftJson does not help here as the exception happens in the client-side Blazor code at runtime.

zijianhuang commented 4 years ago

Can you confirm the following?

  1. You have AddNewtonsoftJson() in Startup.cs as shown in DemoCoreWeb.
  2. All other client calls without dynamic on the server side are successful.
  3. The server has no exception during the offending client call to public async Task<dynamic> GetDistribution().
  4. If item 3 is positive, please check the server HTTP response which hopefully is a JSON object.
  5. If item 4 is positive, then a standalone client such as an integration test suite with the generated client code is working fine? An example is at IntegrationTestsCore in WebApiClientGen's repository.

I haven't cut my teeth on Blazor code, and my impression is that Blazor is to generate browser client codes at the server side and deliver to the browser to run inside a browser page. If all above questions are positive, we may move the focus to how Blazor works.

ChristianWeyer commented 4 years ago

Blazor WebAssembly is client-side: .NET code running inside a .NET runtime (available in WASM bytecode) in the browser. A Blazor WASM app is talking to the server always via API (Web APIs or gRPC-Web).

The Web API returns valid JSON. The exception is at runtime when the deserialization code running in Blazor WASM throws.

Thanks!

zijianhuang commented 4 years ago

I have just a quick look at some posts about Blazor, rather than Microsoft Docs about Blazor. I just wonder if there's some limitation of transforming/rendering C# client codes into WASM/Javascript to be running on the browser.

Would it be possible to generate a client TS library with Fetch or Axios and use the TS lib in the Razor Views? or just go the MVC way of rendering server data directly in the view?

zijianhuang commented 4 years ago

BTW, the thread is already off the topic of the OP which had been fixed in v3.8. So close this thread and move the discussion to #94.

Gambero81 commented 3 years ago

Hi, i'm using blazor wasm, is this library working with it?