2

So we have a middleware that takes the response body of our API responses and wraps it in an ApiResult class under a "data" property.

    namespace Web.Api.ApiResult
{
    public class ApiResultMiddleware
    {
        private readonly RequestDelegate next;

        public ApiResultMiddleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            var originalBody = context.Response.Body;
            var responseBody = string.Empty;
            try
            {
                CsWebException exception = null;

                using (var stream = new MemoryStream())
                {
                    context.Response.Body = stream;

                    try
                    {
                        await next.Invoke(context);
                    }
                    catch (CsWebException e)
                    {
                        exception = e;
                    }

                    stream.Position = 0;
                    responseBody = new StreamReader(stream).ReadToEnd();
                }

                object result = null;

                if (exception != null)
                {
                    result = new ApiResultResponse(null)
                    {
                        ErrorCode = exception.ErrorCode,
                        ErrorData = exception.ErrorData,
                    };
                    context.Response.StatusCode = (int)ApiResultHttpStatusCodeConverter.ConvertToHttpStatusCode(exception.ErrorCode);
                }
                else
                {
                    var data = JsonConvert.DeserializeObject(responseBody);
                    result = new ApiResultResponse(data);
                }

                var buffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(
                    result,
                    new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }));
                if (context.Response.StatusCode != StatusCodes.Status204NoContent)
                {
                    using (var output = new MemoryStream(buffer))
                    {
                        var test = JsonConvert.DeserializeObject<ApiResultResponse>(new StreamReader(output).ReadToEnd());
                        await output.CopyToAsync(originalBody);
                    }
                }
            }
            catch (JsonReaderException)
            {
                var result = new ApiResultResponse(responseBody);
                var buffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(
                    result,
                    new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }));
                using (var output = new MemoryStream(buffer))
                {
                    await output.CopyToAsync(originalBody);
                }
            }
            finally
            {
                context.Response.Body = originalBody;
            }
        }
    }

    public static class ApiResultMiddlewareExtensions
    {
        public static IApplicationBuilder UseApiResultMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ApiResultMiddleware>();
        }
    }
}

It worked perfectly in .NET core 2.2 and even after migrating to 3.1 and using the build in Sytem.Text.Json but because we need newtonsoft in our patch endpoints. By adding it with

services.AddControllers().AddNewtonsoftJson();

the returned JSON gets cut somewhere in the middle. I added the test variable and deserialized the JSON just before writing it and it looks fine but when parsing i in our front end it's not.

An example is where the json beeing written to the body looks like:

"{\"errorCode\":0,\"errorData\":null,\"data\":{\"previousWorkingDayWorkOrders\":[],\"nextWorkingDayWorkOrders\":[],\"todaysWorkOrders\":[],\"nonInvoicedWorkOrders\":[{\"workOrderId\":1232753.0,\"employeeNumber\":5000000037.0,\"employeeName\":\"VERKADM, VERKADM\",\"vehicleRegistrationNumber\":\"PXG948\",\"dealerOrderNumber\":null,\"bookingNumber\":null,\"preplannedDate\":null,\"workOrderStatus\":7,\"shortageIndicator\":true,\"customerWaiting\":false,\"vehicleDescriptionShort\":\"Volvo V40 Cross Country\",\"vehicleModelYear\":2018,\"colorDescription\":\"Blå\",\"fuelDescription\":\"Diesel\",\"email\":null,\"customer\":{\"customerNumber\":null,\"name\":\"Volvo Bil I Göteborg AB\",\"telephone\":null,\"email\":null,\"customerType\":1},\"mainPayerCustomerType\":1,\"notes\":null,\"vehicleIdentificationNumber\":\"YV1MZ79L0J2139968\"}],\"webWorkOrders\":[]}}"

When receiving the same response in postman it can't be parsed to json because it is cut off and in plan text it looks like:

{"errorCode":0,"errorData":null,"data":{"previousWorkingDayWorkOrders":[],"nextWorkingDayWorkOrders":[],"todaysWorkOrders":[{"workOrderId":1229253.0,"employeeNumber":5000000037.0,"employeeName":"VERKADM, VERKADM","vehicleRegistrationNumber":"PXG948","dealerOrderNumber":null,"bookingNumber":"349","preplannedDate":"2020-02-06T07:00:00","workOrderStatus":5,"shortageIndicator":true,"customerWaiting":false,"vehicleDescriptionShort":"Volvo V40 Cross Country","vehicleModelYear":2018,"colorDescription":"Blå","fuelDescription":"Diesel","email":null,"customer":{"customerNumber":null,"name":"Volvo Bil I Göteborg AB","telephone":null,"email":null,"customerType":1},"mainPayerCustomerType":1,"notes":null,"vehicleIdentificationNumber":"YV1MZ79L0J2139968"}],"nonInvoicedWorkOrders":[{"workOrderId":1232753.0,"employeeNumber":5000000037.0,"employeeName":"VERKADM, VERKADM","vehicleRegistrationNumber":"PXG948","dealerOrderNumber":null,"bookingNumber":null,"preplannedDate":null,"workOrderStatus":7,"shortageIndicator":true,"customerWaiting":false,"vehicleDescriptionShort":"Volvo V40 Cross Country","vehicleModelYear":2018,"colorDescription":"Blå","fuelDescription":"Diesel","email":null,"customer":{"customerNumber":null,"name":"Volvo Bil I Göteborg AB","telephone":null,"email":null,"customerType":1},"mainPayerCustomerType":1,"notes":null,"vehicleIdentificationNumber":"Y

Any idea of why this is not working?

Edit: I should also point out that by removing the middleware app.UseApiResultMiddleware(); everything workes fine but we still want to wrap our responses

Edit 2. I managed to solve this thanks to dbc's response. By setting the length of response content to the length of the buffer it works perfectly.

context.Response.ContentLength = buffer.Length;

What puzzles me is that fact that is was working without setting the length with System.Text.Json and when we used .Net core 2.2

10
  • welcome! have you tried flushing the response stream? Commented Feb 6, 2020 at 15:20
  • Thanks. I have tried flushing it but that does not seem to be the issue. Commented Feb 6, 2020 at 15:30
  • 1
    Just a quick note, you do know that {{}} is invalid as json? Commented Feb 6, 2020 at 15:48
  • your json is not valid Commented Feb 6, 2020 at 17:21
  • Sorry, the json i posted was actually the parsed value of the actual json to an anonymous object parsed in the test variable in the code. I updated to post with the serialized json Commented Feb 7, 2020 at 8:21

3 Answers 3

2

Other possibility, you did not follow the migration guide to .NET Core 3.0.

Use the Microsoft.AspNetCore.Mvc.NewtonsoftJson package instead of the Newtonsoft.Json package.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes we did follow the migration guide and we are using the Microsoft.AspNetCore.Mvc.NewtonsoftJson. The parsing of the object works if i remove the line: app.UseApiResultMiddleware();. but then the responses don't get wrapped in out ApiResultResponse class
0

You added NewtonsoftJson with

services.AddControllers().AddNewtonsoftJson();

but I am not sure that middlewares will be able to access it (never tried this service registration method before).

Did you try to add it using the old one ?

services.AddMvc().AddNewtonsoftJson();

1 Comment

I actually replaced that when migrating. Since 3.0 there's new options for adding MvC scenarios. You can find more info about it here:learn.microsoft.com/en-us/aspnet/core/migration/…
0

I am getting same Issue. but I have got success after adding below lines in middleware service.

 string plainBodyText = await new StreamReader(context.response.Body).ReadToEndAsync();    
context.Response.ContentLength = plainBodyText.Length;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.