Проверка и редактирование «Изменяемого» / необязательного типа в Asp.net MVC 3 - PullRequest
0 голосов
/ 06 ноября 2011

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

Поэтому в модели мне бы хотелось следующее:

public class Changeable<T>
{
   public bool Changed { get; set; }
   public T Value { get; set; }
}

public class MyModel
{
    [Range(1, 10)]
    public Changeable<int> SomeInt { get; set; }
}

Представление:

@Html.EditorFor(model => model.SomeInt)

И тогда я бы сгенерировал редактор с текстовым полем (int) и флажком (изменен).Атрибуты проверки (Range и т. Д.) Должны вызываться, когда флажок установлен, но не, когда он не отмечен.

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

Возможно ли решение, которое я хочу, или есть другое разумное элегантное решение?

Прямо сейчас я разрабатываю решение со свойством string[] ChangedProperties и большим количеством Javascript для обработки проверки и т. Д., Но это довольно уродливо и далеко не сделано.

Спасибо ...

Ответы [ 3 ]

2 голосов
/ 06 ноября 2011

Вы можете попробовать использовать динамические типы с пользовательским атрибутом проверки диапазона:

public interface IChangeable
{
    bool Changed { get; set; }
}

public class Changeable<T> : IChangeable
{
    public bool Changed { get; set; }
    public T Value { get; set; }
}

public class MyModel
{
    [MyRange(1, 10)]
    public Changeable<int> SomeInt { get; set; }
}

public class MyRangeAttribute : RangeAttribute
{
    public MyRangeAttribute(double minimum, double maximum): base(minimum, maximum)
    { }

    public MyRangeAttribute(int minimum, int maximum)
        : base(minimum, maximum)
    { }

    public override bool IsValid(object value)
    {
        var changeable = value as IChangeable;
        if (changeable == null || !changeable.Changed)
        {
            return true;
        }
        dynamic dynValue = value;

        return base.IsValid((object)dynValue.Value);
    }
}

, затем контроллер:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyModel
        {
            SomeInt = new Changeable<int>
            {
                Changed = true,
                Value = 5
            }
        });
    }

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

, затем представление (~/Views/Home/Index.cshtml):

@model MyModel

@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.SomeInt)
    <button type="submit">OK</button>
}

и соответствующий шаблон редактора (обратите внимание на имя файла шаблона редактора)

~/Views/Shared/EditorTemplates/Changeable`1.cshtml

@model dynamic
@Html.CheckBox("Changed", (bool)Model.Changed)
@Html.Editor("Value")
@Html.ValidationMessage("")
1 голос
/ 15 ноября 2011

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

Вы сказали, что хотите, чтобы валидаторы сработали только тогда, когда была проверена Changed. Этот код всегда запускает валидаторы, так как я не верю, что это хорошая практика, чтобы предотвратить запуск валидаторов. Вместо этого код проверяет, изменил ли пользователь значение, и автоматически проверяет Changed, когда это происходит. Если пользователь снимает флажок «Изменено», старое значение помещается в поле Value.

Код состоит из помощника HTML, ModelMetadataProvider, ModelBinder и просто небольшого JavaScript. Перед кодом приведена определенная модель, аналогичная модели Дарина, с добавлением одного дополнительного свойства:

public interface IChangeable
{
    bool Changed { get; set; }
}

public class Changeable<T> : IChangeable 
{
    public bool Changed { get; set; }
    public T Value { get; set; }
}

public class MyModel
{
    [Range(1, 10), Display(Name = "Some Integer")]
    public Changeable<int> SomeInt { get; set; }

    [StringLength(32, MinimumLength = 6), Display(Name = "This String")]
    public Changeable<string> TheString { get; set; }
} 

Начиная с помощника HTML:

public static class HtmlHelperExtensions
{
    public static MvcHtmlString ChangeableFor<TModel, TValue, TType>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Changeable<TType> changeable)
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        if (String.IsNullOrEmpty(name)) 
            throw new ArgumentNullException("name", "Name cannot be null");

        var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
        var type = metadata.ModelType;
        var containerType = metadata.ContainerType;
        var arg = Expression.Parameter(containerType, "x");
        Expression expr = arg;
        expr = Expression.Property(expr, name);
        expr = Expression.Property(expr, "Value");
        var funcExpr = Expression.Lambda(expr, arg) as Expression<Func<TModel, TType>>;
        var valueModelMetadata = ModelMetadata.FromLambdaExpression(funcExpr, html.ViewData);

        Expression exprChanged = arg;
        exprChanged = Expression.Property(exprChanged, name);
        exprChanged = Expression.Property(exprChanged, "Changed");
        var funcExprChanged = Expression.Lambda(exprChanged, arg) as Expression<Func<TModel, bool>>;

        var htmlSb = new StringBuilder("\n");
        htmlSb.Append(LabelExtensions.Label(html, metadata.GetDisplayName()));
        htmlSb.Append("<br />\n");
        htmlSb.Append(InputExtensions.CheckBoxFor(html, funcExprChanged));
        htmlSb.Append(" Changed<br />\n");
        htmlSb.Append(InputExtensions.Hidden(html, name + ".OldValue", valueModelMetadata.Model) + "\n");
        htmlSb.Append(EditorExtensions.EditorFor(html, funcExpr, new KeyValuePair<string, object>("parentMetadata", metadata)));
        htmlSb.Append(ValidationExtensions.ValidationMessageFor(html, funcExpr));
        htmlSb.Append("<br />\n");

        return new MvcHtmlString(htmlSb.ToString());
    }
}

Это передает родительские метаданные в ViewData (что позволит нам позже получить валидаторы класса). Он также создает лямбда-выражения, поэтому мы можем использовать CheckBoxFor() и EditorFor(). Вид с использованием нашей модели и этого помощника выглядит следующим образом:

@model MyModel 
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@using (Html.BeginForm())
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("input[id$='Value']").live("keyup blur", function () {
                var prefix = this.id.split("_")[0];
                var oldValue = $("#" + prefix + "_OldValue").val();
                var changed = oldValue != $(this).val()
                $("#" + prefix + "_Changed").attr("checked", changed);
                if (changed) {
                    // validate
                    $(this.form).validate().element($("#" + prefix + "_Value")[0]);
                }
            });

            $("input[id$='Changed']").live("click", function () {
                if (!this.checked) {
                    // replace value with old value
                    var prefix = this.id.split("_")[0];
                    var oldValue = $("#" + prefix + "_OldValue").val();
                    $("#" + prefix + "_Value").val(oldValue);
                    // validate
                    $(this.form).validate().element($("#" + prefix + "_Value")[0]);
                }
            });
        });
    </script>

    @Html.ChangeableFor(x => x.SomeInt, Model.SomeInt)
    @Html.ChangeableFor(x => x.TheString, Model.TheString)

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

Обратите внимание на JavaScript для работы с изменениями в текстовом поле «Значение» и щелчком на флажке «Изменено». Также обратите внимание на необходимость дважды передавать свойство Changeable<T> помощнику ChangeableFor().

Далее пользовательский ModelValidatorProvider:

public class MyDataAnnotationsModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    private bool _provideParentValidators = false;

    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
        if (metadata.ContainerType != null && metadata.ContainerType.Name.IndexOf("Changeable") > -1 && metadata.PropertyName == "Value")
        {
            var viewContext = context as ViewContext;
            if (viewContext != null)
            {
                var viewData = viewContext.ViewData;
                var index = viewData.Keys.ToList().IndexOf("Value");
                var parentMetadata = viewData.Values.ToList()[index] as ModelMetadata;
                _provideParentValidators = true;
                var vals = base.GetValidators(parentMetadata, context);
                _provideParentValidators = false;
                return vals;
            }
            else
            {
                var viewData = context.Controller.ViewData;
                var keyName = viewData.ModelState.Keys.ToList().Last().Split(new string[] { "." }, StringSplitOptions.None).First();
                var index = viewData.Keys.ToList().IndexOf(keyName);
                var parentMetadata = viewData.Values.ToList()[index] as ModelMetadata;
                parentMetadata.Model = metadata.Model;
                _provideParentValidators = true;
                var vals = base.GetValidators(parentMetadata, context);
                _provideParentValidators = false;
                return vals;
            }
        }
        else if (metadata.ModelType.Name.IndexOf("Changeable") > -1 && !_provideParentValidators)
        {
            // DO NOT provide parent's validators, unless it is at the request of the child Value property
            return new List<ModelValidator>();
        }
        return base.GetValidators(metadata, context, attributes).ToList();
    }
}

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

Наконец, ModelBinder:

public class ChangeableModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext.Controller.ViewData.Keys.ToList().IndexOf(bindingContext.ModelName) < 0)
            controllerContext.Controller.ViewData.Add(bindingContext.ModelName, bindingContext.ModelMetadata);
        return base.BindModel(controllerContext, bindingContext);
    }
}

Это берет родительские метаданные и хранит их для последующего доступа в пользовательском ModelValidatorProvider.

Завершите следующее в Application_Start в Global.asax.cs:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MvcApplication5.Extensions.MyDataAnnotationsModelValidatorProvider());
MyDataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

ModelBinders.Binders.Add(typeof(MvcApplication5.Controllers.Changeable<int>), new ChangeableModelBinder());
ModelBinders.Binders.Add(typeof(MvcApplication5.Controllers.Changeable<string>), new ChangeableModelBinder());
// you must add a ModelBinders.Binders.Add() declaration for each type T you
// will use in your Changeable<T>

Viola!

0 голосов
/ 06 ноября 2011

Я не уверен на 100%, но думаю, что вы хотите:

private int _changeable;

public Changeable<int> SomeInt { 
get 
    { return _changeable } 
set 
    { _changeable = value;
      Changed = true;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...