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
167 stars 38 forks source link

.NET 8 has introduced stream operation for JsonSerializer of System.Text.Json, utilized this. #150

Closed zijianhuang closed 2 weeks ago

zijianhuang commented 3 months ago

.NET 6 had introduced Utf8JsonReader in .NET 6, and stream in .NET 8.

ZijianFLG commented 2 months ago

check if https://learn.microsoft.com/en-us/dotnet/api/system.net.http.json.httpclientjsonextensions utilize the stream operation.

zijianhuang commented 2 weeks ago
        public async Task<DemoWebApi.DemoData.Client.Company> CreateCompanyAsync(DemoWebApi.DemoData.Client.Company p, Action<System.Net.Http.Headers.HttpRequestHeaders> handleHeaders = null)
        {
            var requestUri = "api/Entities/createCompany";
   //         using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri);
            //var contentJson = JsonSerializer.Serialize(p, jsonSerializerSettings);
            //var content = new StringContent(contentJson, System.Text.Encoding.UTF8, "application/json");
            //httpRequestMessage.Content = content;
            //handleHeaders?.Invoke(httpRequestMessage.Headers);
            var responseMessage = await client.PostAsJsonAsync<DemoWebApi.DemoData.Client.Company>(requestUri, p, jsonSerializerSettings);
            try
            {
                responseMessage.EnsureSuccessStatusCodeEx();
                if (responseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) { return null; }
                var contentString = await responseMessage.Content.ReadAsStringAsync();
                return JsonSerializer.Deserialize<DemoWebApi.DemoData.Client.Company>(contentString, jsonSerializerSettings);
            }
            finally
            {
                responseMessage.Dispose();
            }
        }

        /// <summary>
        /// POST api/Entities/createCompany
        /// </summary>
        public DemoWebApi.DemoData.Client.Company CreateCompany(DemoWebApi.DemoData.Client.Company p, Action<System.Net.Http.Headers.HttpRequestHeaders> handleHeaders = null)
        {
            var requestUri = "api/Entities/createCompany";
            //using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri);
            //var contentJson = JsonSerializer.Serialize(p, jsonSerializerSettings);
            //var content = new StringContent(contentJson, System.Text.Encoding.UTF8, "application/json");
            //httpRequestMessage.Content = content;
            //handleHeaders?.Invoke(httpRequestMessage.Headers);
            var responseMessage = client.PostAsJsonAsync<DemoWebApi.DemoData.Client.Company>(requestUri, p, jsonSerializerSettings).Result;
            try
            {
                responseMessage.EnsureSuccessStatusCodeEx();
                if (responseMessage.StatusCode == System.Net.HttpStatusCode.NoContent) { return null; }
                var contentString = responseMessage.Content.ReadAsStringAsync().Result;
                return JsonSerializer.Deserialize<DemoWebApi.DemoData.Client.Company>(contentString, jsonSerializerSettings);
            }
            finally
            {
                responseMessage.Dispose();
            }
        }
zijianhuang commented 2 weeks ago

Basically done, however, I found out that I have supported custom request headers manipulation, and the extensions do not support. So now I have to reverse the change, since custom header manipulation is important for many real world applications., then just keep a copy here:

    void RenderPostOrPutImplementation(string httpMethod)
        {
            //Create function parameters in prototype
            CodeParameterDeclarationExpression[] parameters = description.ParameterDescriptions.Where(p => p.ParameterDescriptor.ParameterBinder == ParameterBinder.FromUri
            || p.ParameterDescriptor.ParameterBinder == ParameterBinder.FromQuery || p.ParameterDescriptor.ParameterBinder == ParameterBinder.FromBody
            || p.ParameterDescriptor.ParameterBinder == ParameterBinder.None).Select(d =>
            {
                AddCustomPocoTypeForCs(d.ParameterDescriptor.ParameterType);
                CodeParameterDeclarationExpression exp = new CodeParameterDeclarationExpression(poco2CsGen.TranslateToClientTypeReference(d.ParameterDescriptor.ParameterType), d.Name);
                exp.UserData.Add(Fonlow.TypeScriptCodeDom.UserDataKeys.ParameterDescriptor, d.ParameterDescriptor);
                return exp;
            }
            ).ToArray();
            clientMethod.Parameters.AddRange(parameters);

            if (codeGenOutputsSettings.CancellationTokenEnabled)
            {
                clientMethod.Parameters.Add(new CodeParameterDeclarationExpression("System.Threading.CancellationToken", "cancellationToken"));
            }

            if (codeGenOutputsSettings.HandleHttpRequestHeaders)
            {
                clientMethod.Parameters.Add(new CodeParameterDeclarationExpression("Action<System.Net.Http.Headers.HttpRequestHeaders>", "handleHeaders = null"));
            }

            ParameterDescription[] fromBodyParameterDescriptions = description.ParameterDescriptions.Where(d => d.ParameterDescriptor.ParameterBinder == ParameterBinder.FromBody
                || (TypeHelper.IsComplexType(d.ParameterDescriptor.ParameterType) && (!(d.ParameterDescriptor.ParameterBinder == ParameterBinder.FromUri) || (d.ParameterDescriptor.ParameterBinder == ParameterBinder.None)))).ToArray();
            if (fromBodyParameterDescriptions.Length > 1)
            {
                throw new CodeGenException("Bad Api Definition")
                {
                    Description = String.Format("This API function {0} has more than 1 FromBody bindings in parameters", description.ActionDescriptor.ActionName)
                };
            }

            ParameterDescription singleFromBodyParameterDescription = fromBodyParameterDescriptions.FirstOrDefault();

            void AddRequestUriWithQueryAssignmentStatement()
            {

                string jsUriQuery = UriQueryHelper.CreateUriQuery(description.RelativePath, description.ParameterDescriptions);
                string uriText = jsUriQuery == null ? $"\"{description.RelativePath}\"" : RemoveTrialEmptyString($"\"{jsUriQuery}\"");

                clientMethod.Statements.Add(new CodeVariableDeclarationStatement(
                    new CodeTypeReference("var"), "requestUri",
                    new CodeSnippetExpression(uriText)));
            }

            AddRequestUriWithQueryAssignmentStatement();

            bool hasBody = singleFromBodyParameterDescription != null;
            if (!codeGenOutputsSettings.UseSystemTextJson // use Newtonsoft.Json
                || (codeGenOutputsSettings.UseSystemTextJson && codeGenOutputsSettings.UseHttpClientJsonExtensions && !hasBody))
            {
                clientMethod.Statements.Add(new CodeSnippetStatement(
                    ThreeTabs + $"using var httpRequestMessage = new HttpRequestMessage(HttpMethod.{httpMethod}, requestUri);"));
            }

            if (hasBody)
            {
                if (codeGenOutputsSettings.UseSystemTextJson)
                {
                    if (!codeGenOutputsSettings.UseHttpClientJsonExtensions)
                    {
                        clientMethod.Statements.Add(new CodeSnippetStatement(ThreeTabs + $"var contentJson = JsonSerializer.Serialize({singleFromBodyParameterDescription.ParameterDescriptor.ParameterName}, jsonSerializerSettings);"));
                        clientMethod.Statements.Add(new CodeSnippetStatement(ThreeTabs + @"var content = new StringContent(contentJson, System.Text.Encoding.UTF8, ""application/json"");"));
                    }
                }
                else
                {
                    clientMethod.Statements.Add(new CodeSnippetStatement(
    $"\t\t\tusing var requestWriter = new System.IO.StringWriter();{Environment.NewLine}\t\t\tvar requestSerializer = JsonSerializer.Create(jsonSerializerSettings);"
    ));
                    clientMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeSnippetExpression("requestSerializer"), "Serialize",
                        new CodeSnippetExpression("requestWriter"),
                        new CodeSnippetExpression(singleFromBodyParameterDescription.ParameterDescriptor.ParameterName)));

                    clientMethod.Statements.Add(new CodeSnippetStatement(
    @"          var content = new StringContent(requestWriter.ToString(), System.Text.Encoding.UTF8, ""application/json"");"
                        ));
                }

                if (!codeGenOutputsSettings.UseHttpClientJsonExtensions)
                {
                    clientMethod.Statements.Add(new CodeSnippetStatement("\t\t\thttpRequestMessage.Content = content;"));
                    if (codeGenOutputsSettings.HandleHttpRequestHeaders)
                    {
                        clientMethod.Statements.Add(new CodeSnippetStatement("\t\t\thandleHeaders?.Invoke(httpRequestMessage.Headers);"));
                    }
                }
            }

            if (codeGenOutputsSettings.UseHttpClientJsonExtensions && hasBody)
            {
                AddResponseMessageJsonPostPutAsync(clientMethod, httpMethod, singleFromBodyParameterDescription.ParameterDescriptor.ParameterName);
            }
            else
            {
                AddResponseMessageSendAsync(clientMethod); //output: client.SendAsync(httpRequestMessage)
            }

            CodeVariableReferenceExpression resultReference = new CodeVariableReferenceExpression("responseMessage");

            if (returnTypeIsStream)
            {
                clientMethod.Statements.Add(new CodeMethodInvokeExpression(resultReference, statementOfEnsureSuccessStatusCode));

                //Statement: return something;
                if (returnType != null)
                {
                    AddReturnStatement(clientMethod.Statements);
                }
            }
            else
            {
                CodeTryCatchFinallyStatement try1 = new CodeTryCatchFinallyStatement();
                clientMethod.Statements.Add(try1);
                try1.TryStatements.Add(new CodeMethodInvokeExpression(resultReference, statementOfEnsureSuccessStatusCode));

                //Statement: return something;
                if (returnType != null)
                {
                    AddReturnStatement(try1.TryStatements);
                }

                try1.FinallyStatements.Add(new CodeMethodInvokeExpression(resultReference, "Dispose"));
            }

            if (singleFromBodyParameterDescription != null && !codeGenOutputsSettings.UseSystemTextJson)
            {
                //Add3TEndBacket(clientMethod);
            }

        }

        static string ThreeTabs => "\t\t\t";

        void AddResponseMessageSendAsync(CodeMemberMethod method)
        {
            string cancellationToken = codeGenOutputsSettings.CancellationTokenEnabled ? ", cancellationToken" : String.Empty;
            method.Statements.Add(new CodeVariableDeclarationStatement(
                new CodeTypeReference("var"), "responseMessage", forAsync ? new CodeSnippetExpression($"await client.SendAsync(httpRequestMessage{cancellationToken})") : new CodeSnippetExpression($"client.SendAsync(httpRequestMessage{cancellationToken}).Result")));
        }

        void AddResponseMessageJsonPostPutAsync(CodeMemberMethod method, string httpMethod, string bodyParamName)
        {
            string cancellationToken = codeGenOutputsSettings.CancellationTokenEnabled ? ", cancellationToken" : String.Empty;
            method.Statements.Add(new CodeVariableDeclarationStatement(
                new CodeTypeReference("var"), "responseMessage", forAsync ?
                new CodeSnippetExpression($"await client.{httpMethod}AsJsonAsync(requestUri, {bodyParamName}, jsonSerializerSettings{cancellationToken});")
                : new CodeSnippetExpression($"client.{httpMethod}AsJsonAsync(requestUri, {bodyParamName}, jsonSerializerSettings{cancellationToken}).Result")));
        }
zijianhuang commented 2 weeks ago

use this instead:

var content = System.Net.Http.Json.JsonContent.Create(p, mediaType: null, jsonSerializerSettings);
zijianhuang commented 2 weeks ago

done