在ASP.NET Web API中为单个请求更改JsonFormatter

时间:2015-02-09 10:32:02

标签: c# asp.net serialization asp.net-web-api

我有一个如下定义的动作过滤器,在我的Web API项目中全局注册:

public class ResultCasingFilter : IActionFilter
{
    private static JsonMediaTypeFormatter _pascalCasingFormatter;
    private static JsonMediaTypeFormatter _camelCasingFormatter;

    // Constructor that initializes formatters left out for brevity

    public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
    {
        actionContext.RequestContext.Configuration.Formatters.Clear();
        actionContext.RequestContext.Configuration.Formatters.Add(
            ResponseShouldBePascalCased(actionContext)
            ? _pascalCasingFormatter
            : _camelCasingFormatter);
        return continuation();
    }

    private static bool ResponseShouldBePascalCased(HttpActionContext actionContext)
    {
        return actionContext.ActionDescriptor
            .GetCustomAttributes<PascalCasedResultAttribute>().Any();
    }

    public bool AllowMultiple { get { return false; } }
}

这很有效,但我似乎在请求之间受到干扰;如果我一次发出一个请求行动方法,其中一个人有PascalCasedResultAttribute而一个人没有,那么一切都按预期工作 - 但如果我发出两个非常接近彼此,两者有时最终都会相同的外壳。

我将此行为解释为actionContext.RequestContext.Configuration.Formatters的更改确实更改了整个应用的配置,而不仅仅是当前请求,有时请求重叠。基本上,我的解决方案基于以下一系列事件:

  1. 请求1选择其序列化程序
  2. 使用最后选择的序列化程序
  3. 序列化请求1
  4. 请求2选择其序列化程序
  5. 使用最后选择的序列化程序序列化请求2
  6. 请注意,如果第二步和第三步更改顺序,则会更改行为。我想要的是

    1. 请求1选择其序列化程序
    2. 使用序列化程序1序列化请求1
    3. 请求2选择其序列化程序
    4. 使用序列化程序2序列化请求2
    5. 我(或框架)可以在不改变行为的情况下切换2和3的顺序。

      我如何才能最好地完成这项工作?

2 个答案:

答案 0 :(得分:0)

问题在于您正在更改全局变量,这在订购时显然不一致。

您可以做的是在操作中手动序列化,并根据需要返回字符串。

然后,您可以提供一个标志,以选择在您的请求中使用哪个序列化程序,或使用Cookie记住客户的选择。

答案 1 :(得分:0)

我最后做了以下事情:

public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
    // Let the action method execute, resulting in a serialized response
    var responseMessage = await continuation();

    if (responseMessage.Content is ObjectContent)
    {
        // Get the message content in its unserialized form, and choose formatter
        var content = responseMessage.Content as ObjectContent;
        var formatter = ResponseShouldBePascalCased(actionContext)
                        ? _pascalCasingFormatter
                        : _camelCasingFormatter;

        // Re-serialize content, with the correctly chosen formatter
        responseMessage.Content = new ObjectContent(content.ObjectType, content.Value, 
                                                    formatter);
    }
    // Return the (possibly) re-serialized message
    return responseMessage;
}

在我开始使用此解决方案之前,我跳过的主要障碍是意识到我可以await continuation()让action方法执行,然后使用响应。

这种方法仍有缺点,如果客户要求例如XML,它仍然会获得JSON,因为我在不查看Accepts标题的情况下手动选择序列化程序。在我的用例中,我完全没问题,因为我们只是使用JSON,但是如果它对你很重要,那么你需要更复杂的东西代替我选择格式化器的三元语句。 (如果有一种简单的方法只对已经序列化为给定格式的内容执行此操作,我很乐意了解它!)