Web API 2.2 - ApiController到ODataController(设置Formatter后更改内容类型)

时间:2015-03-05 20:28:55

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

我有一个Web API 2.2控制器,正在转换为使用OData v4。在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主体提取响应以将任何信息转发给用户。如果content-type是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一样工作的任何想法?我是否必须创建自己的格式化程序才能执行此操作?考虑到我唯一要改变的是返回的内容类型,看起来有点傻。

2 个答案:

答案 0 :(得分:1)

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

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

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

我尝试使用WebAPI管道来做到这一点并不成功,因为看起来MessageHandlers实际上是在oData序列化过程之前被点击的,因为响应已经出现了。

但看起来OWIN让我在oData序列化之后处理响应,这正是我想要的。以下是&#34;案例证明&#34;我把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)直接执行此操作,我仍然想知道,所以如果你这样做,请发帖。

答案 1 :(得分:0)

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

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