Инициализация обобщенного поля ASP.NET MVC4

В ASP.NET MVC4 я хочу инициализировать поля значениями. Когда пользователь получает страницу, прежде чем он отправит ее обратно, я хочу, чтобы она начиналась со значений в некоторых полях. Значения будут извлечены из строки запроса, а не жестко закодированы. Скажем, пользователь заполняет форму: он вошел в систему, вы знаете его имя и адрес. Из любезности сделайте их значениями по умолчанию. На самом деле не имеет значения, откуда они берутся, за исключением того, что это набор пар ключ-значение, а значения представляют собой строки. Я просто хочу поместить что-то в поля без того, чтобы пользователь сначала опубликовал форму, и мне бы очень хотелось сделать это без жесткого кодирования длинного списка назначений для каждого свойства в довольно сложной модели.

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

В методе Index() моего контроллера я попытался вызвать ModelState.TrySetModelValue() (который при заполнении ModelState идентифицирует каждое поле одной уникальной строкой), но на данном этапе ModelState пуст, поэтому, конечно, это не сработало. Я попытался изменить Index(), чтобы ожидать экземпляр модели в качестве параметра, но это не помогает.

Должен ли я переписывать каждые @Html.EditorFor()/TextBoxFor()/и т.д. позвонить в заявке? Это кажется безумием. Собственно, это то, что я бы делал в цикле, в одном месте, а не разбросан по нескольким местам в каждом из растущего числа просмотров.

У меня такое чувство, что я не понимаю чего-то фундаментального в том, как должен работать MVC4.

ОБНОВЛЕНИЕ 2

Получается, что если вы украсите свой метод действия [HttpGet], и он у вас ожидает модель в качестве параметра, то если вы используете поле имена (foo.bar), а не идентификаторы (foo_bar) в запросе string, он делает то, что я хочу, автоматически. ModelState заполнено. Должно быть, у меня не было метода действия, украшенного [HttpGet], когда я смотрел на ModelState.

Если поле автоматически устанавливается через строку запроса, это заменяет все, что есть в вашей модели. Это разумно; весь смысл в том, чтобы переопределить значения модели по умолчанию. Но если вы хотите, в свою очередь, переопределить возможные значения строки запроса (например, сказать, что есть флажок для «электронной подписи»; это всегда должно требовать явных усилий со стороны пользователя), то вы должны сделать это через ModelState.

Это означает, что мое первое решение, приведенное ниже, не имело реального эффекта (при условии, что у меня было свойство [HttpGet] в методе действия). Он только устанавливает свойства модели, которые уже были установлены в ModelState фреймворком, и поэтому значения которых в модели игнорировались.

Что немного странно, так это то, что ModelState дает полям другой ключ, если их нет в строке запроса. foo.bar.baz использует именно это как ключ, если он есть в строке запроса, но если это не так, ключ становится foo.footypename.bar.bartypename.baz. По-видимому, существует исключение, если имя свойства совпадает с его типом: у меня есть класс модели Name, а другой класс модели имеет свойство public Name Name { get; set }. Свойства типа Name, которые имеют название name, никогда не сопровождаются именем их типа в ключах ModelState. Однако я еще не исключил другие возможные причины исключения имени типа этого конкретного свойства. Это предположение. Имена типов исключаются для свойств «листья» во всех случаях в моей модели. Это потому, что они являются типами, известными системе, или «листьями», или что? Я не знаю.

В любом случае листовое свойство «корневого» класса модели всегда использует собственное имя в качестве ключа в ModelState.

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

ОБНОВЛЕНИЕ

Решение: вырезано много кода

//  Controller base
public abstract class ControllerBase<TModel> : Controller 
{
    [HttpGet]
    public virtual ActionResult Index(TModel model)
    {
        HttpContext.Request.QueryString.CopyTo(model);

        return View("Index", model);
    }
}

public static class Extensions
{
    /// <summary>
    /// Given NameValueCollection of keys/values in the form 
    /// "foo.bar.baz" = "text", and an object which is the *parent* of
    /// foo, set properties of foo accordingly. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="src"></param>
    /// <param name="model"></param>
    public static void CopyTo<T>(this NameValueCollection src, T target)
    {
        String strkey;
        Object objval;

        foreach (var key in src.Keys)
        {
            strkey = "" + key;
            objval = src[strkey];

            target.TrySetPropertyValue(strkey, objval);
        }
    }

    /// <summary>
    /// Given a reference to an object objThis, the string "foo.bar.baz", 
    /// and an object o of a type optimistically hoped to be convertible 
    /// to that of objThis.foo.bar.baz, set objThis.foo.bar.baz = o
    ///
    /// If foo.bar is null, it must have a default constructor, or we fail 
    /// and return false. 
    /// </summary>
    /// <param name="objThis"></param>
    /// <param name="propPathName"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool TrySetPropertyValue(this object objThis, 
                                           string propPathName, object value)
    {
        if (string.IsNullOrWhiteSpace(propPathName))
        {
            throw new ArgumentNullException(propPathName);
        }
        var names = propPathName.Split(new char[] { '.' }).ToList();

        var nextPropInfo = objThis.GetType().GetProperty(names.First());

        if (null == nextPropInfo)
            return false;

        if (names.Count > 1)
        {
            var nextPropValue = nextPropInfo.GetValue(objThis, null);

            if (null == nextPropValue)
            {
                nextPropValue = Activator
                    .CreateInstance(nextPropInfo.PropertyType);
                nextPropInfo.SetValue(objThis, nextPropValue);
            }

            names.RemoveAt(0);

            return nextPropValue.TrySetPropertyValue(
                                     String.Join(".", names), value);
        }
        else
        {
            try
            {
                var conv = System.ComponentModel.TypeDescriptor
                               .GetConverter(nextPropInfo.PropertyType);
                value = conv.ConvertFrom(value);

                nextPropInfo.SetValue(objThis, value);
            }
            catch (System.FormatException)
            {
                return false;
            }
            return true;
        }
    }
}

person 15ee8f99-57ff-4f92-890c-b56153    schedule 01.02.2014    source источник
comment
Использование EditorFor — это дешевый способ создания прототипа, но IMO не подходит для любого производственного приложения. Что касается использования TextBoxFor, DropDownListFor и т. д.... да, это в значительной степени занимает центральное место в том, как создаются представления MVC.   -  person Yuck    schedule 02.02.2014
comment
@Yuck Я вообще не против использования TextBoxFor и т. Д. Мне они очень нравятся (те, которые работают правильно, HiddenFor — нет). Я просто предпочитаю не разбрасывать реализацию этой маленькой функции по каждому вызову каждой из этих вещей, в конечном счете, в паре десятков различных представлений. Некрасиво (отредактировано выше, чтобы упомянуть об этом). Виды не там, где им место. Что, если мы захотим отключить его или изменить его поведение? Я не замужем за EditorFor. Я заменяю его более конкретными помощниками по мере необходимости.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 02.02.2014


Ответы (1)


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

@Html.TextBoxFor(m => Model.Name)

Инициализация в контроллере:

public ActionResult Index()
{
   MyModel model = new MyModel();
   model.Name = "myname";
   return View("myview", model);
}

Вы также можете установить атрибуты в TextBoxFor

@Html.TextBoxFor(m => Model.Name, new { value = "myname"})

Обновить

Если ваш URL-адрес выглядит как mysite/Edit?id=123, попробуйте декольировать действие вашего контроллера, например

public ActionResult Edit(string id) 
{ ... 

Также попробуйте украсить его атрибутом HttpPost или HttpGet.

person VladL    schedule 01.02.2014
comment
Это не учитывает значения строки запроса. - person Yuck; 02.02.2014
comment
@Yuck, ОП сказал: «Неважно, откуда они взялись, на самом деле я просто хочу поместить что-то в поля без того, чтобы пользователь сначала публиковал форму. Так что совершенно нормально инициализировать свойства модели значениями в контроллере и передать экземпляр в представление. - person VladL; 02.02.2014
comment
Значения будут извлечены из строки запроса, а не жестко запрограммированы. - person Yuck; 02.02.2014
comment
@Yuck да, и после этого он сказал, что я процитировал. - person VladL; 02.02.2014
comment
@VladL В итоге я инициализировал модель из QueryString так, как вы предлагаете. К сожалению, для того, чтобы это происходило в цикле, потребовалась значительная часть кода отражения, а не жесткое кодирование большого и требующего интенсивного обслуживания списка foo.bar.baz = HttpContext.Request.QueryString["foo.bar.baz"]. Я не знаю, достаточно ли я подчеркивал, что хочу сделать это в цикле, но я упомянул об этом дважды, и упоминание TrySetModelState() вряд ли противоречит этому. - person 15ee8f99-57ff-4f92-890c-b56153; 02.02.2014
comment
@EdPlunkett, ваш код не выглядит эффективным, но я действительно не знаю, как устроен ваш URL. Если ваш URL-адрес выглядит как mysite/Edit?id=123, попробуйте декольировать действие контроллера, например public ActionResult Edit(string id) { .... Также попробуйте украсить его атрибутом HttpPost или HttpGet. - person VladL; 02.02.2014
comment
@VladL Это интересно. Я использовал идентификаторы полей (подчеркивание), а не имена полей (точка) для удобства выполнения этого в JS, и поэтому я никогда не замечал, что ЕСЛИ у меня есть Index(), ожидайте TModel в качестве параметра, и он помечен [HttpGet] (что на самом деле был!), и если я использую имена (точка)... он делает то, что я хочу, АВТОМАГИЧЕСКИ. Мальчик, я чувствую себя глупо! Благодарю вас! - person 15ee8f99-57ff-4f92-890c-b56153; 02.02.2014