利用dotnet命令行,快速创建简单WEB三层架构

我们要保证dotnet的开发环境和运行已经安装在本地,我们可以利用“
dotnet –version”命令进行检查,如果发现提示命令行不存在,说明环境还没安装,需要先安装,安装过程本文不在详细描述,参考微软的官方说明即可,本文重点在于如何用dotnet命令行,快速构建简单三层架构

一:首先,我们创建好项目的文件夹,然后进入文件夹,创建一个web的应用程序,具体用到的命令是“dotnet new”,dotnet new – 根据指定的模板,创建新的项目、配置文件或解决方案。常用的有类库“classlib”、控制台应用程序“console”、WebAPI应用“webapi”、ASP.NET Core Web 应用程序“webapp”

好了,我们创建一个名称叫Server的WebAPI程序、业务逻辑层的类库BLL、数据交互层DAL和工具层Common

dotnet new webapi -n Server
dotnet new classlib -n BLL
dotnet new classlib -n DAL
dotnet new classlib -n Common

此时,我们可以用visual code看下目录结构

二:我们要让这些类库至今添加引用关系,让Server引用BLL层和Common;BLL层引用DAL层和Common;DAL层引用Common层;这里,我们用到的命令是“dotnet add/list/remove reference”

dotnet add Server/Server.csproj reference BLL/BLL.csproj
dotnet add Server/Server.csproj reference Common/Common.csproj
dotnet add BLL/BLL.csproj reference DAL/DAL.csproj
dotnet add BLL/BLL.csproj reference Common/Common.csproj
dotnet add DAL/DAL.csproj reference Common/Common.csproj

三:接下来我们要在DAL层引入微软推荐的EntityFrameWork框架来进行数据库数据的访问,此处例子,我已经在“.Net Core + EntityFramework连接数据库”一文中有描述,此处只做简单说明,我用MySQL为例,我们需要引入

Microsoft.EntityFrameworkCore

Microsoft.EntityFrameworkCore.Design

Microsoft.EntityFrameworkCore.Tools

Pomelo.EntityFrameworkCore.MySql

Microsoft.Extensions.Configuration.Json

我们用dotnet add package – 添加或更新项目文件中的包引用。在引用之前,我们可以用dotnet package search 命令搜索 NuGet 包。

dotnet package search Microsoft.EntityFrameworkCore

然后,我们可以选择对应的版本,我这里使用的是8.0.2,进行添加,当然,也可以忽略版本号,用最新版本,前提是你的.net的框架也是最新的,可以兼容此版本

dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore.Design --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore.Tools --version 8.0.2
dotnet add DAL/DAL.csproj package Pomelo.EntityFrameworkCore.MySql --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.Extensions.Configuration.Json

三:下面框架和引用已经搭建完成了,我们需要把项目和数据库连接起来,让我们在DAL项目里面新建一个appsettings.json配置文件,把mysql的数据库连接字符串写上,里面填上你的数据库ip,用户名,密码,以及你想存放的数据库名称

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Port=3306;Database=数据库名称;User=root;Password=你的mysql密码;Charset=utf8mb4;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

在DAL项目里创建Modles文件夹用来存放数据库对象,比如User类,对应数据库User表

namespace DAL.Models;
using System.ComponentModel.DataAnnotations.Schema;

public class User
{
  [Column("Id")]
  public int Id { get; set; }
  
  [Column("UserName")]
  public string? UserName { get; set; }
}

在DAL项目里创建Contexts文件用来存放上下文类,此类建议和数据库名称一一对应

namespace DAL.Contexts;
using Microsoft.EntityFrameworkCore;
using DAL.Models;
using Microsoft.Extensions.Configuration;

public class webapplicationContext : DbContext
{
  public webapplicationContext()
  {
    Configuration = new ConfigurationBuilder().SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build();
  }
  public IConfiguration Configuration;
  public required DbSet<User> Users { get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
  }
}

四:初始化数据库,当我们完成了项目基本的搭建,这时候,我们需要用Entity Framework Core 的命令行工具,创建数据库,首先我们打开终端,定位到项目所在位置,然后,就可以用ef命令来初始化数据库,如图所示

cd DAL
dotnet ef migrations add InitialCreate
dotnet ef database update

完成后,发现我们对应的数据库就会有对应的库和表了

关于EntityFramWork的操作,请参考.Net Core+Entity Framework读写数据

下面,让我们回到Server文件夹下,运行dotnet run命令,找到对应端口,访问网页http://localhost:端口/swagger/index.html,看看是否正常

cd ..
cd Server
dotnet run

最后,我们可以给项目文件根里添加.gitignore文件,忽略bin文件,方便以后版本控制

*.log
temp/
.vs/
BLL/bin
BLL/obj
DAL/bin
DAL/obj
Server/bin
Server/obj
Common/bin
Common/obj

我们也可以在Server项目里新建Controllers文件夹,用来存放控制类

using Microsoft.AspNetCore.Mvc;

namespace Server.Controllers;

[ApiController]
[Route("api/[controller]")]
public class WeatherForcecastController : ControllerBase
{
    private readonly ILogger<WeatherForcecastController> _logger;

    public WeatherForcecastController(ILogger<WeatherForcecastController> logger)
    {
        _logger = logger;
    }

    private static readonly string[] Summaries = new[]
    {
      "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [HttpGet(Name = "GetWeatherForecast")]
    public IEnumerable<WeatherForecast> GetWeatherForecast()
    {
        var forecast =  Enumerable.Range(1, 5).Select(index =>  new WeatherForecast
        (
          DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
          Random.Shared.Next(-20, 55),
          Summaries[Random.Shared.Next(Summaries.Length)]
        )).ToArray();
        return forecast;
    }

    public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
    {
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

最后把Server/Program.cs文件调整一下,加载控制类

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.MapControllers();
app.Run();

IIS 应用程序池和站点的导入导出

1. 在IIS7+上导出所有应用程序池的方法:

%windir%/system32/inetsrv/appcmd list apppool /config /xml > c:/apppools.xml

这个命令会将服务器上全部的应用程序池都导出来,但有些我们是我们不需要的,要将他们删掉.比如:DefaultAppPool、Classic .Net AppPool,如果在导入时发现同名的应用程序池已经存在,那么导入就会失败. 

2.导入应用程序池的方法: 

%windir%/system32/inetsrv/appcmd add apppool /in < c:/apppools.xml

3.导出全部站点的方法:

%windir%/system32/inetsrv/appcmd list site /config /xml > c:/sites.xml

同样,我们需要编辑sites.xml文件删除不需要的站点.如:Default 

4.导入站点的方法:

%windir%/system32/inetsrv/appcmd add site /in < c:/sites.xml

申请免费SSL证书并配置Nginx绑定WordPress

SSL证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。

目前,网上也有一些有效时间较长的免费的证书,以SSL FOR FREE为例

申请好后,下载完成将得到3个文件ca_bundle.crt、certificate.crt以及私钥private.key,值得注意的是,如果我们要把证书配置到Nginx,按照官方说法,则需要把ca_bundle.crt、certificate.crt文件合并。

我们先把文件上传到服务器,然后执行如下命令

cat certificate.crt ca_bundle.crt

然后,我们把得到的新文件certificate.crt和私钥private.key放到Nginx对应的证书目录上,然后,修改nginx的配置文件,以我的网站为例,(location反向代理的跳转部分可以忽略)

server {
	listen 80;
	server_name www.tzqutao.top tzqutao.top;
	return 301 https://$host$request_uri; 
}

server {
    listen 443 ssl;
    server_name www.tzqutao.top tzqutao.top;
    ssl_certificate      /var/log/nginx/certificate.crt; 
    ssl_certificate_key  /var/log/nginx/private.key;
    
    access_log  /var/log/nginx/www.tzqutao.top.access.log;
    error_log /var/log/nginx/www.tzqutao.top.error.log;
    location / {
        proxy_pass http://172.17.0.3;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

此处,我们需要确保服务器对外的80端口和443端口是通畅的,如果Nginx是在DOCKER中,则还要保证容器的端口和本地80端口和443端口是有存在映射的,完成之后,重启Nginx即可

下面,我们来具体说明WordPress网站,如何启用Https,证书安装好后,我们进入到WordPress根目录的\wp-includes\functions.php,找到代码 require( ABSPATH . WPINC . ‘/option.php’ );大概在第8行。在下方添加以下代码:

add_filter('script_loader_src', 'agnostic_script_loader_src', 20,2); function agnostic_script_loader_src($src, $handle) { return preg_replace('/^(http|https):/', '', $src); } 
add_filter('style_loader_src', 'agnostic_style_loader_src', 20,2); function agnostic_style_loader_src($src, $handle) { return preg_replace('/^(http|https):/', '', $src); }

打开网站根目录\wp-config.php文件找到代码,在下方添加如下代码:

*
* @package WordPress
*/

$_SERVER['HTTPS'] = 'on';
define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);

此时,我们登录到WordPress后台,把WordPress站点地址修改为HTTPS的路径,就完成了HTTP到HTTPS的迁移

Amazon Linux系统Docker定时重启

我们部署完DOCKER容器之后,有时候需要定时设定重启时间用来回收容器的内存资源,如果当前服务器是亚马逊OS系统的,可以使用以下方法

首先,我们先安装crontab,所谓crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行。有点像WINDOS系统中的定时任务

sudo yum install cronie

启动并设置crond服务开机自启:

sudo systemctl start crond
sudo systemctl enable crond

然后,我们可以配置当前用户的crontab文件:

crontab -e

格式如下:比如,我们想每日凌晨一点重启容器WordPress、mystifying_hoover、mathapp,就可以按照下面的写法

0 1 * * * /usr/bin/docker restart mystifying_hoover
0 1 * * * /usr/bin/docker restart wordpress
0 1 * * * /usr/bin/docker restart mathapp

保存并退出编辑器。系统会自动安装新的crontab文件。验证计划任务是否已经正确安装

crontab -l

看到下面的结果,说明已经定时任务已经启用,下面3个容器会在凌晨1点重启

Linq多条件字段的多表连接

假设,我们有一个类StationCat,它的具体属性如下:

public class StationCat
{
    public string CustomerModel { get; set; }

    public string baj_cZZLINE { get; set; }

    public string IssuePosition { get; set; }

    public string fact { get; set; }

    public int InputCount { get; set; }

    public int RepCount { get; set; }

    public string S_NM { get; set; }
}

此时,我们有2个由这个类实例化出的实体数组SWAPMaterials和StationCats,当我们想要把SWAPMaterials左连接到StationCats时,条件为SWAPMaterials表中元素的CustomerModel、baj_cZZLINE、S_NM与StationCats表元素对应的属性相等,那我们应该按照如下方式写LINQ

var resSWAPMaterials = (from p in SWAPMaterials
join StationCat in StationCats on new { p.CustomerModel, p.baj_cZZLINE,p.S_NM } equals new { StationCat.CustomerModel,StationCat.baj_cZZLINE,StationCat.S_NM } into p_StationCat
from StationCat_join in p_StationCat.DefaultIfEmpty()
select new StationCat()
{
    CustomerModel = p.CustomerModel,
    baj_cZZLINE = p.baj_cZZLINE,
    IssuePosition = p.IssuePosition,
    fact = p.fact,
    RepCount = p.RepCount,
    S_NM = p.S_NM,
    InputCount = StationCat_join == null ? 0 : StationCat_join.InputCount
}).ToList();

到此,我们就实现了Linq多条件字段的多表连接

使用HTTP请求方式访问.Net的WebServer服务

.Net的WebServer服务,是一项已经过时的技术,随着技术迭代,当前跨平台的通讯,更多使用用JSON格式数据,发起HTTP请求来实现,但是,有时候,我们为了和老的.NET的WebServer框架通讯,不得不处理和面对的问题

WebServer服务,其实说白了,就是用XML的格式来处理通讯问题,我们可以试着写一个.Net的WebServer服务,文件名如下:WMI.asmx

/// <summary>
/// WMI 的摘要说明
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 
[System.Web.Script.Services.ScriptService]
public class WMI : System.Web.Services.WebService
{
    [WebMethod(EnableSession = true)]
    public string WMI01(string Par)
    {
        try
        {
            //业务逻辑
            returnOK;
        }
        catch (Exception ex)
        {
            //处理异常信息
            return "存在异常信息";
        }
        finally
        {
            //如不记录就 就注释以下内容
        }
    }
}

此时,我们可以运行程序,预览页面效果,接下来,我们在向这个server地址提交http请求的时候,可以使用以下方法:

一、在HTTP头信息上,加上“Content-Type”,值为“text/xml; charset=utf-8”

二、撰写XML格式的请求对象文本,注意,<Par>节点就是WebServer的参数节点,<WMI01>节点就是WebServer的控制器中对应的方法函数

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <WMI01 xmlns="http://tempuri.org/">
      <Par>让我们试试看</Par>
    </WMI01>
  </soap:Body>
</soap:Envelope>

最后,我们可以在POSTMAN程序下试试,预览下请求结果,发现<WMI01Result>节点,就是我们程序返回的处理结果

C#路由方式调用RabbitMQ

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

所谓RabbitMQ的路由模式(Routing),就是有选择地(Routing key)接收消息,发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换机时需要指定路由key,仅消费指定路由key的消息

Exchange不再把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断,只有队列的RoutingKey与消息的RoutingKey完全一致,才会接收消息,路由模式使用的Exchange类型为Direct类型

举个例子:如在商品库存中增加了1台iphone12,iphone12促销活动消费者指定routing key为iphone12,只有此促销活动会接收到消息,其它促销活动不关心也不会消费此routing key的消息

下面,我们用C#代码来具体实现一下,以下逻辑需要NuGet里的RabbitMQ.Client包支持,首先安装RabbitMQ.Client包

接下来,我们声明一个类库,用来存放消息对象ReceiverMsgData,为了通用,我特地申明了类型,操作,具体对象实体等内容,以下属性仅供参考,安装自己的具体业务需求设计即可

/// <summary>
/// 消息内容
/// </summary>
public class ReceiverMsg
{
    /// <summary>
    /// 消息类型
    /// 10001->定时任务(队列:calculation_remind)
    /// 23001->订单下单分佣计算(队列:calculation_orderaward)
    /// 23005->订单支付成功计算报表(队列:calculation_orderoperate)
    /// 23006->订单售后成功计算报表(队列:calculation_orderoperate)
    /// </summary>
    [Required(ErrorMessage = "{0} 必须填写")]
    public int msg_type { get; set; }

    /// <summary>
    /// 操作人
    /// </summary>
    [Required(ErrorMessage = "{0} 必须填写")]
    public long creator_id { get; set; }

    /// <summary>
    /// 单据id
    /// </summary>
    [Required(ErrorMessage = "{0} 必须填写")]
    public int source_id { get; set; }

    /// <summary>
    /// 单据来源,定时任务专用(1订单)
    /// </summary>
    public int source_type { get; set; }

    /// <summary>
    /// 单据操作,定时任务专用 (1添加,2发货,3签收)
    /// </summary>
    public int action_type { get; set; }

    /// <summary>
    /// 定时任务执行的时间(无需填写,定时任务自动生成)
    /// </summary>
    public DateTime next_remind_time { get; set; }

    /// <summary>
    /// 错误消息系统编码(无需填写,自动重试用)
    /// </summary>
    public int error_msg_id { get; set; }

    /// <summary>
    /// 错误消息所在的队列名称(无需填写,自动重试用)
    /// </summary>
    public string error_msg_domain { get; set; }

    /// <summary>
    /// 消息附加内容
    /// </summary>
    public object msg_body { get; set; }
}

/// <summary>
/// 消息对象
/// UserName:admin
/// Password:Myun@123jx
/// VirtualHost:Publish
/// Port:5672
/// ExchangeName:calculation
/// </summary>
[Serializable]
public class ReceiverMsgData
{
    /// <summary>
    /// VirtualHost
    /// </summary>
    [Required(ErrorMessage = "{0} 必须填写")]
    public string pattern { get; set; }

    /// <summary>
    /// Msgid
    /// </summary>
    public string id { get; set; }

    /// <summary>
    /// 消息内容
    /// </summary>
    [Required(ErrorMessage = "{0} 必须填写")]
    public ReceiverMsg data { get; set; }
}

然后,我们生命一个生产者,用来产生消息

public class RabbitMQHelp
{
    public static IConfigurationRoot configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
    private static string HostName = EncryptHelp.Decrypt(configuration.GetSection("RabbitMQ")["HostName"]);  //RabbitMQ服务IP地址
    private static string UserName = EncryptHelp.Decrypt(configuration.GetSection("RabbitMQ")["UserName"]);  //RabbitMQ用户名
    private static string Password = EncryptHelp.Decrypt(configuration.GetSection("RabbitMQ")["Password"]);  //RabbitMQ密码
    private static int Port = Convert.ToInt32(EncryptHelp.Decrypt(configuration.GetSection("RabbitMQ")["Port"]));//RabbitMQ端口 默认5672
    private static string VirtualHost = EncryptHelp.Decrypt(configuration.GetSection("RabbitMQ")["VirtualHost"]);//Virtual Hosts名称
    private static string ExchangeName = "calculation";//交换机名称

    /// <summary>
    /// 连接配置
    /// </summary>
    private static readonly ConnectionFactory rabbitMqFactory = new ConnectionFactory()
    {
        HostName = HostName,
        UserName = UserName,
        Password = Password,
        Port = Port,
        VirtualHost = VirtualHost
    };

    /// <summary>
    /// 单点精确路由模式,重发消息
    /// </summary>
    public static void DirectExchangeSendMsg(string QueueName, string msgBody)
    {
        using (IConnection conn = rabbitMqFactory.CreateConnection())
        {
            using (IModel channel = conn.CreateModel())
            {
                channel.ExchangeDeclare(ExchangeName, ExchangeType.Direct, durable: true, autoDelete: false, arguments: null);
                channel.QueueDeclare(QueueName, durable: true, autoDelete: false, exclusive: false, arguments: null);
                channel.QueueBind(QueueName, ExchangeName, routingKey: QueueName);

                var props = channel.CreateBasicProperties();
                props.Persistent = true; //消息永久化硬盘上,打开会降低些许性能,但是可以防止服务器意外断电导致消息队列数据消失的情况
                var BytmsgBody = Encoding.UTF8.GetBytes(msgBody);
                channel.BasicPublish(exchange: ExchangeName, routingKey: QueueName, basicProperties: props, body: BytmsgBody);
            }
        }
    }
}

最后,我们声明一个消费者,用来接收消息

/// <summary>
/// 消费者
/// </summary>
/// <param name="QueueName"></param>
public static void ReceiveWeatherInfo(string QueueName)
{
    using (var connection = rabbitMqFactory.CreateConnection())
    {
        using (var channel = connection.CreateModel())
        {
            channel.ExchangeDeclare(ExchangeName, ExchangeType.Direct, durable: true, autoDelete: false, arguments: null);
            channel.QueueDeclare(QueueName, durable: true, autoDelete: false, exclusive: false, arguments: null);
            channel.QueueBind(QueueName, ExchangeName, routingKey: QueueName);
            channel.BasicQos(0, 1, false);
            var consumer = new EventingBasicConsumer(channel);

            consumer.Received += (model, ea) =>
            {
                var message = Encoding.UTF8.GetString(ea.Body.ToArray());
                Console.WriteLine($"队列{QueueName}收到的气象信息:{message}");
                channel.BasicAck(ea.DeliveryTag, false);
            };
            channel.BasicConsume(queue: QueueName,autoAck: false,consumer: consumer);
            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();
        }
    }
}

下面,我们演示一下消息发送

//自动处理售后消息
public static void doDealReturnOrder(int Return_Id)
{
    RabbitMQHelp.DirectExchangeSendMsg("calculation_orderoperate", JsonConvert.SerializeObject(new ReceiverMsgData
    {
        id = Guid.NewGuid().ToString(),
        pattern = "Publish",
        data = new ReceiverMsg()
        {
            source_id = Return_Id,
            msg_type = 23019,
        }
    }));//重发消息
}

最后,只要由控制台的Main函数指定队列名称,调度这个消费者方法,就能接收指定队列的消息了,注意我这里,应为发送的消息是序列化的JSON,所以,接收消息需要反序列化一下

.Net Core+Entity Framework读写数据

同步操作

查询数据

为了防止连接数过多,忘记及时释放,建议用using方式操作

using (var context = new MySqlTestDBContext())
{
    var blogs = context.Blogs
        .Where(b => b.Url.Contains("dotnet"))
        .ToList();
}

查询数据,使用的是大量的Linq语言,复杂查询也包括Join等多表连接,和Group By后,Sum(),Avg(),Max(),Min(),FirstOrDefault(),LastOrDefault()等聚合函数,具体可查询Linq的语法,这里不再展开

添加数据

using (var context = new MySqlTestDBContext())
{
	var blog = new Blog { Url = "http://example.com" };
	var Id = context.Blogs.Add(blog).Entity.Id;
	context.SaveChanges();
}

使用 DbSet.Add 方法添加实体类的新实例。 调用 SaveChanges 时,数据将插入到数据库中。

更新数据

找到Blogs表中,BlogId为1的数据,将它的Url字段进行更新

using (var context = new MySqlTestDBContext())
{
	var blog = context.Blogs.FirstOrDefault(i=>i.Id==1);
	blog.Url = "http://example.com/blog";
	context.SaveChanges();
}

删除数据

找到Blogs表中,BlogId为1的数据,将它物理删除

using (var context = new MySqlTestDBContext())
{
    var blog = context.Blogs.FirstOrDefault(i=>i.Id==1);
    context.Blogs.Remove(blog);
    context.SaveChanges();
}

事务

对于大多数数据库提供程序,“SaveChanges” 是事务性的。 这意味着所有操作将一起成功或一起失败,绝不会部分的应用这些操作。

using (var context = new MySqlTestDBContext())
{
    // seeding database
    context.Blogs.Add(new Blog { Url = "http://example.com/blog" });
    context.Blogs.Add(new Blog { Url = "http://example.com/another_blog" });
    context.SaveChanges();
}

using (var context = new MySqlTestDBContext())
{
    // add
    context.Blogs.Add(new Blog { Url = "http://example.com/blog_one" });
    context.Blogs.Add(new Blog { Url = "http://example.com/blog_two" });

    // update
    var firstBlog = context.Blogs.First();
    firstBlog.Url = "";

    // remove
    var lastBlog = context.Blogs.Last();
    context.Blogs.Remove(lastBlog);

    context.SaveChanges();
}

异步操作

当在数据库中执行查询时,异步查询可避免阻止线程。 异步查询对于在胖客户端应用程序中保持响应式 UI 非常重要。 异步查询还可以增加 Web 应用程序中的吞吐量,即通过释放线程,以处理 Web 应用程序中的其他请求

查询数据

public async Task<List<Blog>> GetBlogsAsync()
{
    using (var context = new MySqlTestDBContext())
    {
        return await context.Blogs.ToListAsync();
    }
}

数据集除了ToList方法外,还有一个ToListAsync方法,返回的是一个异步的Task对象

保存数据

同理,异步保存也有一个方法SaveChangesAsync(),返回的也是一个异步Task对象

public static async Task AddBlogAsync(string url)
{
    using (var context = new BloggingContext())
    {
        var blog = new Blog { Url = url };
        context.Blogs.Add(blog);
        await context.SaveChangesAsync();
    }
}

.Net Core + EntityFramework连接数据库

在本教程中,将创建一个 .NET Core 控制台应用,该应用使用 Entity Framework Core 对 SQLServer、MySQL数据库执行数据访问。

你可在 Windows 上使用 Visual Studio,或在MacOS上使用Visual Studio For Mac来学习本教程。

运行系统:Windows、MacOS、Linux

.Net Core环境:.Net Core 3.0+

开发架构搭建

首先创建Core控制台应用程序

目标框架选择.Net Core 3.0以上,起个项目名称,(以下用MySqlTest为例子)

创建项目完成后,选中项目,右键NuGet包管理,搜索entityframeworkcore

此时,我们要选择如下的几个包进行安装

Microsoft.EntityFrameworkCore

Microsoft.EntityFrameworkCore.Design

Microsoft.EntityFrameworkCore.Tools

然后,我们要根据我们项目的数据库。选应该使用的包,

如果是MySql数据库,选用

Pomelo.EntityFrameworkCore.MySql

如果是SQL Server数据库,选用

Microsoft.EntityFrameworkCore.SqlServer

最后,我们在搜索NuGet包,输入搜索Microsoft.Extensions.Configuration.Json,进行安装

Microsoft.Extensions.Configuration.Json

(以下用mysql数据库为例)

项目新建完毕后,我们再新建一个项目配置项,选中项目,右键->添加->新增文件->应用程序设置文件,然后把数据库连接字符串放上去

我们给数据库起名为MySqlTestDB,注意不同数据库使用的连接字符串不同

MySql数据库:

Server=localhost;Database=MySqlTestDB;Port=3306;charset=utf8;uid=用户;pwd=密码;

SqlServer数据库:

server=localhost,1433;database=MySqlTestDB;uid=用户;pwd=密码

创建模型和上下文

定义构成模型的上下文类和实体类。

我们在项目里新增“Modles”文件夹,用于存放数据库实体类

新增“Contexts”文件夹,用于存放上下文类

using MySqlTest.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace MySqlTest.Contexts
{
    public class MySqlTestDBContext: DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        private IConfiguration configuration;

        public MySqlTestDBContext()
        {
            configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySql(configuration.GetConnectionString("Default"));
        }
    }
}

Models对应着我们数据库里的表集合。

Entity Framework Core 中,基类默认主键名称为Id,那么在实际表设计中主键名称不一定为Id,那我们可以用Column来指定字段映射

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace MySqlTest.Models
{
    public class Blog
    {
        [Column("BlogId")]
        public int Id { get; set; }

        [Column("Url")]
        public string Url { get; set; }

        public List<Post> Posts { get; } = new List<Post>();
    }
}
using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace MySqlTest.Models
{
    public class Post
    {
        [Column("PostId")]
        public int Id { get; set; }

        [Column("Title")]
        public string Title { get; set; }

        [Column("Content")]
        public string Content { get; set; }
    }
}

创建数据库

以上,我们完成了项目基本的搭建,这时候,我们需要用Entity Framework Core 的命令行工具,创建数据库,首先我们打开终端,定位到项目所在位置,如图所示

然后我们编译项目,输入如下命令(当然,也可以在Visual Studio里面直接编译)

dotnet build

然后我们输入以下命令

dotnet ef migrations add InitialCreate

这时,我们会看见项目里面多了一个文件夹,里面是升级的内容

然后我们在终端输入以下命令

dotnet ef database update

完成之后,我们就可以看见,我们的本地数据库已经有对应的数据库和数据表生成了

数据库添加字段

当然,我们在项目迭代过程中。回遇到数据库新增字段的情况,这时候,我们要先在对应的Modle实体类里面做调整,按Blog类为例,我们新增一个属性Title

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace MySqlTest.Models
{
    public class Blog
    {
        [Column("BlogId")]
        public int Id { get; set; }

        [Column("Url")]
        public string Url { get; set; }

        [Column("Title")]
        public string Title { get; set; }

        public List<Post> Posts { get; } = new List<Post>();
    }
}

然后,我们回到终端,重新编译(当然,也可以在Visual Studio里面直接编译)

dotnet build

然后我们输入以下命令

dotnet ef migrations add InitialUpdate_Title

然后我们在终端输入以下命令

dotnet ef database update

好了,这时候,我们看下数据库,已经有新的字段出来了

当然,除此之外,migrations 命令还可以回退sql,也可以指定时间范围进行升级和降级,更多功能请参考官方文档
此处不再累赘。

数据库新增表

当然,我们在项目迭代过程中。回遇到数据库新增表的情况,这时候,我们要先在对应的Modle实体类里面新增实体Person

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace MySqlTest.Models
{
    public class Person
    {
        [Column("PersonId")]
        public int Id { get; set; }

        [Column("CelPhoneNo")]
        public string CelPhoneNo { get; set; }

        [Column("Name")]
        public string Name { get; set; }
    }
}

然后,在上下文里面添加这个属性 public DbSet<Person> Persons { get; set; }

using MySqlTest.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace MySqlTest.Contexts
{
    public class MySqlTestDBContext: DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
        public DbSet<Person> Persons { get; set; }

        private IConfiguration configuration;

        public MySqlTestDBContext()
        {
            configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseMySql(configuration.GetConnectionString("Default"));
        }
    }
}

然后,我们回到终端,重新编译(当然,也可以在Visual Studio里面直接编译)

dotnet bulid

然后我们输入以下命令

dotnet ef migrations add InitialAdd_TablePerson

然后我们在终端输入以下命令

dotnet ef database update

当我们有多个数据库DBContext的时候,命令后面带个参数即可

dotnet ef  migrations add xxxx --context XXXDBContext

dotnet ef database update --context XXXDBContext

搭建.Net Core WebAPI应用程序

在本章节,我们将着重介绍,如果搭建一个基于“.Net Core 3.1 WebAPI+EntityFrameWork+数据库”的应用程序

首先,我们需要.Net Core 3.1 WebAPI的应用程序

所需要的工具:

.Net Core 3.1运行开发环境

Visual Studio 2019

MySql 数据库或SqlServe数据库

创建空白解决方案

打开Visual Studio 2019 ->创建新项目,搜索“空白解决方案”,输入要搭建的服务名称,一般为“MYun.BPC.WebAPI.XXX”,如:

MYun.BPC.WebAPI.Basic

创建完成后,打开解决方案,选中,右键->添加->新建解决方案文件架。现阶段我们需要新增2个文件夹

01_Host 用于存放站点

02_Server 业务逻辑类库

完成后,我们就可以添加站点了

创建Action和Query站点

选中01_Host文件架,右键->添加->新建项目,搜索“ASP.NET Core Web”,选中,点击“下一步”,输入项目名称,名称一般为“MYun.BPC.WebAPI.XXX.Action”和“MYun.BPC.WebAPI.XXX.Query”如:

MYun.BPC.WebAPI.Basic.Action 用于处理端提交的操作请求

MYun.BPC.WebAPI.Basic.Query 用于处理端提交的查询请求

点击创建。这时,我们选中框架为“ASP.Net Core 3.1”,应用程序为“API”,Https配置关闭

创建完成后,效果如下:

到此,WabAPI站点创建完成,接下来,我们需要添加下面几个引用库,用来支持我们的站点

MYun.MY.Contract.Data.dll 契约类库,由类库Contract项目编译生成,用于承载返回的对象

Service.dll 框架类库,由类库Contract项目编译生成,用于承载返回对象的基类

此外,我们还需要在NuGet添加

Newtonsoft.Json

的包。用来支持Json对象的序列化

右键单击“解决方案资源管理器” -> “管理 NuGet 包”中的项目

将“包源”设置为“nuget.org”

在搜索框中输入“Newtonsoft.Json”

从“浏览”选项卡中选择“Newtonsoft.Json”包,然后单击“安装”

当前版本为“12.0.3”

为站点生成说明文档

在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者的心情。或者详细点,或者简单点。那么有没有一种快速有效的方法来构建api说明文档呢?答案是肯定的, Swagger就是最受欢迎的REST APIs文档生成工具之一!

为什么使用Swagger作为REST APIs文档生成工具

   Swagger 可以生成一个具有互动性的API控制台,开发者可以用来快速学习和尝试API。

   Swagger 可以生成客户端SDK代码用于各种不同的平台上的实现。

   Swagger 文件可以在许多不同的平台上从代码注释中自动生成。

   Swagger 有一个强大的社区,里面有许多强悍的贡献者。

asp.net core中如何使用Swagger生成api说明文档呢

   Swashbuckle.AspNetCore 是一个开源项目,用于生成 ASP.NET Core Web API 的 Swagger 文档。

   NSwag 是另一个用于将 Swagger UI 或 ReDoc 集成到 ASP.NET Core Web API 中的开源项目。 它提供了为 API 生成 C# 和 TypeScript 客户端代码的方法。

下面以Swashbuckle.AspNetCore为例为大家进行展示

右键单击选中站点 -> “管理 NuGet 包”中的项目

将“包源”设置为“nuget.org”

在搜索框中输入“Swashbuckle.AspNetCore”

从“浏览”选项卡中选择“Swashbuckle.AspNetCore”包,然后单击“安装”

当前版本为“5.6.1”

Action和Query两个站点都安装完毕后,我们再右键选中这些站点->属性->生成

XML文档文件,勾选,指定路径bin\Debug\netcoreapp3.1\MYun.BPC.WebAPI.Basic.XXX.xml

如:

bin\Debug\netcoreapp3.1\MYun.BPC.WebAPI.Basic.Query.xml

bin\Debug\netcoreapp3.1\MYun.BPC.WebAPI.Basic.Action.xml

最后,我们打开项目里的Startup.cs文件,新增引用命名空间

using Microsoft.OpenApi.Models;

using Swashbuckle.AspNetCore.Swagger;

using System.IO;

找到ConfigureServices方法。里面添加如下代码

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1", //接口文档版本
        Title = "Myun .NetCore BascQuery API",  //接口文档标题
        Description = "基础模块BaseQuery",   //描述,不同模块自己设计
    });

    // 为 Swagger JSON and UI设置xml文档注释路径
    var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)

    var ServicexmlPath = Path.Combine(basePath, "Service.xml"); //此为框架契约说明文档
    c.IncludeXmlComments(ServicexmlPath);

    var DataxmlPath = Path.Combine(basePath, "MYun.MY.Contract.Data.xml");  //此为返回对象说明文档
    c.IncludeXmlComments(DataxmlPath);

    var xmlPath = Path.Combine(basePath, "MYun.BPC.WebAPI.Basic.Query.xml");  //此为当前站点说明文档,Query或Action按不同站点具体配置
    c.IncludeXmlComments(xmlPath);
});

找到Configure方法。里面添加如下代码

//启用中间件服务生成Swagger作为JSON终结点
app.UseSwagger();
//启用中间件服务对swagger-ui,指定Swagger JSON终结点
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Myun .NetCore BascQuery API");
});

最终效果如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Swagger;
using System.IO;

namespace MYun.BPC.WebAPI.Basic.Action
{
    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.AddControllers();

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1", //接口文档版本
                    Title = "Myun .NetCore BascQuery API",  //接口文档标题
                    Description = "基础模块BaseQuery",   //描述,不同模块自己设计
                });

                // 为 Swagger JSON and UI设置xml文档注释路径
                var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)

                var ServicexmlPath = Path.Combine(basePath, "Service.xml"); //此为框架契约说明文档
                c.IncludeXmlComments(ServicexmlPath);

                var DataxmlPath = Path.Combine(basePath, "MYun.MY.Contract.Data.xml");  //此为返回对象说明文档
                c.IncludeXmlComments(DataxmlPath);

                var xmlPath = Path.Combine(basePath, "MYun.BPC.WebAPI.Basic.Query.xml");  //此为当前站点说明文档,Query或Action按不同站点具体配置
                c.IncludeXmlComments(xmlPath);
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            //启用中间件服务生成Swagger作为JSON终结点
            app.UseSwagger();
            //启用中间件服务对swagger-ui,指定Swagger JSON终结点
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Myun .NetCore BascQuery API");
            });
        }
    }
}

到此,文档自动生成工具配置完成,我们现在试试效果。

我们打开项目自动生成的控制器WeatherForecastController文件和WeatherForecast文件,将各个方放加上备注说明,如下

接下来,我们右键站点->属性->调试,将“启动浏览器”修改为“swagger/index.html

保存后,点击调试“运行”。然后打开站点http://localhost:port/swagger/index.html看下效果。发现描述字段都已经出来了

注:为了方便后期维护和节约文档撰写时间,所有.NET Core项目的公共可见类和成员,必须要有描述,如果不描述,则编译的时候会产生警告。

统一时间格式

指定当前项目时间格式统一为文本。24小时制“yyyy-MM-dd HH:mm:ss”,选中站点->添加->新增项->类,添加一个DatetimeJsonConverter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace MYun.BPC.WebAPI.Basic.Action
{
    public class DatetimeJsonConverter : JsonConverter<DateTime>
    {
        /// <summary>
        /// 读
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="typeToConvert"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                if (DateTime.TryParse(reader.GetString(), out DateTime date))
                    return date;
            }
            return reader.GetDateTime();
        }

        /// <summary>
        /// 写
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="value"></param>
        /// <param name="options"></param>
        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }
}

打开Startup.cs找到ConfigureServices方法,添加一条记录

services.AddMvc().AddJsonOptions((options) =>
{
     options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter());
     options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
     options.JsonSerializerOptions.PropertyNamingPolicy = null;
});

最终效果如下

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddMvc().AddJsonOptions((options) =>
            {
                options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter());
                options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1", //接口文档版本
                    Title = "Myun .NetCore BascQuery API",  //接口文档标题
                    Description = "基础模块BaseQuery",   //描述,不同模块自己设计
                });

                // 为 Swagger JSON and UI设置xml文档注释路径
                var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)

                var ServicexmlPath = Path.Combine(basePath, "Service.xml"); //此为框架契约说明文档
                c.IncludeXmlComments(ServicexmlPath);

                var DataxmlPath = Path.Combine(basePath, "MYun.MY.Contract.Data.xml");  //此为返回对象说明文档
                c.IncludeXmlComments(DataxmlPath);

                var xmlPath = Path.Combine(basePath, "MYun.BPC.WebAPI.Basic.Action.xml");  //此为当前站点说明文档,Query或Action按不同站点具体配置
                c.IncludeXmlComments(xmlPath);
            });
        }

这样,我们所有时间格式的输入输出,都统一为“yyyy-MM-dd HH:mm:ss”,我们用已有的WeatherForecastController控制器试试,发现时间结果都变了

指定标准接口和路由

查询请求

查询通用类库说明

QueryResponse 用来存放查询返回结果基类

QueryRequest 用来存放查询请求基类

查询路由指定”XXXQuery/[controller]/[action]”,如下:

[Route(“BasicQuery/[controller]/[action]”)]

创建一个新的查询控制器,举个例子

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Service;
using MYun.MY.Contract.Data.Order;

namespace MYun.BPC.WebAPI.Basic.Query.Controllers
{
    [ApiController]
    [Route("BasicQuery/[controller]/[action]")]
    public class BasicController : ControllerBase
    {
        /// <summary>
        /// 获取订单详情
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        [HttpPost]
        public QueryResponse<CPSOrder> GetOrderBySysNo(QueryRequest<CPSOrderQuerySysNo> param)
        {
            QueryResponse<CPSOrder> result = new QueryResponse<CPSOrder>();
            #region 检测param
            if (param == null)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = "GetLJChangeOrderBySysNo接口参数JSON对象不能为空" };
                return result;
            }
            #endregion
            try
            {
                #region 路由到Service
                QueryResponse<CPSOrder> temp = new QueryResponse<CPSOrder>() { Body=new CPSOrder() { OrderSysNo=111, CreateTime=DateTime.Now } };
                if (temp.HasError)
                {
                    result.HasError = true;
                    result.Fault = temp.Fault;
                }
                else
                {
                    result.Paging = temp.Paging;
                    result.Body = temp.Body;
                }
                return result;
                #endregion
            }
            catch (Exception ex)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = ex.Message };
                return result;
            }
        }

        /// <summary>
        /// 获取订单列表
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        [HttpPost]
        public QueryResponse<List<CPSOrder>> GetOrderList(QueryRequest<CPSOrderQuery> param)
        {
            QueryResponse<List<CPSOrder>> result = new QueryResponse<List<CPSOrder>>();
            #region 检测param
            if (param == null)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = "GetLJOrderList接口参数JSON对象不能为空" };
                return result;
            }
            #endregion
            try
            {
                #region 路由到Service
                QueryResponse<List<CPSOrder>> temp = new QueryResponse<List<CPSOrder>>() { Body =new List<CPSOrder> { new CPSOrder() { OrderSysNo = 222, CreateTime = DateTime.Now } } };
                if (temp.HasError)
                {
                    result.HasError = true;
                    result.Fault = temp.Fault;
                }
                else
                {
                    result.Paging = temp.Paging;
                    result.Body = temp.Body;
                }
                return result;
                #endregion
            }
            catch (Exception ex)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = ex.Message };
                return result;
            }
        }

    }
}

操作请求

操作通用类库说明

ActionResponse 用来存放操作返回结果基类

ActionRequest 用来存放操作请求基类

操作路由指定”XXXAction/[controller]/[action]”,如下:

[Route(“BasicAction/[controller]/[action]”)]

创建一个新的操作控制器,举个例子

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Service;
using MYun.MY.Contract.Data.Order;

namespace MYun.BPC.WebAPI.Basic.Action.Controllers
{
    [ApiController]
    [Route("BasicAction/[controller]/[action]")]
    public class BasicController : ControllerBase
    {
        /// <summary>
        /// 新增订单
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResponse<int> AddOrderCPS(ActionRequest<OrderCPSAdd> param)
        {
            ActionResponse<int> result = new ActionResponse<int>();
            #region 检测param
            if (param == null)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = "AddOrderRMA接口参数JSON对象不能为空" };
                return result;
            }
            #endregion
            try
            {
                #region 路由到Service
                return new ActionResponse<int> { Body=1 };
                #endregion
            }
            catch (Exception ex)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = ex.Message };
                return result;
            }
        }

        /// <summary>
        /// 编辑订单
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResponse EditOrderCPS(ActionRequest<OrderCPSEdit> param)
        {
            ActionResponse result = new ActionResponse();
            #region 检测param
            if (param == null)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = "EditOrderRMA接口参数JSON对象不能为空" };
                return result;
            }
            #endregion
            try
            {
                #region 路由到Service
                return new ActionResponse();
                #endregion
            }
            catch (Exception ex)
            {
                result.HasError = true;
                result.Fault = new MessageFault() { ErrorCode = 0, ErrorDescription = ex.Message };
                return result;
            }
        }
    }
}

创建业务处理层

新增一个02_Server文件夹用来处理业务逻辑。然后在文件夹下,右键->新建项目

MYun.BPC.WebAPI.XXX.Server文件结构如下

Service 用于承接接口请求

ActionBP用于处理操作逻辑

QueryBP用于处理查询逻辑

Help用于存放帮助类

Transform用于存放翻译类

添加NuGet包,包内有EntityFrameworkCore等相关的引用,以及Newtonsoft.Json,EntityFrameworkCore等知识点,参考《.Net Core + EntityFramework连接数据库》

最后一步,添加相关项目程序集

然后将MYun.BPC.WebAPI.XXX.Query,MYun.BPC.WebAPI.XXX.Action这2个站点,引用MYun.BPC.WebAPI.XXX.Server这个类库