MVC 3 Сложная проверка моделей - PullRequest
4 голосов
/ 02 ноября 2011

Текущий метод проверки для использования в MVC 3 - это ValidationAttributes. У меня есть проверка класса, которая очень специфична для этой модели и взаимодействует между несколькими свойствами.

В основном модель имеет коллекцию других моделей, и все они отредактированы в одной форме. Давайте назовем его ModelA, и у него есть коллекция ModelB. Одна вещь, которую я мог бы проверить, - то, что сумма некоторого свойства ModelB меньше, чем свойство ModelA. У пользователя есть X баллов, которые он может разделить между некоторыми вариантами.

ValidationAttributes очень общие, и я не уверен, что они подходят для этой работы.

Я понятия не имею, как IDateErrorInfo поддерживается в MVC 3 и работает ли он прямо из коробки.

Одним из способов может быть проверка с помощью метода, но это означает, что я не могу выполнить проверку на стороне клиента.

Как правильно сделать что-то подобное? У меня есть еще варианты? Я недооцениваю силу атрибута валидации?

Ответы [ 4 ]

5 голосов
/ 02 ноября 2011

IDateErrorInfo

IDateErrorInfo поддерживается платформой MVC (руководство Microsoft можно найти здесь ). Связыватель модели по умолчанию будет отвечать за воссоздание объектов модели путем привязки элементов формы html к модели. Если связыватель модели обнаружит, что модель реализует интерфейс, он будет использовать методы интерфейса для проверки каждого свойства в модели или для проверки модели в целом. Смотрите учебник для получения дополнительной информации.

Если вы хотите использовать проверку на стороне клиента с использованием этого метода, то (по словам Стива Сандерсона) «самый прямой способ воспользоваться дополнительными правилами проверки - это вручную создать необходимые атрибуты в представлении»:

<p>
@Html.TextBoxFor(m.ClientName, new { data_val = "true", data_val_email = "Enter a valid email address", data_val_required = "Please enter your name"})

@Html.ValidationMessageFor(m => m.ClientName)
</p>

Затем его можно использовать для запуска любой проверки на стороне клиента, которая была определена. Ниже приведен пример определения валидации на стороне клиента.

Явная проверка

Как вы упомянули, вы можете подробно проверить модель в действии. Например:

public ViewResult Register(MyModel theModel)
{
    if (theModel.PropertyB < theModel.PropertyA)
        ModelState.AddModelError("", "PropertyA must not be less then PropertyB");

    if (ModelState.IsValid)
    {
        //save values
        //go to next page
    }
    else
    {
        return View();
    }
}

В представлении вам потребуется использовать @Html.ValidationSummary для отображения сообщения об ошибке, так как приведенный выше код добавит ошибку уровня модели, а не ошибку уровня свойства.

Чтобы указать ошибку уровня свойства, вы можете написать:

ModelState.AddModelError("PropertyA", "PropertyA must not be less then PropertyB");

А затем в представлении используйте:

@Html.ValidationMessageFor(m => m.PropertyA);

для отображения сообщения об ошибке.

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

Атрибут проверки пользовательской модели

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

Для примера, который я приведу, представление предоставит пользователю поле максимального значения и 5 полей значения. Поле максимального значения будет одним значением в модели, где 5 полей значений будут частью коллекции. Проверка обеспечит, чтобы сумма полей значений не превышала поле максимального значения. Проверка будет определена как атрибут в модели, который также будет красиво связан с проверкой на стороне клиента javascript.

Вид:

@model MvcApplication1.Models.ValueModel

<h2>Person Ages</h2>

@using (@Html.BeginForm())
{
    <p>Please enter the maximum total that will be allowed for all values</p>
    @Html.EditorFor(m => m.MaximumTotalValueAllowed)
    @Html.ValidationMessageFor(m => m.MaximumTotalValueAllowed)

    int numberOfValues = 5;

    <p>Please enter @numberOfValues different values.</p>

    for (int i=0; i<numberOfValues; i++)
    {
        <p>@Html.EditorFor(m => m.Values[i])</p>
    }

    <input type="submit" value="submit"/>
}

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

Модель:

public class ValueModel
{
    [Required(ErrorMessage="Please enter the maximum total value")]
    [Numeric] //using DataAnnotationExtensions
    [ValuesMustNotExceedTotal]
    public string MaximumTotalValueAllowed { get; set; }

    public List<string> Values { get; set; }
}

Действия:

public ActionResult Index()
{
    return View();
}

[HttpPost]
public ActionResult Index(ValueModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    else
    {
        return RedirectToAction("complete"); //or whatever action you wish to define.
    }
}

Пользовательский атрибут:

Атрибут [ValuesMustNotExceedTotal], определенный в модели, можно определить путем переопределения класса ValidationAttribute:

public class ValuesMustNotExceedTotalAttribute : ValidationAttribute
{
    private int maxTotalValueAllowed;
    private int valueTotal;

    public ValuesMustNotExceedTotalAttribute()
    {
        ErrorMessage = "The total of all values ({0}) is greater than the maximum value of {1}";
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, valueTotal, maxTotalValueAllowed);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo maxTotalValueAllowedInfo = validationContext.ObjectType.GetProperty("MaximumTotalValueAllowed");
        PropertyInfo valuesInfo = validationContext.ObjectType.GetProperty("Values");

        if (maxTotalValueAllowedInfo == null || valuesInfo == null)
        {
            return new ValidationResult("MaximumTotalValueAllowed or Values is undefined in the model.");
        }

        var maxTotalValueAllowedPropertyValue = maxTotalValueAllowedInfo.GetValue(validationContext.ObjectInstance, null);
        var valuesPropertyValue = valuesInfo.GetValue(validationContext.ObjectInstance, null);

        if (maxTotalValueAllowedPropertyValue != null && valuesPropertyValue != null)
        {
            bool maxTotalValueParsed = Int32.TryParse(maxTotalValueAllowedPropertyValue.ToString(), out maxTotalValueAllowed);

            int dummyValue;
            valueTotal = ((List<string>)valuesPropertyValue).Sum(x => Int32.TryParse(x, out dummyValue) ? Int32.Parse(x) : 0);

            if (maxTotalValueParsed && valueTotal > maxTotalValueAllowed)
            {
                return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
            }
        }

        //if the maximum value is not supplied or could not be parsed then we still return that the validation was successful.
        //why?  because this attribute is only responsible for validating that the total of the values is less than the maximum.
        //we use a [Required] attribute on the model to ensure that the field is required and a [Numeric] attribute
        //on the model to ensure that the fields are input as numeric (supplying appropriate error messages for each).
        return null;
    }
}

Добавление проверки на стороне клиента к пользовательскому атрибуту:

Чтобы добавить проверку на стороне клиента к этому атрибуту, потребуется реализовать интерфейс IClientValidatable:

public class ValuesMustNotExceedTotalAttribute : ValidationAttribute, IClientValidatable
{
//...code as above...

    //this will be called when creating the form html to set the correct property values for the form elements
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule {
            ValidationType = "valuesmustnotexceedtotal", //the name of the client side javascript validation (must be lowercase)
            ErrorMessage = "The total of all values is greater than the maximum value." //I have provided an alternative error message as i'm not sure how you would alter the {0} and {1} in javascript.
        };

        yield return rule;
        //note: if you set the validation type above to "required" or "email" then it would use the default javascript routines (by those names) to validate client side rather than the one we define
    }
}

Если вы запустите приложение на этом этапе и просмотрите исходный html для поля, определяющего атрибут, вы увидите следующее:

<input class="text-box single-line" data-val="true" data-val-number="The MaximumTotalValueAllowed field is not a valid number." data-val-required="Please enter the maximum total value" data-val-valuesmustnotexceedtotal="The total of all values is greater than the maximum value." id="MaximumTotalValueAllowed" name="MaximumTotalValueAllowed" type="text" value="" />

В частности, обратите внимание на атрибут проверки data-val-valuesmustnotexceedtotal. Вот как наша проверка на стороне клиента будет ссылаться на атрибут validation.

Добавление проверки на стороне клиента:

Чтобы добавить проверку на стороне клиента, вам нужно добавить следующие похожие ссылки на библиотеки в тег представления:

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

Необходимо также убедиться, что проверка на стороне клиента включена в web.config, хотя я думаю, что она должна быть включена по умолчанию:

<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>

Осталось только определить клиентскую проверку в представлении. Обратите внимание, что добавленная здесь проверка определяется в представлении, но если она была определена в библиотеке, то пользовательский атрибут (может быть, не этот) может быть добавлен в другие модели для других представлений:

<script type="text/javascript">

    jQuery.validator.unobtrusive.adapters.add('valuesmustnotexceedtotal', [], function (options) {
        options.rules['valuesmustnotexceedtotal'] = '';
        options.messages['valuesmustnotexceedtotal'] = options.message;
    });

    //note: this will only be fired when the user leaves the maximum value field or when the user clicks the submit button.
    //i'm not sure how you would trigger the validation to fire if the user leaves the value fields although i'm sure its possible.
    jQuery.validator.addMethod('valuesmustnotexceedtotal', function (value, element, params) {

        sumValues = 0;

        //determine if any of the value fields are present and calculate the sum of the fields
        for (i = 0; i <= 4; i++) {

            fieldValue = parseInt($('#Values_' + i + '_').val());

            if (!isNaN(fieldValue)) {
                sumValues = sumValues + fieldValue;
                valueFound = true;
            }
        }

        maximumValue = parseInt(value);

        //(if value has been supplied and is numeric) and (any of the fields are present and are numeric)
        if (!isNaN(maximumValue) && valueFound) {

            //perform validation

            if (sumValues > maximumValue) 
            {
                return false;
            }
        }

        return true;
    }, '');

</script>

И так и должно быть.Я уверен, что есть кое-какие улучшения, которые можно внести здесь и там, и что, если я немного неправильно понял проблему, вы сможете настроить проверку для своих нужд.Но я полагаю, что эта проверка является способом, которым большинство разработчиков кодируют пользовательские атрибуты, включая более сложную проверку на стороне клиента.

Надеюсь, это поможет.Дайте мне знать, если у вас есть какие-либо вопросы или предложения относительно вышеуказанного.

1 голос
/ 02 ноября 2011

У меня тоже есть похожая ситуация, мне нужно сравнить значение свойства A со свойством B, и я получаю это следующим образом:

 public sealed class PropertyAAttribute : ValidationAttribute
{
    public string propertyBProperty { get; set; }
    // Override the isValid function
    public override bool IsValid(object value)
    {
         // Do your comparison here, eg:
          return A >= B;
    }
}

Затем просто используйте пользовательский атрибут валидации, подобный этому:

 [PropertyA(propertyBProperty = "PropertyB")]
 public string Property A {get; set;}

Я тоже очень старался и получил это решение от других, надеюсь, это поможет!

1 голос
/ 02 ноября 2011

Ваш класс модели может реализовать интерфейс IValidatableObject.

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

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

1 голос
/ 02 ноября 2011

Это то, что вы ищете:

http://www.a2zdotnet.com/View.aspx?Id=182

...