Защо 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