it-roy-ru.com

Проверка модели 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.

Каков наилучший шаблон для проверки модели из базы данных? 

ПРИМЕЧАНИЕ: Большинство проверок - это поиск по одному полю из базы данных, другая проверка может включать комбинацию полей. Но сейчас я доволен проверкой поиска в одном поле, поскольку она DRY и не использует слишком много отражений, что приемлемо.

НЕТ ВАЛИДАЦИИ НА СТОРОНЕ КЛИЕНТА: Для тех, кто отвечает с точки зрения проверки на стороне клиента, мне не требуется такая проверка, большая часть моей проверки выполняется на стороне сервера, и мне нужно то же самое, пожалуйста, сделайте не отвечает с помощью методов проверки на стороне клиента.


Постскриптум Если кто-нибудь может дать мне подсказку, как сделать проверку на основе атрибутов из базы данных, будет очень здорово.

22
PaRiMaL RaJ

Пожалуйста, проверьте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] и нажимаю кнопку Submit, я могу получить ошибки в своем действии 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, который даст нам конкретную команду на основе Type.

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, где мне нужно выполнить следующие проверки - 

  1. Формат
  2. Длина
  3. Уникальный

В этом случае мы можем создать IEmailCommand, унаследованный от IValidatorCommand. Затем наследуйте IEmailFormatCommand, IEmailLengthCommand и IEmailUniqueCommand от IEmailCommand.

Наша ValidatorFactory будет содержать пул всех трех реализаций команд в Dictionary<Type, IValidatorCommand> Commands.

Теперь вместо того, чтобы украшать наше свойство Email тремя командами, мы можем украсить его с помощью IEmailCommand.

В этом случае наш метод ValidatorFactory.GetCommand() необходимо изменить. Вместо того, чтобы возвращать одну команду каждый раз, он должен возвращать все соответствующие команды для определенного типа. Так что в основном его подпись должна быть List<IValidatorCommand> GetCommand(Type validatetype).

Теперь, когда мы можем получить все команды, связанные со свойством, мы можем зациклить команды и получить результаты проверки в нашей переменной ValidatorAttribute.

27
ramiramilu

Я бы использовал 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 Framework позаботится обо всем остальном.

И, конечно же, исходя из ваших требований, вы можете использовать различные перегрузки удаленного атрибута. Вы также можете включить другие зависимые свойства, определяя обычное сообщение об ошибке и т.д. Вы можете даже передать класс модели в качестве параметра в метод действия результата Json MSDN Ref.

2
Yogi

Я делал это в прошлом, и это сработало для меня:

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

Я думаю, что вы должны использовать пользовательскую проверку

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;
}
1
liviu mamelluc

Я бы предложил очень простое решение для проверки полей на стороне сервера, которые могут иметь только значения, существующие в базе данных. Прежде всего нам понадобится атрибут проверки:

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);
    }
1
Sergio

Вот моя попытка - 

Для начала, чтобы определить, какую проверку мы должны выполнить для свойства, у нас может быть перечисление в качестве идентификатора.

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 и т.д., Это просто для справки).

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

В зависимости от дизайна вашей системы и соглашений, фактическая реализация может немного отличаться, но на высоком уровне она должна хорошо решать проблемы. надеюсь, это поможет

0
Yogi
  • Привет .. Я думаю, что это полезно для вашего вопроса. 
  • Я использую этот метод для
  • вызов одной функции в разных местах. Я подробно объясню Ниже.

В модели:

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);      
0
Saravanan Arunagiri

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

здесь определяется длина строки, т. е. 50, дата и время могут быть обнуляемыми и т. д EF Database сначала с ASP.NET MVC: расширение проверки данных

0
rohit poudel