接上一篇,放弃了 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 还有不少的功能没有使用,还须要更加细化到项目中。路漫漫~~