为 IFormFile 实现流利验证的正确方法是什么

What is a correct way to implement Fluent Validation for IFormFile

我正在努力实现 IFormFile 的验证。我正在使用 FluentValidation,这是我的 FormFile 验证器:

    public class PhotoValidator : AbstractValidator<IFormFile>
    {
        private readonly PhotoSettings _settings;

        public PhotoValidator(IOptionsSnapshot<PhotoSettings> snapshot)
        {
            _settings = snapshot.Value;

            RuleFor(f => f).NotNull().WithMessage("Please Attach your photo");

            RuleFor(f => f.Length).ExclusiveBetween(0, _settings.MaxBytes)
                .WithMessage($"File length should be greater than 0 and less than {_settings.MaxBytes / 1024 / 1024} MB");

            RuleFor(f => f.FileName).Must(HaveSupportedFileType);
        }

        private bool HaveSupportedFileType(string fileName)
        {
            return _settings.IsSupported(fileName);
        }
    }

问题是请求验证器根本没有被调用,它只是跳过了它。 这是我的 api 端点签名:

        [HttpPost("{productId:min(1)}")]
        public IActionResult Upload([FromRoute] int productId, IFormFile file)
        {
          // some code here
        }

在我的项目中,我有其他验证器(但是它们没有实现接口类型的 AbstractValidator)并且它们正在工作。甚至可以验证实现接口的类型吗?

The problem is than during request validator isn't called at all, it just skips it.

更新

除了手动触发的方法,我们确实可以让PhotoValidator函数提交后自动触发

前提是我们需要在startup的ConfigureServices方法中配置FluentValidation如下:

using FluentValidation;
using FluentValidation.AspNetCore;// this need FluentValidation.AspNetCore dll
//...
public void ConfigureServices(IServiceCollection services)
{
   // ...
   services.AddMvc().AddFluentValidation();
   services.AddTransient<IValidator<FileData>, PhotoValidator>();
 }

这里有一个很好的参考document供大家参考

此外,要验证IFormFile的内容,需要将其作为对象放入a class中进行验证。

这里我创建了FileData class,存储了一个IFormFile类型的字段如下:

 public class FileData
    {
        public IFormFile file { get; set; }
    }

并且,在PhotoValidator中验证时,还有一些细节需要修改。

当文件为null时,需要在验证Length和FileName[=55=的语句中加入前置条件When(f => f.file != null) ]字段,否则会报错:

(由于你没有提供PhotoSettings的相关内容,我这里将_settings.MaxBytes设为固定值):

 public class PhotoValidator : AbstractValidator<FileData>
    {
        private readonly PhotoSettings _settings;

        public PhotoValidator(/*PhotoSettings snapshot*/)
        {
            // _settings = snapshot;
            _settings = new PhotoSettings() { MaxBytes = 2048000 };
            RuleFor(f => f.file).NotNull().WithMessage("Please Attach your photo"); 
            RuleFor(f => f.file.Length).ExclusiveBetween(0, _settings.MaxBytes)
                .WithMessage($"File length should be greater than 0 and less than {_settings.MaxBytes / 1024 / 1024} MB")
                .When(f => f.file != null);

            RuleFor(f => f.file.FileName).Must(HaveSupportedFileType).When(f => f.file != null);
        }
        private bool HaveSupportedFileType(string fileName)
        {
            return _settings.IsSupported(fileName);
        } 
    }

这是控制器:

    [HttpPost("{productId}")]
    public IActionResult Upload([FromRoute]int productId, FileData data)
    {
        string errorMessages = null;
        if (!ModelState.IsValid)
        {  
            errorMessages = ModelState.Where(x => x.Value.ValidationState == ModelValidationState.Invalid).FirstOrDefault()
                .Value.Errors.FirstOrDefault().ErrorMessage;
            return Content(errorMessages);//return error message to show in view
        } 
        return Ok();
    }

查看:

@{
    ViewData["Title"] = "Upload";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Upload</h1>

<form enctype="multipart/form-data" method="post">
    <div class="form-group">
        productId: <input name="productId" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <input type="file" name="file" /><br />
        <span class="text-danger" id="fileMessage"></span>
    </div>
    <input id="Submit1" type="submit" value="submit" />
</form>

@section Scripts{

    <script>
        $("form").submit(function () {
            event.preventDefault();
            var productId = $("input[name=productId]").val();
            var fd = new FormData();
            var files = $('input[name=file]')[0].files[0];
            fd.append('file', files);
            var data = {
                "file": fd,
            };
            $.ajax({
                type: "post",
                url: "/" + productId,
                data: fd,
                contentType: false,
                processData: false,
                dataType: "html",
                success: function (message) {
                    $("#fileMessage").html(message);
                    if (message == "") {
                       alert("upload Success!");
                    }
                }
            })
        })
    </script>

} 

测试结果如下: