来自数据库的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; } }
让我们假设我们以下列方式定义了我们的Repository
和UnitOfWork
–
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 –
- 格式
- 长度
- 独特
在这种情况下,我们可以创建从IValidatorCommand
inheritance的IValidatorCommand
。 然后从IEmailCommand
inheritanceIEmailFormatCommand
, IEmailLengthCommand
和IEmailCommand
。
我们的ValidatorFactory
将在Dictionary
保存所有三个命令实现的池。
现在我们可以用IEmailCommand
来装饰它,而不是用三个命令来装饰我们的Email
属性。
在这种情况下,我们的ValidatorFactory.GetCommand()
方法需要更改。 它不应每次返回一个命令,而应返回特定类型的所有匹配命令。 所以基本上它的签名应该是List
。
现在我们可以获取与属性关联的所有命令,我们可以循环命令并在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);