舊 WCF 項目成功遷移到 asp.net core web api

背景

接上一篇,放棄了 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

Entity Framework Core

這部分基本與上一篇的內容一致,爲了保證單篇文章的獨立性。把這部份內容徹底 copy 過來🙄🙄🙄。mysql

舊的WCF項目,數據庫訪問使用的是 Entity Framework + Linq + MySql。須要安裝的 Nuget 包:linux

  • MySql.Data.EntityFrameworkCore Mysql的EF核心庫;
  • Microsoft.EntityFrameworkCore.Proxies 《Lazy loading 》 懶加載的插件;
  • Microsoft.EntityFrameworkCore.DesignMicrosoft.EntityFrameworkCore.Tools 這兩個插件,用於生成代碼;

另外,還須要下載安裝 mysql-connector-net-8.0.21.msi 來訪問數據庫。其中有一個 Scaffold-DbContextbug 99419 TINYINT(1) 轉化爲 byte,而不是預期的 bool。這個問題將會在 8.0.22 版本中修復,目前只能手動修改。
EF固然是 Database First 了,生成EF代碼須要在Package Manager Console用到 Scaffold-DbContext 命令,有三點須要注意:nginx

  • Start up 啓始項目必定要是引用它的項目,而且編譯成功的;
  • Default project 生成後,代碼存放的項目;
  • 若是生成失敗,提示:「Your startup project 'XXXX' doesn't reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again.」。編輯項目文件 csproj 移除 <PrivateAssets>All</PrivateAssets> 從 "Microsoft.EntityFrameworkCore.Design"和"Microsoft.EntityFrameworkCore.Tools"中;

EF remove PrivateAssets

個人命令: 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 -Forcegit

其餘建議:github

  • Library類庫最好是 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);
        }
    }
}
  • 最好採用 asp.net core 的框架注入;鑑於項目的緣由,假如強行採用的話,改動比較大,只好放棄;
public void ConfigureServices(IServiceCollection services)
{
    string _dbString = Configuration.GetConnectionString("MyDatabase");
    services.AddDbContext<DataAccess.Context.Entities>(
        options => options.UseLazyLoadingProxies().UseMySQL(_dbString));
    services.AddGrpc();
}
{
    "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": "*"
}

服務端 asp.net core web api

這部分但是水仍是有點深了。因爲最近幾年主要以 WPF 桌面軟件開發爲主,不多瞭解 asp.net core 。此次算是惡補了一下,下面是我的總結,一切以官方文檔爲準web

啓動類 StartUp

啓動類 StartUp.cs ,在這裏面主要是註冊服務(Swagger、mvc等),註冊中間件(身份認證、全局異常捕獲等),以及不一樣環境的切換(Development、Production)。下面是個人 StartUp 類,有幾點經驗總結:sql

  • 初始化讀取全局配置參數,好比 log4net.config鏈接字符串等;
  • web api 只須要添加 services.AddControllers();,而不是 AddMvc();
  • Swagger 只在開發環境下啓用,而生產環境無效《在 ASP.NET Core 中使用多個環境》 多環境開發測試,真的太好用了,強烈推薦使用
  • 在根路徑下增長返回內容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();
    });
}

路由 和 Controller

真心的以爲 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

NswagSwashbuckle 是微軟官方推薦的 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. 緣由是項目中存在沒有註釋的方法,屬性或類

vs-swagger

客戶端 WebApiClient

在網上尋找有沒有現成的 RESTful 的 C# 工具,發現了WebApiClient,看了一下樣例,確實很是簡單,很是省事兒,只須要寫個簡單的 Interface 接口類,就能夠了,關鍵是它還支持各類奇奇怪怪的 HTTP 接口
PS: 最開始讀 README.md 時候,老是一臉懵逼,一直把它當成 server 端的工具😓。直到開始寫客戶端的時候,才真正看懂了他的文檔。

WebApiClient.Tool

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

WebApiClient.JIT

因爲還有一大部分的 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 + Nginx

項目的服務端,對操做系統沒有特別要求,因此直接選擇最新的 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 服務器實現》。下面是個人具體實現操做:

  1. 根據文檔《在 Linux 上安裝 .NET Core》《安裝 Nginx》 指引,安裝 aspnetcore-runtime-3.1nginx
  2. 建立 Linux 的 web api 的服務文件,並啓動。個人示例,--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
  1. 配置 Nginx ,這部分我用的比較簡單,只用到轉發功能,將來可能在這一層增長 SSL ;另外 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
  1. 開啓防火牆端口,Ubuntu 是默認關閉22端口。安全起見,避免被頻繁掃描,建議把 ssh 默認端口 22 改成其餘不常見的端口號。
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 接口過來,等待部署到生產環境,能夠穩定運行後,再將剩餘部分所有遷移過來。此次的嘗試比較成功:

  1. 一是知足了基本需求,較少改動老舊代碼
  2. 二是大部分代碼由工具生成,好比 API 文檔,接口的實體類;
  3. 三是不少經常使用功能,都有現成的插件來完成。

我只須要修改編輯器的 ERROR 的提示就能夠了。感受沒有寫什麼代碼🤣。。。頂多只寫了幾行粘合代碼🤣,一種搭積木的感受😝。其中 asp.net web api 還有不少的功能沒有使用,還須要更加細化到項目中。路漫漫~~

相關文章
相關標籤/搜索