递归验证自定义模型绑定程序中的复杂模型

时间:2019-07-06 13:18:58

标签: asp.net-core-mvc

下面是我为ASP.NET Core Mvc应用程序创建的一个简单JsonModelBinder的代码。是否有一种简单的方法来递归地验证model,其属性以及其属性的属性等等?

JsonModelBinder

public class JsonModelBinder : IModelBinder
{
    static readonly JsonSerializerSettings settings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    };

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        try
        {
            var json = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).Values;
            if (json.Count > 0)
            {
                var model = JsonConvert.DeserializeObject(json, bindingContext.ModelType, settings);
                // TODO: Validate complex model
                bindingContext.Result = ModelBindingResult.Success(model);
            }
            else
            {
                bindingContext.Result = ModelBindingResult.Success(null);
            }
        }
        catch (JsonException ex)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}

示例模型

public class Foo {
    [Required]
    public string Name { get; set; }
    public List<Bar> Bars { get; set; }
    public Baz Baz { get; set; }
}

public class Bar {
    [Required]
    public string Name { get; set; }
}

public class Baz {
    [Required]
    public string Name { get; set; }
}

控制器动作

public async Task<IActionResult> Edit(Guid id, [Required, ModelBinder(typeof(JsonModelBinder))] Foo foo) {
    if (ModelState.IsValid) {
        // Do stuff
    }
    else {
        return View(foo);
    }
}

1 个答案:

答案 0 :(得分:0)

我创建了以下递归数据注释验证器,并按如下所示使用它。根据我收到的一些反馈,目前看来.NET Core中没有内置这样的东西。

递归数据注释验证器:

public static class RecursiveValidator
{
    /// <summary>
    /// Recursively validates <paramref name="instance"/>.
    /// </summary>
    /// <param name="instance"></param>
    /// <param name="prefix"></param>
    /// <param name="validationContext"></param>
    /// <param name="results"></param>
    /// <param name="validateAllProperties"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"><paramref name="instance"/> is null</exception>
    public static bool TryValidateObject(object instance, IServiceProvider serviceProvider, IDictionary<object, object> items, ICollection<ValidationResult> results, bool validateAllProperties, string prefix)
    {
        if (instance is null)
        {
            throw new ArgumentNullException(nameof(instance));
        }

        var tempResults = new List<ValidationResult>();

        ValidationContext validationContext = new ValidationContext(instance, serviceProvider, items);
        var isValid = Validator.TryValidateObject(instance, validationContext, tempResults, validateAllProperties: validateAllProperties);

        foreach (var item in tempResults)
        {
            IEnumerable<string> memberNames = item.MemberNames.Select(name => (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + name);
            results.Add(new ValidationResult(item.ErrorMessage, memberNames));
        }

        foreach (var prop in instance.GetType().GetProperties())
        {
            if (prop.GetSetMethod() == null)
            {
                continue;
            }
            else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
            {
                var value = prop.GetValue(instance);
                if (value == null)
                {
                    continue;
                }
                else if (value is IEnumerable<object> list)
                {
                    var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
                    int i = 0;
                    foreach (var item in list)
                    {
                        if (!TryValidateObject(item, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: $"{memberPrefix}[{i}]"))
                        {
                            isValid = false;
                        }
                        i++;
                    }
                }
                else
                {
                    var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
                    if (!TryValidateObject(value, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: memberPrefix))
                    {
                        isValid = false;
                    }
                }
            }
        }

        return isValid;
    }
}

使用它向绑定上下文添加模型状态错误的示例:

var validationResults = new List<ValidationResult>();
if (!RecursiveValidator.TryValidateObject(model, bindingContext.HttpContext.RequestServices, null, validationResults, validateAllProperties: true, prefix: bindingContext.ModelName))
{
    foreach (var result in validationResults)
    {
        foreach (var member in result.MemberNames)
        {
            bindingContext.ModelState.AddModelError(member, result.ErrorMessage);
        }
    }
}