johnny 5
johnny 5

Reputation: 21013

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.

Upvotes: 2

Views: 793

Answers (2)

johnny 5
johnny 5

Reputation: 21013

Incase anyone was wondering, Response Content is an HTTPContent, which inherits from ByteArrayContent. So if you have your JSON Serialized already, all you have to do is put it into a byte array.

filterContext.ActionContext.Response.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(pageJson));

Upvotes: 0

ManOVision
ManOVision

Reputation: 1893

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.

Upvotes: 1

Related Questions