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) != -1) { // 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 = 23, 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(1815, 1, 1) }; // 使用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(42); } }
序列化后的值為:{ "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/
部分示例來自於該網站
轉載與引用請注明出處。
時間倉促,水平有限,如有不當之處,歡迎指正。
