简体   繁体   English

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

[英]Change JsonFormatter for a single request in ASP.NET Web API

I have an action filter defined as follows, registered globally in my Web API project: 我有一个如下定义的动作过滤器,在我的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; } }
}

This works quite well, but I seem to be getting interference between requests; 这很有效,但我似乎在请求之间受到干扰; if I issue one request at a time to action methods where one has the PascalCasedResultAttribute and one doesn't, everything works as expected - but if I issue two very close to each-other, both sometimes end up with the same casing. 如果我一次向一个具有PascalCasedResultAttribute但没有一个的动作方法发出一个请求,一切都按预期工作 - 但如果我发出两个非常接近彼此,两者有时最终都会使用相同的大小写。

I interpret this behavior as a sign that the changes to actionContext.RequestContext.Configuration.Formatters really changes the configuration for the entire app, and not just for the current request, and sometimes the requests overlap. 我将此行为解释为对actionContext.RequestContext.Configuration.Formatters的更改确实更改了整个应用程序的配置,而不仅仅是当前请求,有时请求重叠。 Basically, I've based my solution on the following sequence of events: 基本上,我的解决方案基于以下一系列事件:

  1. Request 1 chooses its serializer 请求1选择其序列化器
  2. Request 1 is serialized using the last chosen serializer 使用最后选择的序列化器序列化请求1
  3. Request 2 chooses its serializer 请求2选择其序列化器
  4. Request 2 is serialized using the last chosen serializer 使用最后选择的序列化器序列化请求2

Note that if step two and three change order, the behavior is changed. 请注意,如果第二步和第三步更改顺序,则会更改行为。 What I want is rather 我想要的是相当的

  1. Request 1 chooses its serializer 请求1选择其序列化器
  2. Request 1 is serialized using serializer 1 使用串行器1序列化请求1
  3. Request 2 chooses its serializer 请求2选择其序列化器
  4. Request 2 is serialized using serializer 2 使用串行器2序列化请求2

where I (or the framework) can switch order of 2 and 3 without changing the behavior. 我(或框架)可以在不改变行为的情况下切换2和3的顺序。

How do I best accomplish this? 我怎样才能做到最好?

The problem is that you are changing a global variable, which obviously is not consistent on ordering. 问题是你正在改变一个全局变量,它在排序时显然不一致。

What you could do is manually serialize in the action and just return the string as appropriate. 您可以做的是在操作中手动序列化,并根据需要返回字符串。

You can either then supply a flag to choose which serializer to use in your request or use cookies to remember the client's choice. 然后,您可以提供一个标志来选择在您的请求中使用哪个序列化程序,或者使用cookie来记住客户的选择。

I ended up doing the following instead: 我最终做了以下事情:

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;
}

The main hurdle for me to jump before I got to this solution was to realize that I could await continuation() to let the action method execute, and then work with the response. 在我得到这个解决方案之前,我跳过的主要障碍是意识到我可以await continuation()让action方法执行,然后使用响应。

This approach still has the downside that if the client asks for eg XML, it will still get JSON, because I'm choosing the serializer manually without looking at the Accepts header. 这种方法仍有缺点,如果客户端要求例如XML,它仍将获得JSON,因为我手动选择序列化程序而不查看Accepts标头。 In my use case I'm totally OK with that, since we're only using JSON anyway, but if it does matter to you, then you need something more complicated in place of the ternary statement I have where the formatter is chosen. 在我的用例中,我完全没问题,因为我们只是使用JSON,但是如果它对你很重要,那么你需要更复杂的东西代替我选择格式化器的三元语句。 (If there is a simple way to do this only for stuff that's been serialized to a given format, I'd be happy to learn about it!) (如果有一种简单的方法只针对已经序列化为给定格式的内容,我很乐意了解它!)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM