百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

ASP.NET Core 中的 Mapster 使用入门教程

yund56 2025-07-05 04:36 3 浏览


在本文中,我们将学习如何在 ASP.NET Core 应用程序中使用 Mapster。

首先,我们将了解 Mapster 是什么以及如何将其安装到 .NET Core 应用程序中。 然后,我们将在使用 Mapster 时尝试不同的数据映射选项。 之后,我们将了解 Mapster 的功能,用于在 Web API 项目中生成代码和扁平化对象。

开始吧。

Mapster是啥?

顾名思义,Mapster 是一个将一种对象类型映射到另一种对象类型的库。 它是一个基于约定的映射器,易于配置和使用。 编写代码将一个对象映射到另一个对象可能非常重复且无聊。 正因为如此,Mapster 使我们免于编写容易出错的样板代码。

在大多数情况下,我们不应该尝试重新发明轮子,而应该使用可用的选项。

让我们看看 Mapster 是否是一个不错的选择。

如何获取Mapster?

第一步是安装 Mapster NuGet 包:

Install-Package Mapster

就是这么简单。 现在我们已经可以在我们的项目中使用 Mapster 了。

准备环境

首先,我们创建两个类来表示实体,并创建一个用于 DTO 对象的类,我们将把实体中的值映射到该对象:

public class Person
{
    public string? Title { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateTime? DateOfBirth { get; set; }
    public Address? Address { get; set; }
}


在这里,我们创建将在整个示例中使用的 Person 类。 另外,我们里面有 Address 类,所以我们也添加这个类:

public class Address
{
    public string? Street { get; set; }
    public string? City { get; set; }
    public string? PostCode { get; set; }
    public string? Country { get; set; }
}

现在,我们已经有了实体。 是时候创建我们的第一个 DTO 类了:

public class PersonDto
{
    public string? Title { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateTime? DateOfBirth { get; set; }
}

PersonDto 类具有原始 Person 实体的所有属性,但我们将在本文后面使用的 Address 除外。 需要注意的是,DTO 类中的所有属性都与实体属性命名相同,因此我们无需额外配置即可映射它们。

实体基础数据

为了准备我们的示例,我们将创建一个简单的类 DemoData 来创建一个简单的 Person 实体和一个 Person 集合。 在现实项目中,我们将从数据库中获取这些数据。

让我们添加第一个创建Person的方法:

public static Person CreatePerson()
{
    return new Person()
    {
        Title = "Mr.",
        FirstName = "Peter",
        LastName = "Pan",
        DateOfBirth = new DateTime(2000, 1, 1),
        Address = new Address()
        {
            Country = "Neverland",
            PostCode = "123N",
            Street = "Funny Street 2",
            City = "Neverwood"
        },
    };
}

这里我们创建一个名为 CreatePerson() 的新方法,它返回一个添加了地址的 Person 对象。

类似地,我们添加 CreatePeople() 方法,该方法的工作方式与第一个方法相同,但返回 Person 对象的集合。

使用 Mapster 进行基本映射

现在是时候了解一下 Mapster 在无需配置的情况下动态映射对象和集合的基本用法了。

首先,让我们创建一个名为 MappingFunctions 的新静态类,我们将用它来实现所有不同的映射功能:

public static class MappingFunctions
{
    private static readonly Person _person = DemoData.CreatePerson();
    private static readonly ICollection<Person> _people = DemoData.CreatePeople();
}

这里我们添加两个静态只读字段并初始化单个实体和 Person 实体的集合。

我们有两种不同的方式使用通用的 Adapt() 方法将单个对象映射到另一个对象:

映射到新对象

映射到现有对象

映射到新对象

首先,让我们在 MappingFunctions 类中创建一个新的静态方法:

public static PersonDto MapPersonToNewDto()
{
    var personDto = _person.Adapt<PersonDto>();

    return personDto;
}

在 MapPersonToNewDto() 方法中,我们初始化一个新变量 personDto 作为将 _person 对象中的所有值映射到的目的地。

其次,让我们在项目中创建一个新控制器,并使用从函数中检索数据的方法:

[Route("api/people")]
[ApiController]
public class PeopleController: ControllerBase
{
    [HttpGet("new-person")]
    public IActionResult GetNewPerson()
    {
        var person = MappingFunctions.MapPersonToNewDto();
        return Ok(person);
    }
}

在这里,我们实现 PeopleController 类,以便我们可以使用我们的 API 操作。 我们添加一个名为 GetNewPerson() 的新操作,该操作使用 MappingFunctions 类中的 MapPersonToNewDto() 方法。 将结果存储在 person 变量中后,我们将返回带有 OK 状态代码的数据。

让我们用 Postman 来测试一下:


完美,我们的第一个映射函数可以工作了。

映射到现有对象

当我们想要将数据映射到现有对象时,我们可以使用另一种映射方式:

public static PersonDto MapPersonToExistingDto()
{
    var personDto = new PersonDto();
    _person.Adapt(personDto);

    return personDto;
}

与前面的示例不同,在本例中,我们首先创建一个新的 PersonDto 对象。 之后,我们将数据从 _person 对象映射到现有的 personDto 变量。

我们使用与之前相同的 Adapt() 方法,该方法当前基于属性名称映射数据,无需自定义逻辑。

让我们在控制器中创建一个新操作:

[HttpGet("existing-person")]
public IActionResult GetExistingPerson()
{
    var person = MappingFunctions.MapPersonToExistingDto();

    return Ok(person);
}

在这里,我们添加 GetExistingPerson() 操作以及该操作上指定的新 URI。

同样,让我们用 Postman 来测试一下:


奇迹般有效。

映射集合

Mapster 库还提供了从 .NET 中的 Queryable 进行映射的扩展。 这样,我们可以加快从数据库获取的数据映射到可查询数据类型的过程。

让我们在 MappingFunctions 类中创建另一个方法:

public static IQueryable<PersonDto> MapPersonQueryableToDtoQueryable()
{
    var peopleQueryable = _people.AsQueryable();
    var personDtos = peopleQueryable.ProjectToType<PersonDto>();

    return personDtos;
}


MapPersonQueryableToDtoQueryable() 方法中,我们使用 AsQueryable() 方法将 _people 对象中的数据存储到 peopleQueryable 变量中。 在下一步中,我们使用 Mapster 中的 ProjectToType() 通用方法将数据映射到 personDtos 变量。

再次,让我们在控制器中创建一个新端点来测试功能:

[HttpGet]
public IActionResult GetPeopleQueryable()
{
    var people = MappingFunctions.MapPersonQueryableToDtoQueryable();

    return Ok(people);
}

最后,让我们从 Postman 客户端调用 API 来获取结果:

我们可以看到所有具有正确数据的 DTO 对象。

使用 Mapster 自定义映射

现在,我们将通过自定义映射来实现功能,以了解 Mapster 库的潜力。

让我们解决映射数据时可能遇到的一些挑战,好吗?

我们需要添加自定义 Mapster 配置来实现自定义映射逻辑。 为此,我们将使用 TypeAdapterConfig 类。

首先,我们添加一个 MapsterConfig 静态类,其中包含用于 Mapster 配置的扩展方法:

public static class MapsterConfig
{
    public static void RegisterMapsterConfiguration(this IServiceCollection services)
    {
    }
}

然后,我们可以在 Program 类中的服务中使用
RegisterMapsterConfiguration() 扩展方法:

builder.Services.RegisterMapsterConfiguration();

太好了,现在我们已经准备好使用 Mapster 实现自定义映射了。

映射到不同的成员

如果我们的 DTO 类中有不同名称的属性怎么办? 嗯,Mapster 有一个解决方案。

让我们添加另一个 DTO 类,我们将使用它来显示 Person 实体的信息:

public class PersonShortInfoDto
{
    public string? FullName { get; set; }
}

在我们的 PersonShortInfoDto 类中,我们只有一个属性,我们将使用它来存储 Person 实体的全名,称为 FullName。

之后,我们向
RegisterMapsterConfiguration() 扩展方法添加一个新配置:

public static void RegisterMapsterConfiguration(this IServiceCollection services)
{
    TypeAdapterConfig<Person, PersonShortInfoDto>
        .NewConfig()
        .Map(dest => dest.FullName, src => #34;{src.Title} {src.FirstName} {src.LastName}");
}

我们使用 TypeAdapterConfig 泛型类,其中 Person 类作为源,PersonShortInfoDto 类作为目标。

然后,我们使用接受两个 Func 委托的 Map() 方法。 在这里,我们使用字符串插值将 Title、FirstName 和 LastName 从 src 对象映射到 dest 对象的 FullName 字段。

最后,我们需要调用
RegisterMapsterConfiguration() 方法中的 Scan() 方法:

TypeAdapterConfig.GlobalSettings.Scan(Assembly.GetExecutingAssembly());

Scan() 方法扫描程序集并将注册添加到 TypeAdapterConfig。

现在,我们可以在 MappingFunctions 类中实现一个新方法,稍后我们将在控制器中使用该方法:

public static PersonShortInfoDto CustomMapPersonToShortInfoDto()
{
    var personShortInfoDto = _person.Adapt<PersonShortInfoDto>();

    return personShortInfoDto;
}

在这里,我们使用与 Adapt() 方法相同的功能,但来自 MapsterConfig 类的自定义映射将定义我们如何映射数据。

让我们添加一个控制器Action:

[HttpGet("short-person")]
public IActionResult GetShortPerson()
{
    var person = MappingFunctions.CustomMapPersonToShortInfoDto();

    return Ok(person);
}

检查结果:


这是我们新的 DTO 的全名。

惊人的。

基于条件的映射

Mapster 中的 Map() 方法可以接受第三个参数,我们可以使用该参数根据源对象设置条件。 如果不满足条件,则将为目标对象分配 null 或默认值。

让我们向 PersonShortInfoDto 类添加另一个属性:

public int? Age { get; set; }

接下来,让我们修改 Mapster 配置以添加条件映射:

TypeAdapterConfig<Person, PersonShortInfoDto>
      .NewConfig()
      .Map(dest => dest.FullName, src => #34;{src.Title} {src.FirstName} {src.LastName}")
      .Map(dest => dest.Age,
            src => DateTime.Now.Year - src.DateOfBirth.Value.Year,
            srcCond => srcCond.DateOfBirth.HasValue);

这里我们向 Map() 函数添加第三个参数,它是一个返回布尔值的 Func 委托。 我们通过从今天的日期年份中减去出生年份来为 Age 属性分配一个值,但前提是 DateOfBirth 属性具有值。

现在,我们可以再次从控制器调用 GetShortPerson() 操作来查看结果:


我们开始吧。 我们可以看到我们这个人的年龄。

映射嵌套成员

自定义映射的另一个示例是嵌套成员的映射。

让我们向 PersonShortInfoDto 类添加一个新属性:

public string? FullAddress { get; set; }

现在,我们可以修改配置以填充目标类的 FullAddress 属性:

TypeAdapterConfig<Person, PersonShortInfoDto>
      .NewConfig()
      .Map(dest => dest.FullName, src => #34;{src.Title} {src.FirstName} {src.LastName}")
      .Map(dest => dest.Age,
            src => DateTime.Now.Year - src.DateOfBirth.Value.Year,
            srcCond => srcCond.DateOfBirth.HasValue)
      .Map(dest => dest.FullAddress, src => #34;{src.Address.Street} {src.Address.PostCode} - {src.Address.City}");

在这里,我们通过实体类上的 Address 属性访问嵌套属性。 根据 Address 值,我们通过字符串插值构造 FullAddress。

值得注意的是,Mapster 默认应用空传播。 从上面的示例中,如果 src 对象上的 Address 属性为 null,则会将 null 值分配给 FullAddress,而不是抛出 NullPointerException。

让我们调用控制器操作并检查 Postman 的结果:



忽略自定义成员

默认情况下,Mapster 将按名称映射属性,无需配置。 我们不需要在所有用例中都这样做,因此我们可以使用 Ignore() 方法来忽略特定成员:

TypeAdapterConfig<Person, PersonDto>
        .NewConfig()
        .Ignore(dest => dest.Title);

在本例中,我们忽略目标对象(即 PersonDto)上的 Title 属性。

完成后,让我们调用 GetNewPerson() 操作并再次检查结果:


我们可以看到与之前调用的差异,因为 Title 值现在为 null。

使用 Mapster,我们可以使用 IgnoreNonMapped() 方法来忽略配置中未显式设置的所有成员。 除此之外,我们还可以使用 IgnoreIf() 方法以及基于源或目标对象的条件。 当满足条件时,Mapster将跳过该属性。

使用 Mapster 忽略成员的另一种方法是使用类本身的 AdaptIgnore 属性。

为了实现这一点,我们可以在 Title 属性上方添加注释:

[AdaptIgnore]
public string? Title { get; set; }

这与上一个示例中基于规则的 Ignore() 方法的工作方式相同。

双向映射

Mapster 中的双向映射是一种以两种方式配置映射的简单方法:从源到目标以及从目标到源对象。

我们可以通过在配置中调用 TwoWays() 方法来实现此目的:

TypeAdapterConfig<Person, PersonDto>
        .NewConfig()
        .Ignore(dest => dest.Title)
        .TwoWays();

之后,让我们创建一个新方法来基于 DTO 返回实体:

public static Person MapPersonDtoToPersonEntity()
{
    var personDto = MapPersonToNewDto();
    var person = personDto.Adapt<Person>();

    return person;
}

并向我们的控制器添加一个新操作:

[HttpGet("entity")]
public IActionResult GetPersonEntity()
{
    var person = MappingFunctions.MapPersonDtoToPersonEntity();

    return Ok(person);
}

最后,我们可以调用我们的操作来确认双向映射正在工作:


我们看到 DTO 中的所有属性都已成功映射,但标题被忽略。

映射之前和之后

Mapster 有更多的自定义映射选项,有必要提及之前和之后的映射功能。 我们可以使用 BeforeMapping() 方法在映射操作之前执行操作。

要检查此功能,让我们向 PersonDto 类添加一个新方法:

public void SayHello()
{
    Console.WriteLine("Hello...");
}

这里我们实现了输出一些文本的 SayHello() 方法。

现在,让我们转到 MapsterConfig 类并为 PersonDto 添加新配置:

TypeAdapterConfig<Person, PersonDto>.ForType()
        .BeforeMapping((src, result) => result.SayHello());

我们使用 Mapster 中的 BeforeMapping() 方法,然后从结果变量中调用 SayHello() 方法。

最后,让我们再次调用 GetNewPerson() 操作并检查控制台以查看输出:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5002
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\GITHUB\CodeMaze\CodeMazeGuides\dotnet-client-libraries\HowToUseMapster\HowToUseMapster\
Hello...

我们可以在控制台中看到输出。

同样,现在让我们向 PersonDto 类添加另一个方法:

public void SayGoodbye()
{
    Console.WriteLine("Goodbye...");
}

我们将使用 SayGoodbye() 方法来演示 AfterMapping() 方法在 Mapster 中的工作原理。

也就是说,让我们修改 MapsterConfig 类:

TypeAdapterConfig<Person, PersonDto>.ForType()
        .BeforeMapping((src, result) => result.SayHello())
        .AfterMapping((src, result) => result.SayGoodbye());

然后在调用相同的操作后检查控制台:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5002
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\GITHUB\CodeMaze\CodeMazeGuides\dotnet-client-libraries\HowToUseMapster\HowToUseMapster\
Hello...
Goodbye...

我们可以看到这两种方法的输出。

使用 Mapster 生成代码

使用映射库时我们经常提到的问题之一是更改类中的属性名称时可能发生的潜在回归。 为了避免这种情况,最好能看到 Mapster 用于映射成员的实际代码。

嗯,Mapster 有一个解决方案。 我们可以使用代码生成选项来生成映射函数的源代码。

首先,让我们创建一个新的配置类并实现 Mapster 中的 ICodeGenerationRegister 接口:

public class CodeGenerationConfig : ICodeGenerationRegister
{
    public void Register(Mapster.CodeGenerationConfig config)
    {
        config.AdaptTo("[name]Model")
            .ForType<Person>()
            .ForType<Address>();

        config.GenerateMapper("[name]Mapper")
            .ForType<Person>()
            .ForType<Address>();
    }
}

这里我们根据第一个 AdaptTo() 方法自动创建两个带有“Model”前缀的类。 通过这种方法,我们还可以避免在项目中编写 DTO 类。

然后我们使用GenerateMapper()方法动态创建两个具有映射逻辑的类。 默认情况下,Mapster 将所有新类存储在“Models”文件夹中。

现在,我们将以下代码添加到 csproj 文件中以在构建时生成类:

<Target Name="Mapster" AfterTargets="AfterBuild">
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool restore" />
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster model -a "$(TargetDir)$(ProjectName).dll"" />
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster extension -a "$(TargetDir)$(ProjectName).dll"" />
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster mapper -a "$(TargetDir)$(ProjectName).dll"" />
</Target>

最后,让我们在控制台中运行 dotnet build 命令来查看项目结构中的结果:


太棒了,我们有四个从 Mapster 自动生成的新类。 让我们检查 PersonMapper 类以查看实际的映射代码:

public static PersonModel AdaptToModel(this Person p1)
{
    return p1 == null ? null : new PersonModel()
    {
        Title = p1.Title,
        FirstName = p1.FirstName,
        LastName = p1.LastName,
        DateOfBirth = p1.DateOfBirth,
        Address = p1.Address == null ? null : new AddressModel()
        {
            Street = p1.Address.Street,
            City = p1.Address.City,
            PostCode = p1.Address.PostCode,
            Country = p1.Address.Country
        }
    };
}
public static PersonModel AdaptTo(this Person p2, PersonModel p3)
{
    if (p2 == null)
    {
        return null;
    }
    PersonModel result = p3 ?? new PersonModel();
    
    result.Title = p2.Title;
    result.FirstName = p2.FirstName;
    result.LastName = p2.LastName;
    result.DateOfBirth = p2.DateOfBirth;
    result.Address = funcMain1(p2.Address, result.Address);
    return result;
    
}

我们可以看到现在可以使用的映射函数 AdaptToModel() 和 AdaptTo() 的逻辑。 有了这个,我们就可以确保编译器将报告我们在修改实体和模型的属性时可能出现的任何潜在错误。

使用 Mapster 代码生成的另一种方法是通过数据注释。 我们可以使用 AdaptTo 属性来实现这一点:

[AdaptTo("[name]Model"), GenerateMapper]
public class Person

使用 Mapster 展平和取消展平复杂对象

默认情况下,Mapster 会为我们执行展平操作。 假设我们有一个 Person 实体,它具有 Address 属性,其中包含 Street 属性。 现在我们可以向 PersonDto 类添加一个名为 AddressStreet 的新属性。 此时,Mapster 将执行映射,无需任何其他配置。

让我们将 AddressStreet 属性添加到我们的 PersonDto 类中:

public string? AddressStreet { get; set; }

现在,让我们再次调用 GetNewPerson() 操作(无需额外配置)并检查结果:


我们可以看到地址街道是根据展平自动填充的。

但是,我们需要显式配置展开过程。 因此,如果我们想从 AddressStreet 映射到 Address.Street,我们可以从 TypeAdapterConfig 类调用 Unflattening 方法并将第一个参数设置为 true。

结论

在本文中,我们学习了如何使用 Mapster 对 ASP.NET Core 项目中的数据执行基本映射操作。 我们已经了解了自动代码生成如何与 Mapster 一起工作,以及如何通过几个步骤展平或取消展平我们的对象。

主要结论是,就 .NET 生态系统中的映射而言,Mapster 是一个非常有用且简单的库,因此请检查一下。

相关推荐

如何在Office 中编辑 PDF?附详细化步骤

PDF很受欢迎,因为它能在不同的设备和操作系统上仍然保持原有格式。但是,这也意味着直接更改PDF文件比其他格式更难更复杂。值得庆幸的是,Microsoftoffice和UPDF帮你解决这一难题。一...

我的 Windows 装机必备软件清单

今天给大家分享下我的装机必备软件,都是用了好多年的软件神器。靠谱、好用、无广告,Windows电脑必备软件,收藏这一篇就够了!01.浏览器王者:Chromehttps://www.google.c...

Docnet Core 是一个轻量级、高性能的 .NET PDF 操作库

DocnetCore介绍DocnetCore是一个轻量级、高性能的.NETPDF操作库,依托于PDFium渲染引擎,提供强大的PDF文档解析、渲染、操作等功能。它完全支持.NET...

分享三款好用的PDF编辑软件,轻松处理PDF

作为一名需要经常和PDF文件打交道的工作者,我来分享一下几款好用的PDF编辑软件,并详细分析了它们的优缺点,希望能帮你找到合适的工具。1.AdobeAcrobatDC作为PDF格式的发明者,Ado...

PDF文档创建工具软件:novaPDF OEM 11.9 Build 432 for Windows

novaPDFOEM是一款实用高效的软件,从头开始设计,让您尽可能轻松地在应用程序中添加PDF打印功能。换句话说,这个实用程序是专门为应用程序开发人员设计的,可以直接在程序安装程序中集成为PDF打印...

线性表顺序存储结构求集合的并,交,补,差(源代码附上 超详细)

一:算法分析1)用数组A,B,C,E表示集合。假定A={1,3,4,5,6,7,9,10},  B={2,,3,4,7,8,10},E={1,2,3,4,5,6,7,8,9,10},  输入数组A...

分享一套SpringBoot开发博客系统源码,包含完整开发文档和视频

基本信息项目名称:eblog摘要:eblog是一个基于Springboot2.1.2开发的博客学习项目,为了让项目融合更多的知识点,达到学习目的,编写了详细的从0到1开发文档。主要学习包括:自定义Fr...

通达信指标合集〔源码齐全〕

很多朋友问到我哪款指标好用,这里我说一下,之所以有很多不同的指标是因为我们在针对不同的盘面情况的时候使用的指标是不同的,我给到的指标一般来讲就目前的环境来讲都是比较适合的,今天我就把我平时自己常用的指...

巅峰对决!Spring Boot VS .NET 6

SpringBoot和ASP.NETCore都是企业中流行的Web框架,对于喜欢C#的人会使用ASP.NETCore,而对于Java或Kotlin等基于JVM的语...

在asp.net core 中控制访问权限的方法

Intro#由于项目需要,需要在基于asp.netmvc的Web项目框架中做权限的控制,于是才有了这个权限控制组件,最初只是支持netframework,后来dotnetcore2.0...

ASP.NET是否无生存之地?

ASP.NET,这个已经很久的技术,总觉得已经被时代淘汰,我们公司是一个10人小公司,几个十年十五年的项目还是用ASP.NET开发的。这两年由于客户的需求变化,我们公司也顺势开始对这些项目重新开发,改...

Spring Boot + Vue.js 实现前后端分离(附源码)

作者:梁小生0101链接:juejin.im/post/5c622fb5e51d457f9f2c2381SpringBoot+Vue.js前后端涉及基本概念介绍,搭建记录,本文会列举出用到环...

ASP.NET Core 中的 Mapster 使用入门教程

在本文中,我们将学习如何在ASP.NETCore应用程序中使用Mapster。首先,我们将了解Mapster是什么以及如何将其安装到.NETCore应用程序中。然后,我们将在使用...

Asp.net常用方法及request和response-a

asp.net教程asp.net常用方法:1、Request.UrlReferrer请求的来源,可以根据这个判断从百度搜的哪个关键词、防下载盗链、防图片盗链,可以伪造(比如迅雷)。(使用全局一般处理...

ASP.NET Core使用功能开关控制路由访问

前言在前面的文章,我们介绍了使用Middleware有条件地允许访问路由(《ASP.NETCore使用Middleware有条件地允许访问路由》)。而对于一些试验性的功能,我们并不希望用密码去控制是...