ASP.NET Web API编程——序列化与内容协商
1 多媒体格式化器
多媒体类型又叫MIME类型,指示了数据的格式。在HTTP协议中多媒体类型描述了消息体的格式。一个多媒体类型包括两个字符串:类型和子类型。
例如:
text/html、image/png、application/json、application/pdf。
请求的Content-Type标头指定消息体的格式,指示接收者应如何解析消息体内容。
例如:请求告知服务端请求数据类型为HTML, XHTML, or XML
请求:Accept: text/html,application/xhtml+xml,application/xml
响应:
HTTP/1.1 200 OK
Content-Length: 95267
Content-Type: image/png
多媒体类型为Web Api指明了如何序列化与反序列化HTTP消息体。Web API内建对XML, JSON, BSON,form-urlencoded支持,可以创建多媒体格式化器来自定义格式化方式,自定义的格式化器继承自MediaTypeFormatter或BufferedMediaTypeFormatter,其中MediaTypeFormatter使用异步的读写方法,BufferedMediaTypeFormatter使用同步的读写方法。
例:创建CSV格式化器
定义实体
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
定义ProductCsvFormatter,继承自BufferedMediaTypeFormatter
public class ProductCsvFormatter : BufferedMediaTypeFormatter
{
public ProductCsvFormatter()
{
// 添加被支持的多媒体类型
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}
}
重写CanWriteType方法,指明格式化器可序列化的类型
public override bool CanWriteType(System.Type type)
{
//指明可序列化Product
if (type == typeof(Product))
{
return true;
}
//指明可序列化IEnumerable<Product>
else
{
Type enumerableType = typeof(IEnumerable<Product>);
return enumerableType.IsAssignableFrom(type);
}
}
重写CanReadType方法,指明格式化器可反序列化的类型
public override bool CanReadType(Type type)
{
//设置为不支持反序列化
return false;
}
重写WriteToStream方法,这个方法将序列化数据写入流,若要支持反序列化可重写ReadFromStream方法。
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
using (var writer = new StreamWriter(writeStream))
{
var products = value as IEnumerable<Product>;
if (products != null)
{
foreach (var product in products)
{
WriteItem(product, writer);
}
}
else
{
var singleProduct = value as Product;
if (singleProduct == null)
{
throw new InvalidOperationException("Cannot serialize type");
}
WriteItem(singleProduct, writer);
}
}
} // 帮助方法
private void WriteItem(Product product, StreamWriter writer)
{
writer.WriteLine("{0},{1},{2},{3}", Escape(product.Id),
Escape(product.Name), Escape(product.Category), Escape(product.Price));
} static char[] _specialChars = new char[] { ',', '\n', '\r', '"' }; private string Escape(object o)
{
if (o == null)
{
return "";
}
string field = o.ToString();
if (field.IndexOfAny(_specialChars) != -)
{
// Delimit the entire field with quotes and replace embedded quotes with "".
return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
}
else return field;
}
将多媒体格式化器添加到Web API管道(方法在WebApiConfig类中)
public static void Register(HttpConfiguration config)
{
config.Formatters.Add(new ProductCsvFormatter());
}
字符编码
多媒体格式化器支持多种编码,例如UTF-8或ISO 8859-1。
public ProductCsvFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); // 新的编码:
SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
SupportedEncodings.Add(Encoding.GetEncoding("iso-8859-1"));
}
在WriteToStream方法中添加选择编码方式的代码。如果支持反序列化,那么在ReadFromStream方法中同样添加选择编码方式的代码。
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
//调用MediaTypeFormatter.SelectCharacterEncoding选择编码方式,由于ProductCsvFormatter派生自MediaTypeFormatter,所以也就继承了SelectCharacterEncoding这个方法
Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers); using (var writer = new StreamWriter(writeStream, effectiveEncoding))
{
// Write the object (code not shown)
}
}
2 JSON和XML的序列化
Web API多媒体类型格式化器可以从HTTP消息体中读取CLR对象或将CLR对象写入消息体。Web API框架提供了JSON格式化器和XML格式化器,默认支持JSON和XML序列化。可以在请求的Accept首部字段指定接收的类型。
例:指定返回JSON字符串
HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
HttpResponseMessage response = client.SendAsync(request).Result;
返回结果:
例:指定返回XML字符串
HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
request.Content = content;
HttpResponseMessage response = client.SendAsync(request).Result;
返回结果:
2.1 JSON格式化器
类JsonMediaTypeFormatter提供对JSON数据的格式化。默认地JsonMediaTypeFormatter使用Json.NET来格式化数据,也可以指定DataContractJsonSerializer来格式化数据。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
序列化
- 使用Json.NET时,默认地所有的公有类型的字段和属性都会序列化,除非标记了JsonIgnore特性。
- 可以使用DataContract特性标记数据模型,标记了DataMember特性的属性都会被序列化,即使是私有类型。
- 只读属性默认被序列化。
- 默认地,Json.NET的时间字符串为ISO 8601格式,并保持时区。UTC时间含有“Z”字符后缀,本地时间包括时区偏移量。
例:显示本地时间
控制器
[HttpPost]
public IHttpActionResult ModelValid([FromBody]DataModel model)
{
new TaskCompletionSource<HttpResponseMessage>(); if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
return Ok(model);
}
客户端调用:
HttpClient client = new HttpClient();
string url = "http://localhost/WebApi_Test/api/account/modelvalid?Field1Name=1name&Field2Name=2name";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
{
var cont = new { DT=DateTime.Now};
HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
HttpResponseMessage response = client.SendAsync(request).Result;
Console.WriteLine("状态码:{0}",(int)response.StatusCode);
var task = response.Content.ReadAsStringAsync();
task.Wait();
Console.WriteLine("结果:{0}", task.Result);
}
结果:
- 默认地,Json.NET保留了时区,可以使用DateTimeZoneHandling这一属性改变这种形式。
例:
// 转换所有日期为 UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
- 若想使用Microsoft JSON 日期格式:
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
- 设置Formatting.Indented来支持缩进格式
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
控制器与客户端调用与前例基本一致,缩进的效果为:
- 为了使JSON字符串属性名称具有驼峰式的风格,设置为CamelCasePropertyNamesContractResolver
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
- 匿名类型自动序列化为JSON
例:控制器操作为Get
public object Get()
{
return new {
Name = "Alice",
Age = ,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}
调用控制器获得响应中包含:{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
2.2 XML格式化器
类XmlMediaTypeFormatter 提供对XML数据的格式化。默认地,使用DataContractSerializer执行序列化。
可设置使用XmlSerializer来执行序列化。XmlSerializer支持的类型比DataContractSerializer少,但可以对XML结果做更多地控制。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
默认地DataContractSerializer行为如下:
1)所有的公有类型属性或字段都会被序列化(set和get不加修饰),可使用IgnoreDataMember特性将其排除在外。
2)Private和protected成员不会序列化。
3)只读属性不会序列化,但只读的集合属性会被序列化。
4)类及其成员名称如其定义时所显示的那样,不加改变地被写入XML中。
5)使用默认的XML名称空间。
若想要施加更多的控制那么使用DataContract修饰类,使用DataMember修饰其属性。序列化规则如下:
1)使用DataMember特性修饰成员使其可序列化,即使类属性为私有属性也可将其序列化。
2)对于使用DataContract特性修饰的类,若不对其属性成员使用DataMember特性,那么就不能序列化。
3)只读属性不会被序列化。
4)在DataContract中设置Name属性来指定类在XML中的名称。
5)在DataContract中设置NameSpace属性来指定XML名称空间。
6)在DataMember中设置Name属性来指定类属性在XML中的名称。
时间类型会序列化为ISO 8601格式的字符串。
使用Indent属性设置缩进格式
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
为不同的CLR类型设置不同的格式化器
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// 将XmlSerializer 应用于Product类
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
移除JSON或XML格式化器,在Register中添加以下代码。
// Remove the JSON formatter
config.Formatters.Remove(config.Formatters.JsonFormatter);
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
2.3控制类的循环引用(应避免循环引用)
例:
public class Employee
{
public string Name { get; set; }
public Department Department { get; set; }
} public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
} public class DepartmentsController : ApiController
{
public Department Get(int id)
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return sales;
}
}
文件Global.asax中的Application_Start方法中添加如下代码,如果不添加下述代码运行时会报500错误。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
结果为:{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
对于XML循环引用的问题,有两种解决办法。一是在模型上应用[DataContract(IsReference=true)]特性,二是为DataContractSerializer的构造函数参数preserveObjectReferences赋值为true。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
3 ASP.NET Web API 2.1支持BSON
BSON是二进制序列化格式,与JSON大小相近,对于二进制的文件序列化后比JSON小。BSON数据易扩展,因为元素带有长度字段前缀。解析器能够跳过元素而不对数据解码。编码和解码是高效的,因为数值数据类型被存储为数字,而不是字符串。
例:不支持BOSN的调用
var cont = new { Field1Name = "1name", Field2Name = "2name", DT=DateTime.Now};
HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
content.Headers.ContentType = new MediaTypeHeaderValue("application/bson");
request.Content = content;
HttpResponseMessage response = client.SendAsync(request).Result;
结果:
启用BSON格式化器
设置支持BSON,当客户端请求的Content-Type为application/bson时,Web API会使用BSON格式化器。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Formatters.Add(new BsonMediaTypeFormatter()); // 其他配置
}
}
为了关联其他多媒体类型与BOSN,应如下设置,例如多媒体类型为“application/vnd.contoso”
var bson = new BsonMediaTypeFormatter();
bson.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.contoso"));
config.Formatters.Add(bson);
例:.NET客户端应用HttpClient使用BSON格式化器。
static async Task RunAsync()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost"); // 设置Accept头.
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson")); // 发送请求
result = await client.GetAsync("api/books/1");
result.EnsureSuccessStatusCode(); // 使用BSON格式化器反序列化结果
MediaTypeFormatter[] formatters = new MediaTypeFormatter[] {
new BsonMediaTypeFormatter()
}; var book = await result.Content.ReadAsAsync<Book>(formatters);
}
}
发送post请求:
static async Task RunAsync()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:15192"); // 设置请求头Content-Type为application/bson
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson")); var book = new Book()
{
Author = "Jane Austen",
Title = "Emma",
Price = 9.95M,
PublicationDate = new DateTime(, , )
}; // 使用BSON格式化器
MediaTypeFormatter bsonFormatter = new BsonMediaTypeFormatter();
var result = await client.PostAsync("api/books", book, bsonFormatter);
result.EnsureSuccessStatusCode();
}
}
例:未反序列化BSON结果
客户端调用
using(HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/bson")); string url = "http://localhost/WebApi_Test/api/account/modelvalid?Field1Name=1name&Field2Name=2name";
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
{
var cont = new { Field1Name = "1name", Field2Name = "2name", DT = DateTime.Now };
HttpContent content = new StringContent(JsonConvert.SerializeObject(cont));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
HttpResponseMessage response = client.SendAsync(request).Result;
Console.WriteLine("状态码:{0}", (int)response.StatusCode);
var task = response.Content.ReadAsStringAsync();
task.Wait();
Console.WriteLine("结果:{0}", task.Result);
} Console.Read();
}
结果:
客户端序列化,只要改变处理HTTP响应方式即可:
MediaTypeFormatter[] formatters = new MediaTypeFormatter[]
{
new BsonMediaTypeFormatter()
}; var task = response.Content.ReadAsAsync<DataModel>(formatters);
task.Wait();
var model = task.Result;
再次运行获得结果:
序列化顶级原始类型
BOSN语法中并没有规定如何序列化顶级原始类型,比如int类型,为了突破这一限制,BsonMediaTypeFormatter将顶级原始类型视为一种特殊的情况。在序列化之前将值转换为键值对,键为“Value”。
例:
public class ValuesController : ApiController
{
public IHttpActionResult Get()
{
return Ok();
}
}
序列化后的值为:{ "Value": 42 }
4 内容协商
在HTTP中主要的内容协商机制包括如下的请求头:
Accept:应答中可接受的多媒体类型,如"application/json," "application/xml,"
Accept-Charset:可接受的字符,如UTF-8或ISO 8859-1。
Accept-Encoding:可接受的编码方式,如gzip。
Accept-Language:首先的自然语言,如en-us。
X-Requested-With:服务器据此判断请求是否来自于AJAX。
序列化
如果Web API的控制器操作(Action)返回CLR类型,管道序列化返回值并将其写入HTTP响应消息体。
例如:
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
发送的请求如下,其中请求接收JSON字符串,即通过Accept: application/json来指定的。
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
响应为:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57
Connection: Close
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
也可返回HttpResponseMessage类型:
public HttpResponseMessage GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, product);
}
内容协商工作原理
首选,管道从HttpConfiguration对象中获得IContentNegotiator,并从HttpConfiguration.Formatters集合中获得多媒体格式化器列表。
然后,管道调用IContentNegotiatior.Negotiate,传入待序列化类型、格式化器集合、HTTP请求。Negotiate方法返回两条信息,一是使用了哪个格式化器,二是响应需要的多媒体类型。如果所需的格式化器没有找到,那么Negotiate方法返回NULL,客户端会接受到406(不接受,请求资源不可访问)错误。
默认的内容协商机制
DefaultContentNegotiator是IContentNegotiator默认的实现,其选择格式化器的准则为:
首先,使用MediaTypeFormatter.CanWriteType来验证格式化器是否能够序列化待处理的类型。
其次,内容协商者会查看每个格式化器,并评估其与HTTP请求的匹配程度。为了评估匹配程度,内容协商会做两件事。
- 集合SupportedMediaTypes包含了被支持的多媒体类型,内容协商者依据请求头的Accept标头来匹配这个集合。Accept标头可能包含一个范围,例如"text/plain" 可以匹配 text/* 或*/*。
- MediaTypeMapping类提供了匹配HTTP请求的多媒体类型的一般方法。例如它可以匹配自定的HTTP请求头到特定的多媒体类型。
如果有多个匹配,那么选取质量因数最高的一个匹配。
例如:
Accept: application/json, application/xml; q=0.9, */*; q=0.1
选取质量因数为0.9的,即application/json。
如果没有匹配,内容协商者试图匹配请求消息体的多媒体类型。
如果请求包含JSON格式的数据,内容协商者会查找JSON格式化器。
如果通过以上规则还是无法匹配,内容协商者会选择第一个可以序列化待处理类型的格式化器。
字符编码方式
选好格式化器以后,内容协商者会选取最好的字符编码方式,通过查看格式化器的SupportedEncodings属性,并与请求的Accept-Charset标头值进行匹配。
参考:
https://docs.microsoft.com/en-us/aspnet/web-api/
部分示例来自于该网站
转载与引用请注明出处。 时间仓促,水平有限,如有不当之处,欢迎指正。
ASP.NET Web API编程——序列化与内容协商的更多相关文章
- ASP.NET Web API编程——路由
路由过程大致分为三个阶段: 1)请求URI匹配已存在路由模板 2)选择控制器 3)选择操作 1匹配已存在的路由模板 路由模板 在WebApiConfig.Register方法中定义路由,例如模板默认生 ...
- 让ASP.NET Web API支持text/plain内容协商
ASP.NET Web API的内容协商(Content Negotiation)机制的理想情况是这样的:客户端在请求头的Accept字段中指定什么样的MIME类型,Web API服务端就返回对应的M ...
- ASP.NET Web API编程——构建api帮助文档
1 概要 创建ASP.NET Web Api 时模板自带Help Pages框架. 2 问题 1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目 ...
- ASP.NET Web API编程——异常捕获
1 向客户端发送错误消息 使用throw new HttpResponseException()向客户端抛出错误信息. HttpResponseException包含两个重载的构造函数,其中一个是构造 ...
- ASP.NET Web API编程——客户端调用
可以使用HttpClient这个调用Web API,下面是HttpClient的定义,列举了一些常用的方法,其中还有一些没有列举,包括重载的方法. public class HttpClient : ...
- ASP.NET Web API编程——模型验证与绑定
1.模型验证 使用特性约束模型属性 可以使用System.ComponentModel.DataAnnotations提供的特性来限制模型. 例如,Required特性表示字段值不能为空,Range特 ...
- ASP.NET Web API编程——版本控制
版本控制 版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通过VersionController ...
- ASP.NET Web API编程——文件上传
首先分别介绍正确的做法和错误的做法,然后分析他们的不同和错误之处,以便读者在实现此功能时可避开误区 1正确的做法 public class AvaterController : BaseApiCont ...
- ASP.NET Web API编程——文件下载
断点续传基本原理 HTTP协议中与断点续传相关的HTTP头为:Range和Content-Range标头,断点续传实现流程: 1)客户端请求下载一个文件,文件的总长度为n:已经下载了一部分文件,长度为 ...
随机推荐
- View滑动的常见方式
今天头条的面试官问起了实现View滑动的方式,其实前几天刚刚看过,但还是没能答上来,这里再来总结一遍. 1.使用scrollTo/scrollBy 为了实现View滑动,Android专门提供了这两个 ...
- 【Unity与23种设计模式】解释器模式(Interpreter)
GoF中定义: "定义一个程序设计语言所需要的语句,并提供解释来解析(执行)该语言." 传统上,执行程序代码通常通过两种方式 第一种:编译程序 第二种:解释器 常见的使用解释器的程 ...
- Unity3D判断触摸方向
据说 Temple Run(神庙逃亡) 就是用这种方式操作的 废话不多说 直接上代码 using UnityEngine; using System.Collections; public class ...
- [C#]基于命名管道的一对多进程间通讯
在工作中碰到了一个进程间通讯的问题,大概是这样的: 项目本身是.net Core做的,但是有部分功能Core中不方便实现,有的是依赖Framework,有的是因为权限和安全问题. 那基于这个问题,问了 ...
- windows下安装mongoDB以及配置启动
1.下载MongoDB的windows版本,有32位和64位版本,根据系统情况下载,下载地址:http://www.mongodb.org/downloads 2.解压缩至D:/mongodb即可 3 ...
- 微信小程序学习笔记(阶段二)
二阶段学习过程: (一)看官方文档的框架.组件.API:https://mp.weixin.qq.com/debug/wxadoc/dev/ (二)看极客学院第3.4章视频:http://www.ph ...
- Django+xadmin打造在线教育平台(六)
九.课程章节信息 9.1.模板和urls 拷贝course-comments.html 和 course-video.html放入 templates目录下 先改course-video.html,同 ...
- ASP.NET Core 2.0 : 八.图说管道
本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置.构建以及请求处理流程等方面做一下详细的研究.(ASP.NET Core系列目录) 一.概述 上文说到,请求是经过 ...
- 自动修改博客CSS样式用的代码
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js&qu ...
- (转)SQLite内置函数
一.聚合函数: SQLite中支持的聚合函数在很多其他的关系型数据库中也同样支持,因此我们这里将只是给出每个聚集函数的简要说明,而不在给出更多的示例了.这里还需要进一步说明的是,对于所有聚合函数而言, ...