【问题标题】:MediaTypeFormatter WriteToStreamAsync not called unless I add to Accept headers除非我添加到 Accept 标头,否则不会调用 MediaTypeFormatter WriteToStreamAsync
【发布时间】:2013-03-24 14:47:32
【问题描述】:

我有一个MediaTypeFormatter,它将图像的内部代表转换为 png/jpeg/等。如果有人要求。但是,我的 WriteToStreamAsync 永远不会被调用,除非我添加图像/png 或类似于接受标头。

首先,这是我的 webapi 方法,为简洁起见删除了一些关键位:

public ImageFormatter.BinaryImage GetImage(int cId, int iId)
{
    ....

    using (var input = iFIO.OpenRead())
    {
        input.Read(b.data, 0, (int)iFIO.Length);
    }

    // With this next line my mediatypeformatter is correctly called.
    Request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("image/png"));

    return b;
}

这是我的MediaTypeFormatter 的写入部分(还有一个读取部分,实际上效果很好)。

namespace PivotWebsite.MediaFormatters
{
    public class ImageFormatter : MediaTypeFormatter
    {
        public class BinaryImage
        { 
            public byte[] data;
            public string metaData;
        }

        public ImageFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
        }

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

        public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            var b = value as BinaryImage;
            if (b == null)
                throw new InvalidOperationException("Can only work with BinaryImage types!");

            await writeStream.WriteAsync(b.data, 0, b.data.Length);
        }
    }
}

我希望能够做的是,在WriteToStreamAsync 中,更改传出标题以将 Content-Type 包含为“image/png”(或其他任何内容,具体取决于数据类型)。

但是,当我从带有“http://my.testdomain.net:57441/api/Images?cID=1&iID=1”之类的 URL 的 Web 浏览器调用它时,WriteToStreamAsync 永远不会被调用(接受的标头列为 {text/html, application/xhtml+xml, */* })。如果我在上面添加了添加正确图像类型的行,那么一切都会按我的预期调用。

我在这里缺少什么?接受的“*/*”标头应该触发了我的媒体格式化程序,对吧?或者...我是否缺少有关 Web API 管道的基本知识。

【问题讨论】:

    标签: asp.net-web-api mediatypeformatter


    【解决方案1】:

    如果 Accept 标头为“/”,您是否希望始终使用图像格式化程序?如果是这种情况,那么您应该首先在 Formatters 集合中插入您的格式化程序,如下所示:

    config.Formatters.Insert(0, new ImageFormatter());
    

    当没有像您的情况那样的完全 Accept 标头匹配时会发生什么情况是,第一个可以写入类型的格式化程序被选中来序列化对象。所以如果你先注册你的格式化程序,它就会被使用。

    这可能会产生意想不到的副作用,因为它会影响您的所有控制器。我建议将 CanWriteType 实现更改为仅在为 BinaryImage 时才返回 true。这应该使格式化程序仅在您的返回类型时才被使用。

    您可以做的另一件事是通过返回 HttpResponseMessage 直接在您的操作中选择格式化程序:

    return Request.CreateResponse(HttpStatusCode.OK, image, new ImageFormatter());
    

    这基本上是说“此操作应始终使用图像格式化程序,无论内容类型如何,接受标题等”。如果您总是只返回一个图像并且您需要使用格式化程序对其进行序列化,那么在您的情况下这可能是合理的。

    【讨论】:

    • 感谢您对正在发生的事情的清晰解释! BinaryImage 应该代表 jpg/png/等。到外面的世界,所以没有其他格式化程序可以处理它。并且它永远不应该作为 JSON 发出(这就是现在发生的情况)。我将添加到 config.formatters。而且我已经修复了 CanWrite。
    • 因此,将其添加到列表中就可以完成工作。特别是这个家伙可以序列化为三种格式,具体取决于 BinaryImage 的内容......看起来媒体格式化程序的人只根据类型而不是对象的内容做出内容类型决定(到时 Write 是调用,Content-Type 已经设置)。有什么方法可以参与更早的内容类型协商吗?
    • 我应该补充一点,我在 Write 方法中设置了 ContentType,这似乎成功了,但是返回的网络浏览器不知道内容类型是什么。当 ContentType 与标头匹配时,一切正常。所以,基本上, content.Headers.ContentType=... 在 Write 中被忽略了。
    • 我可以为每种类型制作一个自定义版本的 BinaryImage,然后覆盖 SetDefaultContentHeaders。这是正确的做法吗?
    • 好的,这需要一些工作。除了在配置列表中将其声明为格式化程序外,我还必须将我的方法更改为“return Request.CreateResponse(HttpStatusCode.OK, b, string.Format("image/{0}", b.metaData));"做那个回报。这意味着我的一堆测试没有构建,因为他们期望 BinaryImage 的直接返回......但是,我猜格式化程序框架不是为了完全以这种方式进行返回协商而构建的。
    【解决方案2】:

    我正在编写一个 CsvFormatter,我希望能够从浏览器调用 API 来触发文件下载。由于我无法控制 Accept 标头,因此我想使用扩展来触发我的 CSV 格式化程序,但 XML 格式化程序不断收到请求。我发现通过添加“text/html”媒体类型,我可以处理 CSV 扩展。希望这不会导致其他问题:)。

    public CsvFormatter()
    {
        var header = new MediaTypeHeaderValue("text/csv");
        SupportedMediaTypes.Add(header);
        MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
    
        // From Chrome: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
        // Allow the formatter to run from a standard browser request.
        header = new MediaTypeHeaderValue("text/html");
        SupportedMediaTypes.Add(header);
        MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
    }
    

    【讨论】:

      猜你喜欢
      • 2014-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-19
      • 2018-04-28
      • 2016-03-27
      • 2016-06-12
      相关资源
      最近更新 更多