proudmonkey / AutoWrapper

A simple, yet customizable global exception handler and Http response wrapper for ASP.NET Core APIs.
MIT License
679 stars 82 forks source link

Using Your Own API Response Schema Response Error #63

Closed aparun22 closed 4 years ago

aparun22 commented 4 years ago

Hi Team,

I am trying to use Custom Response class based on your suggestion it doesn't work as expected i follow your code as below
in Startup.cs app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { UseCustomSchema = true });

my controller [HttpGet] public NovatoApiResponse Get() { var rng = new Random(); var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray();

        return new NovatoApiResponse(DateTime.UtcNow, forecast,
    new Pagination
    {
        CurrentPage = 1,
        PageSize = 10,
        TotalItemsCount = 200,
        TotalPages = 20
    });

    }

Api returns me my custom response together with default response as bellow.

{"code":200,"message":"Success","payload":[{"date":"2020-07-03T15:18:21.1216077+08:00","temperatureC":40,"temperatureF":103,"summary":"Cool"},{"date":"2020-07-04T15:18:21.1223563+08:00","temperatureC":-18,"temperatureF":0,"summary":"Scorching"},{"date":"2020-07-05T15:18:21.1223597+08:00","temperatureC":-14,"temperatureF":7,"summary":"Balmy"},{"date":"2020-07-06T15:18:21.1223599+08:00","temperatureC":5,"temperatureF":40,"summary":"Hot"},{"date":"2020-07-07T15:18:21.1223602+08:00","temperatureC":42,"temperatureF":107,"summary":"Warm"}],"sentDate":"2020-07-02T07:18:21.1223606Z","pagination":{"totalItemsCount":200,"pageSize":10,"currentPage":1,"totalPages":20}} { "message": "GET Request successful.", "result": { "code": 200, "message": "Success", "payload": [ { "date": "2020-07-03T15:18:21.1216077+08:00", "temperatureC": 40, "temperatureF": 103, "summary": "Cool" }, { "date": "2020-07-04T15:18:21.1223563+08:00", "temperatureC": -18, "temperatureF": 0, "summary": "Scorching" }, { "date": "2020-07-05T15:18:21.1223597+08:00", "temperatureC": -14, "temperatureF": 7, "summary": "Balmy" }, { "date": "2020-07-06T15:18:21.1223599+08:00", "temperatureC": 5, "temperatureF": 40, "summary": "Hot" }, { "date": "2020-07-07T15:18:21.1223602+08:00", "temperatureC": 42, "temperatureF": 107, "summary": "Warm" } ], "sentDate": "2020-07-02T07:18:21.1223606Z", "pagination": { "totalItemsCount": 200, "pageSize": 10, "currentPage": 1, "totalPages": 20 } } }

Thanks, Arun.

proudmonkey commented 4 years ago

That's interesting. I'll check on that. For the time being, instead of using UseCustomSchema = true try to use `IgnoreWrapForOkRequests = true'

app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions {  
    IgnoreWrapForOkRequests = true,
});
proudmonkey commented 4 years ago

I tested the this scenario and I'm not able to replicate the issue. I'm getting the expected result. Here's my example:

Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
            app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions
            {
                UseCustomSchema = true
            });

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
}

Controller Class


namespace AutoWrapper_Test
{
    public class MyCustomApiResponse
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public object Payload { get; set; }
        public DateTime SentDate { get; set; }
        public Pagination Pagination { get; set; }

        public MyCustomApiResponse(DateTime sentDate,
                                   object payload = null,
                                   string message = "",
                                   int statusCode = 200,
                                   Pagination pagination = null)
        {
            this.Code = statusCode;
            this.Message = message == string.Empty ? "Success" : message;
            this.Payload = payload;
            this.SentDate = sentDate;
            this.Pagination = pagination;
        }

        public MyCustomApiResponse(DateTime sentDate,
                                   object payload = null,
                                   Pagination pagination = null)
        {
            this.Code = 200;
            this.Message = "Success";
            this.Payload = payload;
            this.SentDate = sentDate;
            this.Pagination = pagination;
        }

        public MyCustomApiResponse(object payload)
        {
            this.Code = 200;
            this.Payload = payload;
        }

    }

    public class Pagination
    {
        public int TotalItemsCount { get; set; }
        public int PageSize { get; set; }
        public int CurrentPage { get; set; }
        public int TotalPages { get; set; }
    }

    [Route("api/v1/[controller]")]
    [ApiController]
    public class TestsController : ControllerBase
    {

        private IEnumerable<PersonResponse> GetMockData()
        {
            return new List<PersonResponse>
            {
                new PersonResponse
                {
                 FirstName = "Vince",
                 LastName = "Durano",
                 DateOfBirth = Convert.ToDateTime("01/02/1986")
                },
                new PersonResponse
                {
                 FirstName = "Mitch",
                 LastName = "Durano",
                 DateOfBirth = Convert.ToDateTime("01/02/1986")
                },
                new PersonResponse
                {
                 FirstName = "Vianne",
                 LastName = "Durano",
                 DateOfBirth = Convert.ToDateTime("01/02/1986")
                },
                new PersonResponse
                {
                 FirstName = "Vynn",
                 LastName = "Durano",
                 DateOfBirth = Convert.ToDateTime("01/02/1986")
                }
            };
        }

        [HttpGet("testcustom")]
        public MyCustomApiResponse Get()
        {
            var data = GetMockData();

            return new MyCustomApiResponse(DateTime.UtcNow, data,
                new Pagination
                {
                    CurrentPage = 1,
                    PageSize = 10,
                    TotalItemsCount = 200,
                    TotalPages = 20
                });
        }
}

Here's the result:

GET: https://localhost:44321/api/v1/tests/testcustom

{
    "code": 200,
    "message": "Success",
    "payload": [
        {
            "id": 0,
            "firstName": "Vince",
            "lastName": "Durano",
            "dateOfBirth": "1986-01-02T00:00:00",
            "fullName": "Vince Durano"
        },
        {
            "id": 0,
            "firstName": "Mitch",
            "lastName": "Durano",
            "dateOfBirth": "1986-01-02T00:00:00",
            "fullName": "Mitch Durano"
        },
        {
            "id": 0,
            "firstName": "Vianne",
            "lastName": "Durano",
            "dateOfBirth": "1986-01-02T00:00:00",
            "fullName": "Vianne Durano"
        },
        {
            "id": 0,
            "firstName": "Vynn",
            "lastName": "Durano",
            "dateOfBirth": "1986-01-02T00:00:00",
            "fullName": "Vynn Durano"
        }
    ],
    "sentDate": "2020-07-02T18:18:38.4682669Z",
    "pagination": {
        "totalItemsCount": 200,
        "pageSize": 10,
        "currentPage": 1,
        "totalPages": 20
    }
}
aparun22 commented 4 years ago

I am following same but doesn't work for me. so i have downloaded your source and modified bellow it works as expected am using asp.net core 3.1 API.

My Startup.cs

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
           // app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { UseCustomSchema = true, ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();

            app.UseCors("CorsPolicy");
            app.UseAuthentication();
            app.UseCompression();
            app.UseResponseCompression();
            //app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { UseCustomSchema = true });
            app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions { UseCustomSchema = true , ReferenceLoopHandling = ReferenceLoopHandling.Ignore});
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Novato Api 1.0");
            });
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

My Controller

 public NovatoApiResponse Get()
        {
            var rng = new Random();
          var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

            return new NovatoApiResponse(DateTime.UtcNow, forecast,
        new Pagination
        {
            CurrentPage = 1,
            PageSize = 10,
            TotalItemsCount = 200,
            TotalPages = 20
        });

        }

Modified Autowrapper fix the issue for me.

public async Task HandleSuccessfulRequestAsync(HttpContext context, object body, int httpStatusCode)
        {
            var bodyText = !body.ToString().IsValidJson() ? ConvertToJSONString(body) : body.ToString();

            dynamic bodyContent = JsonConvert.DeserializeObject<dynamic>(bodyText);

            Type type = bodyContent?.GetType();

            string jsonString;
            if (type.Equals(typeof(JObject)))
            {
                ApiResponse apiResponse = new ApiResponse();

                if (_options.UseCustomSchema)
                {
                    var formatJson = _options.IgnoreNullValue ? JSONHelper.RemoveEmptyChildren(bodyContent) : bodyContent;
                    await WriteFormattedResponseToHttpContextAsync(context, httpStatusCode, JsonConvert.SerializeObject(formatJson));
                }
                **else
                {
                    if (_hasSchemaForMappping && (_propertyMappings.Count == 0 || _propertyMappings == null))
                        throw new ApiException(ResponseMessage.NoMappingFound);
                    else
                        apiResponse = JsonConvert.DeserializeObject<ApiResponse>(bodyText);

                    if (apiResponse.StatusCode == 0 && apiResponse.Result == null && apiResponse.ResponseException == null)
                        jsonString = ConvertToJSONString(httpStatusCode, bodyContent, context.Request.Method);
                    else if ((apiResponse.StatusCode != httpStatusCode || apiResponse.Result != null) ||
                            (apiResponse.StatusCode == httpStatusCode && apiResponse.Result == null))
                    {
                        httpStatusCode = apiResponse.StatusCode; // in case response is not 200 (e.g 201)
                        jsonString = ConvertToJSONString(GetSucessResponse(apiResponse, context.Request.Method));

                    }
                    else
                        jsonString = ConvertToJSONString(httpStatusCode, bodyContent, context.Request.Method);

                }**

            }
            else
            {
                var validated = ValidateSingleValueType(bodyContent);
                object result = validated.Item1 ? validated.Item2 : bodyContent;
                jsonString = ConvertToJSONString(httpStatusCode, result, context.Request.Method);
                **await WriteFormattedResponseToHttpContextAsync(context, httpStatusCode, jsonString);**
            }

        }

Thanks, Arun.

proudmonkey commented 4 years ago

Your change makes sense to me. I should fix that in future release.

However, I noticed that you call the AutoWrapper middleware after UseRouting. You should call it before that:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
            app.UseHttpsRedirection();

            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Novato Api 1.0");
            });

            app.UseApiResponseAndExceptionWrapper(new AutoWrapperOptions
            {
                UseCustomSchema = true
            });

            app.UseRouting();

            app.UseCompression();
            app.UseResponseCompression();
            app.UseCors("CorsPolicy");
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
}
aparun22 commented 4 years ago

Thanks for your advise, I will make necessary changes. BTW great work it saved my time 💯 cheers keep rocking 👍