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
}
}