1 多媒體格式化器javascript
多媒體類型又叫MIME類型,指示了數據的格式。在HTTP協議中多媒體類型描述了消息體的格式。一個多媒體類型包括兩個字符串:類型和子類型。html
例如:java
text/html、image/png、application/json、application/pdf。web
請求的Content-Type標頭指定消息體的格式,指示接收者應如何解析消息體內容。json
例如:請求告知服務端請求數據類型爲HTML, XHTML, or XMLapi
請求:Accept: text/html,application/xhtml+xml,application/xml服務器
響應:app
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;
序列化
例:顯示本地時間
控制器
[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); }
結果:
例:
// 轉換全部日期爲 UTC var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
控制器與客戶端調用與前例基本一致,縮進的效果爲:
例:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
例:控制器操做爲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請求的匹配程度。爲了評估匹配程度,內容協商會作兩件事。
若是有多個匹配,那麼選取質量因數最高的一個匹配。
例如:
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/
部分示例來自於該網站
轉載與引用請註明出處。
時間倉促,水平有限,若有不當之處,歡迎指正。