65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Web API 中的内容协商和自定义格式化程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (6投票s)

2016 年 12 月 29 日

CPOL

8分钟阅读

viewsIcon

23005

downloadIcon

175

在本文中,我们将学习内容协商及其在 ASP.NET Web API 中的默认用法。此外,我们将学习如何实现自定义内容格式化程序,以满足客户端对特殊格式的要求。

引言

内容协商是 RESTful Web 服务的关键概念。在本文中,我们将学习内容协商及其在 ASP.NET Web API 中的默认用法。此外,我们将学习如何实现自定义内容格式化程序,以满足客户端对特殊格式的要求。

目录

  • 什么是内容协商
  • 内容协商如何工作
  • 演示 Web API 服务概述
  • 创建演示客户端页面
  • 创建自定义媒体格式化程序

注意

ASP.NET Web API 是一个用于构建 RESTful 服务的平台。在本文中,我们不会详细讨论 Web API 和 RESTful 服务。如果您对此不熟悉,请访问 Web API 主页ASP.NET Web API 教程

什么是内容协商

内容协商是在 HTTP 调用过程中客户端和服务器之间的一种对话。在向服务器发送任何请求之前,客户端可以指定预期的格式类型,服务器将以请求的格式发送数据。如果服务器无法以请求的格式发送数据,则服务器将以其默认格式发送数据。在 ASP.NET Web API 中,JSON 是默认格式。下图显示了客户端和服务器如何在内容协商上下文中进行通信。

在图中,服务器支持三种内容类型:json(默认)、xml(ASP.NET WebAPI 内置)和 mycustomtype(为此演示创建)。从客户端角度来看:

  • 客户端 1 请求以 JSON 格式获取数据,然后服务器将以 JSON 格式发送数据。JSON 是发送数据的默认格式。
  • 客户端 2 请求以 XML 格式获取数据,然后服务器将以 XML 格式发送数据,因为服务器支持该格式。
  • 客户端 3 请求以 XYZ 格式获取数据。服务器不支持此种格式,然后服务器将以 JSON(默认)格式发送数据。
  • 客户端 4 请求以自定义格式 mycustomtype 获取数据,并且服务器也了解请求的自定义格式,然后服务器将以自定义格式发送数据。

Content Negotiation between server and clients

下一节我们将了解客户端如何告知服务器以期望的格式获取数据。

内容协商如何工作

当客户端通过 URI 向服务器发出任何请求时,除了 URI 之外,HTTP 标头也会发送到服务器,其中包含“Accept”参数(位于请求标头下)。服务器以“Content-Type”参数(位于响应标头下)发送响应。这两个参数(“Accept”和“Content-Type”)在内容协商过程中起作用。下面将对这些参数进行说明。

Accept: Accept 是 HTTP 标头(请求标头)下的一个参数。客户端可以指定期望的格式,例如 xml、json、image、video、audio 等。如果客户端未为 Accept 参数指定任何值,则服务器将以其默认格式发送数据。

Content-Type: Content-Type 是 HTTP 标头(响应标头)的一个参数。此参数在响应正文从服务器发送到客户端时可用。服务器根据发送给客户端的数据格式为 Content-Type 指定值。

此处指定的 Accept 参数(来自客户端)或 Content-Type 参数(来自服务器)的值称为媒体类型。除了 Accept 参数,HTTP/1.1 标准还定义了更多标准参数,例如 Accept-CharsetAccept-EncodingAccept-Language,这些参数可以在 HTTP 标头中指定。本文将详细讨论 Accept 参数。

在本文的后续部分,我们将了解如何为 Accept 参数指定值,以及客户端/服务器如何通过代码设置 Accept/Content-Type 参数的值。

演示 Web API 服务概述

为了实际理解内容协商机制,我们首先将创建一个 ASP.NET Web API RESTful 服务,然后创建一个非常简单的客户端来消费该服务。

让我们首先使用 Visual Studio 2012 和 .NET 4.0 框架来创建 ASP.NET Web API 服务。您也可以选择更高版本。现在导航到“ASP.NET MVC 4 Web Application”模板,并将其命名为“ContentNegotiationWebAPI”,然后单击“OK”。



接下来,我们将看到项目模板窗口,选择“Web API”模板,然后单击“Ok”。

项目结构将为我们创建。我们不使用默认添加到项目中的所有文件夹和文件。因此,我已从本文附带的解决方案中删除了所有不必要的文件夹和文件。然后,添加了一个名为 CustomerController 的新控制器,并在 Model 文件夹中添加了一个名为 Customer 的模型。修改后的解决方案结构如下图所示。

CustomerController 继承自 ApiController。我们知道,当一个控制器继承自 ApiController 时,控制器的操作方法始终返回数据,而不是视图。我们在 CustomerController 中编写了一个名为 GetCustomer 的方法,它将返回一个客户。类似的代码如下所示。

    public class CustomerController : ApiController
    {
        // GET api/Customer
        [HttpGet]
        public Customer GetCustomer()
        {
            return new Customer { Name="Jim", City="Bangalore"};
        }
    }

如果客户端发出“Get”请求,ASP.NET Web API 默认会查找一个名为 Get 的方法。但我们的方法名为 GetCustomer。因此,WebApiConfig.cs 文件中定义的默认路由将不起作用。为此,我们需要在 WebApiConfig.cs 文件中的 MapHttpRoute 方法中添加 action 段,如下面的代码所示。

    config.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{action}/{id}",
         defaults: new { id = RouteParameter.Optional }
          );

您需要在 Web.config 文件中的 system.webServer 标记内添加以下代码,以使服务可供客户端使用。它将在发送响应给客户端时设置一个 HTTP 响应标头。由于自定义响应标头包含名称和值对的信息。这些名称和值对将与 Web 服务器的响应一起返回。

<httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*"/>
        <add name="Access-Control-Allow-Headers" value="Content-Type"/>
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
</httpProtocol>

创建演示客户端页面

在上节中,我们创建了一个简单的 ASP.NET Web API 服务,其中包含一个名为 GetCustomer 的方法。现在,在本节中,我们将创建一个客户端来消费该服务。这里的客户端将是一个简单的 HTML 页面,带有一个按钮,在按钮单击事件中,将使用 URI 调用 jQuery 的 ajax 函数。URI 指向服务的 GetCustomer 资源。从服务器的响应将在名为 divResult 的 div 标签内显示为文本。以下代码写入“SendRequest”按钮的单击事件。formatRequested 是一个局部变量,用于保存可能的请求格式类型。然后,将通过传递“Accept”参数的值来调用 beforeSendSpecifyContentFormat 函数中的 setRequestHeader 函数。

            
        $("#btnSendRequestToWeb").click(function () { 
            // To hold various type of content format
            var formatRequested;            
            //formatRequested = "text/json";
            //formatRequested = "text/xml";
            //formatRequested = "someNonSupportedFormat";
            //formatRequested = "text/mycustomtype";
            //formatRequested = "application/mycustomtype";
            // This method is used in AJAX call.
            // You can use any of above metioned content format and see the different results
            beforeSendSpecifyContentFormat = function (req) {
                req.setRequestHeader("Accept", formatRequested);
            },
            $.ajax({
                url: "https://:1764/api/Customer/getcustomer",
                //beforeSend: beforeSendSpecifyContentFormat,
                success: function (result) {
                    var output;
                    var checkIfXML = $.isXMLDoc(result);
                    if (checkIfXML) {
                        var oSerializer = new XMLSerializer();
                        output = oSerializer.serializeToString(result);
                    }
                    else {
                        output = JSON.stringify(result);
                    }
                    $("#divResult").text(output);
                }
            });
            .....
            .....
        });

在浏览器(最好是 Chrome)中打开“ClientPage.html”页面。单击“Send Request”按钮。您将能够以 JSON 格式看到响应。



现在按 F12 键打开 Chrome Web 浏览器的开发者工具。查看响应标头中的 Content-Type:application/json; charset=utf-8 参数。这里的媒体类型是“application/json; charset=utf-8”。这是 ASP.NET Web API 服务的默认媒体类型。在请求标头中,Accept:*/*,这里的媒体类型是“*/*”。这意味着客户端没有指定任何媒体类型以获取特定格式的数据,并且客户端准备好接受来自 ASP.NET Web API 服务的默认格式数据。

现在,让我们尝试从服务器获取 XML 格式的数据。因此,在客户端,在 JavaScript 代码中,首先取消注释以下行。

    formatRequested = "text/xml";
        

现在,在 $.ajax 方法的 url 之前,取消注释以下代码:根据代码示例,它被指定为 formatRequested 的“text/xml”。

    beforeSend: beforeSendSpecifyContentFormat, 
        

这里,在向服务器发送请求之前,我们指定了客户端期望的格式。再次在浏览器中打开/刷新“ClientPage.html”,然后单击“Send Request”按钮。这次收到的响应将是 xml 格式,如下所示。

现在再次按 F12,注意请求标头中的 Accept 参数。这次 Accept 参数的值是“text/xml”,这是因为在向服务器发送请求之前,客户端已指定期望的格式为 XML。同样,注意响应标头中的 Content-Type 参数。由于客户端已指定期望的格式为 xml,因此 Content-Type 参数的值为 text/xml。

创建自定义媒体格式化程序

可能存在某些情况,客户端期望获取 JSON 或 XML 格式以外的其他格式的数据。在这种情况下,我们可以创建自定义内容媒体格式化程序来满足客户端的请求。例如,客户端期望通过某种模板以纯文本格式获取数据。由于服务器默认无法通过某种模板发送数据,我们必须创建一个自定义内容媒体格式化程序。它将以纯字符串格式发送响应,但带有所需的模板。为此,我们首先在服务项目中添加一个名为 CustomFormat 的文件夹。然后,在 CustomFormat 文件夹中添加一个名为 CustomTextFormatter 的类。

每当我们必须创建自定义内容媒体格式化程序时,都可以从 MediaTypeFormatter 或 BufferedMediaTypeFormatter 派生我们的自定义格式化程序类。我们的 CustomTextFormatter 类继承自 BufferedMediaTypeFormatter 类。BufferedMediaTypeFormatter 类是一个抽象类,它继承自 MediaTypeFormatter 抽象类。MediaTypeFormatter 类有三个抽象方法:CanReadType、CanWriteType 和 WriteToStream,我们在 CustomTextFormatter 类中重写了它们。在 CustomTextFormatter 类的构造函数中,我们指定了两个媒体类型字符串。在向服务器发送请求时,客户端需要指定相同的字符串才能以纯文本格式获取数据或使用 CustomTextFormatter 类。

  public class CustomTextFormatter : BufferedMediaTypeFormatter
    {
        public CustomTextFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/mycustomtype"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/mycustomtype"));
            // Important to add the below code otherwise if the client request doesn't contain
            // any encoding header, a 500 server error will be thrown
            SupportedEncodings.Add(new UTF8Encoding(false, true));
            SupportedEncodings.Add(new UnicodeEncoding(false, true, true));
        }
        // CanReadType method will deserialize Customer.
        public override bool CanReadType(Type type)
        {
            //return type == typeof(Customer);
            return false;
        }
        // CanWriteType method will serialize Customer.
        public override bool CanWriteType(Type type)
        {
            //Ensure we are serializing only for Resource object
            // if more types to be allowed, add the conditions
            return type == typeof(Customer);
        }
        // WriteToStream method serializes Customer type by writing it to a stream
        public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
        {
            Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);
            using (StreamWriter sWriter = new StreamWriter(writeStream, effectiveEncoding))
            {
                var str = SerializeResourceToMyType((Customer)value);
                sWriter.Write(str);
            }
        }
        private string SerializeResourceToMyType(Customer customer)
        {
            return "Our valued customer " + customer.Name + " lives in " + customer.City;
        }
    }
            

在 WebApiConfig.cs 文件中注册 CustomTextFormatter: 创建自定义格式化程序后,我们需要在 WebApiConfig.cs 的 Register 方法中注册它。

    config.Formatters.Add(new CustomTextFormatter());
        

现在 CustomTextFormatter 已准备好供客户端使用。因此,让我们从 ClientPage.html 使用 CustomTextFormatter。这次我们将 formatRequested 设置为“text/mycustomtype”。客户端代码应与下面所示类似。

                        
    formatRequested = "text/mycustomtype";            
    // This method is used in AJAX call.
    // You can use any of above metioned content format and see the different results
    beforeSendSpecifyContentFormat = function (req) {
         req.setRequestHeader("Accept", formatRequested);
          },
     $.ajax({
          url: "https://:1764/api/Customer/getcustomer",
          beforeSend: beforeSendSpecifyContentFormat,
    ......
    ......
        

在向服务器发送请求时,媒体类型的名称必须与我们在服务中的 CustomTextFormatter 类的构造函数中给出的名称相同。在 CustomTextFormatter 类的构造函数中,我们通过调用带有“application/mycustomtype”和“text/mycustomtype”字符串的 MediaTypeHeaderValue 添加了两个 SupportedMediaTypes。因此,从客户端,我们可以发送“application/mycustomtype”或“text/mycustomtype”作为 formatRequested。

现在,在浏览器中打开“ClientPage.html”并单击“Send Request”按钮。我们应该能够以纯字符串格式获得响应,如下所示。

结论

在本文中,我们详细介绍了 ASP.NET WebAPI 中的默认内容协商。然后,我们创建了一个自定义内容格式化程序,并在服务器应用程序中使用它,并从客户端进行消费。感谢阅读。欢迎您的评论和改进建议。

参考文献

  1. Mozilla Developer Network - 内容协商
  2. Wikipedia - 内容协商
  3. MSDN - ASP.NET Web API 中的内容协商
© . All rights reserved.