65.9K
CodeProject 正在变化。 阅读更多。
Home

ApiBoilerPlate:用于在 .NET Core 3.x 中构建 ASP.NET Core API 的项目模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (13投票s)

2019年10月1日

CPOL

11分钟阅读

viewsIcon

17647

一个简单但有组织的 ASP.NET Core API 项目模板,用于在 .NET Core 3.x 中构建。

引言

自推出以来,微服务架构在软件开发领域声名鹊起,如今已成为一种非常流行的应用程序构建架构风格,并被许多组织使用。采用微服务方法时,通常会将产品功能分解为执行特定任务的服务或 API,而 .NET Core 是构建此类服务的理想选择。

.NET Core 的问世使得构建可随处部署和托管的 API 变得非常容易。然而,如果您需要在短时间内构建大量 API 来支持各种产品功能,那么事情就会变得棘手。这是因为每个开发人员/团队都有自己的 API 构建方式和自己的项目标准基础结构。您可能需要进行大量复制粘贴才能拥有一致的 API 项目基础结构,并重新配置其核心依赖项,这会占用大量开发时间。基于这些原因,ApiBoilerPlate 应运而生。

项目模板

今天,我很高兴地宣布 ApiBoilerPlate 已正式发布,现在可以从 Visual Studio Market place 或通过 .NET CLI 下载并安装到 Visual Studio 2019 中。我将此项目 开源,以允许其他开发人员为其做出贡献和改进。

ApiBoilerPlate 是一个简单但有组织的 ASP.NET Core API 项目模板,使用 .NET Core 3.x(迄今为止最新/最快的 .NET Core 版本)进行构建,并包含预配置的工具和框架。其目标是帮助您快速设置应用程序的核心结构及其依赖项。这样您就可以专注于实现业务特定需求,而无需复制粘贴项目核心结构并重新安装其依赖项。这将加快构建新 API 项目的开发速度,同时强制执行所有应用程序的标准项目结构及其依赖项和配置。

如果您正在寻找一个可供团队重复使用的 ASP.NET Core API 项目模板,或者如果您是 ASP.NET Core 新手,并希望快速了解其工作原理,而无需配置 API 的大部分基本功能,那么这个模板就是为您准备的。

如何获取?

有两种方法可以安装此模板

使用的工具和框架

请记住,您可以随时替换和选择任何您想用于 API 的框架。毕竟,模板只是您项目结构的骨架,带有默认的预配置中间件。例如,您可以随时将 Dapper 替换为 EntityFramework CorePetaPoco 等,并自行配置。您也可以将 Serilog 替换为任何适用于 ASP.NET Core 的日志框架和提供程序——选择权在于您。

通过 .NET CLI 安装模板

  1. 安装最新的 .NET Core SDK
  2. 运行 dotnet new -i apiboilerplate.aspnetcore。这将把模板安装到您的机器上。
  3. 运行 dotnet new apiboilerplate --name "MyAPI"; -o samples。这将在 samples 目录中生成名为 MyAPI 的项目模板。

安装后,您应该会看到以下输出

引用

模板“用于 .NET Core 3.x 的 ASP.NET Core API 模板”已成功创建。

从 Visual Studio Marketplace 安装模板

  1. 启动 Visual Studio 2019,点击“不使用代码继续”链接
  2. 在“扩展”菜单上,点击“管理扩展”。
  3. 点击“联机”,然后搜索“ApiBoilerPlate”。

  1. 点击“下载”。扩展将被安排安装。关闭所有 Visual Studio 实例。
  2. VISX(扩展)安装程序应该会弹出。点击“修改”开始安装模板,如下图所示

  1. 修改完成后点击“关闭”。

或者,您也可以通过以下链接直接下载并安装 VSIX 扩展:https://marketplace.visualstudio.com/items?itemName=vmsdurano.ApiProjVSExt

从 ApiBoilerPlate 模板创建新项目

  1. 打开 Visual Studio 2019,然后选择“创建新项目”框
  2. 新安装的模板应该会出现在顶部。您也可以在搜索栏中键入“ApiBoilerPlate

  1. 点击“ApiBoilerPlate”项,然后点击“下一步”。
  2. 将您的项目命名为您喜欢的任何名称,然后点击“创建”。
  3. Visual Studio 应该会为您生成文件,如下图所示。

生成的文件包含项目的骨架结构,以及一个非常基础的示例实现,供您开始处理 API。

运行模板的步骤

第一步: 创建一个测试本地数据库

  1. 打开 Visual Studio 2019
  2. 转到 **视图** > **SQL Server 对象资源管理器**
  3. 展开到 **SQL Server** > **(localdb)\MSSQLLocalDB**
  4. 右键单击“数据库”文件夹
  5. 点击“添加新数据库
  6. 将其命名为“TestDB”并点击 OK
  7. 在“TestDB”下,右键单击“”文件夹并选择“添加新表”。或者,您也可以右键单击“TestDB”数据库,选择“新建查询”,然后运行以下脚本来生成“Person”表。
  8. 将表命名为“Person”,包含以下字段
CREATE TABLE [dbo].[Person]
(
       [Id] INT NOT NULL PRIMARY KEY IDENTITY(1,1), 
        [FirstName] NVARCHAR(20) NOT NULL, 
        [LastName] NVARCHAR(20) NOT NULL, 
        [DateOfBirth] DATETIME NOT NULL
)

第二步: 更新数据库连接字符串(可选)

如果您按照第一步操作,那么您可以跳过此步骤并立即运行应用程序。

如果您有不同的 databasetable 名称,那么您需要更改 appsettings.json 中的 connectionString,使其指向新创建的数据库。您可以在 Visual Studio 中“TestDB”数据库的“属性”窗口中找到 connectionString 值。

测试默认 API 控制器

设置好数据库后,就可以测试为您预先配置好的 API 了。运行应用程序并导航到:https://:44321/swagger (请参阅Properties > launchSettings.json 以了解应用程序启动配置方式)

现在,您应该会看到如下所示的 **Swagger UI** 文档页面

Swagger 为您的 API 提供高级文档,允许您查看 API 端点的详细信息并在必要时进行测试。该模板已预先配置,默认使用 Swashbuckle.AspNetCore 工具启用此功能。这意味着每次在项目中添加 API 端点时,swagger 都会自动生成此文档,而无需您进行任何操作。此文档非常有价值,尤其当您的 **API** 公开可用且您期望许多开发人员使用它们时。

Swagger UI 的另一个优点是它允许您测试您的 **API**。例如,您可以向您的 API 发送一个 POST 请求并获取响应。为了让您看到效果,让我们发送一个带有以下正向参数的 POST 请求

{
  "firstName": "Vianne Maverich",
  "lastName": "Durano",
  "dateOfBirth": "2019-09-27T23:27:50.076Z"
}

点击 Execute 按钮会得到以下响应

响应正文

{
  "message": "Created Successfully",
  "isError": false,
  "result": 1
}

响应头

content-type: application/json 
 date: Fri, 27 Sep 2019 23:31:35 GMT 
 server: Microsoft-IIS/10.0 
 status: 201 
 x-powered-by: ASP.NET

要验证数据是否已插入到数据库中,您可以调用 GET 方法查看结果。在此示例中,它应该返回以下 **JSON** 载荷

{
    "message": "Request successful.",
    "isError": false,
    "result": [
        {
            "id": 1,
            "firstName": "Vianne Maverich",
            "lastName": "Durano",
            "dateOfBirth": "2019-09-27T23:27:50.077"
        }
    ]
}

您会注意到,HTTP 响应已为您自动格式化。该模板使用 AutoWrapper 中间件来包装每个 HTTP 响应,无论是成功还是失败,而无需您进行任何操作。更多信息请参阅:AutoWrapper: 使用有意义的响应美化您的 ASP.NET Core API

提示Postman 也是测试 API 的一个很好的替代方案,我仍然在使用它。

日志记录

日志记录也已为您预先配置。它使用 Serilog.AspNetCore 来记录数据。默认情况下,模板预先配置为在 ConsoleFile 中记录数据。您可以在 Logs 文件夹中找到日志。以下是根据此示例捕获的日志样本

2019-09-27T18:08:48.0418932-05:00 [INF] () Starting web host  
2019-09-27T18:31:35.6179254-05:00 [INF] (AutoWrapper.AutoWrapperMiddleware) Request: POST https localhost:44321/api/v1/Persons  {"firstName":"Vianne Maverich","lastName":"Durano","dateOfBirth":"2019-09-27T23:27:50.076Z"} Responded with [201] in 968ms  
2019-09-27T18:34:38.6852072-05:00 [INF] (AutoWrapper.AutoWrapperMiddleware) Request: GET https localhost:44321/api/v1/Persons   Responded with [200] in 87ms

要查看日志的配置方式,请查看项目模板中的 appsettings.logs.json 文件。

演练

如果您能看到这里,那么我假设您已经成功运行了应用程序。此时,您可能会想从哪里开始修改模板以添加您的代码,并感到有些困惑。在本节中,我将尽力引导您。

Data 文件夹

在模板中您首先想查看的是 Data 文件夹,其中包含一个名为“DBFactoryBase”的类。这个类是一个抽象类,实现了几个方法来执行基本数据库操作。

namespace ApiBoilerPlate1.Data  
{
    public abstract class DBFactoryBase
    {
        private readonly IConfiguration _config;
        public DBFactoryBase(IConfiguration config)
        {
            _config = config;
        }

        internal IDbConnection DbConnection
        {
            get {
                return new SqlConnection(_config.GetConnectionString("SQLDBConnectionString"));
            }
        }

        public virtual async Task<IEnumerable<T>> DbQueryAsync<T>(string sql, object parameters = null)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                if (parameters == null)
                    return await dbCon.QueryAsync<T>(sql);

                return await dbCon.QueryAsync<T>(sql, parameters);
            }
        }
        public virtual async Task<T> DbQuerySingleAsync<T>(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.QueryFirstOrDefaultAsync<T>(sql, parameters);
            }
        }

        public virtual async Task<bool> DbExecuteAsync<T>(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.ExecuteAsync(sql, parameters) > 0;
            }
        }

        public virtual async Task<bool> DbExecuteScalarAsync(string sql, object parameters)
        {
            using (IDbConnection dbCon = DbConnection)
            {
                dbCon.Open();
                return await dbCon.ExecuteScalarAsync<bool>(sql, parameters);
            }
        }
    }
}

它使用 Dapper 作为数据访问机制来与数据库通信。再次强调,您可以随意修改它。我选择 Dapper 的主要原因是它的速度和简单性。如果您更看重便利性而非性能,那么您可以随时用其他完整的 ORM(如 Entity Framework Core)替换您的数据访问。

Contracts 文件夹

下一个要查看的地方是 Contracts 文件夹。在这里,您将添加应用程序所需的所有 接口。默认情况下,它包含三个(3)个接口

IRepository 接口

namespace ApiBoilerPlate1.Contracts  
{
    public interface IRepository<T>
    {
        Task<IEnumerable<T>> GetAllAsync();
        Task<T> GetByIdAsync(object id);
        Task<long> CreateAsync(T entity);
        Task<bool> UpdateAsync(T entity);
        Task<bool> DeleteAsync(object id);
        Task<bool> ExistAsync(object id);
    }
}

我们不希望我们的 API Controller 直接访问 DBFactoryBase 类,而是让其他类处理 DBFactoryBaseAPI Controllers 之间的通信。

上面的代码定义了一个简单的泛型存储库接口。interface 只是一个方法的骨架,没有实际实现。

IPersonManager 接口

using ApiBoilerPlate1.Domain.Entity;  
namespace ApiBoilerPlate1.Contracts  
{
    public interface IPersonManager : IRepository<Person>
    {
        //Add object specific methods here when necessary
    }
}

上面的代码是一个用于管理 Person 实体的接口示例。请注意,它实现了 IRepository 接口,以便该接口可以继承通用存储库的所有可用方法。此接口将被注入到 API Controller 中,因此我们只需要与接口通信,而不是与存储库的实际实现通信。使用接口的主要好处之一是使我们的代码可重用、可测试、可维护,并利用 依赖注入

IServiceRegistration 接口

using Microsoft.Extensions.Configuration;  
using Microsoft.Extensions.DependencyInjection;

namespace ApiBoilerPlate1.Contracts  
{
    public interface IServiceRegistration
    {
        void RegisterAppServices(IServiceCollection services, IConfiguration configuration);
    }
}

上面的代码是一个接口,将用于需要添加到 IServiceCollection 的服务。

Domain 文件夹

下一个要查看的地方是 Domain 文件夹。这是我们实现实际数据库操作及其相关逻辑的地方。此文件夹有一个名为“Entity”的子文件夹,您可以在其中定义所有数据库模型。以下是 Person 实体模型示例

namespace ApiBoilerPlate1.Domain.Entity  
{
    public class Person
    {
        public long ID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
    }
}

上面的代码就是一个简单的类,包含几个属性。类名代表数据库表名,属性代表表列。

PersonManager

namespace ApiBoilerPlate1.Domain  
{
    public class PersonManager : DBFactoryBase, IPersonManager
    {
        public PersonManager(IConfiguration config) : base(config)
        {

        }
        public async Task<IEnumerable<Person>> GetAllAsync()
        {
            return await DbQueryAsync<Person>("SELECT * FROM Person");
        }

        public async Task<Person> GetByIdAsync(object id)
        {
            return await DbQuerySingleAsync<Person>("SELECT * FROM Person WHERE ID = @ID", new { id });
        }

        public async Task<long> CreateAsync(Person person)
        {
            string sqlQuery = $@"INSERT INTO Person (FirstName, LastName, DateOfBirth) 
                                     VALUES (@FirstName, @LastName, @DateOfBirth)
                                     SELECT CAST(SCOPE_IDENTITY() as bigint)";

            return await DbQuerySingleAsync<long>(sqlQuery, person);
        }
        public async Task<bool> UpdateAsync(Person person)
        {
            string sqlQuery = $@"IF EXISTS (SELECT 1 FROM Person WHERE ID = @ID) 
                                            UPDATE Person SET FirstName = @FirstName, LastName = @LastName, DateOfBirth = @DateOfBirth
                                            WHERE ID = @ID";

            return await DbExecuteAsync<bool>(sqlQuery, person);
        }
        public async Task<bool> DeleteAsync(object id)
        {
            string sqlQuery = $@"IF EXISTS (SELECT 1 FROM Person WHERE ID = @ID)
                                        DELETE Person WHERE ID = @ID";

            return await DbExecuteAsync<bool>(sqlQuery, new { id });
        }
        public async Task<bool> ExistAsync(object id)
        {
            return await DbExecuteScalarAsync("SELECT COUNT(1) FROM Person WHERE ID = @ID", new { id });
        }
    }
}

上面的代码是一个具体的类,它实现了我们之前创建的 DBFactorBase 基类和 IPersonManager 接口。在这里,您可以实现特定于域的日志和数据库操作。

DTO 文件夹

数据传输对象(也称为 DTO)是定义具有预定义验证的模型类。DTOs 将作为参数传递给您的 API 端点,或用作结果的返回对象。拥有 DTO 的基本思想是将验证与 Entity 模型解耦,并控制您希望允许客户端看到的数据。

以下是 PersonDTO 的示例

using FluentValidation;  
using System;

namespace ApiBoilerPlate1.DTO  
{
    public class PersonDTO
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime DateOfBirth { get; set; }
    }

    public class PersonDTOValidator : AbstractValidator<PersonDTO>
    {
        public PersonDTOValidator()
        {
            RuleFor(o => o.FirstName).NotEmpty();
            RuleFor(o => o.LastName).NotEmpty();
            RuleFor(o => o.DateOfBirth).NotEmpty();
        }
    }
}

上面的类定义了模型的验证。它使用 FluentValidation.AspNetCore 来提供清晰、灵活且强类型的验证规则。

Helpers 文件夹

下一个要查看的地方是 Helpers 文件夹。在这里,您可以添加执行常见任务的任何类。例如,您可以在此文件夹中添加实用程序类或扩展类。默认情况下,它包含一个 Extensions 文件夹和一个 MapperProfile 类。

MappingProfile 类

using AutoMapper;  
using ApiBoilerPlate1.Domain.Entity;  
using ApiBoilerPlate1.DTO;

namespace ApiBoilerPlate1.Helpers  
{
    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Person, PersonDTO>().ReverseMap();
        }
    }
}

上面的类是定义 DTOsEntity 模型之间映射的地方。它使用 AutoMapper 在对象之间进行映射。在此示例中,我们创建了 Person 实体和 PersonDTO 模型之间的映射。

Extensions 文件夹

此文件夹是您添加项目扩展方法类的地方。默认情况下,它包含一个 ServiceRegistrationExtension 类和一个 扩展方法 AddServicesInAssembly(),该方法负责添加程序集中所有可用的服务。该类将在 Startup.cs 文件的 ConfigureServices() 方法中被调用。

以下是 ServiceRegistrationExtension 的实现

namespace ApiBoilerPlate1.Helpers.Extensions  
{
    public static class ServiceRegistrationExtension
    {
        public static void AddServicesInAssembly(this IServiceCollection services, IConfiguration configuration)
        {
            var appServices = typeof(Startup).Assembly.ExportedTypes
                            .Where(x => typeof(IServiceRegistration)
                            .IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)
                            .Select(Activator.CreateInstance)
                            .Cast<IServiceRegistration>().ToList();

            appServices.ForEach(svc => svc.RegisterAppServices(services, configuration));
        }
    }
}

Installers 文件夹

这是您添加要在应用程序启动时配置的服务的地方。默认情况下,它包含两个类

RegisterContractMappings 类

namespace ApiBoilerPlate1.Installers  
{
    public class RegisterContractMappings : IServiceRegistration
    {
        public void RegisterAppServices(IServiceCollection services, IConfiguration configuration)
        {
            services.AddTransient<IPersonManager, PersonManager>();
        }
    }
}

上面的类负责注册您的契约存储库与实现契约的具体类之间的所有接口映射。

RegisterModelValidators 类

namespace ApiBoilerPlate1.Installers  
{
    public class RegisterModelValidators : IServiceRegistration
    {
        public void RegisterAppServices(IServiceCollection services, IConfiguration configuration)
        {
            services.AddTransient<IValidator<PersonDTO>, PersonDTOValidator>();
        }
    }
}

上面的类负责注册您 DTO 模型的所有验证器。
请注意,上面的两个类都实现了 IServiceRegistration 接口,因此在调用 ServiceRegistrationExtension 类中定义的 AddServicesInAssembly() 扩展方法时,它们将被自动配置。

API 文件夹

现在我们已经有了大部分组件,下一个要查看的地方是 API 文件夹。此文件夹包含一个名为“v1”的子文件夹,用于将 Controllers 组织到版本中。默认情况下,v1 文件夹包含 PersonsController,其实现如下

namespace ApiBoilerPlate1.API.v1  
{
    [Route("api/v1/[controller]")]
    [ApiController]
    public class PersonsController : ControllerBase
    {
        private readonly ILogger<PersonsController> _logger;
        private readonly IPersonManager _personManager;
        private readonly IMapper _mapper;
        public PersonsController(IPersonManager personManager, IMapper mapper, ILogger<PersonsController> logger)
        {
            _personManager = personManager;
            _mapper = mapper;
            _logger = logger;
        }

        [HttpGet]
        public async Task<IEnumerable<Person>> Get()
        {
            return await _personManager.GetAllAsync();
        }

        [Route("{id:long}")]
        [HttpGet]
        public async Task<Person> Get(long id)
        {
            var person = await _personManager.GetByIdAsync(id);
            if (person != null)
            {
                return person;
            }
            else
                throw new ApiException($"Record with id: {id} does not exist.", 204);
        }

        [HttpPost]
        public async Task<ApiResponse> Post([FromBody] PersonDTO dto)
        {

            if (ModelState.IsValid)
            {
                try
                {
                    var person = _mapper.Map<Person>(dto);
                    return new ApiResponse("Created Successfully", await _personManager.CreateAsync(person), 201);
                }
                catch (Exception ex)
                {
                    _logger.Log(LogLevel.Error, ex, "Error when trying to insert.");
                    throw;
                }
            }
            else
                throw new ApiException(ModelState.AllErrors());
        }

        [Route("{id:long}")]
        [HttpPut]
        public async Task<ApiResponse> Put(long id, [FromBody] PersonDTO dto)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    var person = _mapper.Map<Person>(dto);
                    person.ID = id;

                    if (await _personManager.UpdateAsync(person))
                        return new ApiResponse("Update successful.", true);
                    else
                        throw new ApiException($"Record with id: {id} does not exist.", 400);
                }
                catch (Exception ex)
                {
                    _logger.Log(LogLevel.Error, ex, "Error when trying to update with ID:{@ID}", id);
                    throw;
                }
            }
            else
                throw new ApiException(ModelState.AllErrors());
        }


        [Route("{id:long}")]
        [HttpDelete]
        public async Task<bool> Delete(long id)
        {
            try
            {
                var isDeleted = await _personManager.DeleteAsync(id);
                if (isDeleted)
                {
                    return isDeleted;
                }
                else
                    throw new ApiException($"Record with id: {id} does not exist.", 400);
            }
            catch (Exception ex)
            {
                _logger.Log(LogLevel.Error, ex, "Error when trying to delete with ID:{@ID}", id);
                throw;
            }

        }
    }
}

上面的类定义了 API 端点及其路由,并带有预配置的实现作为示例。有关创建 ASP.NET Core API 的更多信息,请参阅:使用 ASP.NET Core 创建 Web API

Workers 文件夹

作为附加功能,该模板包含一个默认示例,说明如何在 ASP.NET Core 中运行后台任务。以下是工作服务的一个简单实现

namespace ApiBoilerPlate1.Workers  
{
    public class MessageQueueProcessorService : BackgroundService
    {
        private readonly ILogger<MessageQueueProcessorService> _logger;

        public MessageQueueProcessorService(ILogger<MessageQueueProcessorService> logger)
        {
            _logger = logger;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogDebug($"MessageQueueProcessorService is starting.");

            stoppingToken.Register(() => _logger.LogDebug($" MessageQueueProcessorService background task is stopping."));

            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogDebug($"MessageQueueProcessorService task doing background work.");

                //TO DO:
                //PubSub/Message Queue subcription and process message
                //Save to DB

                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            }

            _logger.LogDebug($"MessageQueueProcessorService background task is stopping.");
        }
    }
}

上面的代码模拟了一个简单的后台任务,该任务每 5 秒运行一次。因此,如果您需要实现工作服务,这应该能给您一些入门思路。

有关使用 ASP.NET Core 中的托管服务运行后台任务的更多信息,请参阅:ASP.NET Core 中的托管服务

总结

要查看模板中每个服务的配置方式,请查看 Startup.csProgram.cs 类。为了给您一个快速的概述,Startup.cs 文件的 ConfigureServices() 方法看起来是这样的

public void ConfigureServices(IServiceCollection services)  
{
    //Uncomment to Register Worker Service
    //services.AddSingleton<IHostedService, MessageQueueProcessorService>();

    //Register DTO Validators and Interface Mappings for Repositories
    services.AddServicesInAssembly(Configuration);

    //Disable Automatic Model State Validation built-in to ASP.NET Core
    services.Configure<ApiBehaviorOptions>(opt => { opt.SuppressModelStateInvalidFilter = true; });

    //Register MVC/Web API and add FluentValidation Support
    services.AddControllers()
            .AddFluentValidation(fv => {  fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; });

    //Register Automapper
    services.AddAutoMapper(typeof(Helpers.MappingProfile));

    //Register Swagger
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "ASP.NET Core Template API", Version = "v1" });
    });
}

有关 StartupProgram 类如何工作的更多信息,请阅读:ASP.NET Core 基础知识

摘要

在这篇文章中,我们学习了如何使用 ApiBoilerPlate 项目模板在 .NET Core 3.0 中构建 API。我们还了解了它的结构以及它是如何实现的。

我很有信心这个项目还有很多可以改进的地方,所以请随时尝试并告诉我您的想法。欢迎评论和建议,请留下信息,我很乐意尽我所能回答任何疑问。

源代码可以在这里找到:https://github.com/proudmonkey/ApiBoilerPlate

© . All rights reserved.