首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在IValidateOptions中使用FluentValidation编写流畅的验证规则?

如何在IValidateOptions中使用FluentValidation编写流畅的验证规则?
EN

Stack Overflow用户
提问于 2021-12-18 22:26:50
回答 3查看 1.2K关注 0票数 3

对于我的.Net 5工人服务应用程序,我想通过实现IValidateOptions接口来验证选项,但不想编写自己的错误消息。这就是为什么我想要使用包FluentValidation.AspNetCore.

给定模型

代码语言:javascript
复制
namespace App.Models
{
    public class MyOptions
    {
        public string Text { get; set; }
    }
}

我添加了验证规则

代码语言:javascript
复制
namespace App.ModelValidators
{
    public class MyOptionsValidator : AbstractValidator<MyOptions>
    {
        public MyOptionsValidator()
        {
            RuleFor(model => model.Text).NotEmpty();
        }
    }
}

接下来,我将在验证中使用这个验证器

代码语言:javascript
复制
namespace App.OptionsValidators
{
    public class MyOptionsValidator : IValidateOptions<MyOptions>
    {
        private readonly IValidator<MyOptions> _validator;
        
        public MyOptionsValidator(IValidator<MyOptions> validator)
        {
            _validator = validator;
        }
        
        public ValidateOptionsResult Validate(string name, MyOptions options)
        {
            var validationResult = _validator.Validate(options);

            if (validationResult.IsValid)
            {
                return ValidateOptionsResult.Success;
            }
            
            return ValidateOptionsResult.Fail(validationResult.Errors.Select(validationFailure => validationFailure.ErrorMessage));
        }
    }
}

最后,我设置了DI容器。

代码语言:javascript
复制
services.AddTransient<IValidator<MyOptions>, ModelValidators.MyOptionsValidator>();
services.AddSingleton<IValidateOptions<MyOptions>, OptionsValidators.MyOptionsValidator>();
services.Configure<MyOptions>(configuration.GetSection("My"));

我想知道这是否可以简化?

也许我可以实现IValidateOptions接口,避免使用AbstractValidator,并在.Validate()方法中编写流畅的规则?

示例代码我想要实现的

代码语言:javascript
复制
namespace App.OptionsValidators
{
    public class MyOptionsValidator : IValidateOptions<MyOptions>
    {
        public ValidateOptionsResult Validate(string name, MyOptions options)
        {
            var validationResult = options.Text.Should.Not.Be.Empty();

            if (validationResult.IsValid)
            {
                return ValidateOptionsResult.Success;
            }
            
            return ValidateOptionsResult.Fail(validationResult.ErrorMessage);
        }
    }
}

所以我不再需要AbstractValidator<MyOptions>了。

--我的第一种方法--我对不确定

我使用的不是FluentValidation,而是DataAnnotations。

我向MyOptionsValidator : AbstractValidator<MyOptions>

  • I属性添加了[Required]属性,我完全删除了类[Required],只在DI安装程序

中注册

代码语言:javascript
复制
services.AddSingleton<IValidateOptions<MyOptions>, OptionsValidators.MyOptionsValidator>();
services.Configure<MyOptions>(configuration.GetSection("My"));

MyOptionsValidator内部,我将验证以下选项

代码语言:javascript
复制
    public ValidateOptionsResult Validate(string name, MyOptions options)
    {
        var validationResults = new List<ValidationResult>();
        
        if (!Validator.TryValidateObject(options, new ValidationContext(options), validationResults, true))
        {
            return ValidateOptionsResult.Fail(validationResults.Select(validationResult => validationResult.ErrorMessage));
        }

        return ValidateOptionsResult.Success;
    }

但也许还有更好的方法:)

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-12-24 12:14:31

我非常喜欢使用相同的方法进行跨堆栈的验证,在我的例子中,这是通过FluentValidation进行的。以下是我的方法。

为选项/设置验证器创建一个新的基本验证器:

代码语言:javascript
复制
public abstract class AbstractOptionsValidator<T> : AbstractValidator<T>, IValidateOptions<T>
    where T : class
{
    public virtual ValidateOptionsResult Validate(string name, T options)
    {
        var validateResult = this.Validate(options);
        return validateResult.IsValid ? ValidateOptionsResult.Success : ValidateOptionsResult.Fail(validateResult.Errors.Select(x => x.ErrorMessage));
    }
}

这扩展了FluentValidation AbstractValidator<T>以支持IValidateOptions<T>。现在您已经有了一个可用于所有选项/设置验证器的基。适用于下列设置:

代码语言:javascript
复制
public class FooSettings
{
    public string Bar { get; set; }
}

最后您将得到一个典型的验证器:

代码语言:javascript
复制
public class FooSettingsValidator : AbstractOptionsValidator<FooSettings>, IFooSettingsValidator
{
    public FooSettingsValidator()
    {
        RuleFor(x => x.Bar).NotEmpty();
    }
}

让DI容器知道它:

代码语言:javascript
复制
serviceCollection.AddSingleton<IValidateOptions<FooSettings>, FooSettingsValidator>();

如果没有内置的东西来做上面的工作,我会期待Scrutor将其转换为一个自动的过程。

以上所有这些都为我提供了使用FluentValidation的所有好处,同时也利用了微软为我们提供的第一类选项验证支持。

LINQPad工作示例:

代码语言:javascript
复制
using static FluentAssertions.FluentActions;

void Main()
{
    var fixture = new Fixture();
    var validator = new FooSettingsValidator();
    validator.Validate(fixture.Build<FooSettings>().Without(x => x.Bar).Create()).Errors.Select(x => x.ErrorMessage).Should().BeEquivalentTo(new string[] { "'Bar' must not be empty." });
    validator.Validate(fixture.Build<FooSettings>().Create()).Errors.Select(x => x.ErrorMessage).Should().BeEquivalentTo(new string[] { });

    using (var scope = ServiceProvider.Create(bar: null).CreateScope())
    {
        Invoking(() => scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FooSettings>>().Value).Should().Throw<OptionsValidationException>();
    }

    using (var scope = ServiceProvider.Create(bar: "asdf").CreateScope())
    {
        scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<FooSettings>>().Value.Bar.Should().Be("asdf");
    }
}

// You can define other methods, fields, classes and namespaces here
public class FooSettings
{
    public string Bar { get; set; }
}

public interface IFooSettingsValidator : IValidator { }

public class FooSettingsValidator : AbstractOptionsValidator<FooSettings>, IFooSettingsValidator
{
    public FooSettingsValidator()
    {
        RuleFor(x => x.Bar).NotEmpty();
    }
}

public abstract class AbstractOptionsValidator<T> : AbstractValidator<T>, IValidateOptions<T>
    where T : class
{
    public virtual ValidateOptionsResult Validate(string name, T options)
    {
        var validateResult = this.Validate(options);
        return validateResult.IsValid ? ValidateOptionsResult.Success : ValidateOptionsResult.Fail(validateResult.Errors.Select(x => x.ErrorMessage));
    }
}

public class ServiceProvider
{
    public static IServiceProvider Create(string bar)
    {
        var serviceCollection = new ServiceCollection();

        var config = new ConfigurationBuilder()
                    .AddInMemoryCollection(
                        new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("Foo:Bar", bar) })
                    .Build();
        serviceCollection.AddSingleton<IConfiguration>(config);
        serviceCollection.AddOptions();
        //serviceCollection.Configure<FooSettings>(config.GetSection("Foo"));
        serviceCollection.AddOptions<FooSettings>()
                            .Bind(config.GetSection("Foo"));

        serviceCollection.AddSingleton<IValidateOptions<FooSettings>, FooSettingsValidator>();
        serviceCollection.AddSingleton<IFooSettingsValidator, FooSettingsValidator>();

        return serviceCollection.BuildServiceProvider();
    }
}
票数 2
EN

Stack Overflow用户

发布于 2021-12-22 11:29:54

我在本文https://dejanstojanovic.net/aspnet/2020/april/validate-configurations-with-fluentvalidation-in-aspnet-core/中找到了下面的代码

代码语言:javascript
复制
public void ConfigureServices(IServiceCollection services)  
{  
    services.Configure<EndpointsConfiguration>(Configuration.GetSection(nameof(EndpointsConfiguration)));  
    services.AddSingleton<AbstractValidator<EndpointsConfiguration>, EndpointsConfigurationValidator>();  
  
    services.AddSingleton<EndpointsConfiguration>(container =>  
    {  
        var config = container.GetService<IOptions<EndpointsConfiguration>>().Value;  
        var validator = container.GetService<AbstractValidator<EndpointsConfiguration>>();  
        validator.Validate(config);  
        return config;  
    });  
  
    services.AddControllers();  
}  

这样,您就可以在AbstractValidator的实现中进行所有的验证,并且它将比当前的实现更简单。使用它,我想您不需要实现IOptionsValidator。这篇文章有一个很好的解释,所以请参考,以获得更多的见解。

票数 1
EN

Stack Overflow用户

发布于 2021-12-21 14:14:40

我可以简化使用System.ComponentModel.DataAnnotations

代码语言:javascript
复制
 public class MyOptions
 {
      [Required]
      public string Text { get; set; }
 }

然后您可以通过管道定制您的BadRequest。

代码语言:javascript
复制
 services.AddControllers()
         .ConfigureApiBehaviorOptions(opts =>
         {
               opts.InvalidModelStateResponseFactory = context =>
               {
                   var problemDetails = new ViolationProblemDetails()
                   {
                         Instance = context.HttpContext.Request.Path,
                         Status = StatusCodes.Status400BadRequest,
                         Detail = "Please refer to the errors property for additional details."
                    };

                    problemDetails.Violations = new List<Violation>();

                    foreach (var modelState in context.ModelState)
                    {
                        problemDetails.Violations.Add(new Violation()
                        {
                            Field = modelState.Key,
                            Message = string.Join(",",modelState.Value.Errors.Select(a => a.ErrorMessage))
                         });
                        }

                        return new BadRequestObjectResult(problemDetails);
                    };
                })

更多信息https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70407637

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档