接上一篇,放棄了 asp.net core + gRPC 的方案後,我靈光一閃,爲何不用 web api 呢?不也是 asp.net core 的嗎?雖然 RESTful 不是強約束,客戶端寫起來也麻煩,但仍是能夠知足基本需求,避免大幅修改舊有的業務邏輯代碼
。php
在網上找到至關多的文章,比較 gRPC 和 RESTful 的優缺點,結論都是 gRPC
推薦用做內部系統間調用, RESTful
推薦用做對外開放接口。
選擇 RESTful
另外一個最重要的緣由是,gRPC
的底層框架須要HTTP2,而 win7 不支持HTTP2,有至關一部分用戶在 win7 上。上篇有人推薦 grpc web
,因爲項目是 WPF 桌面客戶端,這種 web 方式可能就更不適合了。html
這部分基本與上一篇的內容一致,爲了保證單篇文章的獨立性。把這部份內容徹底 copy 過來🙄🙄🙄。mysql
舊的WCF項目,數據庫訪問使用的是 Entity Framework + Linq + MySql。須要安裝的 Nuget 包:linux
MySql.Data.EntityFrameworkCore
Mysql的EF核心庫;Microsoft.EntityFrameworkCore.Proxies
《Lazy loading 》 懶加載的插件;Microsoft.EntityFrameworkCore.Design
和 Microsoft.EntityFrameworkCore.Tools
這兩個插件,用於生成代碼;另外,還須要下載安裝 mysql-connector-net-8.0.21.msi 來訪問數據庫。其中有一個 Scaffold-DbContext
的bug 99419 TINYINT(1) 轉化爲 byte,而不是預期的 bool。這個問題將會在 8.0.22 版本中修復,目前只能手動修改。
EF固然是 Database First
了,生成EF代碼須要在Package Manager Console
用到 Scaffold-DbContext 命令,有三點須要注意:nginx
Start up
啓始項目必定要是引用它的項目,而且編譯成功的;Default project
生成後,代碼存放的項目;<PrivateAssets>All</PrivateAssets>
從 "Microsoft.EntityFrameworkCore.Design"和"Microsoft.EntityFrameworkCore.Tools"中;個人命令: Scaffold-DbContext -Connection "server=10.50.40.50;port=3306;user=myuser;password=123456;database=dbname" -Provider MySql.Data.EntityFrameworkCore -OutputDir "EFModel" -ContextDir "Context" -Project "DataAccess" -Context "BaseEntities" -UseDatabaseNames -Force
git
其餘建議:github
Net Standard
方便移植;BaseEntities
,覆蓋 OnConfiguring
方法,可配置的數據庫鏈接字符串;public class Entities : BaseEntities { private static string _lstDBString; public static void SetDefaultDBString(string _dbString) { if (string.IsNullOrEmpty(_lstDBString)) { _lstDBString = _dbString; } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseLazyLoadingProxies().UseMySQL(_lstDBString); } } }
public void ConfigureServices(IServiceCollection services) { string _dbString = Configuration.GetConnectionString("MyDatabase"); services.AddDbContext<DataAccess.Context.Entities>( options => options.UseLazyLoadingProxies().UseMySQL(_dbString)); services.AddGrpc(); }
appsettings.json
;{ "ConnectionStrings": { "MyDatabase": "server=127.0.0.1;port=3306;user=myuser;password=123456;database=dbname" }, "log4net": "log4net.config", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
這部分但是水仍是有點深了。因爲最近幾年主要以 WPF 桌面軟件開發爲主,不多瞭解 asp.net core 。此次算是惡補了一下,下面是我的總結,一切以官方文檔爲準。web
啓動類 StartUp.cs
,在這裏面主要是註冊服務(Swagger、mvc等),註冊中間件(身份認證、全局異常捕獲等),以及不一樣環境的切換(Development、Production)。下面是個人 StartUp 類,有幾點經驗總結:sql
log4net.config
,鏈接字符串
等;services.AddControllers();
,而不是 AddMvc();Hello Asp.Net Core WebApi 3.1!
,爲了方便測試是否運行成功。public void ConfigureServices(IServiceCollection services) { InitConfig(); services.AddControllers(); services.AddSwaggerDocument(SwaggerDocumentConfig); // Register the Swagger services } private void InitConfig() { Entities.SetDefaultDBString(Configuration.GetConnectionString("MyDatabase")); Common.LogMaker.InitLog4NetConfig(Configuration.GetSection("log4net").Value); Common.WebApiLogger.Singleton.LogMaker.LogInfo("Start WebApi!"); } private void SwaggerDocumentConfig(NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGeneratorSettings config) { config.PostProcess = document => { document.Info.Version = typeof(Startup).Assembly.GetName().Version.ToString(); document.Info.Title = "Test Web Api"; document.Info.Description = "僅供測試和發開使用。"; document.Info.Contact = new NSwag.OpenApiContact { Name = "long", Email = "long@test.com" }; }; } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); // Register the Swagger generator and the Swagger UI middlewares app.UseOpenApi(); app.UseSwaggerUi3(); } //app.UseCustomExceptionMiddleware(); // 全局異常中間件 app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello Asp.Net Core WebApi 3.1!"); }); endpoints.MapControllers(); }); }
真心的以爲 asp.net core 的路由設計,真的是太棒了!而我只用到了其中很小的一部分《REST Api 的屬性路由》。其中有個注意點,全局路由與屬性路由會有衝突,須要特別注意。shell
爲了方便管理路由,靈活使用,以及後期版本的維護,建立一個路由模板
和 Controller基類
,全部 Controller 都繼承自 MyControllerBase
:
public class MyV1ApiRouteAttribute : Attribute, IRouteTemplateProvider { public string Template => "api/v1/[controller]/[action]"; public int? Order => 0; public string Name { get; set; } } [ApiController] [MyV1ApiRoute] [Produces(MediaTypeNames.Application.Json)] public class MyControllerBase : ControllerBase { }
Nswag
和 Swashbuckle
是微軟官方推薦的 Swagger 工具(官方 swagger 在線試用😆)。我選擇 Nswag 的主要緣由是,它提供的工具,根據 API 生成 C# 客戶端代碼,其實到最後我也沒有使用這個功能。 《NSwag 和 ASP.NET Core 入門》。
Nswag 使用起來也很是簡單,參考個人 啓動類 StartUp 中的寫法。若是想要把代碼中的註釋也體如今 Swagger 文檔中,須要執行一些額外的操做。
在 csproj 文件中增長 <GenerateDocumentationFile>true</GenerateDocumentationFile>
,另外,最好在 /Project/PropertyGroup/NoWarn
中增長 1591,不然你會獲得一大堆的 warning : # CS1591: Missing XML comment for publicly visible type or member. 緣由是項目中存在沒有註釋的方法,屬性或類。
在網上尋找有沒有現成的 RESTful 的 C# 工具,發現了WebApiClient,看了一下樣例,確實很是簡單,很是省事兒,只須要寫個簡單的 Interface 接口類,就能夠了,關鍵是它還支持各類奇奇怪怪的 HTTP 接口。
PS: 最開始讀 README.md 時候,老是一臉懵逼,一直把它當成 server 端的工具😓。直到開始寫客戶端的時候,才真正看懂了他的文檔。
Swagger 是一個與語言無關的規範,用於描述 REST API。既然 Swagger 是一種規範,那麼極有可能存在,根據 Swagger.json
生成代碼的工具。想着 WebApiClient 開發者是否是也已經提供了工具,果真不出所料,WebApiClient.Tools
只需運行一行命令,就能夠根據 Swagger.json
直接生成客戶端的實體類,接口,甚至包括註釋,簡直爽的不要不要的,完美的避開了手寫代碼的過程。
個人命令: WebApiClient.Tools.Swagger.exe --swagger=http://10.50.40.237:5000/swagger/v1/swagger.json --namespace=MyWebApiProxy
因爲還有一大部分的 win7 桌面軟件用戶,而他們大機率不會安裝 net core ,因此只能選擇 net framework 的版本 WebApiClient.JIT。使用起來也至關方便,只須要在啓動的時候初始化一下webapi地址,而後在須要的時候調用便可。
WebApiClient 提供的是一個異步的接口,因爲舊項目升級,避免大幅改動,就沒有使用異步的功能。
public static void InitialWebApiConfig(string _baseUrl) { HttpApi.Register<IUserManagementApi>().ConfigureHttpApiConfig(c => { c.HttpHost = new Uri(_baseUrl); c.FormatOptions.DateTimeFormat = DateTimeFormats.ISO8601_WithoutMillisecond; }); } public void Todo() { using (var client = HttpApi.Resolve<IUserManagementApi>()) { var _req = new LoginRequestV2(); _response = client.UserLoginExAsync(_req).InvokeAsync().Result; } }
項目的服務端,對操做系統沒有特別要求,因此直接選擇最新的 Ubuntu 20.04.1 LTS
。吸收了 gRPC 部署的一些經驗,此次只部署 http 服務,額外增長了 nginx 反向代理,只是由於在官網上看到了《使用 Nginx 在 Linux 上託管 ASP.NET Core》😜。
Kestrel 是 ASP.NET Core 項目模板指定的默認 Web 服務器,因此通常狀況下,ASP.NET Core是不須要額外的容器的。《ASP.NET Core 中的 Web 服務器實現》。下面是個人具體實現操做:
aspnetcore-runtime-3.1
和 nginx
;--urls
這個是很是實用的參數,可多端口;重點注意 ASPNETCORE_ENVIRONMENT
在配置是生產環境,仍是開發環境;> sudo nano /etc/systemd/system/kestrel-mywebapi.service [Unit] Description=mywebapi App running on Ubuntu [Service] WorkingDirectory=/home/user/publish ExecStart=/usr/bin/dotnet /home/user/publish/MyWebApi.dll --urls http://localhost:5000 Restart=always # Restart service after 10 seconds if the dotnet service crashes: RestartSec=10 KillSignal=SIGINT SyslogIdentifier=dotnet-example User=user # Production Development Environment=ASPNETCORE_ENVIRONMENT=Development Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target > sudo systemctl enable kestrel-mywebapi.service > sudo systemctl restart kestrel-mywebapi.service > sudo systemctl status kestrel-mywebapi.service
try_files $uri $uri/ =404
這一句須要註釋掉才行> sudo nano /etc/nginx/sites-available/default server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. # try_files $uri $uri/ =404; proxy_pass http://localhost:5000; } } > sudo nginx -t # 驗證配置文件的語法 > sudo nginx -s reload
sudo netstat -aptn sudo apt-get install ufw sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw enable sudo ufw status
瀏覽器測試 http://10.50.40.237
,返回預期的Hello Asp.Net Core WebApi 3.1!
,完美😁。還有一個小坑就是 https 尚未配置。
在開發階段,須要常常得編譯,打包,上傳。雖然 VS2019 具備直接發佈到 FTP 的功能,不過我沒有使用。一方面,該功能歷來沒用過,另外一方面,仍是想本身寫個更加靈活的腳本。
目前只實現了 編譯,打包,上傳的功能,後續再增長 ssh 登陸,解壓,重啓 asp.net 。
echo only win10 cd D:\Projects\lst\01-MyWebApi\MyWebApi rem 已註釋:dotnet publish --output bin/publish/ --configuration Release --runtime linux-x64 --framework netcoreapp3.1 --self-contained false dotnet publish -p:PublishProfileFullPath=/Properties/PublishProfiles/FolderProfile.pubxml --output bin/publish/ cd bin tar.exe -a -c -f publish.zip publish "C:\Program Files\PuTTY\psftp.exe" open 10.50.40.237 Welcome123 put "D:\Projects\lst\01-LstWebApi\LenovoSmartToolWebApi\bin\publish.zip" exit rem 已註釋:"C:\Program Files\PuTTY\putty.exe" user@10.40.50.237 22 -pw password pause
另外,用一個 WinForm 的測試小程序,嘗試了 self-contained = true
這種發佈方式,不須要客戶端安裝 net core 就能運行,發現編譯後從 1M+ 大幅增長到 150M+ ,妥妥的嚇壞了。即便使用了 PublishTrimmed=true
《剪裁獨立部署和可執行文件》,大小也有 100M+,果斷放棄。
log4net 由 1.2.13.0
升級到 2.0.8
後,初始化配置文件方法新增一個參數ILoggerRepository
。
public static void InitLog4NetConfig(string file) { var _rep = LogManager.GetRepository(System.Reflection.Assembly.GetCallingAssembly()); log4net.Config.XmlConfigurator.Configure(_rep, new System.IO.FileInfo(file)); }
部署到 Ubuntu 後,發現 log4net 報錯。須要把 log4net.Appender.ColoredConsoleAppender
替換爲 log4net.Appender.ManagedColoredConsoleAppender
。是因爲不一樣的 Appender 支持的 Framework 不一樣, ColoredConsoleAppender 支持 NET Framework 1.0~4.0 , ManagedColoredConsoleAppender 支持 NET Framework 2.0+ 。詳見:《Apache log4net™ Supported Frameworks》
MD5 摘要也出現問題,須要更改。緣由是 HashPasswordForStoringInConfigFile 在 net core 中已經不可用了,該方法在 framework 4.5 中也提示爲廢棄的方法。修改成下面新的 MD5 方法便可。
public static string GetOldMD5(string str) { string result = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(str, "MD5").ToLower(); return result; } public static string GetNewMD5(string str) { MD5CryptoServiceProvider _MD5Provider = new MD5CryptoServiceProvider(); byte[] bytes = Encoding.Default.GetBytes(str); byte[] encoded = _MD5Provider.ComputeHash(bytes); result = Encoding.ASCII.GetString(encoded).ToLower(); return result; }
目前,只遷移了一部分的 WCF 接口過來,等待部署到生產環境,能夠穩定運行後,再將剩餘部分所有遷移過來。此次的嘗試比較成功:
我只須要修改編輯器的 ERROR 的提示就能夠了。感受沒有寫什麼代碼🤣。。。頂多只寫了幾行粘合代碼🤣,一種搭積木的感受😝。其中 asp.net web api 還有不少的功能沒有使用,還須要更加細化到項目中。路漫漫~~