標籤:
XML被JSON代替的時候,是因為JSON的更小的檔案體積.
現在移步到手機,json 資料包也愈發顯的不可接受了.滿眼的都是 json 的屬性名稱,真正有用的屬性值卻只佔整個JSON包的一小部份.如果能不要"屬性名稱",那可太好了,但是那是不可能的.
老早就聽說過 ProtoBuf ,一直沒有用過.這兩天耗了些時間研究了一下,成功的應用於服務端(WebApi) 和 用戶端(Xamarin.Form) 上.
先貼兩張圖感受一下:
能小多少,和具體的類結構及資料完整性有關. 不過單從這份結果對比來看, 效果真的很吸引人!
如果將它應用於手機端, 可以給你的APP體驗抬高不止一個檔次!
1, 服務端實體類的聲明 (WebApi):
NuGet -> ProtoBuf-net, 要安新版本的.
一開始, 我直接安裝的 WebApiContrib.Formatting.ProtoBuf, 順帶安裝 protobuf-net 2.0.0.448 版的, 結果在有循環參考的實體集上報錯:
Possible recursion detected.
為什麼會有循環參考? 你用過 EF嗎? 搜了一圈, 有說什麼 protobuf 只支援 Tree ,不支援圖的.
Json.Net 序列化的時候,可以設定 LoopReferences = Ignore, 如果 protobuf 不支援這樣的功能, 那真是有辱Google的大牙了.
後來看到 AsReferenceDefault 這個東西, 敲了一下, 沒有這個屬性, 才意思到下載的版本太老了.
服務端的資料實體定義:
1 [ProtoContract(AsReferenceDefault = true, ImplicitFields = ImplicitFields.AllFields)]2 public partial class T_BRANCH3 {4 public decimal BRANCH_ID { get; set; }5 6 public string BRANCH_CODE { get; set; }7 ...
ImplicitFields.AllFields 的功效等於在每個屬性上加 ProtoMember.
2, 註冊 Protobuf 格式器
在 Global 裡添加:
1 GlobalConfiguration.Configuration.Formatters.Add(new ProtoBufFormatter());
ProtoBufFormatter 的源碼:
1 public class ProtoBufFormatter : MediaTypeFormatter { 2 private static readonly MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/x-protobuf"); 3 private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel); 4 5 public static RuntimeTypeModel Model { 6 get { 7 return model.Value; 8 } 9 }10 11 public ProtoBufFormatter() {12 SupportedMediaTypes.Add(mediaType);13 }14 15 public static MediaTypeHeaderValue DefaultMediaType {16 get {17 return mediaType;18 }19 }20 21 public override bool CanReadType(Type type) {22 return CanReadTypeCore(type);23 }24 25 public override bool CanWriteType(Type type) {26 return CanReadTypeCore(type);27 }28 29 public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger) {30 var tcs = new TaskCompletionSource<object>();31 32 try {33 object result = Model.Deserialize(stream, null, type);34 tcs.SetResult(result);35 } catch (Exception ex) {36 tcs.SetException(ex);37 }38 39 return tcs.Task;40 }41 42 public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) {43 var tcs = new TaskCompletionSource<object>();44 45 try {46 Model.Serialize(stream, value);47 tcs.SetResult(null);48 } catch (Exception ex) {49 tcs.SetException(ex);50 }51 52 return tcs.Task;53 }54 55 private static RuntimeTypeModel CreateTypeModel() {56 var typeModel = TypeModel.Create();57 typeModel.UseImplicitZeroDefaults = false;58 return typeModel;59 }60 61 private static bool CanReadTypeCore(Type type) {62 return true;63 }64 }View Code
至此, 服務端完成了, 可以請求一下試試效果. 記得要加要求標頭: Accept ,值為:application/x-protobuf
3, PCL 實體類
一般,我都會將資料實體(POCO)單獨分隔成一個類庫, 方便各個項目使用, 因為這些POCO類庫不涉及任何商務邏輯.
現在,在手機端遇到了一個問題, 用這些類庫,將伺服器上收到的資料還原序列化,無論是 json 還是 protobuf , 都會報錯.
跟蹤了一下, 大至都是因為 : 加到類上的 Serializable; 加到屬性上的 Required/StringLength 等 特性 造成的. 因為這些特性在 PCL 類庫裡是不受支援的.
將這些東西去掉當然是不可能的, 那怎麼辦呢 ?
如果是基於DbFirst / ModelFirst 的 EF 類庫,那好辦, 新增一個對應的PCL類庫, 把 原庫下面的 TT 模板複製一份到PCL庫中, 改一下就可以了.
如果是 CodeFirst 的類庫 , 那就有點小複雜:
1, 還是建立一個對應的 PCL 類庫.
2, 將原庫中的所有類檔案都複製一份到PCL類庫中.
3, 添加一個 attributes 目錄, 將類庫中所有用到的,不被PCL支援的特性 重新定義一遍, 比如 StringLengthAttribute:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace System.ComponentModel.DataAnnotations { 8 public class StringLengthAttribute : Attribute { 9 10 public StringLengthAttribute(int max) {11 }12 13 }14 }
注意, 命名空間一定要和原來的一樣.
4, 將這些PCL類庫拿去代替原庫,在手機中使用.
4, 手機端的 protobuf 類庫
Protobuf-net 是有 PCL 版本的, 只不過在 NuGet 中添加的 Protobuf-net 是無法安裝到 PCL 庫中的, 需要先把 Protobuf-net 下載來, 然後手工添加引用:
不要添加非 PCL 版的 protobuf-net 到 PCL 類庫中, 運行會報錯.
5, WebApi Client 設定
1,添加上面的 ProtoBufFormatter.cs 到手機端的PCL類庫中.
2, 在 ReadAsAsync 方法中使用 ProtoBufFormatter :
1 var a = await this.GetResult(token); 2 var reason = ""; 3 HttpStatusCode? status = null; 4 if (a != null) { 5 if (a.IsSuccessStatusCode) { 6 if (this.SupportProtoBuf) { 7 return await a.Content.ReadAsAsync<T>(new[] { new ProtoBufFormatter() }); 8 } else 9 return await a.Content.ReadAsAsync<T>();10 } else {11 reason = a.ReasonPhrase;12 status = a.StatusCode;13 }14 }
具體請參考:
https://github.com/gruan01/LbcTest/blob/master/Lbc.WebApi/MethodBase.cs
OK, 以上是在 Xamarin.Form 中使用 Protobuf 的全部過程.
在 Xamarin.Form 使用 ProtoBuf, 提升APP的體驗檔次