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

Change JsonFormatter for a single request in 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. 请求 1 使用最后选择的序列化器序列化
  3. 请求 2 选择它的序列化程序
  4. 请求 2 使用最后选择的序列化器序列化

请注意,如果第二步和第三步改变顺序,行为也会改变。我要的是

  1. 请求 1 选择它的序列化器
  2. 请求 1 使用序列化器 1 序列化
  3. 请求 2 选择它的序列化程序
  4. 请求 2 使用序列化器 2 序列化

我(或框架)可以在不改变行为的情况下切换 2 和 3 的顺序。

我怎样才能最好地做到这一点?

问题是您正在更改一个全局变量,这显然在排序上不一致。

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

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

我最终改为执行以下操作:

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()让动作方法执行,然后处理响应。

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