C# 从数据库验证MVC模型

C# 从数据库验证MVC模型,c#,.net,asp.net-mvc,validation,asp.net-mvc-4,C#,.net,Asp.net Mvc,Validation,Asp.net Mvc 4,我有一个非常简单的模型,需要从数据库中得到验证 public class UserAddress { public string CityCode {get;set;} } CityCode可以包含仅在我的数据库表中可用的值 我知道我可以做类似的事情 [HttpPost] public ActionResult Address(UserAddress model) { var connection = ; // create connection var cityRep

我有一个非常简单的模型,需要从数据库中得到验证

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
    }
}
这看起来很湿,因为我不得不在很多地方使用这个模型,在每个地方添加相同的逻辑,似乎我没有正确使用MVC架构

那么,从数据库验证模型的最佳模式是什么

注意: 大多数验证是从数据库中查找单个字段,其他验证可能包括字段的组合。但是现在我对单字段查找验证很满意,只要它是
DRY
,并且没有使用太多的反射,它是可以接受的

无客户端验证: 对于任何回答客户端验证的人,我不需要任何此类验证,我的大多数验证都是服务器端的,我需要相同的,请不要使用客户端验证方法回答


另外,如果有人能告诉我如何从数据库中进行基于属性的验证,那将是非常棒的。

我认为您应该使用

公共类用户地址
{
[CustomValidation(typeof(UserAddress),“ValidateCityCode”)]
公共字符串CityCode{get;set;}
}
公共静态ValidationResult ValidationCode(字符串pNewName,ValidationContext pValidationContext)
{
bool IsNotValid=true//应该在这里实现数据库验证逻辑
如果(无效)
返回新的ValidationResult(“未识别城市代码”,新列表{“城市代码”});
返回ValidationResult.Success;
}

如果您真的想从数据库进行验证,这里有一些技术可以遵循 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<System.DateTime> EnrollmentDate { get; set; }
[StringLength(50)]
 public string MiddleName { get; set; }
public int StudentID{get;set;}
[长度(50)]
公共字符串LastName{get;set;}
[长度(50)]
公共字符串名{get;set;}
公共可为空的EnrollmentDate{get;set;}
[长度(50)]
公共字符串MiddleName{get;set;}
这里定义了字符串长度,即50,日期时间可以为空等
请检查本答案中间所附的编辑,了解更详细和通用的解决方案


下面是我做一个简单的基于属性的验证的解决方案。创建一个属性-

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<string>
            // DbContext db = new DbContext();
            var emails = new List<string>();
            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 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);
    }
}
[Remote("IsCityCodeValid","controller")]
public string CityCode { get; set; }
然后我创建了以下视图-

@model WebApplication1.Controllers.Person
@using WebApplication1.Controllers;

<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

@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)

    <input type="submit" value="click" />
}
假设我们有
存储库
工作单元
的定义如下-

public interface IRepository<TEntity> where TEntity : class
{
    List<TEntity> GetAll();
    TEntity FindById(object id);
    TEntity FindByName(object name);
}

public interface IUnitOfWork
{
    void Dispose();
    void Save();
    IRepository<TEntity> Repository<TEntity>() where TEntity : class;
} 
创建我们的
验证器工厂
,它将根据类型为我们提供特定的命令

public interface IValidatorFactory
{
    Dictionary<Type,IValidatorCommand> Commands { get; }
}

public class ValidatorFactory : IValidatorFactory
{
    private static Dictionary<Type,IValidatorCommand> _commands = new Dictionary<Type, IValidatorCommand>();

    public ValidatorFactory() { }        

    public Dictionary<Type, IValidatorCommand> 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;
    }
}
最后,我们可以使用以下属性-

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

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


编辑详细解释,使此解决方案更通用


假设我有一个属性
电子邮件
,我需要在其中执行以下验证-

  • 格式
  • 长度
  • 独特的
  • 在这种情况下,我们可以创建从
    ivalidtorCommand
    继承的
    IEmailCommand
    。然后从
    IEmailCommand
    继承
    IEmailFormatCommand
    IEmailLengthCommand
    IEmailUniqueCommand

    我们的
    验证器工厂
    将在
    字典命令
    中保存所有三个命令实现的池

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

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


    现在,由于我们可以获得与属性关联的所有命令,我们可以循环这些命令,并在
    ValidatorAttribute
    中获得验证结果,我会使用
    RemoteValidation
    。我发现这对于像针对数据库的验证这样的场景来说是最简单的

    使用远程属性装饰您的财产-

    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<string>
                // DbContext db = new DbContext();
                var emails = new List<string>();
                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 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);
        }
    }
    
    [Remote("IsCityCodeValid","controller")]
    public string CityCode { get; set; }
    
    现在,“IsCityCodeValid”将是一个操作方法,它将返回JsonResult,并将要验证的属性名作为参数,“controller”是将放置方法的控制器的名称。确保参数名称与属性名称相同

    在方法中进行验证,如果有效,则返回json true和false,否则返回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框架将负责剩下的部分

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

    我过去做过这件事,它对我很有效:

    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,并将
    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
        }
    }
    
    这是我的尝试-

    首先,要确定需要对属性执行什么验证,可以使用枚举作为标识符

    public enum ValidationType
    {
        City,
        //Add more for different validations
    }
    
    接下来定义自定义验证属性,如下所示,其中枚举类型声明为属性参数-

    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);
        }
    }
    
    如果需要添加更多验证,则不需要更改属性类

    现在简单地用这个属性装饰你的房子

        [ValidateLookup(ValidationType.City)]
        public int CityId { get; set; }
    
    下面是解决方案的其他连接部分-

    验证程序接口。所有验证程序都将实现此接口。它只有一个方法来验证进入的对象,以及验证失败时特定于验证器的错误消息

    public interface IValidator
    {
        bool Validate(object value);
    
        string ErrorMessage { get; set; }
    }
    
    CityValidator类(当然,您可以使用DI等改进该类,它只是f
     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; }
    }
    
    public class ValidatorFactory
    {
        private Dictionary<ValidationType, IValidator> validators = new Dictionary<ValidationType, IValidator>();
    
        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;}
    }
    
     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);      
    
    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;
    
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="tableName">Lookup table</param>
        /// <param name="field">Lookup field</param>
        public ExistAttribute(string tableName, string field) : this(tableName, field, DependencyResolver.Current.GetService<ExistRepository>())
        {
        }
    
        /// <summary>
        /// same thing
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="field"></param>
        /// <param name="repository">but we also inject validation repository here</param>
        public ExistAttribute(string tableName, string field, ExistRepository repository) : base(DefaultErrorMessage)
        {
            _tableName = tableName;
            _field = field;
            _repository = repository;
    
        }
    
        /// <summary>
        /// checking for existing object
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool IsValid(object value)
        {
            return _repository.Exists(_tableName, _field, value);
        }
    }
    
    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<int>(query, parameters, commandType: CommandType.Text)) > 0;
            return result;
        }
    }
    
    public class Repository
    {
        private readonly string _connectionString;
    
        public Repository(string connectionString)
        {
            _connectionString = connectionString;
        }
    
        protected T GetConnection<T>(Func<IDbConnection, T> getData)
        {
            var connectionString = _connectionString;
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                return getData(connection);
            }
        }
    }
    
    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);
        }
    
    public class CustomValidationAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var service = (IExternalService) validationContext
                             .GetService(typeof(IExternalService));
    
            // ... validation logic
        }
    }