В 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;
}
}
}
EditorFor
— это дешевый способ создания прототипа, но IMO не подходит для любого производственного приложения. Что касается использованияTextBoxFor
,DropDownListFor
и т. д.... да, это в значительной степени занимает центральное место в том, как создаются представления MVC. - person Yuck   schedule 02.02.2014