Проверка модели MVC из базы данных
У меня очень простая модель, которая должна быть проверена с помощью базы данных
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 Architecture должным образом.
Итак, каков наилучший образец для проверки модели из базы данных?
Примечание:
Большая часть проверки - это однопоточный поиск из базы данных, другая проверка может включать в себя комбинацию поля. Но сейчас я доволен проверкой единственного поля поиска, если он DRY
и не использует слишком много отражения, это приемлемо.
НЕТ КЛИЕНТА СТОРОНЫ:
Для всех, кто отвечает с точки зрения проверки на стороне клиента, мне не нужна такая проверка, большая часть моей проверки является односторонней, и мне нужно то же самое, пожалуйста, не отвечайте с помощью методов проверки на стороне клиента.
P.S. Если кто-нибудь может дать мне подсказку о том, как выполнить проверку на основе атрибута из базы данных, будет чрезвычайно полезно.
Ответы
Ответ 1
Пожалуйста, проверьте EDIT от прикрепленного к середине ответа, для более подробного и универсального решения.
Следующее - это мое решение сделать простую проверку на основе атрибутов. Создать атрибут -
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("[email protected]");
emails.Add("[email protected]");
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;
<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" />
}
Теперь, когда я вхожу в тот же Email - [email protected]
и нажимаю кнопку "Отправить", я могу получить ошибки в моем действии POST
, как показано ниже.
![enter image description here]()
EDIT Ниже приводится более общий и подробный ответ.
Создать 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<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;
}
Теперь создадим собственный 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
, который даст нам определенную команду на основе типа.
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 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; }
}
Вывести следующее:
![enter image description here]()
EDIT Подробное объяснение, чтобы сделать это решение более общим.
Допустим, у меня есть свойство Email
, где мне нужно выполнить следующие проверки -
В этом случае мы можем создать IEmailCommand
, унаследованный от IValidatorCommand
. Затем наследуйте IEmailFormatCommand
, IEmailLengthCommand
и IEmailUniqueCommand
из IEmailCommand
.
Наш ValidatorFactory
будет содержать пул из всех трех реализаций команд в Dictionary<Type, IValidatorCommand> Commands
.
Теперь вместо того, чтобы украсить наше свойство Email
тремя командами, мы можем украсить его с помощью IEmailCommand
.
В этом случае наш метод ValidatorFactory.GetCommand()
должен быть изменен. Вместо того, чтобы каждый раз возвращать одну команду, она должна возвращать все соответствующие команды для определенного типа. Поэтому в основном его подпись должна быть List<IValidatorCommand> GetCommand(Type validatetype)
.
Теперь, когда мы можем получить все команды, связанные с свойством, мы можем зацикливать команды и получить результаты проверки в нашем ValidatorAttribute
.
Ответ 2
Я бы использовал RemoteValidation
. Я нашел это простейшим для сценариев, таких как проверки против базы данных.
Украсьте свой объект атрибутом Remote -
[Remote("IsCityCodeValid","controller")]
public string CityCode { get; set; }
Теперь "IsCityCodeValid" будет методом действий, который вернет JsonResult и возьмет имя свойства, которое вы хотите проверить как параметр, а "controller" - это имя контроллера, в котором будет помещен ваш метод. Убедитесь, что имя параметра совпадает с именем свойства.
Сделайте свои проверки в методе, и в случае, если он верен, возвратите 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
Ссылка MSDN
Ответ 3
Я думаю, вы должны использовать пользовательскую проверку
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<string> { "CityCode" });
return ValidationResult.Success;
}
Ответ 4
Я бы предложил очень простое решение для проверки на стороне сервера полей, которые могут иметь только значения, существующие в базе данных. Прежде всего нам понадобится атрибут проверки:
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;
}
}
Здесь базовый класс Repository
:
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);
}
}
}
И теперь, что вам нужно сделать в модели, пометьте ваши поля 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);
}
Ответ 5
если вы действительно хотите проверить из базы данных, вот некоторые методы, которые вы можете выполнить
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; }
здесь задается длина строки l.e 50, а datetime может быть обнуляема и т.д.
База данных EF Сначала с ASP.NET MVC: повышение достоверности данных
Ответ 6
Я сделал это в прошлом, и это сработало для меня:
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
}
}
Ответ 7
Вот моя попытка -
Для начала, чтобы определить, какую проверку мы должны выполнить для свойства, мы можем иметь перечисление как идентификатор.
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 Class (Конечно, вы можете улучшить этот класс с использованием DI и т.д., это просто для целей ref).
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, это отвечает за предоставление правильного валидатора, связанного с типом проверки
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];
}
}
Основываясь на дизайне вашей системы и соглашениях, фактическая реализация может немного отличаться, но на высоком уровне она должна хорошо решать проблемы. Надеюсь, что поможет
Ответ 8
- Привет. Думаю, это полезно для вашего вопроса.
- Я использую этот метод для
- вызов одной функции в разных местах. Я подробно объясню
ниже.
В модели:
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);
Ответ 9
Я столкнулся с ситуацией вроде этого. If User already taken EmailId. i will be Show the Error Message In UI...
Я использовал Проверка удаленной атрибуции
Также моя реализация для проверки электронной почты
Свойство модели
[Required(ErrorMessage = "{0} required")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
ErrorMessage = "Not Valid Email..Enter Again")]
[Remote("CheckEmailId", "Email", ErrorMessage = "Already in use!")]
[StringLength(50, ErrorMessage = "{0}: email addres should be more than 50 charcters")]
public string Email { get; set; }
Здесь CheckEmailId - это действие, а Email - имя контроллера
Посмотрите полный метод, который может вернуть результат в формате json
public JsonResult CheckEmailId(string Email)
{
myDbcontext db = new myDbcontext();
//if email Id already in database table returns true otherwise false
var result = db.tblEmail.Any(x=>x.PrimaryEmail== Email);
return Json(result, JsonRequestBehavior.AllowGet);
}
Удачи....
счастливое кодирование...
Также читайте "Проверка удаленной атрибуции" MSDN