HttpContext在通过HTTPClient.PostAsJsonAsync调用的JsonConverter中不可用

本文关键字:JsonConverter 调用 HTTPClient PostAsJsonAsync HttpContext | 更新日期: 2023-09-27 18:03:09

设置

  1. 我有一个WebAPI控制器,它使用HttpClient调用web端点。PostAsJsonAsync
  2. 假设控制器方法的响应和对web端点的请求是相同的实体类型
  3. 我已经为这个实体类型注册了一个自定义JsonConverter。我有一个用例来访问这个转换器中的HttpContext

问题:当调用转换器的WriteJson方法时,在HttpClient期间序列化实体。PostAsJsonAsync HttpContext。当前为NULL

然而,当在WebAPI响应中序列化实体时调用相同的流时,上下文是可用的。

以前有人遇到过类似的问题吗?我不确定这个问题的原因是什么,什么可能的解决方案/变通方法。

我能够用一个示例WebAPI项目重新生成这种行为。以下是相关的代码片段:

[JsonConverter(typeof(EntityConverter))]
public interface IEntity
{
}
public class Entity : IEntity
{
}
public class EntityConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // httContext is NULL when deserializing the HttpClient request entity
        var httpContext = HttpContext.Current;
        var principal = httpContext?.User;
        Console.WriteLine("");
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return new Entity();
    }
    public override bool CanConvert(Type objectType)
    {
        return typeof(Entity) == objectType;
    }
}
public class ValuesController : ApiController
{
    // POST api/values
    public async Task<HttpResponseMessage> Post([FromBody]string value)
    {
        HttpClient client = new HttpClient();
        var message = await client.PostAsJsonAsync("http://example.com", new Entity());
        Console.WriteLine(message);
        return Request.CreateResponse(HttpStatusCode.Created, new Entity());
    }
}

HttpContext在通过HTTPClient.PostAsJsonAsync调用的JsonConverter中不可用

正如在这个答案中解释的那样,HttpContext.Current实际上是线程静态的,所以可能发生的是HttpClient.PostAsJsonAsync()实际上是在一个单独的线程上进行序列化,其中HttpContext.Current尚未初始化。虽然等待的async任务不一定会在单独的线程上运行,但它可能是——特别是在Json之后。. NET不直接支持异步序列化,建议使用Task.Factory.StartNew()

要解决这个问题,我建议从序列化内部删除对全局状态的依赖。备选方案包括:

  1. 在您的ApiController方法中,从HttpContext和每个Entity构造一个适当的数据传输对象,并将它们序列化。

  2. Entity构造函数中缓存HttpContext的必要信息,以便在序列化期间使用:

    public class Entity : IEntity
    {
        protected internal readonly IPrincipal Principal = HttpContext.Current?.User;
    }
    

    缓存HttpContext本身可能不是一个好主意,因为文档中声明

    此类型的任何公共静态(在Visual Basic中共享)成员都是线程安全的。不能保证任何实例成员都是线程安全的。

  3. 对于PostAsJsonAsync()的调用,您可以预序列化到JToken,然后发布:

    var entity = new Entity();
    var formatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter(); 
    // Or use GlobalConfiguration.Configuration.Formatters.JsonFormatter;
    var token = JToken.FromObject(entity, JsonSerializer.Create(formatter.SerializerSettings));
    HttpClient client = new HttpClient();
    var message = await client.PostAsJsonAsync("http://example.com", token);
    Console.WriteLine(message);
    

    这仍然会在序列化中留下对全局状态的依赖,这可能会在以后引起问题。