2

I'm making a .net Core WebApi using .Net Core 2.2. The API is ready but the failure message and response is where I'm stuck at.

Right now, I'm getting respose like below

json

{
    "empId":1999,
    "empName":"Conroy, Deborah",
    "enrollmentStatus":true,
    "primaryFingerprintScore":65,
    "secondaryFingerprintScore":60,
    "primaryFingerprint":null,
    "secondaryFingerprint":null,
    "primaryFingerprintType":null,
    "secondaryFingerprintType":null}
}

I created a json formatter class and wrote the below code

public class SuperJsonOutputFormatter : JsonOutputFormatter
{
    public SuperJsonOutputFormatter(
        JsonSerializerSettings serializerSettings,
        ArrayPool<char> charPool) : base(serializerSettings, charPool)
    {
    } 

    public override async Task WriteResponseBodyAsync(
        OutputFormatterWriteContext context,
        Encoding selectedEncoding)
    {

        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (selectedEncoding == null)
            throw new ArgumentNullException(nameof(selectedEncoding));
        using (TextWriter writer =
            context.WriterFactory(
                context.HttpContext.Response.Body,
                selectedEncoding))
        {

            var rewrittenValue = new
            {
                resultCode = context.HttpContext.Response.StatusCode,
                resultMessage =
                ((HttpStatusCode)context.HttpContext.Response.StatusCode)
                    .ToString(),
                result = context.Object
            };

            this.WriteObject(writer, rewrittenValue);
            await writer.FlushAsync();
        }
    }

I expect all the error codes to be sent as generic error messages like the JSON below.

FOR STATUS OKAY:

{
    "status" : True,
    "error"  : null,
    "data" : {
        {
            "empId":1999,
            "empName":"Conroy, Deborah",
            "enrollmentStatus":true,
            "primaryFingerprintScore":65,
            "secondaryFingerprintScore":60,
            "primaryFingerprint":null,
            "secondaryFingerprint":null,
            "primaryFingerprintType":null,
            "secondaryFingerprintType":null}
        }
    }   
}

FOR OTHER STATUS LIKE 404, 500, 400, 204

{
    "status" : False,
    "error"  : {
        "error code" : 404,
        "error description" : Not Found
    },
    "data" : null   
}

1 Answer 1

2

I expect all the error codes to be sent as generic error messages like the JSON below

You're almost there. What you need to do is enabling your SuperJsonOutputFormatter.

A Little Change to Your Formatter

Firstly, your formatter didn't return a json with the same schema as you want. So I create a dummy class to hold the information for error code and error description:

public class ErrorDescription{
    public ErrorDescription(HttpStatusCode statusCode)
    {
        this.Code = (int)statusCode;
        this.Description = statusCode.ToString();
    }

    [JsonProperty("error code")]
    public int Code {get;set;}
    [JsonProperty("error description")]
    public string Description {get;set;}
}

And change your WriteResponseBodyAsync() method as below:

    ...

    using (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) {
        var statusCode = context.HttpContext.Response.StatusCode;
        var rewrittenValue = new {
            status = IsSucceeded(statusCode),
            error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
            data  = context.Object,
        };
        this.WriteObject(writer, rewrittenValue);
        await writer.FlushAsync();
    }

Here the IsSucceeded(statusCode) is a simple helper method that you can custom as you need:

private bool IsSucceeded(int statusCode){
    // I don't think 204 indicates that's an error. 
    //     However, you could comment out it if you like
    if(statusCode >= 400 /* || statusCode==204 */ ) { return false; }
    return true;
}

Enable your Formatter

Secondly, to enable your custom Formatter, you have two approaches: One way is to register it as an global Formatter, the other way is to enable it for particular Controller or Action. Personally, I believe the 2nd way is better. So I create a Action Filter to enable your formatter.

Here's an implementation of the Filter that enables your custom formatter dynamically:

public class SuperJsonOutputFormatterFilter : IAsyncActionFilter{
    private readonly SuperJsonOutputFormatter _formatter;
    // inject your SuperJsonOutputFormatter service
    public SuperJsonOutputFormatterFilter(SuperJsonOutputFormatter formatter){
        this._formatter = formatter;
    }
    // a helper method that provides an ObjectResult wrapper over the raw object
    private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj){
        var wrapper = new ObjectResult(obj);
        wrapper.Formatters.Add(this._formatter);
        context.Result= wrapper;
        return wrapper;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        ActionExecutedContext resultContext = await next();
        // in case we get a 500
        if(resultContext.Exception != null && ! resultContext.ExceptionHandled){
            var ewrapper = this.WrapObjectResult(resultContext, new {});
            ewrapper.StatusCode = (int) HttpStatusCode.InternalServerError; 
            resultContext.ExceptionHandled = true;
            return;
        }
        else {
            switch(resultContext.Result){
                case BadRequestObjectResult b :      // 400 with an object
                    var bwrapper=this.WrapObjectResult(resultContext,b.Value);
                    bwrapper.StatusCode = b.StatusCode;
                    break;
                case NotFoundObjectResult n :        // 404 with an object
                    var nwrapper=this.WrapObjectResult(resultContext,n.Value);
                    nwrapper.StatusCode = n.StatusCode;
                    break;
                case ObjectResult o :                // plain object
                    this.WrapObjectResult(resultContext,o.Value);
                    break;
                case JsonResult j :                  // plain json
                    this.WrapObjectResult(resultContext,j.Value);
                    break;
                case StatusCodeResult s:             // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case 
                    var swrapper = this.WrapObjectResult(resultContext, new {});
                    swrapper.StatusCode = s.StatusCode;
                    break;
            }
        }
    }

}

And don't forget to register your formatter as a service :

    services.AddScoped<SuperJsonOutputFormatter>();

Finally, when you want to enable your formatter, just add a [TypeFilter(typeof(SuperJsonOutputFormatterFilter))] annotation for the controller or action.

Demo

Let's create an action method for Test:

[TypeFilter(typeof(SuperJsonOutputFormatterFilter))]
public IActionResult Test(int status)
{
    // test json result(200)
    if(status == 200){ return Json(new { Id = 1, }); }
    // test 400 object result
    else if(status == 400){ return BadRequest( new {}); } 
    // test 404 object result
    else if(status == 404){ return NotFound(new { Id = 1, }); }
    // test exception
    else if(status == 500){ throw new Exception("unexpected exception"); }
    // test status code result
    else if(status == 204){ return new StatusCodeResult(204); }

    // test normal object result(200)
    var raw = new ObjectResult(new XModel{
        empId=1999,
        empName = "Conroy, Deborah",
        enrollmentStatus=true,
        primaryFingerprintScore=65,
        secondaryFingerprintScore=60,
        primaryFingerprint = null,
        secondaryFingerprint= null,
        primaryFingerprintType=null,
        secondaryFingerprintType=null
    });
    return raw;
}

Screenshot:

enter image description here

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

3 Comments

Hey @itminus- How to handle post and put requests as they return status code 204 No Content. So how to send success message in post and put requests
@RahulDev Actually, I don't think 204 should be treated as an error. In your scenario every response should has content and NoContentResult will be casted into StatusCodeResult. I would suggest you use method Created(obj) or CreatedAt(...) or new CreatedAtActionResult(...) to for creation.
Hey @itminus- the above code doesn't work for .net core 3.1. Is there any way to implement the same logic in newtonsoftjsonoutputformatter ?

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.