ASP.NET Core + fluentValidation + Swagger

原文

从基础开始

每次写代码, 都想好好的把接口说明规范号, 不幸的是, 拖拖拉拉直到代码已经很庞大了,回过头来添加缺失的文档就是一个艰巨的任务, 嗯, 艰巨到直接忽略了😭。ASP.NET Core支持API文档有了很大的进步, 特别是在一些第三方库的帮助下, 写API文档就像写注释一样简单明了,与代码逻辑一致(很多东西都可以从代码衍生出来。

首先,通过进入项目Properties并单击Build选项卡,确保您的Web项目生成XML文档。

image

开箱即用,这将导致Visual Studio开始警告您项目中每个缺少的XML注释。您可以通过添加上面的1591来取消警告。


image

Swagger只是一个规范,而不是一个实现。它的正式名称是OpenAPI,但是大多数人仍将它称为Swagger。.NET的两个主要实现是SwashbuckleNSwag。这里我使用Swashbuckle。
打开NuGet包管理器,搜索Swashbuckle.AspNetCore,并将其添加到您的项目中。

image

安装完成后,转到Startup.cs文件并添加几行代码。在ConfigureServices中,提供以下代码:

 services.AddSwaggerGen(c =>
    {
      c.SwaggerDoc("v1", new Info() { Title = "Web App", Version = "v1" });
      // Set the comments path for the Swagger JSON and UI.
      var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
      var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
      c.IncludeXmlComments(xmlPath);
    });

然后,在Configure中,添加以下代码行:

 app.UseSwagger();
    if (env.IsDevelopment())
    {
      app.UseSwaggerUI(c =>
      {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web App V1");
      });
    }

此时,您可以调试您的应用程序。默认情况下,swashbuckle会为您的应用程序添加一个路径,因此如果您导航到/ swagger,将显示SwaggerUI。

image

using Microsoft.AspNetCore.Mvc;
namespace WebApplication.ApiControllers
{
   /// <summary>
   /// Verifies that the swagger documentation generator works as expected.
   /// </summary>
   [Route("api/[controller]")]
   [ApiController]
   public class TestController : ControllerBase
   {
     /// <summary>
     /// Retrieves test data.
     /// </summary>
     /// <returns>The test data.</returns>
     [HttpGet]
     public IActionResult GetTestData()
     {
        var model = new
        {
           Message = "Hello, world!"
        };
        return Ok(model);
     }
   }
}

再次启动SwaggerUI,我们可以看到列出了新的API端点。


image

请注意,<summary>注释显示在路径旁边。我们可以使用[SwaggerResponse]属性提供有关不同响应的其他信息。

/// <summary>
   /// Retrieves test data.
   /// </summary>
   /// <returns>The test data.</returns>
   [HttpGet]
   [SwaggerResponse((int)HttpStatusCode.OK, Description = "The data was returned successfully.")]
   [SwaggerResponse((int)HttpStatusCode.Unauthorized, Description = "You are not authorized to access this resource.")]
   public IActionResult GetTestData()
   {
      var model = new
      {
         Message = "Hello, world!"
      };
      return Ok(model);
   }

这将更新swagger文档:


image

我特别喜欢您可以将不同的返回类型关联到每个响应,因此您可以指定成功时返回正常模型但出错时返回错误模型。
注意:在撰写本文时,您必须指定方法(例如,[HttpGet]),否则SwaggerUI将返回错误。

模型验证

Razor(MVC)集成了许多围绕验证的功能,以ModelState和数据注释为中心。随着SPA和WebAPI的引入,我完全停止使用ModelState,并回到在我的API调用顶部编写显式if语句进行验证。
ModelState通过在System.ComponentModel.DataAnnotations命名空间中定义API模型上的数据注释来工作。这些注释涵盖了最常见的验证形式:必填?有最小长度?有最大长度?有效电邮?匹配正则表达式?在真正投入生产的网站,验证可能与业务逻辑本身一样复杂,嗯, 有时候更复杂, 你无法知道到底是什么样的人在使用你的系统。
特性在编译时就是确定的,这意味着您无法轻松执行复杂逻辑。例如,您必须实现自己的验证属性(或从IValidatableObject继承)才能从配置文件或数据库中读取。
现在,让我们考虑一个使用数据注释和IValidatableObject接口的模型:

public class AddressModel : IValidatableObject
{
   [Required]
   [MaxLength(100)]
   public string Line1 { get; set; }
   [MaxLength(100)]
   public string Line2 { get; set; }
   [Required]
   [MaxLength(100)]
   public string City { get; set; }
   [Required]
   [MaxLength(2)]
   public string State { get; set; }
   [Required]
   [RegularExpression(@"^\d{5}(-?\d{4})?$")]
   public string Zip { get; set; }
   public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
   {
      var stateRepository = (IStateRepository)validationContext.GetService(typeof(IStateRepository));
      var state = stateRepository.GetState(State);
      if (state == null)
      {
          yield return new ValidationResult("Unknown state", new[] { nameof(State) });
      }
   }
}

以下是该模型的swagger文档:


image

我们的AddressModel当然不再是一个简单的POCO,从DI容器中获取IStateRepository很丑陋。另外,随着API倾向于异步操作,这种同步验证变得不可接受。那么,我们是否会回到一英里长的if语句?

FluentValidation为胜利!

如果你像我一样,当Entity Framework 5引入了流畅的配置时,你会非常放心。开发人员可以使用链式方法调用来定义配置,而不是使用讨厌的EDMX文件或更多属性。FluentValidation是一个库,它实现了相同的流畅配置链,但用于验证。
以下是使用FluentValidation 对AddressModel进行的等效验证:

public class AddressModelValidator : AbstractValidator<AddressModel>
{
   private readonly IServiceProvider serviceProvider;
   public AddressModelValidator(IServiceProvider serviceProvider)
   {
      this.serviceProvider = serviceProvider;
      RuleFor(x => x.Line1).NotEmpty();
      RuleFor(x => x.Line1).MaximumLength(100);
      RuleFor(x => x.Line2).MaximumLength(100);
      RuleFor(x => x.City).NotEmpty();
      RuleFor(x => x.City).MaximumLength(100);
      RuleFor(x => x.State).NotEmpty();
      RuleFor(x => x.State).MaximumLength(2);
      RuleFor(x => x.Zip).NotEmpty();
      RuleFor(x => x.Zip).Matches(@"^\d{5}(-?\d{4})?$");
      RuleFor(x => x.State).MustAsync(IsKnownState).When(x => x.State != null);
   }
   private async Task<bool> IsKnownState(string abbreviation, CancellationToken token)
   {
      var stateRepository = serviceProvider.GetRequiredService<IStateRepository>();
      var state = await stateRepository.GetStateAsync(abbreviation, token);
      return state != null;
   }
}

注意我能够轻松地将IServiceProvider注入到构造函数中,这样,我们可以轻松地按需检索依赖项。还可以使用MustAsync提供异步实现来检查提供状态的有效性。
另请注意,我没有直接将IStateRepository接口注入构造函数(也许这样更好?)。这是生命周期范围由依赖注入框架管理的方式的工件。您还会注意到我使用了GetRequiredServices,这是一种扩展方法,可以更轻松地使用IServiceProvider接口。
为了让ASP.NET Core知道使用FluentValidation,我们必须更新Startup.cs再次归档。首先,打开NuGet包管理器并将FluentValidation.AspNetCore添加到您的项目中。在ConfigureServices方法中,将对AddFluentValidation的调用标记到AddMvc方法上。

services.AddMvc()
   .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
   .AddFluentValidation(o =>
{
   o.RegisterValidatorsFromAssemblyContaining<AddressModelValidator>();
});

好消息是FluentValidation将重用ASP.NET Core提供的依赖注入配置。对RegisterValidatorsFromAssemblyContaining的调用会自动将程序集中的所有验证程序类注册到服务集合中。这就是允许我将IServiceProviderIValidatorFactory等东西传递给构造函数的原因。该AddFluentValidation方法提供了配置FluentValidation与ASP.NET核心工作的几种方法。

使用Swagger注册FluentValidation

如果再次打开SwaggerUI,您会注意到我们丢失了所有模型验证信息。默认情况下,Swashbuckle只知道如何生成数据注释的文档。


image

幸运的是,其他一些聪明人已经经历了整合Swashbuckle和FluentValidation的痛苦。只需添加MicroElements.Swashbuckle.FluentValidation NuGet包,现在您可以对AddSwaggerGen调用进行简单修改:

services.AddSwaggerGen(c =>
{
   c.SwaggerDoc("v1", new Info() { Title = "Web App", Version = "v1" });
   // Set the comments path for the Swagger JSON and UI.
   var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
   var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
   c.IncludeXmlComments(xmlPath);
   c.AddFluentValidationRules();
});

这一行代码允许swagger检查您的验证器类,以构建数据注释附带的等效文档。所以现在,当你打开SwaggerUI时,你可以查看原来的相同级别的详细信息:


image

正如我所示,向ASP.NET Core添加API文档非常简单。到目前为止,我喜欢ASP.NET Core的灵活性。

参考

FluentValidation文档 - https://github.com/JeremySkinner/FluentValidation/wiki/a.-Index
Swagger配置 - https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with- swashbuckle?view = aspnetcore-2.1&tabs = visual-studio%2Cvisual-studio-xml
Swagger / FluentValidation Integration - https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation
自定义Swagger文档 - https:// www。 schaeflein.net/adding-implementation-notes-to-swagger-ui-via-swashbuckle-attributes/
ASP.NET模型验证 - https://docs.microsoft.com/en-us/aspnet/core/mvc/models/验证?视图= aspnetcore-2.1

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容