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

Проверка эффективности ViewModel в Best Practices в ASP.NET MVC

Я использую DataAnnotations для проверки моей ViewModel на стороне клиента с jquery.validate.unobtrusive и на стороне сервера в приложении ASP.NET MVC.

Не так давно я понял, что могу написать валидацию следующим образом:

[Required(ErrorMessage = "{0} is required")]
public string Name { get; set; }

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

Также я знаю, что существует библиотека FluentValidation, которая позволяет добавлять правила проверки уже существующим ViewModel. Я знаю, что есть проблема с Add/Edit ViewModels, которая может иметь похожие поля, но различные ValidationRules.

Другая проблема, возникающая в результате проверки клиента, заключается в том, что html, недавно добавленный в DOM (с использованием ajax-запроса), должен быть проанализирован для включения проверки. Вот как я это делаю:

$('#some-ajax-form').data('validator', null); 
$.validator.unobtrusive.parse('#some-ajax-form');

Итак, у меня есть несколько вопросов:

  • Есть ли другие полезные методы, которые могут помочь централизовать все правила проверки в приложении?
  • Каков наилучший способ решить проблему с добавлением/редактированием ViewModel? Могу ли я использовать DataAnnotations с FluentValidation или отдельно добавить и отредактировать ViewModels по-прежнему является лучшим вариантом?
  • Есть ли лучший способ инициализировать проверку для новых элементов DOM, которые были получены с помощью ajax-вызова, который я упоминаю?

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

4b9b3361

Ответ 1

Сначала ответьте на 3-й вопрос: Нет, нет более простого способа, чем то, что вы делаете. Две строки кода для его работы вряд ли могут быть проще. Хотя есть плагин, который вы могли бы использовать, как описано в вопросе ненавязчивая проверка, не работающая с динамическим контентом

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

Итак, у меня есть файл под названием ModelValidation.cs в моем слое данных, и у меня есть код для всех моих моделей, например

/// <summary>
/// Validation rules for the <see cref="Test"/> object
/// </summary>
/// <remarks>
/// 2015-01-26: Created
/// </remarks>
[MetadataType(typeof(TestValidation))]
public partial class Test { }
public class TestValidation
{
    /// <summary>Name is required</summary>
    [Required]
    [StringLength(100)]
    public string Name { get; set; }

    /// <summary>Text is multiline</summary>
    [DataType(DataType.MultilineText)]
    [AllowHtml]
    public string Text { get; set; }
}

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

В основном это сводится к файлу-recource, содержащему что-то вроде:

Test_Name = "Provide name"
Test_Name_Required = "Name is required"

И эти сообщения и именование будут использоваться, когда вы вызываете обычный MVC view код, например

<div class="editor-container">
    <div class="editor-label">
        @Html.LabelFor(model => model.Name) <!--"Provide name"-->
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Name)
        @Html.ValidationMessageFor(model => model.Name) <!--"Name is required"-->
    </div>
</div>

Второй вопрос, касающийся различной проверки для добавления/редактирования, можно обрабатывать двумя способами. Лучшим способом было бы использовать представления, поскольку они на самом деле предназначены. Это означает, что вы не передаете свои фактические модели в представления, но вы создаете модель представления, содержащую только данные. Таким образом, у вас есть модель представления для Create с правильными правилами проверки и моделью просмотра для Edit с правильными правилами, а когда они проходят, вы вставляете результат в свою фактическую модель. Однако для этого требуется гораздо больше кода и ручной работы, поэтому я могу себе представить, что вы не очень хотите это сделать.

Другим вариантом было бы использовать условную проверку, как это объяснялось viperguynaz. Теперь вместо логического, мои классы, требующие изменения между edit/add, имеют primary key Id int. Поэтому я проверяю, если Id>0 определить, является ли это редактированием или нет.

UPDATE:

Если вы хотите обновить валидацию при каждом вызове ajax, вы можете использовать jQuery ajaxComplete. Это будет проверять все формы после каждого запроса ajax.

$( document ).ajaxComplete(function() {
    $('form').each(function() {
        var $el = $(this);
        $el.data('validator', null); 
        $.validator.unobtrusive.parse($el);
    })
});

Если это то, что вы хотите, зависит от того, как часто вы получаете форму через AJAX. Если у вас есть много запросов AJAX, например, опрос статуса каждые 10 секунд, чем вы этого не хотите. Если у вас есть случайный запрос AJAX, который в основном содержит форму, вы можете использовать его.

Если ваш AJAX возвращает форму, которую вы хотите проверить, то да, это хорошая практика для обновления проверки. Но я думаю, лучший вопрос будет: "Мне действительно нужно отправить форму AJAX?" AJAX является забавным и полезным, но его следует использовать с осторожностью и мыслью.

Ответ 2

Как говорили другие, таких трюков нет, нет простого способа централизовать ваши проверки.

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

Я знаю, что есть проблема с Add/Edit ViewModels, которые могут иметь похожие поля, но разные ValidationRules.

Подход наследования

Вы можете добиться централизованной проверки с использованием базового класса и использовать подклассы для конкретных проверок.

// Base class. That will be shared by the add and edit
public class UserModel
{
    public int ID { get; set; }
    public virtual string FirstName { get; set; } // Notice the virtual?

    // This validation is shared on both Add and Edit.
    // A centralized approach.
    [Required]
    public string LastName { get; set; }
}

// Used for creating a new user.
public class AddUserViewModel : UserModel
{
    // AddUser has its own specific validation for the first name.
    [Required]
    public override string FirstName { get; set; } // Notice the override?
}

// Used for updating a user.
public class EditUserViewModel : UserModel
{
    public override string FirstName { get; set; }
}

Расширение подхода ValidationAttribute

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

using System.ComponentModel.DataAnnotations;
public class CustomEmailAttribute : ValidationAttribute
{
    public CustomEmailAttribute()
    {
        this.ErrorMessage = "Error Message Here";
    }

    public override bool IsValid(object value)
    {
        string email = value as string;

        // Put validation logic here.

        return valid;
    }
}

Вы использовали бы как таковой

public class AddUserViewModel
{
    [CustomEmail]
    public string Email { get; set; }

    [CustomEmail]
    public string RetypeEmail { get; set; }
}

Есть ли лучший способ инициализировать проверку на новых элементах DOM, которые были получены с помощью ajax-вызова, о котором я упоминаю?

Вот как я восстанавливаю валидаторы на динамических элементах.

/** 
* Rebinds the MVC unobtrusive validation to the newly written
* form inputs. This is especially useful for forms loaded from
* partial views or ajax.
*
* Credits: http://www.mfranc.com/javascript/unobtrusive-validation-in-partial-views/
* 
* Usage: Call after pasting the partial view
*
*/
function refreshValidators(formSelector) {
    //get the relevant form 
    var form = $(formSelector);
    // delete validator in case someone called form.validate()
    $(form).removeData("validator");
    $.validator.unobtrusive.parse(form);
};

Использование

// Dynamically load the add-user interface from a partial view.
$('#add-user-div').html(partialView);

// Call refresh validators on the form
refreshValidators('#add-user-div form');

Ответ 3

Функция ненавязчивого JQuery работает, применяя атрибуты к элементам INPUT, которые инструктируют библиотеку клиента проверять этот элемент, используя правило, которое сопоставляется с соответствующим атрибутом. Например: атрибут data-val-required html распознается ненавязчивой библиотекой и заставляет его проверять этот элемент на соответствие правилу.

В .NET MVC вы можете сделать это автоматически для некоторых определенных правил, применяя атрибуты к вашим свойствам модели. Атрибуты, такие как Required и MaxLength работают, потому что помощники Html знают, как читать эти атрибуты и добавлять соответствующие атрибуты HTML к их выводам, которые понимает ненавязчивая библиотека.

Если вы добавите правила проверки в свои модели в IValidatableObject или используете FluentValidation, HTML-помощник не увидит эти правила и поэтому не попытается перевести их на ненавязчивые атрибуты.

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

Яркая сторона: вы можете создавать свои собственные атрибуты проверки, и, реализуя IClientValidatable, Html Helper добавит ненавязчивый атрибут с выбранным вами именем, чтобы потом научить ненавязчивую библиотеку уважать.

Это пользовательский атрибут, который мы используем, который гарантирует, что одна дате выпадет после другой даты:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DateGreaterThanAttribute : ValidationAttribute, IClientValidatable
{
    string otherPropertyName;

    public DateGreaterThanAttribute(string otherPropertyName, string errorMessage = null)
        : base(errorMessage)
    {
        this.otherPropertyName = otherPropertyName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ValidationResult validationResult = ValidationResult.Success;
        // Using reflection we can get a reference to the other date property, in this example the project start date
        var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
        // Let check that otherProperty is of type DateTime as we expect it to be
        if (otherPropertyInfo.PropertyType.Equals(new DateTime().GetType()))
        {
            DateTime toValidate = (DateTime)value;
            DateTime referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
            // if the end date is lower than the start date, than the validationResult will be set to false and return
            // a properly formatted error message
            if (toValidate.CompareTo(referenceProperty) < 1)
            {
                validationResult = new ValidationResult(this.GetErrorMessage(validationContext));
            }
        }
        else
        {
            // do nothing. We're not checking for a valid date here
        }

        return validationResult;
    }

    public override string FormatErrorMessage(string name)
    {
        return "must be greater than " + otherPropertyName;
    }

    private string GetErrorMessage(ValidationContext validationContext)
    {
        if (!this.ErrorMessage.IsNullOrEmpty())
            return this.ErrorMessage;
        else
        {
            var thisPropName = !validationContext.DisplayName.IsNullOrEmpty() ? validationContext.DisplayName : validationContext.MemberName;
            var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
            var otherPropName = otherPropertyInfo.Name;
            // Check to see if there is a Displayname attribute and use that to build the message instead of the property name
            var displayNameAttrs = otherPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), false);
            if (displayNameAttrs.Length > 0)
                otherPropName = ((DisplayNameAttribute)displayNameAttrs[0]).DisplayName;

            return "{0} must be on or after {1}".FormatWith(thisPropName, otherPropName);
        }
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        //string errorMessage = this.FormatErrorMessage(metadata.DisplayName);
        string errorMessage = ErrorMessageString;

        // The value we set here are needed by the jQuery adapter
        ModelClientValidationRule dateGreaterThanRule = new ModelClientValidationRule();
        dateGreaterThanRule.ErrorMessage = errorMessage;
        dateGreaterThanRule.ValidationType = "dategreaterthan"; // This is the name the jQuery adapter will use
        //"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
        dateGreaterThanRule.ValidationParameters.Add("otherpropertyname", otherPropertyName);

        yield return dateGreaterThanRule;
    }
}

Мы можем применить атрибут к модели как таковой:

    [DateGreaterThan("Birthdate", "You have to be born before you can die")]
    public DateTime DeathDate { get; set; }

Это приводит к тому, что хелпер Html отображает следующие два атрибута в элементе INPUT при вызове Html.EditorFor для свойства модели, имеющего этот атрибут:

data-val-dategreaterthan="You have to be born before you can die" 
data-val-dategreaterthan-otherpropertyname="Birthdate" 

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

    // Value is the element to be validated, params is the array of name/value pairs of the parameters extracted from the HTML, element is the HTML element that the validator is attached to
jQuery.validator.addMethod("dategreaterthan", function (value, element, params) {
    return Date.parse(value) > Date.parse($(params).val());
});

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

jQuery.validator.unobtrusive.adapters.add("dategreaterthan", ["otherpropertyname"], function (options) {
    options.rules["dategreaterthan"] = "#" + options.params.otherpropertyname;
    options.messages["dategreaterthan"] = options.message;
});

После того, как я все это сделал, я могу получить это правило проверки для "свободного" в другом месте в моем приложении, просто применив этот атрибут к модели.

Чтобы решить вопрос о том, как применять правила условно на основе того, используется ли модель в операции добавления или редактирования, это, вероятно, можно сделать, добавив дополнительную логику к вашим пользовательским атрибутам и имея как метод IsValid метод правил GetClientValidation пытается извлечь некоторый контекст из модели с использованием отражения. Но, честно говоря, это кажется беспорядочным для меня. Для этого я просто полагаюсь на проверку сервера и любые правила, которые вы решите применять с помощью метода IValidatableObject.Validate().

Ответ 4

Существуют различные способы проверки клиента, как тот, который Microsoft использует для MVC, работает с библиотекой ubobtrusive, созданной самостоятельно для интеграции с DataAnnotations. Но, после нескольких лет работы с этим полезным инструментом, я устал от этого, что скучно и утомительно использовать в случаях, когда нам нужен отдельный ViewModels (и, вероятно, отдельный ViewModels для создания/редактирования шаблоны).

Другой способ - использовать MVVM, который хорошо работает с MVC, поскольку две парадигмы весьма схожи. В MVC у вас есть модель, ограниченная только на стороне сервера, когда клиент отправляет контент на сервер. Хотя MVVM связывает локальную модель с пользовательским интерфейсом непосредственно на клиенте. Взгляните на Knockoutjs, известный, который поможет вам понять, как работать с MVVM.

С учетом этого я отвечу на ваши вопросы по порядку:

  • Вы не можете централизовать правила проверки в приложении, если создания общих классов и повторного использования их путем вызова в отдельном Модели /ViewModels.
  • Если вы хотите использовать Microsoft Validator, разделив Add/Edit ViewModels - лучший вариант из-за его читаемости и более простого способа изменение.
  • Я никогда не говорил, что Knockoutjs лучше, они разные друг от друга, просто дает вам некоторую гибкость для создания представлений на основе требований модели. Это также уводит вас от централизация валидаций: (