4

I've built a Web API application and found an issue (which currently treated badly in my code), the issue summarized in wrapping all Json objects which returned from All API actions with custom nodes(roots).

i.e: I have this json (array) response:

[
  {
    "Category": "Pages",
    "Users": [
      {
        "ID": "1",
        "Fname": "Foo",
        "Lname": "Bar"
      }
    ]
  }
]

And Need this response:

{
  "Object": {
    "Body": [
      {
        "Category": "Pages",
        "Users": [
          {
            "ID": "1",
            "Fname": "Foo",
            "Lname": "Bar"
          }
        ]
      }
    ]
  }
}

So here I just wrapped the response inside {"Object":{"Body": <Response Here>}}

And this I need it to be applied on all API Json responses of type Array.

And for simple Json object response, I need it just to be wrapped like {"Object": <Response Here>}

I wrapped the Json response currently in each controller action by this code:

 public JsonResult Categories()
 {
   return Json(new { Object= new { Body= GetCategoriesList() } }, JsonRequestBehavior.AllowGet);
 }

Sure this achievement is so bad because I have to repeat this wrapping in each action.

My Question Is:

How to create ActionFilterAttribute to be called after each action execution to wrap the response as per the above Json sample?

i.e. for creating the filter:

 public class JsonWrapper: System.Web.Mvc.ActionFilterAttribute
 {
   public override void OnActionExecuted(ActionExecutedContext filterContext)
   {
   }
 }

i.e. for calling the filter:

[JsonWrapper]
public class APIController : Controller

And also to set the response content type in the same filter "application/json"

teo van kot
  • 12,350
  • 10
  • 38
  • 70
Moamen Naanou
  • 1,683
  • 1
  • 21
  • 45
  • 1
    Possible duplicate of [Proper JSON serialization in MVC 4](http://stackoverflow.com/questions/17244774/proper-json-serialization-in-mvc-4) – MrClan Aug 24 '16 at 10:47
  • You need a **custom json serializer** instead of a filter. – MrClan Aug 24 '16 at 10:48

2 Answers2

4

If suppose here if what you looking for:

public class JsonWrapperAttribute : ActionFilterAttribute, IActionFilter
{
    void IActionFilter.OnActionExecuted(ActionExecutedContext context)
    {
        //Check it's JsonResult that we're dealing with
        JsonResult jsonRes = context.Result as JsonResult;
        if (jsonRes == null)
            return;

        jsonRes.Data = new { Object = new { Body = jsonRes.Data } }
    }
}

Here is how you can use it:

[JsonWrapper]
public JsonResult Index()
{
    var data = new
    {
        a = 1,
        b = 2
    };
    return Json(data, JsonRequestBehavior.AllowGet);
}

Result will be:

{"Object":{"Body":{"a":1,"b":2}}}
Graham King
  • 436
  • 5
  • 14
teo van kot
  • 12,350
  • 10
  • 38
  • 70
  • Thanks it's perfect – Moamen Naanou Aug 24 '16 at 11:38
  • What if I need to create different wrapping for Json Object (not array of Json Objects) like just {"Body":{"a":1,"b":2} and keep array the same – Moamen Naanou Aug 24 '16 at 13:23
  • @MoamenNaanou change line `Data = new { Object = new { Body = jsonRes.Data } }` to whatever you like structure. – teo van kot Aug 24 '16 at 13:40
  • 1
    @MoamenNaanou it is JsonArray becouse we checked it on JsonResult in here: `JsonResult jsonRes = context.Result as JsonResult; if (jsonRes == null) return` – teo van kot Aug 24 '16 at 13:43
  • I think the use of an attribute will be quite confusing. Without looking at the code, what happens if you apply the attribute but return a ViewResult etc? If you insist on using the attribute, there's no need to create a new JsonResult and assign it to the context, just do `jsonRes.Data = new { Object = new { Body = jsonRes.Data } }` – Graham King Aug 24 '16 at 14:43
  • @GrayKing test it. I'm open to any improvement. You can edit code in my answer with your changes and comments and i accept it. – teo van kot Aug 24 '16 at 14:45
  • @teovankot didn't know you could do that. Edit submitted. – Graham King Aug 24 '16 at 14:49
2

To prevent yourself having to repeat wrapping in each action you could either write an extension method which would do the wrapping for you

public static class ControllerExtensions 
{
    public static JsonResult WrappedJson(this Controller controller, object data, JsonRequestBehavior behavior)
    {
        return new JsonResult
        {
            Data = new { Object = new { Body = data } },
            JsonRequestBehavior = behavior
        };
    }
}

or create a new ActionResult class (and add extension methods to return that)

public class WrappedJsonResult : JsonResult
{
    public new object Data
    {
        get
        {
            if (base.Data == null)
            {
                return null;
            }

            return (object) ((dynamic) base.Data).Object.Body;
        }

        set { base.Data = new {Object = new {Body = value}}; }
    }
}
Graham King
  • 436
  • 5
  • 14