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

时间:2016-08-20 08:13:53

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

设置

  1. 我是一个WebAPI控制器,它使用HttpClient.PostAsJsonAsync调用Web端点
  2. 让我们说,控制器方法和请求对Web端点的响应是相同的实体类型
  3. 我为此实体类型注册了自定义JsonConverter。我有一个用例来访问此转换器中的HttpContext
  4. 问题:调用转换器的WriteJson方法时,要在HttpClient.PostAsJsonAsync期间序列化实体,HttpContext.Current为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());
        }
    }
    

1 个答案:

答案 0 :(得分:1)

正如this answer中所解释的,HttpContext.Current实际上是线程静态的,所以可能发生的是HttpClient.PostAsJsonAsync()实际上是在单独进行序列化线程,其中HttpContext.Current尚未初始化的线程。虽然等待async任务不一定会在单独的线程上运行,但它可能是 - 尤其是因为Json.NET不直接支持异步序列化而是recommends using Task.Factory.StartNew()

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

  1. ApiController方法中,从HttpContext和每个Entity构建一个合适的data transfer object,并将其序列化。

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

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

    documentation

    以来缓存HttpContext本身可能不是一个好主意
      

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

  3. 对于PostAsJsonAsync()的来电,您可以预先序列化为JToken,然后发布:{/ p>

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

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