1

我对微软全新的框架 ASP.NET MVC WebAPI 感到非常困惑。我尝试使用 JSONP 数据为跨站点 API 创建完整的解决方案。

首先,我将它们的默认 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;
    }
}
4

1 回答 1

0

您的操作不会受到打击,因为它无法对您的查询参数进行模型绑定。也JsonP仅适用于 HTTP GET,因此不会选择您的格式化程序进行反序列化。您如何期望您的FlightAvailabilityQuery反序列化?我从你的URL中看到了很多查询参数,你想把它变成FlightAvailabilityQuery吗?

最简单的方法是使用 FromUri。

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

如果由于某种原因不起作用,您可以尝试在操作上添加单独的查询参数名称,例如origin、isOneWay、destination。等等。然后在您的操作中构造 FlightAvailabilityQuery 对象。此外,如果您有很多想要重用此模型绑定逻辑的操作,您可以注册一个自定义参数绑定来解决这个问题。请参阅此链接了解如何注册自定义参数绑定来解决此问题。

希望这可以帮助!

于 2013-03-01T06:32:44.070 回答