Search code examples
c#asp.net-web-apiaction-filteronactionexecuted

WebAPI Temporarily Override JsonFormatter from OnActionExecuted


I'm trying to create an attribute that will serialize data return from an action differently

public override void OnActionExecuted(HttpActionExecutedContext filterContext)
{
    var content = (filterContext.Response.Content as ObjectContent);

    if (content == null)
    {
        return;
    }

    if (content.ObjectType.IsGenericType 
        && content.ObjectType.GetGenericTypeDefinition() == typeof (Page<>))
    {
        var pageObject = (content.Value as IPage);
        var jsonFormatterRule = new JsonFormatterRule();
        var pageJson = JsonConvert.SerializeObject(pageObject.ItemsArray, 
                                                jsonFormatterRule.GetPascalCasedSettings());

       //How do I set the content that \/ doesn't compile?
       //filterContext.Response.Content = pageJson;
   }
}

This is the JsonFormatterRules incase anyone wanted to see them.

public JsonSerializerSettings GetDefaultSettings()
{
    var settings = new JsonSerializerSettings()
    {
        Formatting = Formatting.Indented,
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
    };

    settings.Converters.AddRange(defaultConfiguredConverters);
    return settings;
}

public JsonSerializerSettings GetPascalCasedSettings()
{
    var settings = this.GetDefaultSettings();
    settings.ContractResolver = new DefaultContractResolver();

    return settings;
}

How can I Set the Content From On Action Executed? I cannot change the default serializer to the DefaultContract Globally because it could threading issues.

Also I'd prefer not to have to create a new response and copy over the Headers from the old one that seems like over kill.


Solution

  • One way to do this would be to define a custom formatter.

    First, define your attribute:

    [AttributeUsage(AttributeTargets.Class)]
    public sealed class SpecialSerializeAttribute : Attribute
    {
    }
    

    Now create a formatter that will find the attribute:

    public class SpecialSerializeFormatter : MediaTypeFormatter
    {
        public SpecialSerializeFormatter()
        {
            //You can add any other supported types here.
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        }
    
        public override bool CanReadType(Type type)
        {
            //you can just return false if you don't want to read any differently than your default way
            //if you return true here, you should override the ReadFromStreamAsync method to do custom deserialize
            return type.IsDefined(typeof(SpecialSerializeAttribute), true));
        }
    
        public override bool CanWriteType(Type type)
        {
            return type.IsDefined(typeof(SpecialSerializeAttribute), true));
        }
    
        public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
            TransportContext transportContext)
        {
    
            //value will be your object that you want to serialize
    
            //add any custom serialize settings here
            var json = JsonConvert.SerializeObject(value);
    
            //Use the right encoding for your application here
            var byteArray = Encoding.UTF8.GetBytes(json);
            await writeStream.WriteAsync(byteArray, 0, byteArray.Length);
        }
    }
    

    Register the formatter in you WebApiConfig.cs

    You can also build a formatter for each type directly and then you don't have to do the Attribute. Just change your CanRead and CanWrite methods. I find basing these of the direct Type's gives better results since it's not such a generic formatter and you may need to apply custom logic based on the type, but the above answer should get you what you need.