Почему WebAPI не использует форматировщик JSONP для десериализации модели?

Я очень, очень запутался в новой платформе Microsoft, ASP.NET MVC WebAPI. Я пытаюсь создать полное решение для межсайтового API с данными JSONP.

Во-первых, я изменяю их WebApiConfig по умолчанию на следующий код.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new {id = RouteParameter.Optional});

        // Custom customization
        config.Formatters.Clear();
        config.Formatters.Add(new JsonpFormatter());
    }
}

Я использую jQuery для создания запроса на этот веб-сайт API.

// jQuery will create HTTP GET the following URL
// http://localhost:3557/api/FlightAvailability/SearchFlight?callback=jQuery18206342989655677229_1353568617029&origin=JFK&destination=SLC&isOneWayFlight=false&departFlightDate=Wed%2C+28+Nov+2012+17%3A00%3A00+GMT&returnFlightDate=Wed%2C+05+Dec+2012+17%3A00%3A00+GMT&numberOfGuests=1&numberOfChildren=1&numberOfInfants=1&preferredCurrency=USD&query=%7B+Origin%3A+'JFK'+%7D&flightDate=Wed%2C+28+Nov+2012+17%3A00%3A00+GMT&_=1353568618465

$.ajax
({
    url: 'http://localhost:3557/api/FlightAvailability/SearchFlight',
    dataType: 'jsonp',
    data: $.postify(model),
    success: processResponse
});

Я создаю действие для обработки вышеуказанного запроса. Все правильно. Я могу вызвать это действие, но WebAPI не использует средство форматирования JSONP для десериализации моего объекта запроса.

Однако я пытаюсь напрямую вызвать ContentNegotiator, чтобы узнать, какой модуль форматирования обработает мой запрос. Удивительно, что negotiatorResult является моим средством форматирования JSONP.

[HttpGet]
public List<FlightInfo> SearchFlight(FlightAvailabilityQuery query)
{
    var negotiator = Configuration.Services.GetContentNegotiator();
    var negotiatorResult = negotiator.Negotiate(typeof (FlightAvailabilityQuery), Request, Configuration.Formatters);

    var flight = new FlightsAvailability();

    var result = flight.GetAvailability(WebApiAuthentication.UserInfo.SessionService, query);

    return result;
}

Почему WebAPI не использует форматировщик JSONP для десериализации объекта запроса FlightAvailabilityQuery?

введите здесь описание изображения

PS. Я пытаюсь разбить всю возможную строку в форматировщике JSONP, но Visual Studio не попадает ни в одну точку останова, напрямую переходя к методу действия без вызова в моем единственном форматере. Однако, когда я напрямую вызываю ContentNegotiator, он правильно попадает в мою точку останова.

введите здесь описание изображения

Обновление №1: добавлен исходный код средства форматирования JSONP

public class JsonpFormatter : JsonMediaTypeFormatter
{
    private readonly JsonSerializerSettings _serializerSettings;
    private string _jsonpCallbackFunction;

    public JsonpFormatter()
    {
        JsonpParameterName = "callback";
        _serializerSettings = new JsonSerializerSettings();
        _serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
        _serializerSettings.Converters.Add(new IsoDateTimeConverter());

        MediaTypeMappings.Add(new ExtendedQueryStringMapping(JsonpParameterName, "application/json"));
    }

    public string JsonpParameterName { get; set; }

    public override bool CanReadType(Type type)
    {
        return true;
    }

    public override bool CanWriteType(Type type)
    {
        return true;
    }

    public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
    {
        var formatter = new JsonpFormatter()
        {
            _jsonpCallbackFunction = GetJsonCallbackFunction(request)
        };

        // this doesn't work unfortunately
        //formatter.SerializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;

        formatter.SerializerSettings.Converters.Add(new StringEnumConverter());
        formatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        formatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

        return formatter;
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger)
    {
        // Create a serializer
        var serializer = JsonSerializer.Create(_serializerSettings);

        // Create task reading the content
        return Task.Factory.StartNew(() =>
        {
            using (var streamReader = new StreamReader(stream, Encoding.UTF8))
            {
                using (var jsonTextReader = new JsonTextReader(streamReader))
                {
                    return serializer.Deserialize(jsonTextReader, type);
                }
            }
        });
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
    {
        if (string.IsNullOrEmpty(_jsonpCallbackFunction))
            return base.WriteToStreamAsync(type, value, stream, content, transportContext);

        StreamWriter writer = null;

        // write the pre-amble
        try
        {
            writer = new StreamWriter(stream);
            writer.Write(_jsonpCallbackFunction + "(");
            writer.Flush();
        }
        catch (Exception ex)
        {
            try
            {
                if (writer != null)
                    writer.Dispose();
            }
            catch { }

            var tcs = new TaskCompletionSource<object>();
            tcs.SetException(ex);
            return tcs.Task;
        }

        return base.WriteToStreamAsync(type, value, stream, content, transportContext)
                   .ContinueWith(innerTask =>
                        {
                            if (innerTask.Status == TaskStatus.RanToCompletion)
                            {
                                writer.Write(")");
                                writer.Flush();
                            }

                        }, TaskContinuationOptions.ExecuteSynchronously)
                    .ContinueWith(innerTask =>
                        {
                            writer.Dispose();
                            return innerTask;

                        }, TaskContinuationOptions.ExecuteSynchronously)
                    .Unwrap();
    }

    private string GetJsonCallbackFunction(HttpRequestMessage request)
    {
        if (request.Method != HttpMethod.Get)
            return null;

        var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
        var queryVal = query[this.JsonpParameterName];

        if (string.IsNullOrEmpty(queryVal))
            return null;

        return queryVal;
    }
}

person Soul_Master    schedule 22.11.2012    source источник
comment
Я думаю, что могу неправильно понять какую-то концепцию WebAPI. Я трачу почти 3 дня на работу над этой проблемой, но так и не нашел полного решения для JSONP.   -  person Soul_Master    schedule 22.11.2012
comment
Кстати, ContentNegotiator используется только на стороне ответа... но я удивлен, почему вы видите такое поведение, учитывая, что у вас есть только один форматтер, который является форматером JsonP, и его нужно было использовать для десериализации запроса... Вы можете поделиться своим кодом приложения?   -  person Kiran Challa    schedule 22.11.2012
comment
Что предлагает метод ReadFromStreamAsync? Прямо сейчас я пытаюсь создать собственный класс HttpParameterBinding для десериализации модели.   -  person Soul_Master    schedule 22.11.2012
comment
ReadFromStreamAsync вызывается при чтении тела запроса/ответа. не могли бы вы поделиться исходным кодом с [email protected], я мог бы взглянуть на то, что происходит?   -  person Kiran Challa    schedule 22.11.2012


Ответы (1)


Ваше действие не будет выполнено, потому что оно не может привязать модель к вашему параметру запроса. Также JsonP предназначен только для HTTP GET, поэтому ваш модуль форматирования не будет выбран для десериализации. Как вы ожидаете, что ваш FlightAvailabilityQuery будет десериализован? Я видел много параметров запроса из вашего URL. Вы хотите преобразовать их в FlightAvailabilityQuery?

Самый простой способ получить это — использовать FromUri.

public List<FlightInfo> SearchFlight([FromUri]FlightAvailabilityQuery query)

Если по какой-то причине это не сработает, вы можете попробовать добавить в действие отдельное имя параметра запроса, например origin, isOneWay, destination. и т. д. Затем внутри действия создайте объект FlightAvailabilityQuery. Кроме того, если у вас есть много действий, в которых вы хотите повторно использовать эту логику привязки модели, вы можете зарегистрировать привязку настраиваемого параметра, чтобы решить эту проблему. См. эту ссылку о том, как зарегистрировать привязку настраиваемого параметра, чтобы решить эту проблему.

Надеюсь это поможет!

person Hongmei Ge    schedule 01.03.2013