2019-06-10

javascript 前端,使用 google-protobuf,後端用 asp.net core 2 接收

markdown 這篇是記錄在 Windows 10 環境中的 ASP.Net Core 的 WebAPI 使用 protobuf 取代 json 的資料傳輸 [本文範例程式碼](https://github.com/WenWei/ProtobufWebSample) 系統中需已預安裝: - 安裝 .NET Core 2.2 - 安裝 protoc.exe - 安裝 Browserify ## protoc.exe 安裝 到 GitHub 的 [Protocol Buffers Releases](https://github.com/protocolbuffers/protobuf/releases) [下載 protoc-3.8.0-win64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-win64.zip) 我是解壓到 C:\bin,這裡的 c:\bin 是已加到環境變數 PATH 中。 ``` C:\bin ├───include │ └───google │ └───protobuf └───protoc.exe ``` ## Browserify 安裝 ``` npm install browserify -g ``` ## 專案檔案結構 此文建立的範例專案名稱: ProtobufWebSample 省略部份檔案和目錄後結構如下: ``` ~/ProtobufWebSample ├───ProtobufWeb │ ├───Controllers │ │ └───EchoController.cs │ └───ProtobufWeb.csproj └───protos ├───EchoData.proto └───gen.bat ``` ## 新增 asp.net core 的 WebAPI 專案
mkdir ProtobufWebSample
cd ProtobufWebSample
mkdir ProtobufWeb
cd ProtobufWeb
dotnet new webapi
## 安裝 Google.Protobuf 套件 在 `ProtobufWebSample/ProtobufWeb` 資料夾安裝 `Google.Protobuf` 套件
dotnet add package Google.Protobuf
## 建立 EchoData.proto ``` syntax = "proto3"; option csharp_namespace = "ProtobufWeb.ProtoGen"; message EchoData { string text = 1; int32 age = 2; } ``` ## 使用 protoc.exe 將 EchoData.proto 產生 C# 類別和 JavaScript 程式碼 ### 產生 C# 程式碼
protoc.exe --proto_path=路徑\ProtobufWebSample\protos --js_out=路徑\ProtobufWebSample\protos\gen\csharp 路徑\ProtobufWebSample\protos\EchoData.proto
### 產生 JavaScript 程式碼
protoc.exe --proto_path=路徑\ProtobufWebSample\protos --csharp_out=路徑\ProtobufWebSample\protos\gen\js路徑\ProtobufWebSample\protos\EchoData.proto
因為要在瀏覽器中使用,所以要透過 browserify 將 google-protobuf.js 和產生出來的 EchoData_pb.js 綁在一起輸出為 bundle.js,瀏覽器使用時只要引用此 `bundle.js`即可。 `ProtobufWebSample/protos/gen.bat` ``` @echo on SET PWD=%~dp0 set PROTOC=C:\bin\protoc.exe set CSHARP_OUT=%PWD%gen\csharp set JS_OUT=%PWD%gen\js rd /S /Q %CSHARP_OUT% md %CSHARP_OUT% rd /S /Q %JS_OUT% md %JS_OUT% %PROTOC% --proto_path=C:/bin/include/google/protobuf --proto_path=%PWD%^ --csharp_out=%CSHARP_OUT%^ %PWD%EchoData.proto %PROTOC% --proto_path=C:/bin/include/google/protobuf --proto_path=%PWD%^ --js_out=import_style=commonjs,binary:%JS_OUT%^ %PWD%EchoData.proto echo var echodataProto = require('./EchoData_pb'); > %JS_OUT%\exports.js echo module.exports = { >> %JS_OUT%\exports.js echo EchoDataProto: echodataProto >> %JS_OUT%\exports.js echo } >> %JS_OUT%\exports.js cd %JS_OUT% call npm install google-protobuf browserify exports.js > bundle.js cd %PWD% ``` ## 建立使用 EchoData 傳遞的 API EchoController 將 `ProtobufWebSample/protos/gen/csharp/EchoData.cs` 移動到Web專案中 ``` mkdir ProtobufWebSample/ProtobufWeb/ProtoGen copy ProtobufWebSample/protos/gen/csharp/EchoData.cs ProtobufWebSample/ProtobufWeb/ProtoGen/EchoData.cs ``` 新增 `ProtobufWebSample/ProtobufWeb/Controllers/EchoController.cs`,內容如下: ``` using Microsoft.AspNetCore.Mvc; using ProtobufWeb.ProtoGen; namespace ProtobufWeb.Controllers { [Route("api/[controller]")] [ApiController] public class EchoController : ControllerBase { // POST api/Echo [HttpPost] public ActionResult Post([FromBody] EchoData value) { value.Text = $"ECHO: {value.Text}"; return value; } } } ``` ## 要能接收和傳送 protobuf 格式,要建立 formatter `ProtobufWeb/Formatters/ProtobufFormatter.cs` ``` namespace ProtobufWeb.Formatters { using Google.Protobuf; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; using System.Threading.Tasks; using System.Collections.Generic; using System; public static class ServicesConfiguration { public static void AddProtobufFormatter(this IServiceCollection services) { services.Configure(options => { options.InputFormatters.Add(new ProtobufInputFormatter(new ProtobufFormatterOptions())); options.OutputFormatters.Add(new ProtobufOutputFormatter(new ProtobufFormatterOptions())); options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf", Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/x-protobuf")); }); } } public class ProtobufFormatterOptions { public HashSet SupportedContentTypes { get; set; } = new HashSet { "application/x-protobuf", "application/protobuf", "application/x-google-protobuf" }; public HashSet SupportedExtensions { get; set; } = new HashSet { "proto" }; public bool SuppressReadBuffering { get; set; } = false; } public class ProtobufInputFormatter : InputFormatter { private readonly ProtobufFormatterOptions _options; public ProtobufInputFormatter(ProtobufFormatterOptions protobufFormatterOptions) { _options = protobufFormatterOptions ?? throw new ArgumentNullException(nameof(protobufFormatterOptions)); foreach (var contentType in protobufFormatterOptions.SupportedContentTypes) { SupportedMediaTypes.Add(new MediaTypeHeaderValue(contentType)); } } public override Task ReadRequestBodyAsync(InputFormatterContext context) { try { var request = context.HttpContext.Request; var obj = (IMessage)Activator.CreateInstance(context.ModelType); obj.MergeFrom(request.Body); return InputFormatterResult.SuccessAsync(obj); } catch (Exception ex) { Console.WriteLine("Exception: " + ex); return InputFormatterResult.FailureAsync(); } } } public class ProtobufOutputFormatter : OutputFormatter { private readonly ProtobufFormatterOptions _options; public string ContentType { get; private set; } public ProtobufOutputFormatter(ProtobufFormatterOptions protobufFormatterOptions) { ContentType = "application/x-protobuf"; _options = protobufFormatterOptions ?? throw new ArgumentNullException(nameof(protobufFormatterOptions)); foreach (var contentType in protobufFormatterOptions.SupportedContentTypes) { SupportedMediaTypes.Add(new MediaTypeHeaderValue(contentType)); } } public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var response = context.HttpContext.Response; // Proto-encode var protoObj = context.Object as IMessage; var serialized = protoObj.ToByteArray(); return response.Body.WriteAsync(serialized, 0, serialized.Length); } } } ``` 到 startup.cs 加入 `services.AddProtobufFormatter();`,startup.cs 完整內容如下: ``` using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ProtobufWeb.Formatters; namespace ProtobufWeb { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddProtobufFormatter(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } DefaultFilesOptions options = new DefaultFilesOptions(); options.DefaultFileNames.Clear(); options.DefaultFileNames.Add("index.html"); app.UseDefaultFiles(options); app.UseStaticFiles(); app.UseHttpsRedirection(); app.UseMvc(); } } } ``` ## 建立測試頁 `ProtobufWebSample/ProtobufWeb/wwwroot/index.html` ``` mkdir wwwroot mkdir wwwroot/scripts ``` 將 bundle.js 移到 wwwroot/scripts 資料夾 建立 index.html 內容如下 ``` Document ``` ## 執行網站,啟動 開發者工具查看結果 啟動網站 ``` dotnet run ``` 瀏覽器開啟 http://localhost:5001/index.html 使用開發者工具查看 Network 欄位的傳送結果 ## 參考資料 * [Protocol Buffers - C# Generated Code](https://developers.google.com/protocol-buffers/docs/reference/csharp-generated) * [Protocol Buffers - JavaScript Generated Code](https://developers.google.com/protocol-buffers/docs/reference/javascript-generated) * [protobuf 前端怎么使用 - 蝈蝈250 - 2017/11/21](https://blog.csdn.net/vteihlll/article/details/78592976)

沒有留言:

adsense