HttpContext недоступен в JsonConverter, вызываемом через HTTPClient.PostAsJsonAsync

c# asp.net-web-api json.net

398 просмотра

1 ответ

Настройка

  1. У меня есть контроллер WebAPI, который делает вызов к конечной точке сети, используя HttpClient.PostAsJsonAsync
  2. Допустим, что ответ метода контроллера и запрос к конечной точке сети - это один и тот же тип объекта
  3. У меня есть пользовательский JsonConverter, зарегистрированный для этого типа объекта. У меня есть случай использования для доступа к HttpContext в этом конвертере

Проблема : когда вызывается метод 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());
    }
}
Автор: madhurtanwani Источник Размещён: 08.11.2019 11:03

Ответы (1)


1 плюс

Как объясняется в этом ответе , HttpContext.Currentна самом деле поток статичен, поэтому возможно, что HttpClient.PostAsJsonAsync()он выполняет сериализацию в отдельном потоке, один из которых HttpContext.Currentне был инициализирован. Хотя ожидаемая asyncзадача не обязательно будет выполняться в отдельном потоке, это может произойти, тем более что Json.NET напрямую не поддерживает асинхронную сериализацию и рекомендует использоватьTask.Factory.StartNew() .

Чтобы обойти эту проблему, я бы рекомендовал удалить зависимость от глобального состояния изнутри сериализации. Альтернативы включают в себя:

  1. Внутри ваших ApiControllerметодов создайте соответствующий объект передачи данных из HttpContextи каждого Entityи сериализуйте их.

  2. Кэширование необходимой информации HttpContextвнутри Entityконструктора для использования во время сериализации:

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

    Кэширование HttpContextсамо по себе не может быть хорошей идеей, так как в документации говорится

    Любые открытые статические (Shared в 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);
    

    Это по-прежнему оставляет зависимость от глобального состояния внутри сериализации, что может вызвать проблемы в дальнейшем.

Автор: dbc Размещён: 20.08.2016 07:21
32x32