Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing ASP Web API Json serialization by which Action is invoked

I am looking at converting an existing JSON api from a hacky MVC3 implementation to the latest MVC4 Web Api. The MVC3 implementation uses JSON.NET to do all the serialization which will make the upgrade nice and smooth.

I am stuck on customizing how the results of some action get serialized. For instance I want some actions to return only a few properties of the outputted objects, whilst others may do rather deep serialization. In my current implementation, an action can add a bunch of serialization overrides by setting appropriate settings in the HttpContext. These are later picked up for custom serialization through a class derived from JsonResult. The main use of adding custom JsonConverters is to control and reduce the number of key/values getting serialized, and vary the parameters to serialize depending on the action (certain actions should return more object parameters than others).

Condensed example of a controller and the class controlling json serialization in my current MVC3 implementation:

public class TestController : JsonController {
    public JsonResult Persons() {
        ControllerContext.HttpContext.Items[typeof(IEnumerable<JsonConverter>)] = new JsonConverter[] {
            new InterfaceExtractorJsonConverter<IPersonForList>(),
            new StringEnumConverter()
        };

        ControllerContext.HttpContext.Items[typeof(IContractResolver)] = new SpecialCamelCasePropertyNamesContractResolver();
     }
}

public class JsonNetResult : JsonResult {
    public override void ExecuteResult(ControllerContext context) {
        var response = context.HttpContext.Response;

        var additionalConverters = context.HttpContext.Items[typeof(IEnumerable<JsonConverter>)] as IEnumerable<JsonConverter> ?? Enumerable.Empty<JsonConverter>();

        var contractResolver = context.HttpContext.Items[typeof(IContractResolver)] as IContractResolver ?? new JsonContractResolver();

        var typeNameHandling = TypeNameHandling.None;
        if (context.HttpContext.Items.Contains(typeof(TypeNameHandling)))
            typeNameHandling = (TypeNameHandling)context.HttpContext.Items[typeof(TypeNameHandling)];

        response.Write(JsonConvert.SerializeObject(Data, Formatting.Indented, new JsonSerializerSettings {
            ContractResolver = contractResolver,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            Converters = additionalConverters,
            TypeNameHandling = typeNameHandling
        }));
    }
}

In Web Api I see that I can get the Json formatter from the configuration and alter the serializations globally.

var config = new HttpSelfHostConfiguration("http://localhost:8080");

var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().Single();

jsonFormatter.SerializerSettings.ContractResolver = new SpecialCamelCasePropertyNamesContractResolver();
jsonFormatter.SerializerSettings.Converters = new[] { new InterfaceExtractorJsonConverter<ITesting>() };

However, I was planning on controlling the serialization of actions on an individual basis (or per controller) by adding some attributes to specify which JsonConverter's to use. Thus I would like the Json serializer to find the relevant attributes given to the invoked action/controller and alter serialization accordingly. I am not sure where and how to do this. Should I inherit from JsonMediaTypeFormatter and do the work there somehow? What other options do I have?

like image 621
sh54 Avatar asked Aug 01 '12 11:08

sh54


1 Answers

I've never seen anyone want to control the serialization in that way. But to achieve your goal with the minimal amount of rework, I would return all of that information from your method directly:

class JsonNetResponse {
    public IContractResolver ContractResolver { get;set; }
    // Other Json.Net bits
    public object Value { get; set; }
}

I would then create a custom Formatter than can handle those objects:

class JsonNetFormatter : MediaTypeFormatter {
    public override bool CanWriteType(Type t) {
        return typeof(JsonNetResponse).IsAssignableFrom(t);
    }
    // TODO WriteToStreamAsync which is basically a copy of your original JsonNetResult
}
like image 181
Chris Avatar answered Sep 21 '22 20:09

Chris