Подтвердить что ты не робот

Проверка модели 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. Если кто-нибудь может дать мне подсказку о том, как выполнить проверку на основе атрибута из базы данных, будет чрезвычайно полезно.

4b9b3361

Ответ 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