来自数据库的MVC模型validation

我有一个非常简单的模型,需要从数据库中进行validation

public class UserAddress { public string CityCode {get;set;} } 

CityCode可以具有仅在我的数据库表中可用的值。

我知道我可以做点什么。

 [HttpPost] public ActionResult Address(UserAddress model) { var connection = ; // create connection var cityRepository = new CityRepository(connection); if (!cityRepository.IsValidCityCode(model.CityCode)) { // Added Model error } } 

这似乎非常WET因为我必须在很多放置时使用这个模型并且添加相同的逻辑,每个地方似乎我没有正确使用MVC架构。

那么,从数据库validation模型的最佳模式是什么?

注意:大多数validation是从数据库进行单字段查找,其他validation可能包括字段组合。 但是现在我对单场查找validation很满意,只要它是DRY并且没有使用过多的reflection就可以接受。

没有客户端validation: 对于在客户端validation方面回答的任何人,我不需要任何此类validation,我的大多数validation都是服务器端的,我需要相同的,请不要回答客户端validation方法。


PS如果任何人可以给我提示如何从数据库进行基于属性的validation,将会非常有用。


请查看本答复中间附带的编辑 ,以获得更详细和通用的解决方案。


以下是我做一个简单的基于属性的validation的解决方案。 创建一个属性 –

 public class Unique : ValidationAttribute { public Type ObjectType { get; private set; } public Unique(Type type) { ObjectType = type; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (ObjectType == typeof(Email)) { // Here goes the code for creating DbContext, For testing I created List // DbContext db = new DbContext(); var emails = new List(); emails.Add("ra@ra.com"); emails.Add("ve@ve.com"); var email = emails.FirstOrDefault(u => u.Contains(((Email)value).EmailId)); if (String.IsNullOrEmpty(email)) return ValidationResult.Success; else return new ValidationResult("Mail already exists"); } return new ValidationResult("Generic Validation Fail"); } } 

我创建了一个简单的模型来测试 –

 public class Person { [Required] [Unique(typeof(Email))] public Email PersonEmail { get; set; } [Required] public GenderType Gender { get; set; } } public class Email { public string EmailId { get; set; } } 

然后我创建了以下视图 –

 @model WebApplication1.Controllers.Person @using WebApplication1.Controllers;    @using (Html.BeginForm("CreatePersonPost", "Sale")) { @Html.EditorFor(m => m.PersonEmail) @Html.RadioButtonFor(m => m.Gender, GenderType.Male) @GenderType.Male.ToString() @Html.RadioButtonFor(m => m.Gender, GenderType.Female) @GenderType.Female.ToString() @Html.ValidationMessageFor(m => m.Gender)  } 

现在,当我输入相同的电子邮件 – ra@ra.com并单击“提交”按钮时,我的POST操作可能会出现错误,如下所示。

在此处输入图像描述


编辑这里更通用和详细的答案。


创建IValidatorCommand

 public interface IValidatorCommand { object Input { get; set; } CustomValidationResult Execute(); } public class CustomValidationResult { public bool IsValid { get; set; } public string ErrorMessage { get; set; } } 

让我们假设我们以下列方式定义了我们的RepositoryUnitOfWork

 public interface IRepository where TEntity : class { List GetAll(); TEntity FindById(object id); TEntity FindByName(object name); } public interface IUnitOfWork { void Dispose(); void Save(); IRepository Repository() where TEntity : class; } 

现在让我们创建自己的Validator Commands

 public interface IUniqueEmailCommand : IValidatorCommand { } public interface IEmailFormatCommand : IValidatorCommand { } public class UniqueEmail : IUniqueEmailCommand { private readonly IUnitOfWork _unitOfWork; public UniqueEmail(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public object Input { get; set; } public CustomValidationResult Execute() { // Access Repository from Unit Of work here and perform your validation based on Input return new CustomValidationResult { IsValid = false, ErrorMessage = "Email not unique" }; } } public class EmailFormat : IEmailFormatCommand { private readonly IUnitOfWork _unitOfWork; public EmailFormat(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public object Input { get; set; } public CustomValidationResult Execute() { // Access Repository from Unit Of work here and perform your validation based on Input return new CustomValidationResult { IsValid = false, ErrorMessage = "Email format not matched" }; } } 

创建我们的Validator Factory ,它将根据Type为我们提供特定的命令。

 public interface IValidatorFactory { Dictionary Commands { get; } } public class ValidatorFactory : IValidatorFactory { private static Dictionary _commands = new Dictionary(); public ValidatorFactory() { } public Dictionary Commands { get { return _commands; } } private static void LoadCommand() { // Here we need to use little Dependency Injection principles and // populate our implementations from a XML File dynamically // at runtime. For demo, I am passing null in place of UnitOfWork _commands.Add(typeof(IUniqueEmailCommand), new UniqueEmail(null)); _commands.Add(typeof(IEmailFormatCommand), new EmailFormat(null)); } public static IValidatorCommand GetCommand(Type validatetype) { if (_commands.Count == 0) LoadCommand(); var command = _commands.FirstOrDefault(p => p.Key == validatetype); return command.Value ?? null; } } 

并且经过翻新的validation属性 –

 public class MyValidateAttribute : ValidationAttribute { public Type ValidateType { get; private set; } private IValidatorCommand _command; public MyValidateAttribute(Type type) { ValidateType = type; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { _command = ValidatorFactory.GetCommand(ValidateType); _command.Input = value; var result = _command.Execute(); if (result.IsValid) return ValidationResult.Success; else return new ValidationResult(result.ErrorMessage); } } 

最后我们可以使用我们的属性如下 –

 public class Person { [Required] [MyValidate(typeof(IUniqueEmailCommand))] public string Email { get; set; } [Required] public GenderType Gender { get; set; } } 

输出如下 –

在此处输入图像描述


编辑详细解释,使这个解决方案更通用。


假设我有一个属性Email ,我需要做以下validation –

  1. 格式
  2. 长度
  3. 独特

在这种情况下,我们可以创建从IValidatorCommandinheritance的IValidatorCommand 。 然后从IEmailCommandinheritanceIEmailFormatCommandIEmailLengthCommandIEmailCommand

我们的ValidatorFactory将在Dictionary Commands保存所有三个命令实现的池。

现在我们可以用IEmailCommand来装饰它,而不是用三个命令来装饰我们的Email属性。

在这种情况下,我们的ValidatorFactory.GetCommand()方法需要更改。 它不应每次返回一个命令,而应返回特定类型的所有匹配命令。 所以基本上它的签名应该是List GetCommand(Type validatetype)

现在我们可以获取与属性关联的所有命令,我们可以循环命令并在ValidatorAttribute获取validation结果。

我会使用RemoteValidation 。 我发现这对于数据库validation这样的场景最简单。

使用Remote属性装饰您的属性 –

 [Remote("IsCityCodeValid","controller")] public string CityCode { get; set; } 

现在,“IsCityCodeValid”将是一个操作方法,它将返回JsonResult并将您要validation的属性名称作为参数,“controller”是将放置方法的控制器的名称。 确保参数名称与属性名称相同。

在方法中进行validation,如果有效则返回json true和false。 简单快捷!

  public JsonResult IsCityCodeValid(string CityCode) { //Do you DB validations here if (!cityRepository.IsValidCityCode(cityCode)) { //Invalid return Json(false, JsonRequestBehavior.AllowGet); } else { //Valid return Json(true, JsonRequestBehavior.AllowGet); } } 

你完成了!! MVC框架将负责其余的工作。

当然,根据您的要求,您可以使用远程属性的不同重载。 您还可以包含其他依赖属性,定义自定义错误消息等。您甚至可以将模型类作为参数传递给Json结果操作方法MSDN Ref。

我认为你应该使用自定义validation

 public class UserAddress { [CustomValidation(typeof(UserAddress), "ValidateCityCode")] public string CityCode {get;set;} } public static ValidationResult ValidateCityCode(string pNewName, ValidationContext pValidationContext) { bool IsNotValid = true // should implement here the database validation logic if (IsNotValid) return new ValidationResult("CityCode not recognized", new List { "CityCode" }); return ValidationResult.Success; } 

我过去做过这个,它对我有用:

 public interface IValidation { void AddError(string key, string errorMessage); bool IsValid { get; } } public class MVCValidation : IValidation { private ModelStateDictionary _modelStateDictionary; public MVCValidation(ModelStateDictionary modelStateDictionary) { _modelStateDictionary = modelStateDictionary; } public void AddError(string key, string errorMessage) { _modelStateDictionary.AddModelError(key, errorMessage); } public bool IsValid { get { return _modelStateDictionary.IsValid; } } } 

在您的业务层级别执行以下操作:

 public class UserBLL { private IValidation _validator; private CityRepository _cityRepository; public class UserBLL(IValidation validator, CityRepository cityRep) { _validator = validator; _cityRepository = cityRep; } //other stuff... public bool IsCityCodeValid(CityCode cityCode) { if (!cityRepository.IsValidCityCode(model.CityCode)) { _validator.AddError("Error", "Message."); } return _validator.IsValid; } } 

现在在控制器级别用户将您最喜欢的IoC注册到您的UserBLL中并将this.ModelState实例注册到您的UserBLL

 public class MyController { private UserBLL _userBll; public MyController(UserBLL userBll) { _userBll = userBll; } [HttpPost] public ActionResult Address(UserAddress model) { if(userBll.IsCityCodeValid(model.CityCode)) { //do whatever } return View();//modelState already has errors in it so it will display in the view } } 

我为一个只能在数据库中存在值的字段的服务器端validation提供了一个非常简单的解决方案。 首先,我们需要一个validation属性:

 public class ExistAttribute : ValidationAttribute { //we can inject another error message or use one from resources //aint doing it here to keep it simple private const string DefaultErrorMessage = "The value has invalid value"; //use it for validation purpose private readonly ExistRepository _repository; private readonly string _tableName; private readonly string _field; ///  /// constructor ///  /// Lookup table /// Lookup field public ExistAttribute(string tableName, string field) : this(tableName, field, DependencyResolver.Current.GetService()) { } ///  /// same thing ///  ///  ///  /// but we also inject validation repository here public ExistAttribute(string tableName, string field, ExistRepository repository) : base(DefaultErrorMessage) { _tableName = tableName; _field = field; _repository = repository; } ///  /// checking for existing object ///  ///  ///  public override bool IsValid(object value) { return _repository.Exists(_tableName, _field, value); } } 

validation存储库本身看起来也很简单:

 public class ExistRepository : Repository { public ExistRepository(string connectionString) : base(connectionString) { } public bool Exists(string tableName, string fieldName, object value) { //just check if value exists var query = string.Format("SELECT TOP 1 1 FROM {0} l WHERE {1} = @value", tableName, fieldName); var parameters = new DynamicParameters(); parameters.Add("@value", value); //i use dapper here, and "GetConnection" is inherited from base repository var result = GetConnection(c => c.ExecuteScalar(query, parameters, commandType: CommandType.Text)) > 0; return result; } } 

这是基础Repository类:

 public class Repository { private readonly string _connectionString; public Repository(string connectionString) { _connectionString = connectionString; } protected T GetConnection(Func getData) { var connectionString = _connectionString; using (var connection = new SqlConnection(connectionString)) { connection.Open(); return getData(connection); } } } 

现在,您需要在模型中执行的操作是使用ExistAttribute标记您的字段,为查找指定表名和字段名:

 public class UserAddress { [Exist("dbo.Cities", "city_id")] public int CityCode { get; set; } [Exist("dbo.Countries", "country_id")] public int CountryCode { get; set; } } 

控制器动作:

  [HttpPost] public ActionResult UserAddress(UserAddress model) { if (ModelState.IsValid) //you'll get false here if CityCode or ContryCode don't exist in Db { //do stuff } return View("UserAddress", model); } 

如果你真的想从数据库validation这里有一些你可以遵循的技术1.使用System.ComponentModel.DataAnnotations添加对类的引用

 public int StudentID { get; set; } [StringLength(50)] public string LastName { get; set; } [StringLength(50)] public string FirstName { get; set; } public Nullable EnrollmentDate { get; set; } [StringLength(50)] public string MiddleName { get; set; } 

这里定义了字符串lenght,即50,日期时间可以为空等等EF数据库第一次使用ASP.NET MVC:增强数据validation

这是我的尝试 –

首先,为了确定我们需要对属性执行哪些validation,我们可以将枚举作为标识符。

 public enum ValidationType { City, //Add more for different validations } 

接下来定义我们的自定义validation属性,如下所示,其中枚举类型被声明为属性参数 –

 public class ValidateLookupAttribute : ValidationAttribute { //Use this to identify what validation needs to be performed public ValidationType ValidationType { get; private set; } public ValidateLookupAttribute(ValidationType validationType) { ValidationType = validationType; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { //Use the validation factory to get the validator associated //with the validator type ValidatorFactory validatorFactory = new ValidatorFactory(); var Validator = validatorFactory.GetValidator(ValidationType); //Execute the validator bool isValid = Validator.Validate(value); //Validation is successful, return ValidationResult.Succes if (isValid) return ValidationResult.Success; else //Return validation error return new ValidationResult(Validator.ErrorMessage); } } 

如果需要添加更多validation,可以进一步了解,不需要更改属性类。

现在只需使用此属性装饰您的属性即可

  [ValidateLookup(ValidationType.City)] public int CityId { get; set; } 

以下是该解决方案的其他连接部分 –

validation器接口。 所有validation器都将实现此接口。 它只有一种方法来validation进入的对象和validation失败时validation器特定的错误消息。

 public interface IValidator { bool Validate(object value); string ErrorMessage { get; set; } } 

CityValidator Class(当然你可以使用DI等来改进这个类,它只是用于参考目的)。

  public class CityValidator : IValidator { public bool Validate(object value) { //Validate your city here var connection = ; // create connection var cityRepository = new CityRepository(connection); if (!cityRepository.IsValidCityCode((int)value)) { // Added Model error this.ErrorMessage = "City already exists"; } return true; } public ErrorMessage { get; set; } } 

Validator Factory,负责提供与validation类型相关的正确validation器

 public class ValidatorFactory { private Dictionary validators = new Dictionary(); public ValidatorFactory() { validators.Add(ValidationType.City, new CityValidator()); } public IValidator GetValidator(ValidationType validationType) { return this.validators[validationType]; } } 

根据您的系统和约定的设计,实际实现可能略有不同,但在高级别它应该很好地解决问题。 希望有所帮助

  • 嗨..我认为这对你的问题有用。
  • 我使用那种方法
  • 在各种位置调用单个函数。 我将在下面详细解释。

在模型中:

 public class UserAddress { public string CityCode {get;set;} } 

在Controller中:首先创建单个函数以validation单个连接

  public dynamic GetCity(string cityCode) { var connection = ; // create connection var cityRepository = new CityRepository(connection); if (!cityRepository.IsValidCityCode(model.CityCode)) { // Added Model error } return(error); } 

来自其他控制器的函数调用如:

 var error = controllername.GetCity(citycode); 

许多连接的其他方法

  public dynamic GetCity(string cityCode,string connection) { var cityRepository = new CityRepository(connection); if (!cityRepository.IsValidCityCode(model.CityCode)) { // Added Model error } return(error); } 

来自其他控制器的函数调用如:

 var error = controllername.GetCity(citycode,connection);