【问题标题】:Web API 2.2 - ApiController to ODataController (changing Content Type after setting Formatter)Web API 2.2 - ApiController 到 ODataController(设置格式化程序后更改内容类型)
【发布时间】:2015-05-07 08:51:19
【问题描述】:

我有一个正在转换为使用 OData v4 的 Web API 2.2 控制器。在 ApiController 我能够做到这一点:

HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, MyObject);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
return ResponseMessage(response);

它发回 201 的 Http 状态代码 - 已创建,主体中序列化为 JSON 的对象(Web API 2.2 的默认格式化程序),以及 text/html 的内容类型。

这个工作的原因是 Request.CreateResponse() 将格式化程序设置为 JSON,尽管我之后直接更改了内容类型,但它仍然存在。所以对象被序列化为 JSON,并且返回的响应内容类型为 text/html。

我需要这个的原因是,现有的前端利用 iframe 执行上传,然后从 iframe 正文中提取响应以将任何信息传递给用户。如果内容类型是 application/json,浏览器会尝试将其保存为文件。但是作为 text/html,它很容易被注入到 iframe 中。我们可以将其提取出来并反序列化为一个 javascript 对象。

如果使用 ODataController,现在尝试执行相同的中断,但出现以下错误:

{
  "error":{
    "code":"","message":"An error has occurred.","innererror":{
      "message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'text/html'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
        "message":"A supported MIME type could not be found that matches the content type of the response. None of the supported type(s) 'application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false, application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=true, application/json;odata.metadata=minimal;odata.streaming=true, application/json;odata.metadata=minimal;odata.streaming=false;IEEE754Compatible=false, application/json;odata.metadata=minimal;odata.streaming=false;IEEE754Compatible=true, application/json;odata.metadata=minimal;odata.streaming=false, application/json;odata.metadata=minimal;IEEE754Compatible=false, application/json;odata.metadata=minimal;IEEE754Compatible=true, application/json;odata.metadata=minimal, application/json;odata.metadata=full;odata.streaming=true;IEEE754Compatible=false, application/json;odata.metadata=full;odata.streaming=true;IEEE754Compatible=true, application/json;odata.metadata=full;odata.streaming=true, application/json;odata.metadata=full;odata.streaming=false;IEEE754Compatible=false, application/json;odata.metadata=full;odata.streaming=false;IEEE754Compatib...' matches the content type 'text/html'.","type":"Microsoft.OData.Core.ODataContentTypeException","stacktrace":"   at Microsoft.OData.Core.MediaTypeUtils.GetFormatFromContentType(String contentTypeName, ODataPayloadKind[] supportedPayloadKinds, ODataMediaTypeResolver mediaTypeResolver, ODataMediaType& mediaType, Encoding& encoding, ODataPayloadKind& selectedPayloadKind)\r\n   at Microsoft.OData.Core.MediaTypeUtils.GetFormatFromContentType(String contentTypeHeader, ODataPayloadKind[] supportedPayloadKinds, ODataMediaTypeResolver mediaTypeResolver, ODataMediaType& mediaType, Encoding& encoding, ODataPayloadKind& selectedPayloadKind, String& batchBoundary)\r\n   at Microsoft.OData.Core.ODataMessageWriter.EnsureODataFormatAndContentType()\r\n   at Microsoft.OData.Core.ODataMessageWriter.SetHeaders(ODataPayloadKind payloadKind)\r\n   at Microsoft.OData.Core.ODataMessageWriter.SetOrVerifyHeaders(ODataPayloadKind payloadKind)\r\n   at Microsoft.OData.Core.ODataMessageWriter.WriteToOutput[TResult](ODataPayloadKind payloadKind, Action verifyHeaders, Func`2 writeFunc)\r\n   at Microsoft.OData.Core.ODataMessageWriter.CreateODataEntryWriter(IEdmNavigationSource navigationSource, IEdmEntityType entityType)\r\n   at System.Web.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n   at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n   at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
      }
    }
  }
}

OData 格式化程序的工作方式似乎有点不同。并且在删除我的内容类型分配行的同时,将正确序列化的 OData 发送回,它使用 application/json 的内容类型执行此操作,然后浏览器尝试将其保存到文件中...

关于如何让 ODataController 像 ApiController 一样工作的任何想法?我是否必须创建自己的格式化程序才能做到这一点?考虑到我唯一要更改的是返回的内容类型,这似乎有点傻。

【问题讨论】:

    标签: iframe odata asp.net-web-api content-type


    【解决方案1】:

    实际上,通过使用 OWIN 并编写我自己的中间件,我或多或少地让它按照我想要的方式工作。

    基本上,我想要一种强制内容类型的方法,而不必为每个实例编写特定的 oData 格式化程序。所以 oData 可以继续格式化它被告知的任何方式,我只会告诉浏览器将其视为纯文本(或我想要的任何其他内容)。

    这样我就可以通过 iframe 发出请求(通常是上传或下载文件),如果发生错误,则返回纯文本。然后哪个 Internet Explorer 会忽略(application/json 或 application/xml 响应被视为文件,并且总是启动下载提示)。大多数其他浏览器接受 application/json 或 application/xml 就好了,但 IE 总是想将其作为文件下载?!?!

    我尝试使用 WebAPI 管道来执行此操作的尝试不太成功,因为似乎 MessageHandlers 实际上在 oData 序列化过程之前就已被命中,因为响​​应会走出大门。

    但看起来 OWIN 让我在 oData 序列化之后处理响应,这正是我想要的。下面是我放在一起的“案例证明”OWIN 中间件类,它采用特定的查询字符串值并使用它在响应发送到下游之前强制响应的内容类型。

    它可以稍微改进一下,但现在它可以完成工作(它适用于任何请求,我有并行的 WebAPI 和 oData 控制器,两者都可以使用查询字符串来强制内容类型)。

    public class OWINCustomMiddleware : OwinMiddleware {
    
      public OWINCustomMiddleware(OwinMiddleware next): base(next) {
      }
    
      public async override Task Invoke(IOwinContext context) {
    
      //Check if a content-type coercion querystring token is present
        if (context.Request.QueryString.HasValue) {
          NameValueCollection queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
          String coerce = queryString.Get("$coerce");
          if (!String.IsNullOrWhiteSpace(coerce)) {
          //Remove $coerce token so it doesn't impact future methods
            queryString.Remove("$coerce");
            context.Request.QueryString = new QueryString(queryString.ToString());
          //Append a header to the request, to be later used in Response Content-Type coercion
            context.Request.Headers.Add("coerce", new String[] { coerce });
          }
        }
    
        await Next.Invoke(context);
    
      //Coerce existing response content-type to value of coerce header (if present)
        if (context.Request.Headers != null) {
          if (context.Request.Headers["coerce"] != null) {
            Int32 index = context.Response.ContentType.IndexOf(';');
            if (index > 0) {
              context.Response.ContentType = context.Request.Headers["coerce"] + context.Response.ContentType.Substring(index, context.Response.ContentType.Length - index);
            } else {
              context.Response.ContentType = context.Request.Headers["coerce"];
            }
          }
        }
    
      }
    }
    

    如果有人知道直接使用 WebAPI 管道(没有 OWIN)执行此操作的方法,我仍然想知道,所以如果你知道,请发布。

    【讨论】:

      【解决方案2】:

      在您的启动/配置代码中调用它:

      var odataFormatters = ODataMediaTypeFormatters.Create();
      config.Formatters.Clear();
      config.Formatters.AddRange(odataFormatters);
      

      【讨论】:

      • 不幸的是杀死了所有的格式化程序(基本上是杀死了标准的 APIController 我与这个新的 ODataController 并排运行),并且仍然没有创建允许我序列化为 JSON 但响应的 OData 格式化程序带有文本/html MediaTypeHeader。不过我或许可以把它当做一个跳跃点,让我看看我在哪里。
      • 我有相同的设置(ApiControllers 和 ODataControllers 生活在一起)。诀窍(对我来说)是在 WebApi 配置之前在启动中调用 OData 配置。我的 api 配置中没有格式化程序,只有路由;之后我调用'app.UseWebApi(config)'。这在我的 Startup.cs(Owin 风格)中使用了“IAppBuilder 应用程序”。
      • 对此我还是有点迷茫。真的,我不希望 OData 做任何不同的事情,除了返回它是带有 text/html MediaTypeHeaderValue 的序列化数据。我确实想要格式化的 xml 或 json,但我希望浏览器将其视为 text/html 而不是 applicaiton/xml 或 application/json。
      • 你能发布一个示例项目吗?
      猜你喜欢
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-03
      相关资源
      最近更新 更多