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

相关文章
相关标签/搜索