4

作業中のプロジェクトでは、ユーザーが他のユーザーが値を入力できるフォームを動的に作成できるプロセスを作成しようとしています。ただし、組み込みのモデル バインディングと ASP MVC 3 での検証を使用して、これをうまく機能させる方法を理解するのに苦労しています。

ビューモデルは次のように設定されています。サンプルコードを単純化しすぎていることに注意してください。

public class Form 
{
    public FieldValue[] FieldValues { get; set; }
}

public class Field
{
    public bool IsRequired { get; set; }
}

public class FieldValue 
{
    public Field Field { get; set; }
    public string Value { get; set; }
}

ビューは次のようになります。

@model Form
@using (Html.BeginForm("Create", "Form", FormMethod.Post))
{
    @for(var i = 0; i < Model.Fields.Count(); i++)
    {
        @Html.TextBoxFor(_ => @Model.Fields[i].Value) 
    }
    <input type="submit" value="Save" name="Submit" />
}

FieldValue インスタンスを分析し、その Field.IsRequired プロパティが true かどうかを判断し、その特定のインスタンスのバリデーターに RequiredFieldValidator を追加できるカスタム ModelValidatorProvider または ModelMetadataProvider クラスを作成できることを期待していました。しかし、私はこれで運がありません。ModelValidatorProvider(および ModelMetadataProvider) を使用すると、親コンテナーの値にアクセスできないようです (つまり、FieldValue.Value に対して GetValidators() が呼び出されますが、そこから FieldValue オブジェクトを取得する方法はありません)。

私が試したこと:

  • ModelValidatorProvider で、ControllerContext.Controller.ViewData.Model を使用してみましたが、ネストされた型がある場合は機能しません。バリデータ Form.FieldValues[3] を理解しようとしている場合、使用する FieldValue がわかりません。

  • 内部 modelAccessor の Target プロパティを使用して親を取得しようとするカスタム ModelMetadata を使用してみましたが、ネストされた型がある場合はこれも機能しません。MVC の内部のどこかで、私の例のような式は、ターゲットが FieldValue ではなく、モデルの型 (フォーム) になるという結果になります。そのため、FieldValue のどのインスタンスと比較するのかわからないという上記と同じ問題が発生します。

  • FieldValue クラス自体に設定できるクラス レベルの検証属性ですが、これはサーバーの検証中にのみ呼び出されます。クライアント側の検証も必要です。

私がやろうとしていることはMVCでも可能ですか? または、私が完全に見逃しているものはありますか?

4

1 に答える 1

2

1つの可能性は、カスタム検証属性を使用することです。

しかし、実装に入る前に、シナリオの潜在的な欠陥を指摘したいと思います。IsRequiredプロパティはモデルの一部です。つまり、フォームを送信するときは、必要なルールを対応するプロパティに条件付きで適用できるように、その値を知る必要があります。ただし、フォームが送信されたときにこの値がわかるようにするには、フォームの一部(非表示または標準の入力フィールドとして)であるか、どこか(データストアなど)から取得する必要があることを意味します。最初のアプローチの問題は明らかです=>隠しフィールドは、ユーザーが好きな値を設定できることを意味します。したがって、必要なフィールドを決定するのはユーザーであるため、実際の検証ではなくなります。

この警告は言われていますが、ユーザーを信頼し、IsRequired値を格納するために隠しフィールドアプローチを採用することにしたとしましょう。サンプルの実装方法を見てみましょう。

モデル:

public class Form
{
    public FieldValue[] Fields { get; set; }
}

public class FieldValue
{
    public Field Field { get; set; }

    [ConditionalRequired("Field")]
    public string Value { get; set; }
}

public class Field
{
    public bool IsRequired { get; set; }
}

コントローラ:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new Form
        {
            Fields = new[]
            {
                new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
                new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
                new FieldValue { Field = new Field { IsRequired = false }, Value = "value 3" },
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Form model)
    {
        return View(model);
    }
}

意見:

@model Form
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Fields)
    <input type="submit" value="Save" name="Submit" />
}

ConditionalRequiredAttribute:

public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
{
    private RequiredAttribute _innerAttribute = new RequiredAttribute();

    private readonly string _fieldProperty;

    public ConditionalRequiredAttribute(string fieldProperty)
    {
        _fieldProperty = fieldProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var containerType = validationContext.ObjectInstance.GetType();
        var field = containerType.GetProperty(_fieldProperty);
        if (field == null)
        {
            return new ValidationResult(string.Format("Unknown property {0}", _fieldProperty));
        }

        var fieldValue = (Field)field.GetValue(validationContext.ObjectInstance, null);
        if (fieldValue == null)
        {
            return new ValidationResult(string.Format("The property {0} was null", _fieldProperty));
        }

        if (fieldValue.IsRequired && !_innerAttribute.IsValid(value))
        {
            return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
        }

        return ValidationResult.Success;
    }


    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "conditionalrequired",
        };

        rule.ValidationParameters.Add("iserquiredproperty", _fieldProperty + ".IsRequired");

        yield return rule;
    }
}

関連する目立たないアダプター:

(function ($) {
    $.validator.unobtrusive.adapters.add('conditionalrequired', ['iserquiredproperty'], function (options) {
        options.rules['conditionalrequired'] = options.params;
        if (options.message) {
            options.messages['conditionalrequired'] = options.message;
        }
    });

    $.validator.addMethod('conditionalrequired', function (value, element, parameters) {
        var name = $(element).attr('name'),
        prefix = name.substr(0, name.lastIndexOf('.') + 1),
        isRequiredFiledName = prefix + parameters.iserquiredproperty,
        requiredElement = $(':hidden[name="' + isRequiredFiledName + '"]'),
        isRequired = requiredElement.val().toLowerCase() === 'true';

        if (!isRequired) {
            return true;
        }

        return value && value !== '';
    });

})(jQuery);
于 2012-05-10T11:40:47.320 に答える